mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
fix(frontend): run initial props for children components after getting the user
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useSWRInfinite } from 'swr';
|
||||
import { MovieResult } from '../../../server/models/Search';
|
||||
import type { MovieResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
discovermovies: 'Discover Movies',
|
||||
});
|
||||
|
||||
interface SearchResult {
|
||||
page: number;
|
||||
@@ -11,13 +17,14 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const DiscoverMovies: React.FC = () => {
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `/api/v1/discover/movies?page=${pageIndex + 1}`;
|
||||
return `/api/v1/discover/movies?page=${pageIndex + 1}&language=${locale}`;
|
||||
},
|
||||
{
|
||||
initialSize: 3,
|
||||
@@ -47,7 +54,7 @@ const DiscoverMovies: React.FC = () => {
|
||||
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||
Discover Movies
|
||||
<FormattedMessage {...messages.discovermovies} />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useSWRInfinite } from 'swr';
|
||||
import { TvResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
|
||||
const messages = defineMessages({
|
||||
discovertv: 'Discover Series',
|
||||
});
|
||||
|
||||
interface SearchResult {
|
||||
page: number;
|
||||
@@ -11,13 +17,14 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const DiscoverTv: React.FC = () => {
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `/api/v1/discover/tv?page=${pageIndex + 1}`;
|
||||
return `/api/v1/discover/tv?page=${pageIndex + 1}&language=${locale}`;
|
||||
},
|
||||
{
|
||||
initialSize: 3,
|
||||
@@ -44,7 +51,7 @@ const DiscoverTv: React.FC = () => {
|
||||
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||
Discover Series
|
||||
<FormattedMessage {...messages.discovertv} />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import useSearchInput from '../../../hooks/useSearchInput';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
searchPlaceholder: 'Search Movies & TV',
|
||||
});
|
||||
|
||||
const SearchInput: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { searchValue, setSearchValue, setIsOpen } = useSearchInput();
|
||||
return (
|
||||
<div className="flex-1 flex">
|
||||
@@ -22,7 +28,7 @@ const SearchInput: React.FC = () => {
|
||||
<input
|
||||
id="search_field"
|
||||
className="block w-full h-full pl-8 pr-3 py-2 rounded-md bg-cool-gray-600 text-white placeholder-gray-300 focus:outline-none focus:placeholder-gray-400 sm:text-base"
|
||||
placeholder="Search"
|
||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||
type="search"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
|
@@ -2,6 +2,13 @@ import React, { ReactNode } from 'react';
|
||||
import Transition from '../../Transition';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
dashboard: 'Dashboard',
|
||||
requests: 'Requests',
|
||||
settings: 'Settings',
|
||||
});
|
||||
|
||||
interface SidebarProps {
|
||||
open?: boolean;
|
||||
@@ -11,7 +18,7 @@ interface SidebarProps {
|
||||
interface SidebarLinkProps {
|
||||
href: string;
|
||||
svgIcon: ReactNode;
|
||||
name: string;
|
||||
messagesKey: keyof typeof messages;
|
||||
activeRegExp: RegExp;
|
||||
as?: string;
|
||||
}
|
||||
@@ -19,7 +26,7 @@ interface SidebarLinkProps {
|
||||
const SidebarLinks: SidebarLinkProps[] = [
|
||||
{
|
||||
href: '/',
|
||||
name: 'Dashboard',
|
||||
messagesKey: 'dashboard',
|
||||
svgIcon: (
|
||||
<svg
|
||||
className="mr-3 h-6 w-6 text-gray-300 group-hover:text-gray-300 group-focus:text-gray-300 transition ease-in-out duration-150"
|
||||
@@ -40,7 +47,7 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
},
|
||||
{
|
||||
href: '/requests',
|
||||
name: 'Requests',
|
||||
messagesKey: 'requests',
|
||||
svgIcon: (
|
||||
<svg
|
||||
className="mr-3 h-6 w-6 text-gray-300 group-hover:text-gray-300 group-focus:text-gray-300 transition ease-in-out duration-150"
|
||||
@@ -61,7 +68,7 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
},
|
||||
{
|
||||
href: '/settings',
|
||||
name: 'Settings',
|
||||
messagesKey: 'settings',
|
||||
svgIcon: (
|
||||
<svg
|
||||
className="mr-3 h-6 w-6 text-gray-300 group-hover:text-gray-300 group-focus:text-gray-300 transition ease-in-out duration-150"
|
||||
@@ -148,11 +155,19 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
{SidebarLinks.map((sidebarLink) => {
|
||||
return (
|
||||
<Link
|
||||
key={`mobile-${sidebarLink.name}`}
|
||||
key={`mobile-${sidebarLink.messagesKey}`}
|
||||
href={sidebarLink.href}
|
||||
as={sidebarLink.as}
|
||||
>
|
||||
<a
|
||||
onClick={() => setClosed()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setClosed();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`group flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150
|
||||
${
|
||||
router.pathname.match(
|
||||
@@ -164,7 +179,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
`}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
{sidebarLink.name}
|
||||
<FormattedMessage
|
||||
{...messages[sidebarLink.messagesKey]}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
@@ -192,7 +209,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
{SidebarLinks.map((sidebarLink) => {
|
||||
return (
|
||||
<Link
|
||||
key={`desktop-${sidebarLink.name}`}
|
||||
key={`desktop-${sidebarLink.messagesKey}`}
|
||||
href={sidebarLink.href}
|
||||
as={sidebarLink.as}
|
||||
>
|
||||
@@ -208,7 +225,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
`}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
{sidebarLink.name}
|
||||
<FormattedMessage
|
||||
{...messages[sidebarLink.messagesKey]}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
|
@@ -30,6 +30,8 @@ const messages = defineMessages({
|
||||
overview: 'Overview',
|
||||
runtime: '{minutes} minutes',
|
||||
cast: 'Cast',
|
||||
recommendations: 'Recommendations',
|
||||
similar: 'Similar Titles',
|
||||
});
|
||||
|
||||
interface MovieDetailsProps {
|
||||
@@ -280,14 +282,16 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
</div>
|
||||
<div className="w-full md:w-80 mt-8 md:mt-0">
|
||||
<div className="bg-cool-gray-900 rounded-lg shadow border border-cool-gray-800">
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.userrating} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
{data.voteAverage}/10
|
||||
</span>
|
||||
</div>
|
||||
{data.voteCount > 0 && (
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.userrating} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
{data.voteAverage}/10
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.releasedate} />
|
||||
@@ -309,30 +313,34 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
{data.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.revenue} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.revenue}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.budget} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.budget}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{data.revenue > 0 && (
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.revenue} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.revenue}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{data.budget > 0 && (
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.budget} />
|
||||
</span>
|
||||
<span className="flex-1 text-right text-cool-gray-400 text-sm">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.budget}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex px-4 py-2 border-b border-cool-gray-800 last:border-b-0">
|
||||
<span className="text-sm">
|
||||
<FormattedMessage {...messages.originallanguage} />
|
||||
@@ -389,7 +397,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
as={`/movie/${data.id}/recommendations`}
|
||||
>
|
||||
<a className="inline-flex text-xl leading-7 text-cool-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
||||
<span>Recommendations</span>
|
||||
<span>
|
||||
<FormattedMessage {...messages.recommendations} />
|
||||
</span>
|
||||
<svg
|
||||
className="w-6 h-6 ml-2"
|
||||
fill="none"
|
||||
@@ -434,7 +444,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
as={`/movie/${data.id}/similar`}
|
||||
>
|
||||
<a className="inline-flex text-xl leading-7 text-cool-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
||||
<span>Similar Titles</span>
|
||||
<span>
|
||||
<FormattedMessage {...messages.similar} />
|
||||
</span>
|
||||
<svg
|
||||
className="w-6 h-6 ml-2"
|
||||
fill="none"
|
||||
@@ -454,7 +466,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
sliderKey="recommendations"
|
||||
sliderKey="similar"
|
||||
isLoading={!similar && !similarError}
|
||||
isEmpty={false}
|
||||
items={similar?.results.map((title) => (
|
||||
|
@@ -2,6 +2,14 @@ import React from 'react';
|
||||
import Modal from '../Common/Modal';
|
||||
import { useUser } from '../../hooks/useUser';
|
||||
import { Permission } from '../../../server/lib/permissions';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
requestadmin:
|
||||
'Your request will be immediately approved. Do you wish to continue?',
|
||||
cancelrequest:
|
||||
'This will remove your request. Are you sure you want to continue?',
|
||||
});
|
||||
|
||||
interface RequestModalProps {
|
||||
type: 'request' | 'cancel';
|
||||
@@ -18,14 +26,15 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
onOk,
|
||||
title,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
|
||||
let text = hasPermission(Permission.MANAGE_REQUESTS)
|
||||
? 'Your request will be immediately approved. Do you wish to continue?'
|
||||
? intl.formatMessage(messages.requestadmin)
|
||||
: undefined;
|
||||
|
||||
if (type === 'cancel') {
|
||||
text = 'This will remove your request. Are you sure you want to continue?';
|
||||
text = intl.formatMessage(messages.cancelrequest);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
TvResult,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../../server/models/Search';
|
||||
import { useSWRInfinite } from 'swr';
|
||||
import ListView from '../Common/ListView';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
|
||||
interface SearchResult {
|
||||
page: number;
|
||||
@@ -16,6 +17,7 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
const Search: React.FC = () => {
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const router = useRouter();
|
||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||
@@ -25,7 +27,7 @@ const Search: React.FC = () => {
|
||||
|
||||
return `/api/v1/search/?query=${router.query.query}&page=${
|
||||
pageIndex + 1
|
||||
}`;
|
||||
}&language=${locale}`;
|
||||
},
|
||||
{
|
||||
initialSize: 3,
|
||||
|
@@ -1,15 +1,25 @@
|
||||
{
|
||||
"components.Discover.discovermovies": "Discover Movies",
|
||||
"components.Discover.discovertv": "Discover Series",
|
||||
"components.Discover.popularmovies": "Popular Movies",
|
||||
"components.Discover.populartv": "Popular Series",
|
||||
"components.Discover.recentrequests": "Recent Requests",
|
||||
"components.Layout.LanguagePicker.changelanguage": "Change Language",
|
||||
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
|
||||
"components.Layout.Sidebar.dashboard": "Dashboard",
|
||||
"components.Layout.Sidebar.requests": "Requests",
|
||||
"components.Layout.Sidebar.settings": "Settings",
|
||||
"components.MovieDetails.budget": "Budget",
|
||||
"components.MovieDetails.cast": "Cast",
|
||||
"components.MovieDetails.originallanguage": "Original Language",
|
||||
"components.MovieDetails.overview": "Overview",
|
||||
"components.MovieDetails.recommendations": "Recommendations",
|
||||
"components.MovieDetails.releasedate": "Release Date",
|
||||
"components.MovieDetails.revenue": "Revenue",
|
||||
"components.MovieDetails.runtime": "{minutes} minutes",
|
||||
"components.MovieDetails.similar": "Similar Titles",
|
||||
"components.MovieDetails.status": "Status",
|
||||
"components.MovieDetails.userrating": "User Rating"
|
||||
"components.MovieDetails.userrating": "User Rating",
|
||||
"components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?",
|
||||
"components.RequestModal.requestadmin": "Your request will be immediately approved. Do you wish to continue?"
|
||||
}
|
||||
|
@@ -1,15 +1,25 @@
|
||||
{
|
||||
"components.Discover.popularmovies": "人気映画",
|
||||
"components.Discover.populartv": "人気テレビ番組",
|
||||
"components.Discover.discovermovies": "人気の映画",
|
||||
"components.Discover.discovertv": "人気のテレビ番組",
|
||||
"components.Discover.popularmovies": "人気の映画",
|
||||
"components.Discover.populartv": "人気のテレビ番組",
|
||||
"components.Discover.recentrequests": "最近のリクエスト",
|
||||
"components.Layout.LanguagePicker.changelanguage": "言語",
|
||||
"components.Layout.SearchInput.searchPlaceholder": "作品名で検索",
|
||||
"components.Layout.Sidebar.dashboard": "ホーム",
|
||||
"components.Layout.Sidebar.requests": "リクエスト",
|
||||
"components.Layout.Sidebar.settings": "設定",
|
||||
"components.MovieDetails.budget": "興行収入",
|
||||
"components.MovieDetails.cast": "キャスト",
|
||||
"components.MovieDetails.originallanguage": "言語",
|
||||
"components.MovieDetails.overview": "ストーリー",
|
||||
"components.MovieDetails.recommendations": "オススメの作品",
|
||||
"components.MovieDetails.releasedate": "公開日",
|
||||
"components.MovieDetails.revenue": "製作費",
|
||||
"components.MovieDetails.runtime": "{minutes}分",
|
||||
"components.MovieDetails.similar": "関連作品",
|
||||
"components.MovieDetails.status": "状態",
|
||||
"components.MovieDetails.userrating": "ユーザー評価"
|
||||
"components.MovieDetails.userrating": "ユーザー評価",
|
||||
"components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?",
|
||||
"components.RequestModal.requestadmin": "このリクエストが今すぐ承認致します。よろしいですか?"
|
||||
}
|
||||
|
@@ -85,22 +85,12 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
|
||||
};
|
||||
|
||||
CoreApp.getInitialProps = async (initialProps) => {
|
||||
// Run the default getInitialProps for the main nextjs initialProps
|
||||
const appInitialProps: AppInitialProps = await App.getInitialProps(
|
||||
initialProps
|
||||
);
|
||||
const { ctx, router } = initialProps;
|
||||
let user = undefined;
|
||||
|
||||
let locale = 'en';
|
||||
|
||||
if (ctx.res) {
|
||||
const cookies = parseCookies(ctx);
|
||||
|
||||
if (cookies.locale) {
|
||||
locale = cookies.locale;
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to get the user by running a request to the local api
|
||||
const response = await axios.get<User>(
|
||||
@@ -126,8 +116,19 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
ctx.res.end();
|
||||
}
|
||||
}
|
||||
|
||||
const cookies = parseCookies(ctx);
|
||||
|
||||
if (!!cookies.locale) {
|
||||
locale = cookies.locale;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the default getInitialProps for the main nextjs initialProps
|
||||
const appInitialProps: AppInitialProps = await App.getInitialProps(
|
||||
initialProps
|
||||
);
|
||||
|
||||
const messages = await loadLocaleData(locale);
|
||||
|
||||
return { ...appInitialProps, user, messages, locale };
|
||||
|
@@ -19,8 +19,12 @@ MoviePage.getInitialProps = async (ctx) => {
|
||||
const response = await axios.get<MovieDetailsType>(
|
||||
`http://localhost:${process.env.PORT || 3000}/api/v1/movie/${
|
||||
ctx.query.movieId
|
||||
}?language=${cookies.locale}`,
|
||||
{ headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined }
|
||||
}${cookies.locale ? `?language=${cookies.locale}` : ''}`,
|
||||
{
|
||||
headers: ctx.req?.headers?.cookie
|
||||
? { cookie: ctx.req.headers.cookie }
|
||||
: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
|
Reference in New Issue
Block a user