Compare commits
2 Commits
develop
...
feat-exper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a94aa376 | ||
|
|
7c376a9d0c |
@@ -1,10 +1,16 @@
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||
assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||
env: {
|
||||
commitTag: process.env.COMMIT_TAG || 'local',
|
||||
forceIpv4First: process.env.FORCE_IPV4_FIRST === 'true' ? 'true' : 'false',
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
@@ -20,6 +26,9 @@ module.exports = {
|
||||
issuer: /\.(js|ts)x?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
config.resolve.alias['next/image'] = path.resolve(
|
||||
'./src/components/Common/BaseImage/index.ts'
|
||||
);
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -166,11 +166,13 @@ app
|
||||
});
|
||||
if (settings.main.csrfProtection) {
|
||||
server.use(
|
||||
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
|
||||
csurf({
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: !dev,
|
||||
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -186,7 +188,7 @@ app
|
||||
// Set up sessions
|
||||
const sessionRespository = getRepository(Session);
|
||||
server.use(
|
||||
'/api',
|
||||
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api`,
|
||||
session({
|
||||
secret: settings.clientId,
|
||||
resave: false,
|
||||
@@ -196,6 +198,7 @@ app
|
||||
httpOnly: true,
|
||||
sameSite: settings.main.csrfProtection ? 'strict' : 'lax',
|
||||
secure: 'auto',
|
||||
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
|
||||
},
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
@@ -204,8 +207,13 @@ app
|
||||
})
|
||||
);
|
||||
const apiDocs = YAML.load(API_SPEC_PATH);
|
||||
server.use('/api-docs', swaggerUi.serve, swaggerUi.setup(apiDocs));
|
||||
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({
|
||||
apiSpec: API_SPEC_PATH,
|
||||
validateRequests: true,
|
||||
@@ -223,11 +231,12 @@ app
|
||||
};
|
||||
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
|
||||
server.use('/imageproxy', clearCookies, imageproxy);
|
||||
server.use('/avatarproxy', clearCookies, avatarproxy);
|
||||
server.use(`${basePath}/imageproxy`, clearCookies, imageproxy);
|
||||
server.use(`${basePath}/avatarproxy`, clearCookies, avatarproxy);
|
||||
|
||||
server.get('*', (req, res) => handle(req, res));
|
||||
server.use(
|
||||
|
||||
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 type { ImageLoader, ImageProps } from 'next/image';
|
||||
import Image from 'next/image';
|
||||
|
||||
const imageLoader: ImageLoader = ({ src }) => src;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import type { Permission } from '@server/lib/permissions';
|
||||
import { hasPermission } from '@server/lib/permissions';
|
||||
import Link from 'next/link';
|
||||
@@ -85,10 +86,10 @@ const SettingsTabs = ({
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
router.push(e.target.value);
|
||||
router.push(getBasedPath(e.target.value));
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
router.push(e.target.value);
|
||||
router.push(getBasedPath(e.target.value));
|
||||
}}
|
||||
defaultValue={
|
||||
settingsRoutes.find((route) => !!router.pathname.match(route.regex))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Header from '@app/components/Common/Header';
|
||||
import ListView from '@app/components/Common/ListView';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -7,7 +8,6 @@ import Error from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import type { TvNetwork } from '@server/models/common';
|
||||
import type { TvResult } from '@server/models/Search';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Header from '@app/components/Common/Header';
|
||||
import ListView from '@app/components/Common/ListView';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -7,7 +8,6 @@ import Error from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import type { ProductionCompany } from '@server/models/common';
|
||||
import type { MovieResult } from '@server/models/Search';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import ErrorPage from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import {
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
@@ -174,7 +175,7 @@ const IssueDetails = () => {
|
||||
appearance: 'success',
|
||||
autoDismiss: true,
|
||||
});
|
||||
router.push('/issues');
|
||||
router.push(getBasedPath('/issues'));
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.toastissuedeletefailed), {
|
||||
appearance: 'error',
|
||||
|
||||
@@ -6,6 +6,7 @@ import IssueItem from '@app/components/IssueList/IssueItem';
|
||||
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import {
|
||||
BarsArrowDownIcon,
|
||||
ChevronLeftIcon,
|
||||
@@ -107,7 +108,7 @@ const IssueList = () => {
|
||||
onChange={(e) => {
|
||||
setCurrentFilter(e.target.value as Filter);
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
pathname: getBasedPath(router.pathname),
|
||||
query: router.query.userId
|
||||
? { userId: router.query.userId }
|
||||
: {},
|
||||
@@ -137,7 +138,7 @@ const IssueList = () => {
|
||||
onChange={(e) => {
|
||||
setCurrentSort(e.target.value as Sort);
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
pathname: getBasedPath(router.pathname),
|
||||
query: router.query.userId
|
||||
? { userId: router.query.userId }
|
||||
: {},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||
import VersionStatus from '@app/components/Layout/VersionStatus';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
UsersIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Fragment, useRef } from 'react';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Accordion from '@app/components/Common/Accordion';
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import LanguagePicker from '@app/components/Layout/LanguagePicker';
|
||||
@@ -7,11 +8,11 @@ import PlexLoginButton from '@app/components/PlexLoginButton';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { XCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { useRouter } from 'next/dist/client/router';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
@@ -76,7 +77,7 @@ const Login = () => {
|
||||
// valid user, we redirect the user to the home page as the login was successful.
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
router.push('/');
|
||||
router.push(getBasedPath('/'));
|
||||
}
|
||||
}, [user, router]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import TitleCard from '@app/components/TitleCard';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowRightCircleIcon } from '@heroicons/react/24/solid';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import type { SonarrSeries } from '@server/api/servarr/sonarr';
|
||||
import Image from 'next/image';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -5,7 +6,6 @@ import LanguagePicker from '@app/components/Layout/LanguagePicker';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
@@ -6,7 +7,6 @@ import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { LifebuoyIcon } from '@heroicons/react/24/outline';
|
||||
import { Form, Formik } from 'formik';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -2,6 +2,7 @@ import EmbyLogo from '@app/assets/services/emby.svg';
|
||||
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
||||
import PlexLogo from '@app/assets/services/plex.svg';
|
||||
import AppDataWarning from '@app/components/AppDataWarning';
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -13,9 +14,9 @@ import SetupSteps from '@app/components/Setup/SetupSteps';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { Library } from '@server/lib/settings';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
@@ -77,7 +78,7 @@ const Setup = () => {
|
||||
});
|
||||
if (!mainRes.ok) throw new Error();
|
||||
|
||||
mutate('/api/v1/settings/public');
|
||||
mutate(getBasedPath('/api/v1/settings/public'));
|
||||
router.push('/');
|
||||
}
|
||||
};
|
||||
@@ -85,9 +86,9 @@ const Setup = () => {
|
||||
const validateLibraries = useCallback(async () => {
|
||||
try {
|
||||
const endpointMap: Record<MediaServerType, string> = {
|
||||
[MediaServerType.JELLYFIN]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.EMBY]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.PLEX]: '/api/v1/settings/plex',
|
||||
[MediaServerType.JELLYFIN]: getBasedPath('/api/v1/settings/jellyfin'),
|
||||
[MediaServerType.EMBY]: getBasedPath('/api/v1/settings/jellyfin'),
|
||||
[MediaServerType.PLEX]: getBasedPath('/api/v1/settings/plex'),
|
||||
[MediaServerType.NOT_CONFIGURED]: '',
|
||||
};
|
||||
|
||||
@@ -121,7 +122,7 @@ const Setup = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.currentSettings.initialized) {
|
||||
router.push('/');
|
||||
router.push(getBasedPath('/'));
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import AirDateBadge from '@app/components/AirDateBadge';
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import type { SeasonWithEpisodes } from '@server/models/Tv';
|
||||
import Image from 'next/image';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import globalMessages from '@app/i18n/globalMessages';
|
||||
import Error from '@app/pages/_error';
|
||||
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||
import { Disclosure, Transition } from '@headlessui/react';
|
||||
import {
|
||||
@@ -533,7 +534,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
onClose={() => {
|
||||
setShowManager(false);
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
pathname: getBasedPath(router.pathname),
|
||||
query: { tvId: router.query.tvId },
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Image from '@app/components/Common/BaseImage';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import Image from 'next/image';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
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 globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import {
|
||||
BarsArrowDownIcon,
|
||||
@@ -558,7 +559,7 @@ const UserList = () => {
|
||||
name="sort"
|
||||
onChange={(e) => {
|
||||
setCurrentSort(e.target.value as Sort);
|
||||
router.push(router.pathname);
|
||||
router.push(getBasedPath(router.pathname));
|
||||
}}
|
||||
value={currentSort}
|
||||
className="rounded-r-only"
|
||||
|
||||
@@ -17,6 +17,7 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
|
||||
const { user, error, revalidate } = useUser({ initialData: initialUser });
|
||||
const router = useRouter();
|
||||
const routing = useRef(false);
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
useEffect(() => {
|
||||
revalidate();
|
||||
@@ -29,7 +30,7 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
|
||||
!routing.current
|
||||
) {
|
||||
routing.current = true;
|
||||
location.href = '/login';
|
||||
location.href = `${API_BASE}/login`;
|
||||
}
|
||||
}, [router, user, error]);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ const useDiscover = <
|
||||
{ hideAvailable = true } = {}
|
||||
): DiscoverResult<T, S> => {
|
||||
const settings = useSettings();
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
const { data, error, size, setSize, isValidating, mutate } = useSWRInfinite<
|
||||
BaseSearchResult<T> & S
|
||||
>(
|
||||
@@ -76,7 +77,10 @@ const useDiscover = <
|
||||
)
|
||||
.join('&');
|
||||
|
||||
return `${endpoint}?${finalQueryString}`;
|
||||
const fullEndpoint = endpoint.startsWith('/')
|
||||
? `${basePath}${endpoint}`
|
||||
: endpoint;
|
||||
return `${fullEndpoint}?${finalQueryString}`;
|
||||
},
|
||||
{
|
||||
initialSize: 3,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import type { Permission, PermissionCheckOptions } from './useUser';
|
||||
@@ -12,7 +13,7 @@ const useRouteGuard = (
|
||||
|
||||
useEffect(() => {
|
||||
if (user && !hasPermission(permission, options)) {
|
||||
router.push('/');
|
||||
router.push(getBasedPath('/'));
|
||||
}
|
||||
}, [user, permission, router, hasPermission, options]);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import type { Nullable } from '@app/utils/typeHelpers';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
@@ -35,7 +36,7 @@ const useSearchInput = (): SearchObject => {
|
||||
if (debouncedValue !== '' && searchOpen) {
|
||||
if (router.pathname.startsWith('/search')) {
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
pathname: getBasedPath(router.pathname),
|
||||
query: {
|
||||
...router.query,
|
||||
query: debouncedValue,
|
||||
@@ -45,7 +46,7 @@ const useSearchInput = (): SearchObject => {
|
||||
setLastRoute(router.asPath);
|
||||
router
|
||||
.push({
|
||||
pathname: '/search',
|
||||
pathname: getBasedPath('/search'),
|
||||
query: { query: debouncedValue },
|
||||
})
|
||||
.then(() => window.scrollTo(0, 0));
|
||||
@@ -66,9 +67,14 @@ const useSearchInput = (): SearchObject => {
|
||||
!searchOpen
|
||||
) {
|
||||
if (lastRoute) {
|
||||
router.push(lastRoute).then(() => window.scrollTo(0, 0));
|
||||
const route =
|
||||
typeof lastRoute === 'string'
|
||||
? getBasedPath(lastRoute)
|
||||
: getBasedPath(lastRoute.pathname || '/');
|
||||
|
||||
router.push(route).then(() => window.scrollTo(0, 0));
|
||||
} else {
|
||||
router.replace('/').then(() => window.scrollTo(0, 0));
|
||||
router.replace(getBasedPath('/')).then(() => window.scrollTo(0, 0));
|
||||
}
|
||||
}
|
||||
}, [searchOpen]);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getBasedPath } from '@app/utils/navigationUtil';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ParsedUrlQuery } from 'querystring';
|
||||
@@ -106,9 +107,15 @@ export const useQueryParams = (): UseQueryParamReturnedFunction => {
|
||||
|
||||
if (newRoute.path !== router.asPath) {
|
||||
if (routerAction === 'replace') {
|
||||
router.replace(newRoute.pathname, newRoute.path);
|
||||
router.replace(
|
||||
getBasedPath(newRoute.pathname),
|
||||
getBasedPath(newRoute.path)
|
||||
);
|
||||
} else {
|
||||
router.push(newRoute.pathname, newRoute.path);
|
||||
router.push(
|
||||
getBasedPath(newRoute.pathname),
|
||||
getBasedPath(newRoute.path)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -142,12 +142,17 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: async (resource, init) => {
|
||||
const res = await fetch(resource, init);
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
const fullUrl =
|
||||
resource.startsWith('/') && !resource.startsWith(API_BASE)
|
||||
? `${API_BASE}${resource}`
|
||||
: resource;
|
||||
const res = await fetch(fullUrl, init);
|
||||
if (!res.ok) throw new Error();
|
||||
return await res.json();
|
||||
},
|
||||
fallback: {
|
||||
'/api/v1/auth/me': user,
|
||||
[`${process.env.NEXT_PUBLIC_BASE_PATH}/api/v1/auth/me`]: user,
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -207,11 +212,14 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
emailEnabled: false,
|
||||
newPlexLogin: true,
|
||||
};
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
if (ctx.res) {
|
||||
// Check if app is initialized and redirect if necessary
|
||||
const res = await fetch(
|
||||
`http://localhost:${process.env.PORT || 5055}/api/v1/settings/public`
|
||||
`http://localhost:${
|
||||
process.env.PORT || 5055
|
||||
}${API_BASE}/api/v1/settings/public`
|
||||
);
|
||||
if (!res.ok) throw new Error();
|
||||
currentSettings = await res.json();
|
||||
@@ -221,7 +229,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
if (!initialized) {
|
||||
if (!router.pathname.match(/(setup|login\/plex)/)) {
|
||||
ctx.res.writeHead(307, {
|
||||
Location: '/setup',
|
||||
Location: `${API_BASE}/setup`,
|
||||
});
|
||||
ctx.res.end();
|
||||
}
|
||||
@@ -229,7 +237,9 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
try {
|
||||
// Attempt to get the user by running a request to the local api
|
||||
const res = await fetch(
|
||||
`http://localhost:${process.env.PORT || 5055}/api/v1/auth/me`,
|
||||
`http://localhost:${
|
||||
process.env.PORT || 5055
|
||||
}${API_BASE}/api/v1/auth/me`,
|
||||
{
|
||||
headers:
|
||||
ctx.req && ctx.req.headers.cookie
|
||||
@@ -242,7 +252,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
|
||||
if (router.pathname.match(/(setup|login)/)) {
|
||||
ctx.res.writeHead(307, {
|
||||
Location: '/',
|
||||
Location: `/`,
|
||||
});
|
||||
ctx.res.end();
|
||||
}
|
||||
@@ -252,7 +262,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
// before anything actually renders
|
||||
if (!router.pathname.match(/(login|setup|resetpassword)/)) {
|
||||
ctx.res.writeHead(307, {
|
||||
Location: '/login',
|
||||
Location: `${API_BASE}/login`,
|
||||
});
|
||||
ctx.res.end();
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ const MoviePage: NextPage<MoviePageProps> = ({ movie }) => {
|
||||
return <MovieDetails movie={movie} />;
|
||||
};
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
|
||||
ctx
|
||||
) => {
|
||||
const res = await fetch(
|
||||
`http://localhost:${process.env.PORT || 5055}/api/v1/movie/${
|
||||
`http://localhost:${process.env.PORT || 5055}${API_BASE}/api/v1/movie/${
|
||||
ctx.query.movieId
|
||||
}`,
|
||||
{
|
||||
|
||||
@@ -10,11 +10,15 @@ const TvPage: NextPage<TvPageProps> = ({ tv }) => {
|
||||
return <TvDetails tv={tv} />;
|
||||
};
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
|
||||
ctx
|
||||
) => {
|
||||
const res = await fetch(
|
||||
`http://localhost:${process.env.PORT || 5055}/api/v1/tv/${ctx.query.tvId}`,
|
||||
`http://localhost:${process.env.PORT || 5055}${API_BASE}/api/v1/tv/${
|
||||
ctx.query.tvId
|
||||
}`,
|
||||
{
|
||||
headers: ctx.req?.headers?.cookie
|
||||
? { cookie: ctx.req.headers.cookie }
|
||||
|
||||
@@ -18,11 +18,17 @@ const isSameOrigin = (url: RequestInfo | URL): boolean => {
|
||||
// to all requests. This is required when CSRF protection is enabled.
|
||||
if (typeof window !== 'undefined') {
|
||||
const originalFetch: typeof fetch = window.fetch;
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
(window as typeof globalThis).fetch = async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit
|
||||
): Promise<Response> => {
|
||||
let url = input;
|
||||
if (typeof input === 'string' && input.startsWith('/api/')) {
|
||||
url = `${API_BASE}${input}`;
|
||||
}
|
||||
|
||||
if (!isSameOrigin(input)) {
|
||||
return originalFetch(input, init);
|
||||
}
|
||||
@@ -39,7 +45,7 @@ if (typeof window !== 'undefined') {
|
||||
headers,
|
||||
};
|
||||
|
||||
return originalFetch(input, newInit);
|
||||
return originalFetch(url, newInit);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
6
src/utils/fetcher.ts
Normal file
6
src/utils/fetcher.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
|
||||
export const fetcher = (url: string) => {
|
||||
const fullUrl = url.startsWith('/') ? `${API_BASE}${url}` : url;
|
||||
return fetch(fullUrl).then((r) => r.json());
|
||||
};
|
||||
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