8 changed files with 257 additions and 7 deletions
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import classNames from "classnames"; |
||||
|
||||
export function Divider(props: { marginClass?: string }) { |
||||
return ( |
||||
<hr |
||||
className={classNames( |
||||
"w-full h-px border-0 bg-utils-divider bg-opacity-50", |
||||
props.marginClass ?? "my-8" |
||||
)} |
||||
/> |
||||
); |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
import { Icon, Icons } from "@/components/Icon"; |
||||
import { WideContainer } from "@/components/layout/WideContainer"; |
||||
import { Divider } from "@/components/utils/Divider"; |
||||
import { Heading1 } from "@/components/utils/Text"; |
||||
import { conf } from "@/setup/config"; |
||||
|
||||
import { SubPageLayout } from "./layouts/SubPageLayout"; |
||||
|
||||
// TODO Put all of this not here (when I'm done writing them)
|
||||
|
||||
function SidebarSection(props: { title: string; children: React.ReactNode }) { |
||||
return ( |
||||
<section> |
||||
<p className="text-sm font-bold uppercase text-settings-sidebar-type-secondary mb-2"> |
||||
{props.title} |
||||
</p> |
||||
{props.children} |
||||
</section> |
||||
); |
||||
} |
||||
|
||||
function SidebarLink(props: { children: React.ReactNode; icon: Icons }) { |
||||
return ( |
||||
<div className="w-full px-2 py-1 flex items-center space-x-3"> |
||||
<Icon |
||||
className="text-2xl text-settings-sidebar-type-icon" |
||||
icon={props.icon} |
||||
/> |
||||
<span>{props.children}</span> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
function SettingsSidebar() { |
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const hostname = location.hostname; |
||||
|
||||
return ( |
||||
<div> |
||||
<div className="sticky top-24 text-settings-sidebar-type-inactive"> |
||||
<SidebarSection title="Settings"> |
||||
<SidebarLink icon={Icons.WAND}>Account</SidebarLink> |
||||
</SidebarSection> |
||||
<Divider /> |
||||
<SidebarSection title="App information"> |
||||
<div className="flex justify-between items-center space-x-3"> |
||||
<span>Version</span> |
||||
<span>{conf().APP_VERSION}</span> |
||||
</div> |
||||
<div className="flex justify-between items-center space-x-3"> |
||||
<span>Domain</span> |
||||
<span className="text-right">{hostname}</span> |
||||
</div> |
||||
</SidebarSection> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
function SettingsLayout(props: { children: React.ReactNode }) { |
||||
return ( |
||||
<WideContainer ultraWide> |
||||
<div className="grid grid-cols-[260px,1fr] gap-12"> |
||||
<SettingsSidebar /> |
||||
{props.children} |
||||
</div> |
||||
</WideContainer> |
||||
); |
||||
} |
||||
|
||||
export function SettingsPage() { |
||||
return ( |
||||
<SubPageLayout> |
||||
<SettingsLayout> |
||||
<Heading1>Setting</Heading1> |
||||
</SettingsLayout> |
||||
</SubPageLayout> |
||||
); |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
import { ReactNode } from "react"; |
||||
|
||||
import { Divider } from "@/components/utils/Divider"; |
||||
import { Heading2 } from "@/components/utils/Text"; |
||||
import { conf } from "@/setup/config"; |
||||
|
||||
function ConfigValue(props: { name: string; children?: ReactNode }) { |
||||
return ( |
||||
<> |
||||
<div className="flex"> |
||||
<p className="flex-1 font-bold text-white">{props.name}</p> |
||||
<p>{props.children}</p> |
||||
</div> |
||||
<Divider marginClass="my-3" /> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export function ConfigValuesPart() { |
||||
const normalRouter = conf().NORMAL_ROUTER; |
||||
const appVersion = conf().APP_VERSION; |
||||
|
||||
return ( |
||||
<> |
||||
<Heading2 className="mb-8 mt-12">Configured values</Heading2> |
||||
<ConfigValue name="Routing mode"> |
||||
{normalRouter ? "Normal routing" : "Hash based routing"} |
||||
</ConfigValue> |
||||
<ConfigValue name="Application version">v{appVersion}</ConfigValue> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
import { useState } from "react"; |
||||
import { useAsyncFn } from "react-use"; |
||||
|
||||
import { getMediaDetails } from "@/backend/metadata/tmdb"; |
||||
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; |
||||
import { Button } from "@/components/Button"; |
||||
import { Icon, Icons } from "@/components/Icon"; |
||||
import { Box } from "@/components/layout/Box"; |
||||
import { Spinner } from "@/components/layout/Spinner"; |
||||
import { Heading2 } from "@/components/utils/Text"; |
||||
import { conf } from "@/setup/config"; |
||||
|
||||
export function TMDBTestPart() { |
||||
const tmdbApiKey = conf().TMDB_READ_API_KEY; |
||||
const [status, setStatus] = useState({ |
||||
hasTested: false, |
||||
success: false, |
||||
errorText: "", |
||||
}); |
||||
|
||||
const [testState, runTests] = useAsyncFn(async () => { |
||||
setStatus({ |
||||
hasTested: false, |
||||
success: false, |
||||
errorText: "", |
||||
}); |
||||
|
||||
if (tmdbApiKey.length === 0) { |
||||
return setStatus({ |
||||
hasTested: true, |
||||
success: false, |
||||
errorText: "TMDB api key is not set", |
||||
}); |
||||
} |
||||
const isJWT = tmdbApiKey.split(".").length > 2; |
||||
if (!isJWT) { |
||||
return setStatus({ |
||||
hasTested: true, |
||||
success: false, |
||||
errorText: "TMDB api key is not a read only key", |
||||
}); |
||||
} |
||||
|
||||
try { |
||||
await getMediaDetails("556574", TMDBContentTypes.MOVIE); |
||||
} catch (err) { |
||||
return setStatus({ |
||||
hasTested: true, |
||||
success: false, |
||||
errorText: |
||||
"Failed to call tmdb, double check api key and your internet connection", |
||||
}); |
||||
} |
||||
|
||||
return setStatus({ |
||||
hasTested: true, |
||||
success: true, |
||||
errorText: "", |
||||
}); |
||||
}, [tmdbApiKey, setStatus]); |
||||
|
||||
return ( |
||||
<> |
||||
<Heading2 className="mb-8 mt-12">TMDB tests</Heading2> |
||||
<Box> |
||||
<div className="flex items-center"> |
||||
<div className="flex-1"> |
||||
{!status.hasTested ? ( |
||||
<p>Run the test to validate TMDB</p> |
||||
) : status.success ? ( |
||||
<p className="flex items-center"> |
||||
<Icon |
||||
icon={Icons.CIRCLE_CHECK} |
||||
className="text-video-scraping-success mr-2" |
||||
/> |
||||
TMDB is working as expected |
||||
</p> |
||||
) : ( |
||||
<> |
||||
<p className="text-white font-bold">TMDB is not working</p> |
||||
<p>{status.errorText}</p> |
||||
</> |
||||
)} |
||||
</div> |
||||
<Button theme="purple" onClick={runTests}> |
||||
{testState.loading ? <Spinner className="text-base mr-2" /> : null} |
||||
Test TMDB |
||||
</Button> |
||||
</div> |
||||
</Box> |
||||
</> |
||||
); |
||||
} |
Loading…
Reference in new issue