mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
Compare commits
6 Commits
4e9ba75377
...
feat-exper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2786424f70 | ||
|
|
89c4e119fe | ||
|
|
9d9ce8072a | ||
|
|
65d9efbd8e | ||
|
|
2f14de8545 | ||
|
|
1d5378cf35 |
@@ -1,9 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* @type {import('next').NextConfig}
|
* @type {import('next').NextConfig}
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||||
|
assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||||
env: {
|
env: {
|
||||||
commitTag: process.env.COMMIT_TAG || 'local',
|
commitTag: process.env.COMMIT_TAG || 'local',
|
||||||
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
@@ -19,6 +25,9 @@ module.exports = {
|
|||||||
issuer: /\.(js|ts)x?$/,
|
issuer: /\.(js|ts)x?$/,
|
||||||
use: ['@svgr/webpack'],
|
use: ['@svgr/webpack'],
|
||||||
});
|
});
|
||||||
|
config.resolve.alias['next/image'] = path.resolve(
|
||||||
|
'./src/components/Common/BaseImage/index.ts'
|
||||||
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -154,11 +154,13 @@ app
|
|||||||
});
|
});
|
||||||
if (settings.network.csrfProtection) {
|
if (settings.network.csrfProtection) {
|
||||||
server.use(
|
server.use(
|
||||||
|
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
|
||||||
csurf({
|
csurf({
|
||||||
cookie: {
|
cookie: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: true,
|
sameSite: true,
|
||||||
secure: !dev,
|
secure: !dev,
|
||||||
|
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -174,7 +176,7 @@ app
|
|||||||
// Set up sessions
|
// Set up sessions
|
||||||
const sessionRespository = getRepository(Session);
|
const sessionRespository = getRepository(Session);
|
||||||
server.use(
|
server.use(
|
||||||
'/api',
|
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api`,
|
||||||
session({
|
session({
|
||||||
secret: settings.clientId,
|
secret: settings.clientId,
|
||||||
resave: false,
|
resave: false,
|
||||||
@@ -184,6 +186,7 @@ app
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
|
sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
|
||||||
secure: 'auto',
|
secure: 'auto',
|
||||||
|
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
|
||||||
},
|
},
|
||||||
store: new TypeormStore({
|
store: new TypeormStore({
|
||||||
cleanupLimit: 2,
|
cleanupLimit: 2,
|
||||||
@@ -192,8 +195,13 @@ app
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const apiDocs = YAML.load(API_SPEC_PATH);
|
const apiDocs = YAML.load(API_SPEC_PATH);
|
||||||
server.use('/api-docs', swaggerUi.serve, swaggerUi.setup(apiDocs));
|
|
||||||
server.use(
|
server.use(
|
||||||
|
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api-docs`,
|
||||||
|
swaggerUi.serve,
|
||||||
|
swaggerUi.setup(apiDocs)
|
||||||
|
);
|
||||||
|
server.use(
|
||||||
|
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
|
||||||
OpenApiValidator.middleware({
|
OpenApiValidator.middleware({
|
||||||
apiSpec: API_SPEC_PATH,
|
apiSpec: API_SPEC_PATH,
|
||||||
validateRequests: true,
|
validateRequests: true,
|
||||||
@@ -211,11 +219,12 @@ app
|
|||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
server.use('/api/v1', routes);
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
server.use(`${basePath}/api/v1`, routes);
|
||||||
|
|
||||||
// Do not set cookies so CDNs can cache them
|
// Do not set cookies so CDNs can cache them
|
||||||
server.use('/imageproxy', clearCookies, imageproxy);
|
server.use(`${basePath}/imageproxy`, clearCookies, imageproxy);
|
||||||
server.use('/avatarproxy', clearCookies, avatarproxy);
|
server.use(`${basePath}/avatarproxy`, clearCookies, avatarproxy);
|
||||||
|
|
||||||
server.get('*', (req, res) => handle(req, res));
|
server.get('*', (req, res) => handle(req, res));
|
||||||
server.use(
|
server.use(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
|
|||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import Error from '@app/pages/_error';
|
import Error from '@app/pages/_error';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
@@ -122,7 +123,7 @@ const Blacklist = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentFilter(e.target.value as Filter);
|
setCurrentFilter(e.target.value as Filter);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
|
|||||||
32
src/components/Common/BaseImage/index.ts
Normal file
32
src/components/Common/BaseImage/index.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// src/components/Common/BaseImage/index.ts
|
||||||
|
import type { ImageProps } from 'next/image';
|
||||||
|
import NextImage from 'next/image';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// Instead of defining our own props, extend from Next's ImageProps
|
||||||
|
const BaseImage = React.forwardRef<HTMLImageElement, ImageProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
|
const modifiedSrc =
|
||||||
|
typeof props.src === 'string' && props.src.startsWith('/')
|
||||||
|
? `${basePath}${props.src}`
|
||||||
|
: props.src;
|
||||||
|
|
||||||
|
const shouldUnoptimize =
|
||||||
|
typeof props.src === 'string' && props.src.endsWith('.svg');
|
||||||
|
|
||||||
|
return React.createElement(NextImage, {
|
||||||
|
...props,
|
||||||
|
ref,
|
||||||
|
src: modifiedSrc,
|
||||||
|
unoptimized: shouldUnoptimize || props.unoptimized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BaseImage.displayName = 'Image';
|
||||||
|
|
||||||
|
export default BaseImage;
|
||||||
|
// Re-export ImageProps type for consumers
|
||||||
|
export type { ImageProps };
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import useSettings from '@app/hooks/useSettings';
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import type { ImageLoader, ImageProps } from 'next/image';
|
import type { ImageLoader, ImageProps } from 'next/image';
|
||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
const imageLoader: ImageLoader = ({ src }) => src;
|
const imageLoader: ImageLoader = ({ src }) => src;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useUser } from '@app/hooks/useUser';
|
import { useUser } from '@app/hooks/useUser';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import type { Permission } from '@server/lib/permissions';
|
import type { Permission } from '@server/lib/permissions';
|
||||||
import { hasPermission } from '@server/lib/permissions';
|
import { hasPermission } from '@server/lib/permissions';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@@ -85,10 +86,10 @@ const SettingsTabs = ({
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
router.push(e.target.value);
|
router.push(getBasedPath(e.target.value));
|
||||||
}}
|
}}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
router.push(e.target.value);
|
router.push(getBasedPath(e.target.value));
|
||||||
}}
|
}}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
settingsRoutes.find((route) => !!router.pathname.match(route.regex))
|
settingsRoutes.find((route) => !!router.pathname.match(route.regex))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
|
|||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import ErrorPage from '@app/pages/_error';
|
import ErrorPage from '@app/pages/_error';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import {
|
import {
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
@@ -166,7 +167,7 @@ const IssueDetails = () => {
|
|||||||
appearance: 'success',
|
appearance: 'success',
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
});
|
});
|
||||||
router.push('/issues');
|
router.push(getBasedPath('/issues'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addToast(intl.formatMessage(messages.toastissuedeletefailed), {
|
addToast(intl.formatMessage(messages.toastissuedeletefailed), {
|
||||||
appearance: 'error',
|
appearance: 'error',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import IssueItem from '@app/components/IssueList/IssueItem';
|
|||||||
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import {
|
import {
|
||||||
BarsArrowDownIcon,
|
BarsArrowDownIcon,
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
@@ -107,7 +108,7 @@ const IssueList = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentFilter(e.target.value as Filter);
|
setCurrentFilter(e.target.value as Filter);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
@@ -137,7 +138,7 @@ const IssueList = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentSort(e.target.value as Sort);
|
setCurrentSort(e.target.value as Sort);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Badge from '@app/components/Common/Badge';
|
import Badge from '@app/components/Common/Badge';
|
||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||||
import VersionStatus from '@app/components/Layout/VersionStatus';
|
import VersionStatus from '@app/components/Layout/VersionStatus';
|
||||||
import useClickOutside from '@app/hooks/useClickOutside';
|
import useClickOutside from '@app/hooks/useClickOutside';
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import Image from 'next/image';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { Fragment, useEffect, useRef } from 'react';
|
import { Fragment, useEffect, useRef } from 'react';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import EmbyLogo from '@app/assets/services/emby-icon-only.svg';
|
import EmbyLogo from '@app/assets/services/emby-icon-only.svg';
|
||||||
import JellyfinLogo from '@app/assets/services/jellyfin-icon.svg';
|
import JellyfinLogo from '@app/assets/services/jellyfin-icon.svg';
|
||||||
import PlexLogo from '@app/assets/services/plex.svg';
|
import PlexLogo from '@app/assets/services/plex.svg';
|
||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import ImageFader from '@app/components/Common/ImageFader';
|
import ImageFader from '@app/components/Common/ImageFader';
|
||||||
import PageTitle from '@app/components/Common/PageTitle';
|
import PageTitle from '@app/components/Common/PageTitle';
|
||||||
@@ -11,12 +12,12 @@ import PlexLoginButton from '@app/components/Login/PlexLoginButton';
|
|||||||
import useSettings from '@app/hooks/useSettings';
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import { useUser } from '@app/hooks/useUser';
|
import { useUser } from '@app/hooks/useUser';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import { XCircleIcon } from '@heroicons/react/24/solid';
|
import { XCircleIcon } from '@heroicons/react/24/solid';
|
||||||
import { MediaServerType } from '@server/constants/server';
|
import { MediaServerType } from '@server/constants/server';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useRouter } from 'next/dist/client/router';
|
import { useRouter } from 'next/dist/client/router';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { CSSTransition, SwitchTransition } from 'react-transition-group';
|
import { CSSTransition, SwitchTransition } from 'react-transition-group';
|
||||||
@@ -44,6 +45,8 @@ const Login = () => {
|
|||||||
settings.currentSettings.mediaServerLogin
|
settings.currentSettings.mediaServerLogin
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
||||||
// We take the token and attempt to sign in. If we get a success message, we will
|
// We take the token and attempt to sign in. If we get a success message, we will
|
||||||
// ask swr to revalidate the user which _should_ come back with a valid user.
|
// ask swr to revalidate the user which _should_ come back with a valid user.
|
||||||
@@ -71,7 +74,7 @@ const Login = () => {
|
|||||||
// valid user, we redirect the user to the home page as the login was successful.
|
// valid user, we redirect the user to the home page as the login was successful.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
router.push('/');
|
router.push(getBasedPath('/'));
|
||||||
}
|
}
|
||||||
}, [user, router]);
|
}, [user, router]);
|
||||||
|
|
||||||
@@ -129,7 +132,7 @@ const Login = () => {
|
|||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src="/os_icon.svg"
|
src={basePath + '/os_icon.svg'}
|
||||||
alt={settings.currentSettings.applicationTitle}
|
alt={settings.currentSettings.applicationTitle}
|
||||||
className="mr-2 h-5"
|
className="mr-2 h-5"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import globalMessages from '@app/i18n/globalMessages';
|
|||||||
import ErrorPage from '@app/pages/_error';
|
import ErrorPage from '@app/pages/_error';
|
||||||
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||||
import {
|
import {
|
||||||
ArrowRightCircleIcon,
|
ArrowRightCircleIcon,
|
||||||
@@ -468,7 +469,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowManager(false);
|
setShowManager(false);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: { movieId: router.query.movieId },
|
query: { movieId: router.query.movieId },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3,153 +3,154 @@ interface PWAHeaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => {
|
const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => {
|
||||||
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
sizes="180x180"
|
sizes="180x180"
|
||||||
href="/apple-touch-icon.png"
|
href={`${basePath}/apple-touch-icon.png`}
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
sizes="32x32"
|
sizes="32x32"
|
||||||
href="/favicon-32x32.png"
|
href={`${basePath}/favicon-32x32.png`}
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
sizes="16x16"
|
sizes="16x16"
|
||||||
href="/favicon-16x16.png"
|
href={`${basePath}/favicon-16x16.png`}
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2048-2732.jpg"
|
href={`${basePath}/apple-splash-2048-2732.jpg`}
|
||||||
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2732-2048.jpg"
|
href={`${basePath}/apple-splash-2732-2048.jpg`}
|
||||||
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1668-2388.jpg"
|
href={`${basePath}/apple-splash-1668-2388.jpg`}
|
||||||
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2388-1668.jpg"
|
href={`${basePath}/apple-splash-2388-1668.jpg`}
|
||||||
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1536-2048.jpg"
|
href={`${basePath}/apple-splash-1536-2048.jpg`}
|
||||||
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2048-1536.jpg"
|
href={`${basePath}/apple-splash-2048-1536.jpg`}
|
||||||
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1668-2224.jpg"
|
href={`${basePath}/apple-splash-1668-2224.jpg`}
|
||||||
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2224-1668.jpg"
|
href={`${basePath}/apple-splash-2224-1668.jpg`}
|
||||||
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1620-2160.jpg"
|
href={`${basePath}/apple-splash-1620-2160.jpg`}
|
||||||
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2160-1620.jpg"
|
href={`${basePath}/apple-splash-2160-1620.jpg`}
|
||||||
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1284-2778.jpg"
|
href={`${basePath}/apple-splash-1284-2778.jpg`}
|
||||||
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2778-1284.jpg"
|
href={`${basePath}/apple-splash-2778-1284.jpg`}
|
||||||
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1170-2532.jpg"
|
href={`${basePath}/apple-splash-1170-2532.jpg`}
|
||||||
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2532-1170.jpg"
|
href={`${basePath}/apple-splash-2532-1170.jpg`}
|
||||||
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1125-2436.jpg"
|
href={`${basePath}/apple-splash-1125-2436.jpg`}
|
||||||
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2436-1125.jpg"
|
href={`${basePath}/apple-splash-2436-1125.jpg`}
|
||||||
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1242-2688.jpg"
|
href={`${basePath}/apple-splash-1242-2688.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2688-1242.jpg"
|
href={`${basePath}/apple-splash-2688-1242.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-828-1792.jpg"
|
href={`${basePath}/apple-splash-828-1792.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1792-828.jpg"
|
href={`${basePath}/apple-splash-1792-828.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1242-2208.jpg"
|
href={`${basePath}/apple-splash-1242-2208.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-2208-1242.jpg"
|
href={`${basePath}/apple-splash-2208-1242.jpg`}
|
||||||
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-750-1334.jpg"
|
href={`${basePath}/apple-splash-750-1334.jpg`}
|
||||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1334-750.jpg"
|
href={`${basePath}/apple-splash-1334-750.jpg`}
|
||||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-640-1136.jpg"
|
href={`${basePath}/apple-splash-640-1136.jpg`}
|
||||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-startup-image"
|
rel="apple-touch-startup-image"
|
||||||
href="/apple-splash-1136-640.jpg"
|
href={`${basePath}/apple-splash-1136-640.jpg`}
|
||||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||||
/>
|
/>
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
@@ -159,7 +160,7 @@ const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => {
|
|||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="manifest"
|
rel="manifest"
|
||||||
href="/site.webmanifest"
|
href={`${basePath}/site.webmanifest`}
|
||||||
crossOrigin="use-credentials"
|
crossOrigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<meta name="apple-mobile-web-app-title" content={applicationTitle} />
|
<meta name="apple-mobile-web-app-title" content={applicationTitle} />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
|||||||
import { useUser } from '@app/hooks/useUser';
|
import { useUser } from '@app/hooks/useUser';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
@@ -173,7 +174,7 @@ const RequestList = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentMediaType(e.target.value as MediaType);
|
setCurrentMediaType(e.target.value as MediaType);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
@@ -203,7 +204,7 @@ const RequestList = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentFilter(e.target.value as Filter);
|
setCurrentFilter(e.target.value as Filter);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
@@ -251,7 +252,7 @@ const RequestList = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentSort(e.target.value as Sort);
|
setCurrentSort(e.target.value as Sort);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: router.query.userId
|
query: router.query.userId
|
||||||
? { userId: router.query.userId }
|
? { userId: router.query.userId }
|
||||||
: {},
|
: {},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import ImageFader from '@app/components/Common/ImageFader';
|
import ImageFader from '@app/components/Common/ImageFader';
|
||||||
import PageTitle from '@app/components/Common/PageTitle';
|
import PageTitle from '@app/components/Common/PageTitle';
|
||||||
@@ -6,7 +7,6 @@ import defineMessages from '@app/utils/defineMessages';
|
|||||||
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
|
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Field, Form, Formik } from 'formik';
|
import { Field, Form, Formik } from 'formik';
|
||||||
import Image from 'next/image';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import ImageFader from '@app/components/Common/ImageFader';
|
import ImageFader from '@app/components/Common/ImageFader';
|
||||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||||
@@ -7,7 +8,6 @@ import defineMessages from '@app/utils/defineMessages';
|
|||||||
import { LifebuoyIcon } from '@heroicons/react/24/outline';
|
import { LifebuoyIcon } from '@heroicons/react/24/outline';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Form, Formik } from 'formik';
|
import { Form, Formik } from 'formik';
|
||||||
import Image from 'next/image';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
|||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import Error from '@app/pages/_error';
|
import Error from '@app/pages/_error';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
@@ -286,7 +287,7 @@ const SettingsLogs = () => {
|
|||||||
name="filter"
|
name="filter"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentFilter(e.target.value as Filter);
|
setCurrentFilter(e.target.value as Filter);
|
||||||
router.push(router.pathname);
|
router.push(getBasedPath(router.pathname));
|
||||||
}}
|
}}
|
||||||
value={currentFilter}
|
value={currentFilter}
|
||||||
className="rounded-r-only"
|
className="rounded-r-only"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import EmbyLogo from '@app/assets/services/emby.svg';
|
|||||||
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
||||||
import PlexLogo from '@app/assets/services/plex.svg';
|
import PlexLogo from '@app/assets/services/plex.svg';
|
||||||
import AppDataWarning from '@app/components/AppDataWarning';
|
import AppDataWarning from '@app/components/AppDataWarning';
|
||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import ImageFader from '@app/components/Common/ImageFader';
|
import ImageFader from '@app/components/Common/ImageFader';
|
||||||
import PageTitle from '@app/components/Common/PageTitle';
|
import PageTitle from '@app/components/Common/PageTitle';
|
||||||
@@ -13,10 +14,10 @@ import SetupSteps from '@app/components/Setup/SetupSteps';
|
|||||||
import useLocale from '@app/hooks/useLocale';
|
import useLocale from '@app/hooks/useLocale';
|
||||||
import useSettings from '@app/hooks/useSettings';
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { MediaServerType } from '@server/constants/server';
|
import { MediaServerType } from '@server/constants/server';
|
||||||
import type { Library } from '@server/lib/settings';
|
import type { Library } from '@server/lib/settings';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
@@ -67,7 +68,7 @@ const Setup = () => {
|
|||||||
await axios.post('/api/v1/settings/main', { locale });
|
await axios.post('/api/v1/settings/main', { locale });
|
||||||
mutate('/api/v1/settings/public');
|
mutate('/api/v1/settings/public');
|
||||||
|
|
||||||
router.push('/');
|
router.push(getBasedPath('/'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ const Setup = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (settings.currentSettings.initialized) {
|
if (settings.currentSettings.initialized) {
|
||||||
router.push('/');
|
router.push(getBasedPath('/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import globalMessages from '@app/i18n/globalMessages';
|
|||||||
import Error from '@app/pages/_error';
|
import Error from '@app/pages/_error';
|
||||||
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||||
import { Disclosure, Transition } from '@headlessui/react';
|
import { Disclosure, Transition } from '@headlessui/react';
|
||||||
import {
|
import {
|
||||||
@@ -520,7 +521,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowManager(false);
|
setShowManager(false);
|
||||||
router.push({
|
router.push({
|
||||||
pathname: router.pathname,
|
pathname: getBasedPath(router.pathname),
|
||||||
query: { tvId: router.query.tvId },
|
query: { tvId: router.query.tvId },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Alert from '@app/components/Common/Alert';
|
import Alert from '@app/components/Common/Alert';
|
||||||
|
import Image from '@app/components/Common/BaseImage';
|
||||||
import Modal from '@app/components/Common/Modal';
|
import Modal from '@app/components/Common/Modal';
|
||||||
import useSettings from '@app/hooks/useSettings';
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type { User } from '@app/hooks/useUser';
|
|||||||
import { Permission, UserType, useUser } from '@app/hooks/useUser';
|
import { Permission, UserType, useUser } from '@app/hooks/useUser';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import {
|
import {
|
||||||
BarsArrowDownIcon,
|
BarsArrowDownIcon,
|
||||||
@@ -550,7 +551,7 @@ const UserList = () => {
|
|||||||
name="sort"
|
name="sort"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentSort(e.target.value as Sort);
|
setCurrentSort(e.target.value as Sort);
|
||||||
router.push(router.pathname);
|
router.push(getBasedPath(router.pathname));
|
||||||
}}
|
}}
|
||||||
value={currentSort}
|
value={currentSort}
|
||||||
className="rounded-r-only"
|
className="rounded-r-only"
|
||||||
@@ -717,7 +718,7 @@ const UserList = () => {
|
|||||||
className="mr-2"
|
className="mr-2"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
'/users/[userId]/settings',
|
getBasedPath('/users/[userId]/settings'),
|
||||||
`/users/${user.id}/settings`
|
`/users/${user.id}/settings`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
|
|||||||
const { user, error, revalidate } = useUser({ initialData: initialUser });
|
const { user, error, revalidate } = useUser({ initialData: initialUser });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const routing = useRef(false);
|
const routing = useRef(false);
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
revalidate();
|
revalidate();
|
||||||
@@ -29,9 +30,9 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
|
|||||||
!routing.current
|
!routing.current
|
||||||
) {
|
) {
|
||||||
routing.current = true;
|
routing.current = true;
|
||||||
location.href = '/login';
|
location.href = `${API_BASE}/login`;
|
||||||
}
|
}
|
||||||
}, [router, user, error]);
|
}, [router, user, error, API_BASE]);
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ const useDiscover = <
|
|||||||
)
|
)
|
||||||
.join('&');
|
.join('&');
|
||||||
|
|
||||||
return `${endpoint}?${finalQueryString}`;
|
const fullEndpoint = endpoint.startsWith('/') ? `${endpoint}` : endpoint;
|
||||||
|
return `${fullEndpoint}?${finalQueryString}`;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialSize: 3,
|
initialSize: 3,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import type { Permission, PermissionCheckOptions } from './useUser';
|
import type { Permission, PermissionCheckOptions } from './useUser';
|
||||||
@@ -12,7 +13,7 @@ const useRouteGuard = (
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && !hasPermission(permission, options)) {
|
if (user && !hasPermission(permission, options)) {
|
||||||
router.push('/');
|
router.push(getBasedPath('/'));
|
||||||
}
|
}
|
||||||
}, [user, permission, router, hasPermission, options]);
|
}, [user, permission, router, hasPermission, options]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||||
import type { NextRouter } from 'next/router';
|
import type { NextRouter } from 'next/router';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type { ParsedUrlQuery } from 'querystring';
|
import type { ParsedUrlQuery } from 'querystring';
|
||||||
@@ -106,9 +107,15 @@ export const useQueryParams = (): UseQueryParamReturnedFunction => {
|
|||||||
|
|
||||||
if (newRoute.path !== router.asPath) {
|
if (newRoute.path !== router.asPath) {
|
||||||
if (routerAction === 'replace') {
|
if (routerAction === 'replace') {
|
||||||
router.replace(newRoute.pathname, newRoute.path);
|
router.replace(
|
||||||
|
getBasedPath(newRoute.pathname),
|
||||||
|
getBasedPath(newRoute.path)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
router.push(newRoute.pathname, newRoute.path);
|
router.push(
|
||||||
|
getBasedPath(newRoute.pathname),
|
||||||
|
getBasedPath(newRoute.path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -99,6 +99,14 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
axios.interceptors.request.use((config) => {
|
||||||
|
if (config.url?.startsWith('/')) {
|
||||||
|
config.url = `${API_BASE}${config.url}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
// Custom types so we can correctly type our GetInitialProps function
|
// Custom types so we can correctly type our GetInitialProps function
|
||||||
// with our combined user prop
|
// with our combined user prop
|
||||||
// This is specific to _app.tsx. Other pages will not need to do this!
|
// This is specific to _app.tsx. Other pages will not need to do this!
|
||||||
@@ -185,9 +193,9 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
|
|||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRConfig
|
||||||
value={{
|
value={{
|
||||||
fetcher: (url) => axios.get(url).then((res) => res.data),
|
fetcher: async (url) => axios.get(url).then((res) => res.data),
|
||||||
fallback: {
|
fallback: {
|
||||||
'/api/v1/auth/me': user,
|
[`${API_BASE}/api/v1/auth/me`]: user,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -256,7 +264,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
const response = await axios.get<PublicSettingsResponse>(
|
const response = await axios.get<PublicSettingsResponse>(
|
||||||
`http://${process.env.HOST || 'localhost'}:${
|
`http://${process.env.HOST || 'localhost'}:${
|
||||||
process.env.PORT || 5055
|
process.env.PORT || 5055
|
||||||
}/api/v1/settings/public`
|
}${API_BASE}/api/v1/settings/public`
|
||||||
);
|
);
|
||||||
|
|
||||||
currentSettings = response.data;
|
currentSettings = response.data;
|
||||||
@@ -266,7 +274,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
if (!router.pathname.match(/(setup|login\/plex)/)) {
|
if (!router.pathname.match(/(setup|login\/plex)/)) {
|
||||||
ctx.res.writeHead(307, {
|
ctx.res.writeHead(307, {
|
||||||
Location: '/setup',
|
Location: `${API_BASE}/setup`,
|
||||||
});
|
});
|
||||||
ctx.res.end();
|
ctx.res.end();
|
||||||
}
|
}
|
||||||
@@ -276,7 +284,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
const response = await axios.get<User>(
|
const response = await axios.get<User>(
|
||||||
`http://${process.env.HOST || 'localhost'}:${
|
`http://${process.env.HOST || 'localhost'}:${
|
||||||
process.env.PORT || 5055
|
process.env.PORT || 5055
|
||||||
}/api/v1/auth/me`,
|
}${API_BASE}/api/v1/auth/me`,
|
||||||
{
|
{
|
||||||
headers:
|
headers:
|
||||||
ctx.req && ctx.req.headers.cookie
|
ctx.req && ctx.req.headers.cookie
|
||||||
@@ -288,7 +296,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
|
|
||||||
if (router.pathname.match(/(setup|login)/)) {
|
if (router.pathname.match(/(setup|login)/)) {
|
||||||
ctx.res.writeHead(307, {
|
ctx.res.writeHead(307, {
|
||||||
Location: '/',
|
Location: `/`,
|
||||||
});
|
});
|
||||||
ctx.res.end();
|
ctx.res.end();
|
||||||
}
|
}
|
||||||
@@ -298,7 +306,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
// before anything actually renders
|
// before anything actually renders
|
||||||
if (!router.pathname.match(/(login|setup|resetpassword)/)) {
|
if (!router.pathname.match(/(login|setup|resetpassword)/)) {
|
||||||
ctx.res.writeHead(307, {
|
ctx.res.writeHead(307, {
|
||||||
Location: '/login',
|
Location: `${API_BASE}/login`,
|
||||||
});
|
});
|
||||||
ctx.res.end();
|
ctx.res.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ const CollectionPage: NextPage<CollectionPageProps> = ({ collection }) => {
|
|||||||
return <CollectionDetails collection={collection} />;
|
return <CollectionDetails collection={collection} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<
|
export const getServerSideProps: GetServerSideProps<
|
||||||
CollectionPageProps
|
CollectionPageProps
|
||||||
> = async (ctx) => {
|
> = async (ctx) => {
|
||||||
const response = await axios.get<Collection>(
|
const response = await axios.get<Collection>(
|
||||||
`http://${process.env.HOST || 'localhost'}:${
|
`http://${process.env.HOST || 'localhost'}:${
|
||||||
process.env.PORT || 5055
|
process.env.PORT || 5055
|
||||||
}/api/v1/collection/${ctx.query.collectionId}`,
|
}${API_BASE}/api/v1/collection/${ctx.query.collectionId}`,
|
||||||
{
|
{
|
||||||
headers: ctx.req?.headers?.cookie
|
headers: ctx.req?.headers?.cookie
|
||||||
? { cookie: ctx.req.headers.cookie }
|
? { cookie: ctx.req.headers.cookie }
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ const MoviePage: NextPage<MoviePageProps> = ({ movie }) => {
|
|||||||
return <MovieDetails movie={movie} />;
|
return <MovieDetails movie={movie} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
|
export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
|
||||||
ctx
|
ctx
|
||||||
) => {
|
) => {
|
||||||
const response = await axios.get<MovieDetailsType>(
|
const response = await axios.get<MovieDetailsType>(
|
||||||
`http://${process.env.HOST || 'localhost'}:${
|
`http://${process.env.HOST || 'localhost'}:${
|
||||||
process.env.PORT || 5055
|
process.env.PORT || 5055
|
||||||
}/api/v1/movie/${ctx.query.movieId}`,
|
}${API_BASE}/api/v1/movie/${ctx.query.movieId}`,
|
||||||
{
|
{
|
||||||
headers: ctx.req?.headers?.cookie
|
headers: ctx.req?.headers?.cookie
|
||||||
? { cookie: ctx.req.headers.cookie }
|
? { cookie: ctx.req.headers.cookie }
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ const TvPage: NextPage<TvPageProps> = ({ tv }) => {
|
|||||||
return <TvDetails tv={tv} />;
|
return <TvDetails tv={tv} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
|
export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
|
||||||
ctx
|
ctx
|
||||||
) => {
|
) => {
|
||||||
const response = await axios.get<TvDetailsType>(
|
const response = await axios.get<TvDetailsType>(
|
||||||
`http://${process.env.HOST || 'localhost'}:${
|
`http://${process.env.HOST || 'localhost'}:${
|
||||||
process.env.PORT || 5055
|
process.env.PORT || 5055
|
||||||
}/api/v1/tv/${ctx.query.tvId}`,
|
}${API_BASE}/api/v1/tv/${ctx.query.tvId}`,
|
||||||
{
|
{
|
||||||
headers: ctx.req?.headers?.cookie
|
headers: ctx.req?.headers?.cookie
|
||||||
? { cookie: ctx.req.headers.cookie }
|
? { cookie: ctx.req.headers.cookie }
|
||||||
|
|||||||
4
src/utils/navigationUtil.ts
Normal file
4
src/utils/navigationUtil.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const getBasedPath = (path: string) => {
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||||
|
return path.startsWith('/') && path !== '/' ? `${API_BASE}${path}` : path;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user