diff --git a/package.json b/package.json
index 54779261d..2297f355a 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"react-spring": "^8.0.27",
"react-toast-notifications": "^2.4.0",
"react-transition-group": "^4.4.1",
+ "react-use-clipboard": "1.0.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.0",
"swagger-ui-express": "^4.1.4",
diff --git a/server/lib/settings.ts b/server/lib/settings.ts
index 50edd6cb1..250871ba7 100644
--- a/server/lib/settings.ts
+++ b/server/lib/settings.ts
@@ -39,7 +39,7 @@ export interface SonarrSettings extends DVRSettings {
enableSeasonFolders: boolean;
}
-interface MainSettings {
+export interface MainSettings {
apiKey: string;
}
diff --git a/src/components/Settings/CopyButton.tsx b/src/components/Settings/CopyButton.tsx
new file mode 100644
index 000000000..c4921a16e
--- /dev/null
+++ b/src/components/Settings/CopyButton.tsx
@@ -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 (
+
+ );
+};
+
+export default CopyButton;
diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx
new file mode 100644
index 000000000..64b095236
--- /dev/null
+++ b/src/components/Settings/SettingsLayout.tsx
@@ -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 (
+
+
+ {text}
+
+
+ );
+ };
+ return (
+ <>
+
+
+
Settings
+
+
+
+
+
+ {children}
+ >
+ );
+};
+
+export default SettingsLayout;
diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx
new file mode 100644
index 000000000..fd1093bca
--- /dev/null
+++ b/src/components/Settings/SettingsMain.tsx
@@ -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('/api/v1/settings/main');
+
+ if (!data && !error) {
+ return ;
+ }
+
+ return (
+ <>
+
+
+ General Settings
+
+
+ These are settings related to general Overseerr configuration.
+
+
+
+ >
+ );
+};
+
+export default SettingsMain;
diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx
new file mode 100644
index 000000000..a3e267e41
--- /dev/null
+++ b/src/pages/settings/index.tsx
@@ -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 (
+
+
+
+ );
+};
+
+export default SettingsPage;
diff --git a/src/pages/settings/main.tsx b/src/pages/settings/main.tsx
new file mode 100644
index 000000000..84b845979
--- /dev/null
+++ b/src/pages/settings/main.tsx
@@ -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 (
+
+
+
+ );
+};
+
+export default SettingsMainPage;
diff --git a/yarn.lock b/yarn.lock
index f42b16e73..af1535dcb 100644
--- a/yarn.lock
+++ b/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"
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:
version "3.6.5"
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"
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:
version "16.13.1"
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"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"