mirror of
https://github.com/movie-web/movie-web.git
synced 2025-01-12 14:39:09 +01:00
error handle pages + migration page
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
7731938729
commit
6a125a593d
@ -67,3 +67,33 @@ export function Button(props: Props) {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ButtonPlainProps {
|
||||||
|
onClick?: () => void;
|
||||||
|
children?: ReactNode;
|
||||||
|
theme?: "white" | "purple" | "secondary";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonPlain(props: ButtonPlainProps) {
|
||||||
|
let colorClasses = "bg-white hover:bg-gray-200 text-black";
|
||||||
|
if (props.theme === "purple")
|
||||||
|
colorClasses =
|
||||||
|
"bg-video-buttons-purple hover:bg-video-buttons-purpleHover text-white";
|
||||||
|
if (props.theme === "secondary")
|
||||||
|
colorClasses =
|
||||||
|
"bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white";
|
||||||
|
|
||||||
|
const classes = classNames(
|
||||||
|
"cursor-pointer inline-flex items-center justify-center rounded-lg font-medium transition-[transform,background-color] duration-100 active:scale-105 md:px-8",
|
||||||
|
"px-4 py-3",
|
||||||
|
props.className,
|
||||||
|
colorClasses
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" onClick={props.onClick} className={classes}>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
import { Component } from "react";
|
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
|
||||||
import { Icons } from "@/components/Icon";
|
|
||||||
import { Link } from "@/components/text/Link";
|
|
||||||
import { Title } from "@/components/text/Title";
|
|
||||||
import { conf } from "@/setup/config";
|
|
||||||
|
|
||||||
interface ErrorShowcaseProps {
|
|
||||||
error: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ErrorShowcase(props: ErrorShowcaseProps) {
|
|
||||||
return (
|
|
||||||
<div className="w-4xl mt-12 max-w-full rounded bg-denim-300 px-6 py-4">
|
|
||||||
<p className="mb-1 break-words font-bold text-white">
|
|
||||||
{props.error.name} - {props.error.description}
|
|
||||||
</p>
|
|
||||||
<p className="break-words">{props.error.path}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorMessageProps {
|
|
||||||
error?: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
localSize?: boolean;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ErrorMessage(props: ErrorMessageProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
props.localSize ? "h-full" : "min-h-screen"
|
|
||||||
} flex w-full flex-col items-center justify-center px-4 py-12`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col items-center justify-start text-center">
|
|
||||||
<IconPatch icon={Icons.WARNING} className="mb-6 text-red-400" />
|
|
||||||
<Title>{t("media.errors.genericTitle")}</Title>
|
|
||||||
{props.children ? (
|
|
||||||
<p className="my-6 max-w-lg">{props.children}</p>
|
|
||||||
) : (
|
|
||||||
<p className="my-6 max-w-lg">
|
|
||||||
<Trans i18nKey="media.errors.videoFailed">
|
|
||||||
<Link url={conf().DISCORD_LINK} newTab />
|
|
||||||
<Link url={conf().GITHUB_LINK} newTab />
|
|
||||||
</Trans>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{props.error ? <ErrorShowcase error={props.error} /> : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorBoundaryState {
|
|
||||||
hasError: boolean;
|
|
||||||
error?: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ErrorBoundary extends Component<
|
|
||||||
Record<string, unknown>,
|
|
||||||
ErrorBoundaryState
|
|
||||||
> {
|
|
||||||
constructor(props: { children: any }) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
hasError: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDerivedStateFromError() {
|
|
||||||
return {
|
|
||||||
hasError: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(error: any, errorInfo: any) {
|
|
||||||
console.error("Render error caught", error, errorInfo);
|
|
||||||
if (error instanceof Error) {
|
|
||||||
const realError: Error = error as Error;
|
|
||||||
this.setState((s) => ({
|
|
||||||
...s,
|
|
||||||
hasError: true,
|
|
||||||
error: {
|
|
||||||
name: realError.name,
|
|
||||||
description: realError.message,
|
|
||||||
path: errorInfo.componentStack.split("\n")[1],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.state.hasError) return this.props.children as any;
|
|
||||||
|
|
||||||
return <ErrorMessage error={this.state.error} />;
|
|
||||||
}
|
|
||||||
}
|
|
@ -144,13 +144,13 @@ export function ProgressBar() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
"relative w-full h-1 bg-video-progress-background bg-opacity-25 rounded-full transition-[height] duration-100 group-hover:h-1.5",
|
"relative w-full h-1 bg-progress-background bg-opacity-25 rounded-full transition-[height] duration-100 group-hover:h-1.5",
|
||||||
dragging ? "!h-1.5" : "",
|
dragging ? "!h-1.5" : "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
{/* Pre-loaded content bar */}
|
{/* Pre-loaded content bar */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 h-full rounded-full bg-video-progress-preloaded bg-opacity-50 flex justify-end items-center"
|
className="absolute top-0 left-0 h-full rounded-full bg-progress-preloaded bg-opacity-50 flex justify-end items-center"
|
||||||
style={{
|
style={{
|
||||||
width: `${(buffered / duration) * 100}%`,
|
width: `${(buffered / duration) * 100}%`,
|
||||||
}}
|
}}
|
||||||
@ -158,7 +158,7 @@ export function ProgressBar() {
|
|||||||
|
|
||||||
{/* Actual progress bar */}
|
{/* Actual progress bar */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 h-full rounded-full bg-video-progress-watched flex justify-end items-center"
|
className="absolute top-0 left-0 h-full rounded-full bg-progress-filled flex justify-end items-center"
|
||||||
style={{
|
style={{
|
||||||
width: `${
|
width: `${
|
||||||
Math.max(
|
Math.max(
|
||||||
|
@ -31,11 +31,15 @@ export function EmbedOption(props: {
|
|||||||
const setSource = usePlayerStore((s) => s.setSource);
|
const setSource = usePlayerStore((s) => s.setSource);
|
||||||
const setSourceId = usePlayerStore((s) => s.setSourceId);
|
const setSourceId = usePlayerStore((s) => s.setSourceId);
|
||||||
const progress = usePlayerStore((s) => s.progress.time);
|
const progress = usePlayerStore((s) => s.progress.time);
|
||||||
|
|
||||||
|
const unknownEmbedName = "Unknown";
|
||||||
|
|
||||||
const embedName = useMemo(() => {
|
const embedName = useMemo(() => {
|
||||||
if (!props.embedId) return "...";
|
if (!props.embedId) return unknownEmbedName;
|
||||||
const sourceMeta = providers.getMetadata(props.embedId);
|
const sourceMeta = providers.getMetadata(props.embedId);
|
||||||
return sourceMeta?.name ?? "...";
|
return sourceMeta?.name ?? unknownEmbedName;
|
||||||
}, [props.embedId]);
|
}, [props.embedId]);
|
||||||
|
|
||||||
const [request, run] = useAsyncFn(async () => {
|
const [request, run] = useAsyncFn(async () => {
|
||||||
const result = await providers.runEmbedScraper({
|
const result = await providers.runEmbedScraper({
|
||||||
id: props.embedId,
|
id: props.embedId,
|
||||||
@ -46,26 +50,14 @@ export function EmbedOption(props: {
|
|||||||
router.close();
|
router.close();
|
||||||
}, [props.embedId, props.sourceId, meta, router]);
|
}, [props.embedId, props.sourceId, meta, router]);
|
||||||
|
|
||||||
let content: ReactNode = null;
|
|
||||||
if (request.loading)
|
|
||||||
content = (
|
|
||||||
<Menu.TextDisplay noIcon>
|
|
||||||
<Loading />
|
|
||||||
</Menu.TextDisplay>
|
|
||||||
);
|
|
||||||
else if (request.error)
|
|
||||||
content = (
|
|
||||||
<Menu.TextDisplay title="Failed to scrape">
|
|
||||||
We were unable to find any videos for this source. Don't come
|
|
||||||
bitchin' to us about it, just try another source.
|
|
||||||
</Menu.TextDisplay>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectableLink onClick={run}>
|
<SelectableLink
|
||||||
|
loading={request.loading}
|
||||||
|
error={request.error}
|
||||||
|
onClick={run}
|
||||||
|
>
|
||||||
<span className="flex flex-col">
|
<span className="flex flex-col">
|
||||||
<span>{embedName}</span>
|
<span>{embedName}</span>
|
||||||
{content}
|
|
||||||
</span>
|
</span>
|
||||||
</SelectableLink>
|
</SelectableLink>
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ import { HelmetProvider } from "react-helmet-async";
|
|||||||
import { BrowserRouter, HashRouter } from "react-router-dom";
|
import { BrowserRouter, HashRouter } from "react-router-dom";
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
import { ErrorBoundary } from "@/components/layout/ErrorBoundary";
|
import { ErrorBoundary } from "@/pages/errors/ErrorBoundary";
|
||||||
import App from "@/setup/App";
|
import App from "@/setup/App";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
import i18n from "@/setup/i18n";
|
import i18n from "@/setup/i18n";
|
||||||
|
@ -9,6 +9,7 @@ import { HomeLayout } from "@/pages/layouts/HomeLayout";
|
|||||||
import { BookmarksPart } from "@/pages/parts/home/BookmarksPart";
|
import { BookmarksPart } from "@/pages/parts/home/BookmarksPart";
|
||||||
import { HeroPart } from "@/pages/parts/home/HeroPart";
|
import { HeroPart } from "@/pages/parts/home/HeroPart";
|
||||||
import { WatchingPart } from "@/pages/parts/home/WatchingPart";
|
import { WatchingPart } from "@/pages/parts/home/WatchingPart";
|
||||||
|
import { MigrationPart } from "@/pages/parts/migrations/MigrationPart";
|
||||||
import { SearchListPart } from "@/pages/parts/search/SearchListPart";
|
import { SearchListPart } from "@/pages/parts/search/SearchListPart";
|
||||||
import { SearchLoadingPart } from "@/pages/parts/search/SearchLoadingPart";
|
import { SearchLoadingPart } from "@/pages/parts/search/SearchLoadingPart";
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ export function HomePage() {
|
|||||||
const [search] = searchParams;
|
const [search] = searchParams;
|
||||||
const s = useSearch(search);
|
const s = useSearch(search);
|
||||||
|
|
||||||
|
return <MigrationPart />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HomeLayout showBg={showBg}>
|
<HomeLayout showBg={showBg}>
|
||||||
<div className="mb-16 sm:mb-24">
|
<div className="mb-16 sm:mb-24">
|
||||||
|
@ -1,80 +1,12 @@
|
|||||||
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
|
import { useState } from "react";
|
||||||
import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay";
|
|
||||||
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
|
||||||
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
|
|
||||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
|
||||||
|
|
||||||
// simple empty view, perfect for putting in tests
|
import { Button } from "@/components/Button";
|
||||||
|
|
||||||
|
// mostly empty view, add whatever you need
|
||||||
export default function TestView() {
|
export default function TestView() {
|
||||||
const router = useOverlayRouter("test");
|
const [val, setVal] = useState(false);
|
||||||
|
|
||||||
return (
|
if (val) throw new Error("I crashed");
|
||||||
<OverlayDisplay>
|
|
||||||
<div className="h-[400px] w-[800px] flex justify-center items-center">
|
return <Button onClick={() => setVal(true)}>Crash me!</Button>;
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.open();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Open
|
|
||||||
</button>
|
|
||||||
<OverlayAnchor
|
|
||||||
id={router.id}
|
|
||||||
className="h-20 w-20 hover:w-24 mt-[50rem] bg-white"
|
|
||||||
/>
|
|
||||||
<Overlay id={router.id}>
|
|
||||||
<OverlayRouter id={router.id}>
|
|
||||||
<OverlayPage id={router.id} path="/" width={400} height={400}>
|
|
||||||
<div className="bg-blue-900 p-4">
|
|
||||||
<p>HOME</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.navigate("/two");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
open page two
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.navigate("/one");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
open page one
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</OverlayPage>
|
|
||||||
<OverlayPage id={router.id} path="/one" width={300} height={300}>
|
|
||||||
<div className="bg-blue-900 p-4">
|
|
||||||
<p>ONE</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.navigate("/");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
back home
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</OverlayPage>
|
|
||||||
<OverlayPage id={router.id} path="/two" width={200} height={200}>
|
|
||||||
<div className="bg-blue-900 p-4">
|
|
||||||
<p>TWO</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.navigate("/");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
back home
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</OverlayPage>
|
|
||||||
</OverlayRouter>
|
|
||||||
</Overlay>
|
|
||||||
</div>
|
|
||||||
</OverlayDisplay>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
44
src/pages/errors/ErrorBoundary.tsx
Normal file
44
src/pages/errors/ErrorBoundary.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Component } from "react";
|
||||||
|
|
||||||
|
import { ErrorPart } from "@/pages/parts/errors/ErrorPart";
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
error?: {
|
||||||
|
error: any;
|
||||||
|
errorInfo: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends Component<
|
||||||
|
Record<string, unknown>,
|
||||||
|
ErrorBoundaryState
|
||||||
|
> {
|
||||||
|
constructor(props: { children: any }) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
error: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: any, errorInfo: any) {
|
||||||
|
console.error("Render error caught", error, errorInfo);
|
||||||
|
this.setState((s) => ({
|
||||||
|
...s,
|
||||||
|
error: {
|
||||||
|
error,
|
||||||
|
errorInfo,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.error) return this.props.children as any;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorPart
|
||||||
|
error={this.state.error.error}
|
||||||
|
errorInfo={this.state.error.errorInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { NotFoundPart } from "@/pages/parts/errors/ErrorWrapperPart";
|
||||||
|
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
|
||||||
import { Icons } from "@/components/Icon";
|
|
||||||
import { ArrowLink } from "@/components/text/ArrowLink";
|
|
||||||
import { Title } from "@/components/text/Title";
|
|
||||||
import { ErrorWrapperPart } from "@/pages/parts/errors/ErrorWrapperPart";
|
|
||||||
|
|
||||||
export function NotFoundPage() {
|
export function NotFoundPage() {
|
||||||
const { t } = useTranslation();
|
return <NotFoundPart />;
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorWrapperPart>
|
|
||||||
<IconPatch
|
|
||||||
icon={Icons.EYE_SLASH}
|
|
||||||
className="mb-6 text-xl text-bink-600"
|
|
||||||
/>
|
|
||||||
<Title>{t("notFound.page.title")}</Title>
|
|
||||||
<p className="mb-12 mt-5 max-w-sm">{t("notFound.page.description")}</p>
|
|
||||||
<ArrowLink to="/" linkText={t("notFound.backArrow")} />
|
|
||||||
</ErrorWrapperPart>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
import { FooterView } from "@/components/layout/Footer";
|
import { FooterView } from "@/components/layout/Footer";
|
||||||
import { Navigation } from "@/components/layout/Navigation";
|
import { Navigation } from "@/components/layout/Navigation";
|
||||||
|
|
||||||
|
export function BlurEllipsis() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Blur elipsis */}
|
||||||
|
<div className="absolute top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25" />
|
||||||
|
<div className="absolute top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function SubPageLayout(props: { children: React.ReactNode }) {
|
export function SubPageLayout(props: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -10,10 +20,7 @@ export function SubPageLayout(props: { children: React.ReactNode }) {
|
|||||||
"linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)",
|
"linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Blur elipsis */}
|
<BlurEllipsis />
|
||||||
<div className="absolute top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25" />
|
|
||||||
<div className="absolute top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25" />
|
|
||||||
|
|
||||||
{/* Main page */}
|
{/* Main page */}
|
||||||
<FooterView>
|
<FooterView>
|
||||||
<Navigation noLightbar />
|
<Navigation noLightbar />
|
||||||
|
33
src/pages/parts/errors/ErrorPart.tsx
Normal file
33
src/pages/parts/errors/ErrorPart.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { ButtonPlain } from "@/components/Button";
|
||||||
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { IconPill } from "@/components/layout/IconPill";
|
||||||
|
import { Title } from "@/components/text/Title";
|
||||||
|
import { Paragraph } from "@/components/utils/Text";
|
||||||
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
|
|
||||||
|
export function ErrorPart(props: { error: any; errorInfo: any }) {
|
||||||
|
const data = JSON.stringify({
|
||||||
|
error: props.error,
|
||||||
|
errorInfo: props.errorInfo,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-1 flex-col">
|
||||||
|
<div className="flex h-full flex-1 flex-col items-center justify-center p-5 text-center">
|
||||||
|
<ErrorLayout>
|
||||||
|
<ErrorContainer>
|
||||||
|
<IconPill icon={Icons.EYE_SLASH}>It broke</IconPill>
|
||||||
|
<Title>Failed to load meta data</Title>
|
||||||
|
<Paragraph>{data}</Paragraph>
|
||||||
|
<ButtonPlain
|
||||||
|
theme="purple"
|
||||||
|
className="mt-6 md:px-12 p-2.5"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Reload the page
|
||||||
|
</ButtonPlain>
|
||||||
|
</ErrorContainer>
|
||||||
|
</ErrorLayout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
import { ReactNode } from "react";
|
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { IconPill } from "@/components/layout/IconPill";
|
||||||
import { Navigation } from "@/components/layout/Navigation";
|
import { Navigation } from "@/components/layout/Navigation";
|
||||||
|
import { Title } from "@/components/text/Title";
|
||||||
|
import { Paragraph } from "@/components/utils/Text";
|
||||||
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
|
|
||||||
export function ErrorWrapperPart(props: { children?: ReactNode }) {
|
export function NotFoundPart() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -14,7 +19,28 @@ export function ErrorWrapperPart(props: { children?: ReactNode }) {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<div className="flex h-full flex-1 flex-col items-center justify-center p-5 text-center">
|
<div className="flex h-full flex-1 flex-col items-center justify-center p-5 text-center">
|
||||||
{props.children}
|
<ErrorLayout>
|
||||||
|
<ErrorContainer>
|
||||||
|
<IconPill icon={Icons.EYE_SLASH}>
|
||||||
|
{t("notFound.genericTitle")}
|
||||||
|
</IconPill>
|
||||||
|
<Title>Failed to load meta data</Title>
|
||||||
|
<Paragraph>
|
||||||
|
Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost
|
||||||
|
bestest, but alas, no wucky videos to be spotted anywhere (´⊙ω⊙`)
|
||||||
|
Please don't be angwy, wittle movie-web ish twying so hard.
|
||||||
|
Can you find it in your heart to forgive? UwU 💖
|
||||||
|
</Paragraph>
|
||||||
|
<Button
|
||||||
|
href="/"
|
||||||
|
theme="purple"
|
||||||
|
padding="md:px-12 p-2.5"
|
||||||
|
className="mt-6"
|
||||||
|
>
|
||||||
|
Go home
|
||||||
|
</Button>
|
||||||
|
</ErrorContainer>
|
||||||
|
</ErrorLayout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
26
src/pages/parts/migrations/MigrationPart.tsx
Normal file
26
src/pages/parts/migrations/MigrationPart.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { BrandPill } from "@/components/layout/BrandPill";
|
||||||
|
import { Loading } from "@/components/layout/Loading";
|
||||||
|
import { BlurEllipsis } from "@/pages/layouts/SubPageLayout";
|
||||||
|
|
||||||
|
export function MigrationPart() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center items-center h-screen text-center font-medium">
|
||||||
|
{/* Overlaid elements */}
|
||||||
|
<BlurEllipsis />
|
||||||
|
<div className="right-[calc(2rem+env(safe-area-inset-right))] top-6 absolute">
|
||||||
|
<BrandPill />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<Loading />
|
||||||
|
<p className="max-w-[19rem] mt-3 mb-12 text-type-secondary">
|
||||||
|
Please hold, we are migrating your data. This shouldn't take long.
|
||||||
|
Also, fuck you.
|
||||||
|
</p>
|
||||||
|
<div className="w-[8rem] h-1 rounded-full bg-progress-background bg-opacity-25 mb-2">
|
||||||
|
<div className="w-1/4 h-full bg-progress-filled rounded-full" />
|
||||||
|
</div>
|
||||||
|
<p>25%</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -26,23 +26,23 @@ module.exports = {
|
|||||||
"ash-400": "#3D394D",
|
"ash-400": "#3D394D",
|
||||||
"ash-300": "#2C293A",
|
"ash-300": "#2C293A",
|
||||||
"ash-200": "#2B2836",
|
"ash-200": "#2B2836",
|
||||||
"ash-100": "#1E1C26",
|
"ash-100": "#1E1C26"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* fonts */
|
/* fonts */
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
"open-sans": "'Open Sans'",
|
"open-sans": "'Open Sans'"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* animations */
|
/* animations */
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"loading-pin": {
|
"loading-pin": {
|
||||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||||
"20%": { height: "1em", "background-color": "white" },
|
"20%": { height: "1em", "background-color": "white" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require("tailwind-scrollbar"),
|
require("tailwind-scrollbar"),
|
||||||
@ -52,31 +52,31 @@ module.exports = {
|
|||||||
colors: {
|
colors: {
|
||||||
// Branding
|
// Branding
|
||||||
pill: {
|
pill: {
|
||||||
background: "#1C1C36",
|
background: "#1C1C36"
|
||||||
},
|
},
|
||||||
|
|
||||||
// meta data for the theme itself
|
// meta data for the theme itself
|
||||||
global: {
|
global: {
|
||||||
accentA: "#505DBD",
|
accentA: "#505DBD",
|
||||||
accentB: "#3440A1",
|
accentB: "#3440A1"
|
||||||
},
|
},
|
||||||
|
|
||||||
// light bar
|
// light bar
|
||||||
lightBar: {
|
lightBar: {
|
||||||
light: "#2A2A71",
|
light: "#2A2A71"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
buttons: {
|
buttons: {
|
||||||
toggle: "#8D44D6",
|
toggle: "#8D44D6",
|
||||||
toggleDisabled: "#202836",
|
toggleDisabled: "#202836"
|
||||||
},
|
},
|
||||||
|
|
||||||
// only used for body colors/textures
|
// only used for body colors/textures
|
||||||
background: {
|
background: {
|
||||||
main: "#0A0A10",
|
main: "#0A0A10",
|
||||||
accentA: "#6E3B80",
|
accentA: "#6E3B80",
|
||||||
accentB: "#1F1F50",
|
accentB: "#1F1F50"
|
||||||
},
|
},
|
||||||
|
|
||||||
// typography
|
// typography
|
||||||
@ -85,7 +85,7 @@ module.exports = {
|
|||||||
text: "#73739D",
|
text: "#73739D",
|
||||||
dimmed: "#926CAD",
|
dimmed: "#926CAD",
|
||||||
divider: "#262632",
|
divider: "#262632",
|
||||||
secondary: "#64647B",
|
secondary: "#64647B"
|
||||||
},
|
},
|
||||||
|
|
||||||
// search bar
|
// search bar
|
||||||
@ -94,7 +94,7 @@ module.exports = {
|
|||||||
focused: "#24243C",
|
focused: "#24243C",
|
||||||
placeholder: "#4A4A71",
|
placeholder: "#4A4A71",
|
||||||
icon: "#545476",
|
icon: "#545476",
|
||||||
text: "#FFFFFF",
|
text: "#FFFFFF"
|
||||||
},
|
},
|
||||||
|
|
||||||
// media cards
|
// media cards
|
||||||
@ -106,7 +106,7 @@ module.exports = {
|
|||||||
barColor: "#4B4B63",
|
barColor: "#4B4B63",
|
||||||
barFillColor: "#BA7FD6",
|
barFillColor: "#BA7FD6",
|
||||||
badge: "#151522",
|
badge: "#151522",
|
||||||
badgeText: "#5F5F7A",
|
badgeText: "#5F5F7A"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Error page
|
// Error page
|
||||||
@ -115,14 +115,20 @@ module.exports = {
|
|||||||
border: "#252534",
|
border: "#252534",
|
||||||
|
|
||||||
type: {
|
type: {
|
||||||
secondary: "#62627D",
|
secondary: "#62627D"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// About page
|
// About page
|
||||||
about: {
|
about: {
|
||||||
circle: "#262632",
|
circle: "#262632",
|
||||||
circleText: "#9A9AC3",
|
circleText: "#9A9AC3"
|
||||||
|
},
|
||||||
|
|
||||||
|
progress: {
|
||||||
|
background: "#8787A8",
|
||||||
|
preloaded: "#8787A8",
|
||||||
|
filled: "#A75FC9"
|
||||||
},
|
},
|
||||||
|
|
||||||
// video player
|
// video player
|
||||||
@ -134,17 +140,11 @@ module.exports = {
|
|||||||
error: "#E44F4F",
|
error: "#E44F4F",
|
||||||
success: "#40B44B",
|
success: "#40B44B",
|
||||||
loading: "#B759D8",
|
loading: "#B759D8",
|
||||||
noresult: "#64647B",
|
noresult: "#64647B"
|
||||||
},
|
|
||||||
|
|
||||||
progress: {
|
|
||||||
background: "#8787A8",
|
|
||||||
preloaded: "#8787A8",
|
|
||||||
watched: "#A75FC9",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
audio: {
|
audio: {
|
||||||
set: "#A75FC9",
|
set: "#A75FC9"
|
||||||
},
|
},
|
||||||
|
|
||||||
buttons: {
|
buttons: {
|
||||||
@ -157,7 +157,7 @@ module.exports = {
|
|||||||
purple: "#6b298a",
|
purple: "#6b298a",
|
||||||
purpleHover: "#7f35a1",
|
purpleHover: "#7f35a1",
|
||||||
cancel: "#252533",
|
cancel: "#252533",
|
||||||
cancelHover: "#3C3C4A",
|
cancelHover: "#3C3C4A"
|
||||||
},
|
},
|
||||||
|
|
||||||
context: {
|
context: {
|
||||||
@ -177,19 +177,19 @@ module.exports = {
|
|||||||
|
|
||||||
buttons: {
|
buttons: {
|
||||||
list: "#161C26",
|
list: "#161C26",
|
||||||
active: "#0D1317",
|
active: "#0D1317"
|
||||||
},
|
},
|
||||||
|
|
||||||
type: {
|
type: {
|
||||||
main: "#617A8A",
|
main: "#617A8A",
|
||||||
secondary: "#374A56",
|
secondary: "#374A56",
|
||||||
accent: "#A570FA",
|
accent: "#A570FA"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user