mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-07 15:18:08 -05:00
Merge remote-tracking branch 'overseerr/develop' into develop
This commit is contained in:
@@ -1,19 +1,17 @@
|
||||
import type { AvailableLocale } from '@app/context/LanguageContext';
|
||||
import { availableLanguages } from '@app/context/LanguageContext';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { TranslateIcon } from '@heroicons/react/solid';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import {
|
||||
availableLanguages,
|
||||
AvailableLocale,
|
||||
} from '../../../context/LanguageContext';
|
||||
import useClickOutside from '../../../hooks/useClickOutside';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import Transition from '../../Transition';
|
||||
|
||||
const messages = defineMessages({
|
||||
displaylanguage: 'Display Language',
|
||||
});
|
||||
|
||||
const LanguagePicker: React.FC = () => {
|
||||
const LanguagePicker = () => {
|
||||
const intl = useIntl();
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const { locale, setLocale } = useLocale();
|
||||
@@ -34,6 +32,7 @@ const LanguagePicker: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
<Transition
|
||||
as="div"
|
||||
show={isDropdownOpen}
|
||||
enter="transition ease-out duration-100 opacity-0"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BellIcon } from '@heroicons/react/outline';
|
||||
import React from 'react';
|
||||
|
||||
const Notifications: React.FC = () => {
|
||||
const Notifications = () => {
|
||||
return (
|
||||
<button
|
||||
className="rounded-full p-1 text-gray-400 hover:bg-gray-500 hover:text-white focus:text-white focus:outline-none focus:ring"
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import useSearchInput from '@app/hooks/useSearchInput';
|
||||
import { XCircleIcon } from '@heroicons/react/outline';
|
||||
import { SearchIcon } from '@heroicons/react/solid';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSearchInput from '../../../hooks/useSearchInput';
|
||||
|
||||
const messages = defineMessages({
|
||||
searchPlaceholder: 'Search Movies & TV',
|
||||
});
|
||||
|
||||
const SearchInput: React.FC = () => {
|
||||
const SearchInput = () => {
|
||||
const intl = useIntl();
|
||||
const { searchValue, setSearchValue, setIsOpen, clear } = useSearchInput();
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||
import VersionStatus from '@app/components/Layout/VersionStatus';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import {
|
||||
ClockIcon,
|
||||
CogIcon,
|
||||
@@ -8,13 +13,8 @@ import {
|
||||
} from '@heroicons/react/outline';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { ReactNode, useRef } from 'react';
|
||||
import { Fragment, useRef } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useClickOutside from '../../../hooks/useClickOutside';
|
||||
import { Permission, useUser } from '../../../hooks/useUser';
|
||||
import Transition from '../../Transition';
|
||||
import VersionStatus from '../VersionStatus';
|
||||
import UserWarnings from '../UserWarnings';
|
||||
|
||||
const messages = defineMessages({
|
||||
dashboard: 'Discover',
|
||||
@@ -31,12 +31,13 @@ interface SidebarProps {
|
||||
|
||||
interface SidebarLinkProps {
|
||||
href: string;
|
||||
svgIcon: ReactNode;
|
||||
svgIcon: React.ReactNode;
|
||||
messagesKey: keyof typeof messages;
|
||||
activeRegExp: RegExp;
|
||||
as?: string;
|
||||
requiredPermission?: Permission | Permission[];
|
||||
permissionType?: 'and' | 'or';
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
const SidebarLinks: SidebarLinkProps[] = [
|
||||
@@ -72,17 +73,19 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
svgIcon: <UsersIcon className="mr-3 h-6 w-6" />,
|
||||
activeRegExp: /^\/users/,
|
||||
requiredPermission: Permission.MANAGE_USERS,
|
||||
dataTestId: 'sidebar-menu-users',
|
||||
},
|
||||
{
|
||||
href: '/settings',
|
||||
messagesKey: 'settings',
|
||||
svgIcon: <CogIcon className="mr-3 h-6 w-6" />,
|
||||
activeRegExp: /^\/settings/,
|
||||
requiredPermission: Permission.MANAGE_SETTINGS,
|
||||
requiredPermission: Permission.ADMIN,
|
||||
dataTestId: 'sidebar-menu-settings',
|
||||
},
|
||||
];
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
const Sidebar = ({ open, setClosed }: SidebarProps) => {
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
@@ -92,9 +95,10 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="lg:hidden">
|
||||
<Transition show={open}>
|
||||
<Transition as={Fragment} show={open}>
|
||||
<div className="fixed inset-0 z-40 flex">
|
||||
<Transition
|
||||
<Transition.Child
|
||||
as="div"
|
||||
enter="transition-opacity ease-linear duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -105,8 +109,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
<div className="fixed inset-0">
|
||||
<div className="absolute inset-0 bg-gray-900 opacity-90"></div>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition
|
||||
</Transition.Child>
|
||||
<Transition.Child
|
||||
as="div"
|
||||
enter="transition ease-in-out duration-300 transform"
|
||||
enterFrom="-translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
@@ -115,7 +120,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
leaveTo="-translate-x-full"
|
||||
>
|
||||
<>
|
||||
<div className="sidebar relative flex w-full max-w-xs flex-1 flex-col bg-gray-800">
|
||||
<div className="sidebar relative flex h-full w-full max-w-xs flex-1 flex-col bg-gray-800">
|
||||
<div className="sidebar-close-button absolute top-0 right-0 -mr-14 p-1">
|
||||
<button
|
||||
className="flex h-12 w-12 items-center justify-center rounded-full focus:bg-gray-600 focus:outline-none"
|
||||
@@ -127,7 +132,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
</div>
|
||||
<div
|
||||
ref={navRef}
|
||||
className="flex h-0 flex-1 flex-col overflow-y-auto pt-8 pb-8 sm:pb-4"
|
||||
className="flex flex-1 flex-col overflow-y-auto pt-8 pb-8 sm:pb-4"
|
||||
>
|
||||
<div className="flex flex-shrink-0 items-center px-2">
|
||||
<span className="px-4 text-xl text-gray-50">
|
||||
@@ -168,6 +173,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
: 'hover:bg-gray-700 focus:bg-gray-700'
|
||||
}
|
||||
`}
|
||||
data-testid={`${sidebarLink.dataTestId}-mobile`}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
{intl.formatMessage(
|
||||
@@ -193,7 +199,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
{/* <!-- Force sidebar to shrink to fit close icon --> */}
|
||||
</div>
|
||||
</>
|
||||
</Transition>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
@@ -233,6 +239,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
: 'hover:bg-gray-700 focus:bg-gray-700'
|
||||
}
|
||||
`}
|
||||
data-testid={sidebarLink.dataTestId}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
{intl.formatMessage(messages[sidebarLink.messagesKey])}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import Infinity from '@app/assets/infinity.svg';
|
||||
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
|
||||
import ProgressCircle from '@app/components/Common/ProgressCircle';
|
||||
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const messages = defineMessages({
|
||||
movierequests: 'Movie Requests',
|
||||
seriesrequests: 'Series Requests',
|
||||
});
|
||||
|
||||
type MiniQuotaDisplayProps = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
const MiniQuotaDisplay = ({ userId }: MiniQuotaDisplayProps) => {
|
||||
const intl = useIntl();
|
||||
const { data, error } = useSWR<QuotaResponse>(`/api/v1/user/${userId}/quota`);
|
||||
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!data && !error) {
|
||||
return <SmallLoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{((data?.movie.limit ?? 0) !== 0 || (data?.tv.limit ?? 0) !== 0) && (
|
||||
<div className="flex">
|
||||
<div className="flex basis-1/2 flex-col space-y-2">
|
||||
<div className="text-sm text-gray-200">
|
||||
{intl.formatMessage(messages.movierequests)}
|
||||
</div>
|
||||
<div className="flex h-full items-center space-x-2 text-gray-200">
|
||||
{data?.movie.limit ?? 0 > 0 ? (
|
||||
<>
|
||||
<ProgressCircle
|
||||
className="h-8 w-8"
|
||||
progress={Math.round(
|
||||
((data?.movie.remaining ?? 0) /
|
||||
(data?.movie.limit ?? 1)) *
|
||||
100
|
||||
)}
|
||||
useHeatLevel
|
||||
/>
|
||||
<span className="text-lg font-bold">
|
||||
{data?.movie.remaining} / {data?.movie.limit}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Infinity className="w-7" />
|
||||
<span className="font-bold">Unlimited</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex basis-1/2 flex-col space-y-2">
|
||||
<div className="text-sm text-gray-200">
|
||||
{intl.formatMessage(messages.seriesrequests)}
|
||||
</div>
|
||||
<div className="flex h-full items-center space-x-2 text-gray-200">
|
||||
{data?.tv.limit ?? 0 > 0 ? (
|
||||
<>
|
||||
<ProgressCircle
|
||||
className="h-8 w-8"
|
||||
progress={Math.round(
|
||||
((data?.tv.remaining ?? 0) / (data?.tv.limit ?? 1)) * 100
|
||||
)}
|
||||
useHeatLevel
|
||||
/>
|
||||
<span className="text-lg font-bold text-gray-200">
|
||||
{data?.tv.remaining} / {data?.tv.limit}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Infinity className="w-7" />
|
||||
<span className="font-bold">Unlimited</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MiniQuotaDisplay;
|
||||
@@ -1,25 +1,39 @@
|
||||
import { LogoutIcon } from '@heroicons/react/outline';
|
||||
import MiniQuotaDisplay from '@app/components/Layout/UserDropdown/MiniQuotaDisplay';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { ClockIcon, LogoutIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, UserIcon } from '@heroicons/react/solid';
|
||||
import axios from 'axios';
|
||||
import type { LinkProps } from 'next/link';
|
||||
import Link from 'next/link';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { forwardRef, Fragment } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useClickOutside from '../../../hooks/useClickOutside';
|
||||
import { useUser } from '../../../hooks/useUser';
|
||||
import Transition from '../../Transition';
|
||||
|
||||
const messages = defineMessages({
|
||||
myprofile: 'Profile',
|
||||
settings: 'Settings',
|
||||
requests: 'Requests',
|
||||
signout: 'Sign Out',
|
||||
});
|
||||
|
||||
const UserDropdown: React.FC = () => {
|
||||
const ForwardedLink = forwardRef<
|
||||
HTMLAnchorElement,
|
||||
LinkProps & React.ComponentPropsWithoutRef<'a'>
|
||||
>(({ href, children, ...rest }, ref) => {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a ref={ref} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
ForwardedLink.displayName = 'ForwardedLink';
|
||||
|
||||
const UserDropdown = () => {
|
||||
const intl = useIntl();
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const { user, revalidate } = useUser();
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
useClickOutside(dropdownRef, () => setDropdownOpen(false));
|
||||
|
||||
const logout = async () => {
|
||||
const response = await axios.post('/api/v1/auth/logout');
|
||||
@@ -30,86 +44,119 @@ const UserDropdown: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative ml-3">
|
||||
<Menu as="div" className="relative ml-3">
|
||||
<div>
|
||||
<button
|
||||
<Menu.Button
|
||||
className="flex max-w-xs items-center rounded-full text-sm ring-1 ring-gray-700 hover:ring-gray-500 focus:outline-none focus:ring-gray-500"
|
||||
id="user-menu"
|
||||
aria-label="User menu"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(true)}
|
||||
data-testid="user-menu"
|
||||
>
|
||||
<img
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user?.avatar}
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
show={isDropdownOpen}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
appear
|
||||
>
|
||||
<div
|
||||
className="absolute right-0 mt-2 w-48 origin-top-right rounded-md shadow-lg"
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<div
|
||||
className="rounded-md bg-gray-700 py-1 ring-1 ring-black ring-opacity-5"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="user-menu"
|
||||
>
|
||||
<Link href={`/profile`}>
|
||||
<a
|
||||
className="flex items-center px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out hover:bg-gray-600"
|
||||
role="menuitem"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
}}
|
||||
onClick={() => setDropdownOpen(false)}
|
||||
>
|
||||
<UserIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.myprofile)}</span>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/profile/settings`}>
|
||||
<a
|
||||
className="flex items-center px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out hover:bg-gray-600"
|
||||
role="menuitem"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
}}
|
||||
onClick={() => setDropdownOpen(false)}
|
||||
>
|
||||
<CogIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.settings)}</span>
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out hover:bg-gray-600"
|
||||
role="menuitem"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
<LogoutIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.signout)}</span>
|
||||
</a>
|
||||
<Menu.Items className="absolute right-0 mt-2 w-72 origin-top-right rounded-md shadow-lg">
|
||||
<div className="divide-y divide-gray-700 rounded-md bg-gray-800 bg-opacity-80 ring-1 ring-gray-700 backdrop-blur">
|
||||
<div className="flex flex-col space-y-4 px-4 py-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user?.avatar}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="truncate text-xl font-semibold text-gray-200">
|
||||
{user?.displayName}
|
||||
</span>
|
||||
<span className="truncate text-sm text-gray-400">
|
||||
{user?.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{user && <MiniQuotaDisplay userId={user?.id} />}
|
||||
</div>
|
||||
<div className="p-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<ForwardedLink
|
||||
href={`/profile`}
|
||||
className={`flex items-center rounded px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out ${
|
||||
active
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 text-white'
|
||||
: ''
|
||||
}`}
|
||||
data-testid="user-menu-profile"
|
||||
>
|
||||
<UserIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.myprofile)}</span>
|
||||
</ForwardedLink>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<ForwardedLink
|
||||
href={`/users/${user?.id}/requests?filter=all`}
|
||||
className={`flex items-center rounded px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out ${
|
||||
active
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 text-white'
|
||||
: ''
|
||||
}`}
|
||||
data-testid="user-menu-settings"
|
||||
>
|
||||
<ClockIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.requests)}</span>
|
||||
</ForwardedLink>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<ForwardedLink
|
||||
href={`/profile/settings`}
|
||||
className={`flex items-center rounded px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out ${
|
||||
active
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 text-white'
|
||||
: ''
|
||||
}`}
|
||||
data-testid="user-menu-settings"
|
||||
>
|
||||
<CogIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.settings)}</span>
|
||||
</ForwardedLink>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="#"
|
||||
className={`flex items-center rounded px-4 py-2 text-sm font-medium text-gray-200 transition duration-150 ease-in-out ${
|
||||
active
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 text-white'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() => logout()}
|
||||
>
|
||||
<LogoutIcon className="mr-2 inline h-5 w-5" />
|
||||
<span>{intl.formatMessage(messages.signout)}</span>
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { ExclamationIcon } from '@heroicons/react/outline';
|
||||
import Link from 'next/link';
|
||||
import type React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useUser } from '../../../hooks/useUser';
|
||||
|
||||
const messages = defineMessages({
|
||||
emailRequired: 'An email address is required.',
|
||||
|
||||
@@ -4,11 +4,10 @@ import {
|
||||
CodeIcon,
|
||||
ServerIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import type { StatusResponse } from '@server/interfaces/api/settingsInterfaces';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import { StatusResponse } from '../../../../server/interfaces/api/settingsInterfaces';
|
||||
|
||||
const messages = defineMessages({
|
||||
streamdevelop: 'Overseerr Develop',
|
||||
@@ -22,7 +21,7 @@ interface VersionStatusProps {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const VersionStatus: React.FC<VersionStatusProps> = ({ onClick }) => {
|
||||
const VersionStatus = ({ onClick }: VersionStatusProps) => {
|
||||
const intl = useIntl();
|
||||
const { data } = useSWR<StatusResponse>('/api/v1/status', {
|
||||
refreshInterval: 60 * 1000,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import SearchInput from '@app/components/Layout/SearchInput';
|
||||
import Sidebar from '@app/components/Layout/Sidebar';
|
||||
import UserDropdown from '@app/components/Layout/UserDropdown';
|
||||
import type { AvailableLocale } from '@app/context/LanguageContext';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { MenuAlt2Icon } from '@heroicons/react/outline';
|
||||
import { ArrowLeftIcon } from '@heroicons/react/solid';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AvailableLocale } from '../../context/LanguageContext';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { useUser } from '../../hooks/useUser';
|
||||
import SearchInput from './SearchInput';
|
||||
import Sidebar from './Sidebar';
|
||||
import UserDropdown from './UserDropdown';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Layout = ({ children }: LayoutProps) => {
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const { user } = useUser();
|
||||
@@ -69,6 +73,7 @@ const Layout: React.FC = ({ children }) => {
|
||||
} transition duration-300 focus:outline-none lg:hidden`}
|
||||
aria-label="Open sidebar"
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
data-testid="sidebar-toggle"
|
||||
>
|
||||
<MenuAlt2Icon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user