mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(frontend): initial Settings design
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
"react-toast-notifications": "^2.4.0",
|
"react-toast-notifications": "^2.4.0",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
|
"react-use-clipboard": "1.0.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sqlite3": "^5.0.0",
|
"sqlite3": "^5.0.0",
|
||||||
"swagger-ui-express": "^4.1.4",
|
"swagger-ui-express": "^4.1.4",
|
||||||
|
@@ -39,7 +39,7 @@ export interface SonarrSettings extends DVRSettings {
|
|||||||
enableSeasonFolders: boolean;
|
enableSeasonFolders: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainSettings {
|
export interface MainSettings {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
src/components/Settings/CopyButton.tsx
Normal file
38
src/components/Settings/CopyButton.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import useClipboard from 'react-use-clipboard';
|
||||||
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
|
||||||
|
const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => {
|
||||||
|
const [isCopied, setCopied] = useClipboard(textToCopy, {
|
||||||
|
successDuration: 1000,
|
||||||
|
});
|
||||||
|
const { addToast } = useToasts();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCopied) {
|
||||||
|
addToast('Copied API key to clipboard', {
|
||||||
|
appearance: 'info',
|
||||||
|
autoDismiss: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isCopied, addToast]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={setCopied}
|
||||||
|
className="-ml-px relative inline-flex items-center px-4 py-2 border border-cool-gray-500 text-sm leading-5 font-medium text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 text-white"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M8 2a1 1 0 000 2h2a1 1 0 100-2H8z" />
|
||||||
|
<path d="M3 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v6h-4.586l1.293-1.293a1 1 0 00-1.414-1.414l-3 3a1 1 0 000 1.414l3 3a1 1 0 001.414-1.414L10.414 13H15v3a2 2 0 01-2 2H5a2 2 0 01-2-2V5zM15 11h2a1 1 0 110 2h-2v-2z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyButton;
|
66
src/components/Settings/SettingsLayout.tsx
Normal file
66
src/components/Settings/SettingsLayout.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
const SettingsLayout: React.FC = ({ children }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const activeLinkColor =
|
||||||
|
'border-indigo-600 text-indigo-500 focus:outline-none focus:text-indigo-500 focus:border-indigo-500';
|
||||||
|
|
||||||
|
const inactiveLinkColor =
|
||||||
|
'border-transparent text-cool-gray-500 hover:text-cool-gray-400 hover:border-cool-gray-300 focus:outline-none focus:text-cool-gray-4700 focus:border-cool-gray-300';
|
||||||
|
|
||||||
|
const settingsLink = ({
|
||||||
|
text,
|
||||||
|
route,
|
||||||
|
regex,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
route: string;
|
||||||
|
regex: RegExp;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Link href={route}>
|
||||||
|
<a
|
||||||
|
className={`whitespace-no-wrap pb-4 px-1 border-b-2 font-medium text-sm leading-5 ${
|
||||||
|
!!router.pathname.match(regex) ? activeLinkColor : inactiveLinkColor
|
||||||
|
}`}
|
||||||
|
aria-current="page"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="border-b border-cool-gray-600 mt-10">
|
||||||
|
<div className="space-y-4 sm:flex sm:items-baseline sm:space-y-0 sm:space-x-10">
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-white">Settings</h3>
|
||||||
|
<div>
|
||||||
|
<nav className="-mb-px flex space-x-8">
|
||||||
|
{settingsLink({
|
||||||
|
text: 'General Settings',
|
||||||
|
route: '/settings/main',
|
||||||
|
regex: /^\/settings(\/main)?$/,
|
||||||
|
})}
|
||||||
|
{settingsLink({
|
||||||
|
text: 'Plex Settings',
|
||||||
|
route: '/settings/plex',
|
||||||
|
regex: /^\/settings\/plex/,
|
||||||
|
})}
|
||||||
|
{settingsLink({
|
||||||
|
text: 'Services',
|
||||||
|
route: '/settings/services',
|
||||||
|
regex: /^\/settings\/services/,
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 text-white">{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsLayout;
|
63
src/components/Settings/SettingsMain.tsx
Normal file
63
src/components/Settings/SettingsMain.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import LoadingSpinner from '../Common/LoadingSpinner';
|
||||||
|
import type { MainSettings } from '../../../server/lib/settings';
|
||||||
|
import CopyButton from './CopyButton';
|
||||||
|
|
||||||
|
const SettingsMain: React.FC = () => {
|
||||||
|
const { data, error } = useSWR<MainSettings>('/api/v1/settings/main');
|
||||||
|
|
||||||
|
if (!data && !error) {
|
||||||
|
return <LoadingSpinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-cool-gray-200">
|
||||||
|
General Settings
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 max-w-2xl text-sm leading-5 text-cool-gray-500">
|
||||||
|
These are settings related to general Overseerr configuration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 sm:mt-5">
|
||||||
|
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
||||||
|
<label
|
||||||
|
htmlFor="username"
|
||||||
|
className="block text-sm font-medium leading-5 text-cool-gray-400 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
API Key
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<div className="max-w-lg flex rounded-md shadow-sm">
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500"
|
||||||
|
value={data?.apiKey}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<CopyButton textToCopy={data?.apiKey ?? ''} />
|
||||||
|
<button className="-ml-px relative inline-flex items-center px-4 py-2 border border-cool-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsMain;
|
14
src/pages/settings/index.tsx
Normal file
14
src/pages/settings/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
|
import SettingsMain from '../../components/Settings/SettingsMain';
|
||||||
|
|
||||||
|
const SettingsPage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<SettingsLayout>
|
||||||
|
<SettingsMain />
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsPage;
|
14
src/pages/settings/main.tsx
Normal file
14
src/pages/settings/main.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
|
import SettingsMain from '../../components/Settings/SettingsMain';
|
||||||
|
|
||||||
|
const SettingsMainPage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<SettingsLayout>
|
||||||
|
<SettingsMain />
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsMainPage;
|
19
yarn.lock
19
yarn.lock
@@ -3607,6 +3607,13 @@ copy-descriptor@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||||
|
|
||||||
|
copy-to-clipboard@^3.0.8:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
|
||||||
|
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
|
||||||
|
dependencies:
|
||||||
|
toggle-selection "^1.0.6"
|
||||||
|
|
||||||
core-js-compat@^3.6.2:
|
core-js-compat@^3.6.2:
|
||||||
version "3.6.5"
|
version "3.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c"
|
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c"
|
||||||
@@ -8509,6 +8516,13 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.1:
|
|||||||
loose-envify "^1.4.0"
|
loose-envify "^1.4.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
|
react-use-clipboard@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-use-clipboard/-/react-use-clipboard-1.0.1.tgz#9e774b9c8e06a1497838085e3bfe2e1c6c0d6cf6"
|
||||||
|
integrity sha512-c9lYIdyndVF+rMIHUvZSeoqlFw/NsongkrCtbRLnODsuYe1kIV7oIFRjj5H51SBMOWUNbFHPRTfn10LbHswidw==
|
||||||
|
dependencies:
|
||||||
|
copy-to-clipboard "^3.0.8"
|
||||||
|
|
||||||
react@16.13.1:
|
react@16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||||
@@ -9927,6 +9941,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
toggle-selection@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||||
|
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
|
||||||
|
|
||||||
toidentifier@1.0.0:
|
toidentifier@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
|
Reference in New Issue
Block a user