mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 20:28:40 -05:00
feat(ui): Add custom title functionality (#825)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
@@ -18,6 +17,7 @@ import Modal from '../Common/Modal';
|
||||
import Slider from '../Slider';
|
||||
import TitleCard from '../TitleCard';
|
||||
import Transition from '../Transition';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
overviewunavailable: 'Overview unavailable.',
|
||||
@@ -108,9 +108,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
||||
}}
|
||||
>
|
||||
<Head>
|
||||
<title>{data.name} - Overseerr</title>
|
||||
</Head>
|
||||
<PageTitle title={data.name} />
|
||||
<Transition
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
|
||||
22
src/components/Common/PageTitle/index.tsx
Normal file
22
src/components/Common/PageTitle/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import useSettings from '../../../hooks/useSettings';
|
||||
import Head from 'next/head';
|
||||
|
||||
interface PageTitleProps {
|
||||
title: string | (string | undefined)[];
|
||||
}
|
||||
|
||||
const PageTitle: React.FC<PageTitleProps> = ({ title }) => {
|
||||
const settings = useSettings();
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>
|
||||
{Array.isArray(title) ? title.filter(Boolean).join(' - ') : title} -{' '}
|
||||
{settings.currentSettings.applicationTitle}
|
||||
</title>
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
@@ -3,10 +3,11 @@ import { useSWRInfinite } from 'swr';
|
||||
import type { MovieResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Header from '../Common/Header';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
discovermovies: 'Popular Movies',
|
||||
@@ -20,6 +21,7 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const DiscoverMovies: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
@@ -68,6 +70,7 @@ const DiscoverMovies: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.discovermovies)} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>
|
||||
<FormattedMessage {...messages.discovermovies} />
|
||||
|
||||
@@ -2,11 +2,12 @@ import React, { useContext } from 'react';
|
||||
import { useSWRInfinite } from 'swr';
|
||||
import type { TvResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import Header from '../Common/Header';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
discovertv: 'Popular Series',
|
||||
@@ -20,6 +21,7 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const DiscoverTv: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
@@ -67,6 +69,7 @@ const DiscoverTv: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.discovertv)} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>
|
||||
<FormattedMessage {...messages.discovertv} />
|
||||
|
||||
@@ -7,10 +7,11 @@ import type {
|
||||
} from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Header from '../Common/Header';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
trending: 'Trending',
|
||||
@@ -24,6 +25,7 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const Trending: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
@@ -74,6 +76,7 @@ const Trending: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.trending)} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>
|
||||
<FormattedMessage {...messages.trending} />
|
||||
|
||||
@@ -3,10 +3,11 @@ import { useSWRInfinite } from 'swr';
|
||||
import type { MovieResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Header from '../Common/Header';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
upcomingmovies: 'Upcoming Movies',
|
||||
@@ -20,6 +21,7 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const UpcomingMovies: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
@@ -69,6 +71,7 @@ const UpcomingMovies: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.upcomingmovies)} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>
|
||||
<FormattedMessage {...messages.upcomingmovies} />
|
||||
|
||||
@@ -8,8 +8,10 @@ import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaI
|
||||
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
||||
import RequestCard from '../RequestCard';
|
||||
import MediaSlider from '../MediaSlider';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
discover: 'Discover',
|
||||
recentrequests: 'Recent Requests',
|
||||
popularmovies: 'Popular Movies',
|
||||
populartv: 'Popular Series',
|
||||
@@ -35,6 +37,7 @@ const Discover: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.discover)} />
|
||||
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||
|
||||
@@ -176,7 +176,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
<div className="flex-shrink-0 flex items-center px-4">
|
||||
<span className="text-xl text-gray-50">
|
||||
<a href="/">
|
||||
<img src="/logo.png" alt="Overseerr Logo" />
|
||||
<img src="/logo.png" alt="Logo" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
@@ -238,7 +238,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
<div className="flex items-center flex-shrink-0 px-4">
|
||||
<span className="text-2xl text-gray-50">
|
||||
<a href="/">
|
||||
<img src="/logo.png" alt="Overseerr Logo" />
|
||||
<img src="/logo.png" alt="Logo" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,10 @@ import LanguagePicker from '../Layout/LanguagePicker';
|
||||
import LocalLogin from './LocalLogin';
|
||||
import Accordion from '../Common/Accordion';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
signin: 'Sign In',
|
||||
signinheader: 'Sign in to continue',
|
||||
signinwithplex: 'Use your Plex account',
|
||||
signinwithoverseerr: 'Use your Overseerr account',
|
||||
@@ -59,6 +61,7 @@ const Login: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col min-h-screen bg-gray-900 py-14">
|
||||
<PageTitle title={intl.formatMessage(messages.signin)} />
|
||||
<ImageFader
|
||||
backgroundImages={[
|
||||
'/images/rotate1.jpg',
|
||||
@@ -73,11 +76,7 @@ const Login: React.FC = () => {
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="w-auto mx-auto max-h-32"
|
||||
alt="Overseerr Logo"
|
||||
/>
|
||||
<img src="/logo.png" className="w-auto mx-auto max-h-32" alt="Logo" />
|
||||
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
||||
<FormattedMessage {...messages.signinheader} />
|
||||
</h2>
|
||||
|
||||
@@ -9,6 +9,7 @@ import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullcast: 'Full Cast',
|
||||
@@ -32,6 +33,7 @@ const MovieCast: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={[intl.formatMessage(messages.fullcast), data.title]} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullcrew: 'Full Crew',
|
||||
@@ -32,6 +33,7 @@ const MovieCrew: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={[intl.formatMessage(messages.fullcrew), data.title]} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
recommendations: 'Recommendations',
|
||||
@@ -77,6 +78,9 @@ const MovieRecommendations: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[intl.formatMessage(messages.recommendations), movieData?.title]}
|
||||
/>
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { MovieDetails } from '../../../server/models/Movie';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
similar: 'Similar Titles',
|
||||
@@ -77,6 +78,9 @@ const MovieSimilar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[intl.formatMessage(messages.similar), movieData?.title]}
|
||||
/>
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -27,7 +27,6 @@ import RTAudFresh from '../../assets/rt_aud_fresh.svg';
|
||||
import RTAudRotten from '../../assets/rt_aud_rotten.svg';
|
||||
import type { RTRating } from '../../../server/api/rottentomatoes';
|
||||
import Error from '../../pages/_error';
|
||||
import Head from 'next/head';
|
||||
import ExternalLinkBlock from '../ExternalLinkBlock';
|
||||
import { sortCrewPriority } from '../../utils/creditHelpers';
|
||||
import StatusBadge from '../StatusBadge';
|
||||
@@ -36,6 +35,7 @@ import MediaSlider from '../MediaSlider';
|
||||
import ConfirmButton from '../Common/ConfirmButton';
|
||||
import DownloadBlock from '../DownloadBlock';
|
||||
import ButtonWithDropdown from '../Common/ButtonWithDropdown';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
releasedate: 'Release Date',
|
||||
@@ -137,10 +137,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
||||
}}
|
||||
>
|
||||
<Head>
|
||||
<title>{data.title} - Overseerr</title>
|
||||
</Head>
|
||||
|
||||
<PageTitle title={data.title} />
|
||||
<SlideOver
|
||||
show={showManager}
|
||||
title={intl.formatMessage(messages.manageModalTitle)}
|
||||
@@ -181,7 +178,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
<div className="mb-6">
|
||||
{data?.mediaInfo &&
|
||||
data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
|
||||
<div className="flex flex-col mb-2 sm:flex-row flex-nowrap">
|
||||
<Button
|
||||
onClick={() => markAvailable()}
|
||||
className="w-full sm:mb-0"
|
||||
@@ -205,7 +202,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
)}
|
||||
{data?.mediaInfo &&
|
||||
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
|
||||
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
|
||||
<div className="flex flex-col mb-2 sm:flex-row flex-nowrap">
|
||||
<Button
|
||||
onClick={() => markAvailable(true)}
|
||||
className="w-full sm:mb-0"
|
||||
|
||||
@@ -12,6 +12,7 @@ import { LanguageContext } from '../../context/LanguageContext';
|
||||
import ImageFader from '../Common/ImageFader';
|
||||
import Ellipsis from '../../assets/ellipsis.svg';
|
||||
import { groupBy } from 'lodash';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
appearsin: 'Appears in',
|
||||
@@ -172,6 +173,7 @@ const PersonDetails: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={data.name} />
|
||||
{(sortedCrew || sortedCast) && (
|
||||
<div className="absolute top-0 left-0 right-0 z-0 h-96">
|
||||
<ImageFader
|
||||
|
||||
@@ -7,6 +7,7 @@ import Header from '../Common/Header';
|
||||
import Table from '../Common/Table';
|
||||
import Button from '../Common/Button';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
requests: 'Requests',
|
||||
@@ -54,6 +55,7 @@ const RequestList: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.requests)} />
|
||||
<div className="flex flex-col justify-between md:items-end md:flex-row">
|
||||
<Header>{intl.formatMessage(messages.requests)}</Header>
|
||||
<div className="flex flex-col mt-2 md:flex-row">
|
||||
|
||||
@@ -10,8 +10,10 @@ import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Header from '../Common/Header';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
search: 'Search',
|
||||
searchresults: 'Search Results',
|
||||
});
|
||||
|
||||
@@ -65,6 +67,7 @@ const Search: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.search)} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>{intl.formatMessage(messages.searchresults)}</Header>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,10 @@ import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
settings: 'Settings',
|
||||
menuGeneralSettings: 'General Settings',
|
||||
menuPlexSettings: 'Plex',
|
||||
menuServices: 'Services',
|
||||
@@ -91,6 +93,7 @@ const SettingsLayout: React.FC = ({ children }) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.settings)} />
|
||||
<div className="mt-6">
|
||||
<div className="sm:hidden">
|
||||
<select
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useToasts } from 'react-toast-notifications';
|
||||
import Badge from '../Common/Badge';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import PermissionEdit from '../PermissionEdit';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages({
|
||||
generalsettings: 'General Settings',
|
||||
@@ -20,6 +21,7 @@ const messages = defineMessages({
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
apikey: 'API Key',
|
||||
applicationTitle: 'Application Title',
|
||||
applicationurl: 'Application URL',
|
||||
toastApiKeySuccess: 'New API key generated!',
|
||||
toastApiKeyFailure: 'Something went wrong while generating a new API key.',
|
||||
@@ -38,6 +40,7 @@ const messages = defineMessages({
|
||||
localLogin: 'Enable Local User Sign-In',
|
||||
localLoginTip:
|
||||
'Disabling this option only prevents new sign-ins (no user data is deleted)',
|
||||
validationApplicationTitle: 'You must provide an application title',
|
||||
});
|
||||
|
||||
const SettingsMain: React.FC = () => {
|
||||
@@ -47,6 +50,11 @@ const SettingsMain: React.FC = () => {
|
||||
const { data, error, revalidate } = useSWR<MainSettings>(
|
||||
'/api/v1/settings/main'
|
||||
);
|
||||
const MainSettingsSchema = Yup.object().shape({
|
||||
applicationTitle: Yup.string().required(
|
||||
intl.formatMessage(messages.validationApplicationTitle)
|
||||
),
|
||||
});
|
||||
|
||||
const regenerate = async () => {
|
||||
try {
|
||||
@@ -82,6 +90,7 @@ const SettingsMain: React.FC = () => {
|
||||
<div className="section">
|
||||
<Formik
|
||||
initialValues={{
|
||||
applicationTitle: data?.applicationTitle,
|
||||
applicationUrl: data?.applicationUrl,
|
||||
csrfProtection: data?.csrfProtection,
|
||||
defaultPermissions: data?.defaultPermissions ?? 0,
|
||||
@@ -90,9 +99,11 @@ const SettingsMain: React.FC = () => {
|
||||
trustProxy: data?.trustProxy,
|
||||
}}
|
||||
enableReinitialize
|
||||
validationSchema={MainSettingsSchema}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await axios.post('/api/v1/settings/main', {
|
||||
applicationTitle: values.applicationTitle,
|
||||
applicationUrl: values.applicationUrl,
|
||||
csrfProtection: values.csrfProtection,
|
||||
defaultPermissions: values.defaultPermissions,
|
||||
@@ -115,7 +126,7 @@ const SettingsMain: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, values, setFieldValue }) => {
|
||||
{({ errors, touched, isSubmitting, values, setFieldValue }) => {
|
||||
return (
|
||||
<Form className="section">
|
||||
{userHasPermission(Permission.ADMIN) && (
|
||||
@@ -160,6 +171,24 @@ const SettingsMain: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-row">
|
||||
<label htmlFor="applicationTitle" className="text-label">
|
||||
{intl.formatMessage(messages.applicationTitle)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
id="applicationTitle"
|
||||
name="applicationTitle"
|
||||
type="text"
|
||||
placeholder="Overseerr"
|
||||
/>
|
||||
</div>
|
||||
{errors.applicationTitle && touched.applicationTitle && (
|
||||
<div className="error">{errors.applicationTitle}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="applicationUrl" className="text-label">
|
||||
{intl.formatMessage(messages.applicationurl)}
|
||||
|
||||
@@ -10,8 +10,10 @@ import axios from 'axios';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Badge from '../Common/Badge';
|
||||
import LanguagePicker from '../Layout/LanguagePicker';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
setup: 'Setup',
|
||||
finish: 'Finish Setup',
|
||||
finishing: 'Finishing…',
|
||||
continue: 'Continue',
|
||||
@@ -44,6 +46,7 @@ const Setup: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col justify-center min-h-screen py-12 bg-gray-900">
|
||||
<PageTitle title={intl.formatMessage(messages.setup)} />
|
||||
<ImageFader
|
||||
backgroundImages={[
|
||||
'/images/rotate1.jpg',
|
||||
@@ -61,11 +64,11 @@ const Setup: React.FC = () => {
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="w-auto mx-auto mb-10 max-h-32"
|
||||
alt="Overseerr Logo"
|
||||
alt="Logo"
|
||||
/>
|
||||
<nav className="relative z-50">
|
||||
<ul
|
||||
className="bg-gray-800 bg-opacity-50 border border-gray-600 divide-y divide-gray-600 rounded-md md:flex md:divide-y-0"
|
||||
className="bg-gray-800 bg-opacity-50 border border-gray-600 divide-y divide-gray-600 rounded-md md:flex md:divide-y-0"
|
||||
style={{ backdropFilter: 'blur(5px)' }}
|
||||
>
|
||||
<SetupSteps
|
||||
|
||||
@@ -9,6 +9,7 @@ import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullseriescast: 'Full Series Cast',
|
||||
@@ -32,6 +33,9 @@ const TvCast: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[intl.formatMessage(messages.fullseriescast), data.name]}
|
||||
/>
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullseriescrew: 'Full Series Crew',
|
||||
@@ -32,6 +33,9 @@ const TvCrew: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[intl.formatMessage(messages.fullseriescrew), data.name]}
|
||||
/>
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { TvDetails } from '../../../server/models/Tv';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
recommendations: 'Recommendations',
|
||||
@@ -77,6 +78,9 @@ const TvRecommendations: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[intl.formatMessage(messages.recommendations), tvData?.name]}
|
||||
/>
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { TvDetails } from '../../../server/models/Tv';
|
||||
import Header from '../Common/Header';
|
||||
import { MediaStatus } from '../../../server/constants/media';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
similar: 'Similar Series',
|
||||
@@ -77,6 +78,7 @@ const TvSimilar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={[intl.formatMessage(messages.similar), tvData?.name]} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
|
||||
@@ -27,7 +27,6 @@ import RTRotten from '../../assets/rt_rotten.svg';
|
||||
import RTAudFresh from '../../assets/rt_aud_fresh.svg';
|
||||
import RTAudRotten from '../../assets/rt_aud_rotten.svg';
|
||||
import type { RTRating } from '../../../server/api/rottentomatoes';
|
||||
import Head from 'next/head';
|
||||
import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb/constants';
|
||||
import ExternalLinkBlock from '../ExternalLinkBlock';
|
||||
import { sortCrewPriority } from '../../utils/creditHelpers';
|
||||
@@ -38,6 +37,7 @@ import MediaSlider from '../MediaSlider';
|
||||
import ConfirmButton from '../Common/ConfirmButton';
|
||||
import DownloadBlock from '../DownloadBlock';
|
||||
import ButtonWithDropdown from '../Common/ButtonWithDropdown';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
firstAirDate: 'First Air Date',
|
||||
@@ -156,9 +156,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
||||
}}
|
||||
>
|
||||
<Head>
|
||||
<title>{data.name} - Overseerr</title>
|
||||
</Head>
|
||||
<PageTitle title={data.name} />
|
||||
<RequestModal
|
||||
tmdbId={data.id}
|
||||
show={showRequestModal}
|
||||
@@ -209,7 +207,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
<div className="mb-6">
|
||||
{data?.mediaInfo &&
|
||||
data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
|
||||
<div className="flex flex-col mb-2 sm:flex-row flex-nowrap">
|
||||
<Button
|
||||
onClick={() => markAvailable()}
|
||||
className="w-full sm:mb-0"
|
||||
@@ -233,7 +231,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
)}
|
||||
{data?.mediaInfo &&
|
||||
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
|
||||
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
|
||||
<div className="flex flex-col mb-2 sm:flex-row flex-nowrap">
|
||||
<Button
|
||||
onClick={() => markAvailable(true)}
|
||||
className="w-full sm:mb-0"
|
||||
|
||||
@@ -11,6 +11,7 @@ import PermissionEdit from '../PermissionEdit';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { UserType } from '../../../server/constants/user';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
export const messages = defineMessages({
|
||||
edituser: 'Edit User',
|
||||
@@ -85,6 +86,7 @@ const UserEdit: React.FC = () => {
|
||||
>
|
||||
{({ isSubmitting, handleSubmit }) => (
|
||||
<Form>
|
||||
<PageTitle title={intl.formatMessage(messages.edituser)} />
|
||||
<div>
|
||||
<div className="flex flex-col justify-between sm:flex-row">
|
||||
<Header>
|
||||
|
||||
@@ -20,8 +20,10 @@ import * as Yup from 'yup';
|
||||
import AddUserIcon from '../../assets/useradd.svg';
|
||||
import Alert from '../Common/Alert';
|
||||
import BulkEditModal from './BulkEditModal';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
users: 'Users',
|
||||
userlist: 'User List',
|
||||
importfromplex: 'Import Users from Plex',
|
||||
importfromplexerror: 'Something went wrong while importing users from Plex.',
|
||||
@@ -178,6 +180,7 @@ const UserList: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.users)} />
|
||||
<Transition
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
|
||||
Reference in New Issue
Block a user