mirror of
https://github.com/sussy-code/smov.git
synced 2025-01-01 16:37:39 +01:00
Localize all onboarding screens
This commit is contained in:
parent
9ec273e78c
commit
6a81b30585
4 changed files with 128 additions and 50 deletions
|
@ -97,7 +97,8 @@
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"pagetitle": "{{title}} - movie-web",
|
"pagetitle": "{{title}} - movie-web",
|
||||||
"register": "Register",
|
"register": "Register",
|
||||||
"settings": "Settings"
|
"settings": "Settings",
|
||||||
|
"onboarding": "Setup"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
|
@ -429,5 +430,64 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unsaved": "You have unsaved changes"
|
"unsaved": "You have unsaved changes"
|
||||||
|
},
|
||||||
|
"onboarding": {
|
||||||
|
"start": {
|
||||||
|
"title": "Let's get you setup with movie-web",
|
||||||
|
"explainer": "To get the best streams possible. You will need to choose which streaming method you want to use.",
|
||||||
|
"options": {
|
||||||
|
"proxy": {
|
||||||
|
"quality": "Good quality",
|
||||||
|
"title": "Custom proxy",
|
||||||
|
"description": "Setup a proxy in just 5 minutes and gain access to great sources.",
|
||||||
|
"action": "Setup proxy"
|
||||||
|
},
|
||||||
|
"extension": {
|
||||||
|
"quality": "Best quality",
|
||||||
|
"title": "Browser extension",
|
||||||
|
"description": "Install browser extension and gain access to the best sources.",
|
||||||
|
"action": "Install extension"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"text": "I don't want good quality streams,<0 /> <1>use the default setup</1>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"title": "Let's make a new proxy",
|
||||||
|
"explainer": "With the proxy method, you can get great quality streams by making a self-service proxy.",
|
||||||
|
"link": "Learn how to make a proxy",
|
||||||
|
"input": {
|
||||||
|
"label": "Proxy URL",
|
||||||
|
"placeholder": "https://",
|
||||||
|
"errorInvalidUrl": "Not a valid URL",
|
||||||
|
"errorConnection": "Could not connect to proxy",
|
||||||
|
"errorNotProxy": "Expected a proxy but got a website"
|
||||||
|
},
|
||||||
|
"back": "Go back",
|
||||||
|
"submit": "Submit proxy"
|
||||||
|
},
|
||||||
|
"extension": {
|
||||||
|
"title": "Let's start with an extension",
|
||||||
|
"explainer": "Using the browser extension, you can get the best streams we have to offer. With just a simple install.",
|
||||||
|
"link": "Install extension",
|
||||||
|
"back": "Go back",
|
||||||
|
"status": {
|
||||||
|
"loading": "Waiting on extension",
|
||||||
|
"disallowed": "Extension disabled for this page",
|
||||||
|
"failed": "Failed to request status",
|
||||||
|
"outdated": "Extension version too old",
|
||||||
|
"noperms": "Extension does not have sufficient permissions",
|
||||||
|
"success": "Extension is working as expected!"
|
||||||
|
},
|
||||||
|
"submitCheck": "Check for extension",
|
||||||
|
"submitFinal": "Continue"
|
||||||
|
},
|
||||||
|
"defaultConfirm": {
|
||||||
|
"title": "Are you sure?",
|
||||||
|
"description": "The default setup does not have the best streams and can be unbearably slow.",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Use default setup"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
|
@ -23,20 +24,23 @@ export function OnboardingPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const skipModal = useModal("skip");
|
const skipModal = useModal("skip");
|
||||||
const { completeAndRedirect } = useRedirectBack();
|
const { completeAndRedirect } = useRedirectBack();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
<PageTitle subpage k="global.pages.about" />
|
<PageTitle subpage k="global.pages.onboarding" />
|
||||||
<Modal id={skipModal.id}>
|
<Modal id={skipModal.id}>
|
||||||
<ModalCard>
|
<ModalCard>
|
||||||
<ModalCard>
|
<ModalCard>
|
||||||
<Heading1 className="!mt-0">Lorem ipsum</Heading1>
|
<Heading1 className="!mt-0">
|
||||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
{t("onboarding.defaultConfirm.title")}
|
||||||
|
</Heading1>
|
||||||
|
<Paragraph>{t("onboarding.defaultConfirm.description")}</Paragraph>
|
||||||
<Button theme="secondary" onClick={skipModal.hide}>
|
<Button theme="secondary" onClick={skipModal.hide}>
|
||||||
Lorem ipsum
|
{t("onboarding.defaultConfirm.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button theme="danger" onClick={() => completeAndRedirect()}>
|
<Button theme="danger" onClick={() => completeAndRedirect()}>
|
||||||
Lorem ipsum
|
{t("onboarding.defaultConfirm.confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalCard>
|
</ModalCard>
|
||||||
</ModalCard>
|
</ModalCard>
|
||||||
|
@ -44,22 +48,21 @@ export function OnboardingPage() {
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={1} className="mb-12" />
|
<Stepper steps={2} current={1} className="mb-12" />
|
||||||
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
Let's get you set up with movie-web
|
{t("onboarding.start.title")}
|
||||||
</Heading2>
|
</Heading2>
|
||||||
<Paragraph className="max-w-[320px]">
|
<Paragraph className="max-w-[320px]">
|
||||||
To get the best streams possible, you will need to choose which
|
{t("onboarding.start.explainer")}
|
||||||
streaming method you want to use.
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<div className="w-full grid grid-cols-[1fr,auto,1fr] gap-3">
|
<div className="w-full grid grid-cols-[1fr,auto,1fr] gap-3">
|
||||||
<Card onClick={() => navigate("/onboarding/proxy")}>
|
<Card onClick={() => navigate("/onboarding/proxy")}>
|
||||||
<CardContent
|
<CardContent
|
||||||
colorClass="!text-onboarding-good"
|
colorClass="!text-onboarding-good"
|
||||||
title="Custom proxy"
|
title={t("onboarding.start.options.proxy.title")}
|
||||||
subtitle="Good quality"
|
subtitle={t("onboarding.start.options.proxy.quality")}
|
||||||
description="Set up a proxy in only 5 minutes and gain access to great sources."
|
description={t("onboarding.start.options.proxy.description")}
|
||||||
>
|
>
|
||||||
<Link>Set up proxy</Link>
|
<Link>{t("onboarding.start.options.proxy.action")}</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="grid grid-rows-[1fr,auto,1fr] justify-center gap-4">
|
<div className="grid grid-rows-[1fr,auto,1fr] justify-center gap-4">
|
||||||
|
@ -70,24 +73,24 @@ export function OnboardingPage() {
|
||||||
<Card onClick={() => navigate("/onboarding/extension")}>
|
<Card onClick={() => navigate("/onboarding/extension")}>
|
||||||
<CardContent
|
<CardContent
|
||||||
colorClass="!text-onboarding-best"
|
colorClass="!text-onboarding-best"
|
||||||
title="Browser extension"
|
title={t("onboarding.start.options.extension.title")}
|
||||||
subtitle="Best quality"
|
subtitle={t("onboarding.start.options.extension.quality")}
|
||||||
description="Install browser extension and gain access to the best sources."
|
description={t("onboarding.start.options.extension.description")}
|
||||||
>
|
>
|
||||||
<Link>Install extension</Link>
|
<Link>{t("onboarding.start.options.extension.action")}</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-center mt-12">
|
<p className="text-center mt-12">
|
||||||
I don't want good quality, <br />
|
<Trans i18nKey="onboarding.start.options.default.text">
|
||||||
<a
|
<br />
|
||||||
onClick={skipModal.show}
|
<a
|
||||||
type="button"
|
onClick={skipModal.show}
|
||||||
className="text-onboarding-link hover:opacity-75 cursor-pointer"
|
type="button"
|
||||||
>
|
className="text-onboarding-link hover:opacity-75 cursor-pointer"
|
||||||
use the default setup
|
/>
|
||||||
</a>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAsyncFn, useInterval } from "react-use";
|
import { useAsyncFn, useInterval } from "react-use";
|
||||||
|
|
||||||
|
@ -36,20 +37,26 @@ export function ExtensionStatus(props: {
|
||||||
status: ExtensionStatus;
|
status: ExtensionStatus;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let content: ReactNode = null;
|
let content: ReactNode = null;
|
||||||
if (props.loading || props.status === "unknown")
|
if (props.loading || props.status === "unknown")
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<Loading />
|
<Loading />
|
||||||
<p>waiting on extension</p>
|
<p>{t("onboarding.extension.status.loading")}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
if (props.status === "disallowed")
|
if (props.status === "disallowed")
|
||||||
content = <p>Extension disabled for this page</p>;
|
content = <p>{t("onboarding.extension.status.disallowed")}</p>;
|
||||||
else if (props.status === "failed") content = <p>Failed to request status</p>;
|
else if (props.status === "failed")
|
||||||
else if (props.status === "outdated") content = <p>Extension too old</p>;
|
content = <p>{t("onboarding.extension.status.failed")}</p>;
|
||||||
else if (props.status === "noperms") content = <p>No permissions to act</p>;
|
else if (props.status === "outdated")
|
||||||
else if (props.status === "success") content = <p>Extension is working!</p>;
|
content = <p>{t("onboarding.extension.status.outdated")}</p>;
|
||||||
|
else if (props.status === "noperms")
|
||||||
|
content = <p>{t("onboarding.extension.status.noperms")}</p>;
|
||||||
|
else if (props.status === "success")
|
||||||
|
content = <p>{t("onboarding.extension.status.success")}</p>;
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="flex py-6 flex-col space-y-2 items-center justify-center">
|
<div className="flex py-6 flex-col space-y-2 items-center justify-center">
|
||||||
|
@ -60,6 +67,7 @@ export function ExtensionStatus(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingExtensionPage() {
|
export function OnboardingExtensionPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { completeAndRedirect } = useRedirectBack();
|
const { completeAndRedirect } = useRedirectBack();
|
||||||
|
|
||||||
|
@ -75,27 +83,30 @@ export function OnboardingExtensionPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
<PageTitle subpage k="global.pages.about" />
|
<PageTitle subpage k="global.pages.onboarding" />
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
Let's start with an extension
|
{t("onboarding.extension.title")}
|
||||||
</Heading2>
|
</Heading2>
|
||||||
<Paragraph className="max-w-[320px] mb-4">
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
Using the browser extension, you can get the best streams we have to
|
{t("onboarding.extension.explainer")}
|
||||||
offer. With just a simple install.
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link href="https://google.com" target="_blank" className="mb-12">
|
<Link href="https://google.com" target="_blank" className="mb-12">
|
||||||
Install extension
|
{t("onboarding.extension.link")}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
||||||
<div className="flex justify-between items-center mt-8">
|
<div className="flex justify-between items-center mt-8">
|
||||||
<Button onClick={() => navigate("/onboarding")} theme="secondary">
|
<Button onClick={() => navigate("/onboarding")} theme="secondary">
|
||||||
Back
|
{t("onboarding.extension.back")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => exec(true)} theme="purple">
|
<Button onClick={() => exec(true)} theme="purple">
|
||||||
{value === "success" ? "Continue" : "Check extension"}{" "}
|
{t(
|
||||||
|
value === "success"
|
||||||
|
? "onboarding.extension.submitFinal"
|
||||||
|
: "onboarding.extension.submitCheck",
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAsyncFn } from "react-use";
|
import { useAsyncFn } from "react-use";
|
||||||
|
|
||||||
|
@ -19,52 +20,55 @@ import { useAuthStore } from "@/stores/auth";
|
||||||
const testUrl = "https://postman-echo.com/get";
|
const testUrl = "https://postman-echo.com/get";
|
||||||
|
|
||||||
export function OnboardingProxyPage() {
|
export function OnboardingProxyPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { completeAndRedirect } = useRedirectBack();
|
const { completeAndRedirect } = useRedirectBack();
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const setProxySet = useAuthStore((s) => s.setProxySet);
|
const setProxySet = useAuthStore((s) => s.setProxySet);
|
||||||
|
|
||||||
const [{ loading, error }, test] = useAsyncFn(async () => {
|
const [{ loading, error }, test] = useAsyncFn(async () => {
|
||||||
if (!url.startsWith("http")) throw new Error("Not a valid URL");
|
if (!url.startsWith("http"))
|
||||||
|
throw new Error("onboarding.proxy.input.errorInvalidUrl");
|
||||||
try {
|
try {
|
||||||
const res = await singularProxiedFetch(url, testUrl, {});
|
const res = await singularProxiedFetch(url, testUrl, {});
|
||||||
if (res.url !== testUrl) throw new Error("Not a proxy");
|
if (res.url !== testUrl)
|
||||||
|
throw new Error("onboarding.proxy.input.errorNotProxy");
|
||||||
setProxySet([url]);
|
setProxySet([url]);
|
||||||
completeAndRedirect();
|
completeAndRedirect();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Could not connect to proxy");
|
throw new Error("onboarding.proxy.input.errorConnection");
|
||||||
}
|
}
|
||||||
}, [url, completeAndRedirect, setProxySet]);
|
}, [url, completeAndRedirect, setProxySet]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
<PageTitle subpage k="global.pages.about" />
|
<PageTitle subpage k="global.pages.onboarding" />
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
Let's setup a custom proxy
|
{t("onboarding.proxy.title")}
|
||||||
</Heading2>
|
</Heading2>
|
||||||
<Paragraph className="max-w-[320px] !mb-5">
|
<Paragraph className="max-w-[320px] !mb-5">
|
||||||
Using a custom proxy, you can get great quality streams!
|
{t("onboarding.proxy.explainer")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link>Learn how to make a custom proxy</Link>
|
<Link>{t("onboarding.proxy.link")}</Link>
|
||||||
<div className="w-[400px] max-w-full mt-14 mb-28">
|
<div className="w-[400px] max-w-full mt-14 mb-28">
|
||||||
<AuthInputBox
|
<AuthInputBox
|
||||||
label="Proxy URL"
|
label={t("onboarding.proxy.input.label")}
|
||||||
value={url}
|
value={url}
|
||||||
onChange={setUrl}
|
onChange={setUrl}
|
||||||
placeholder="lorem ipsum"
|
placeholder={t("onboarding.proxy.input.placeholder")}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
/>
|
/>
|
||||||
{error ? <ErrorLine>{error.message}</ErrorLine> : null}
|
{error ? <ErrorLine>{t(error.message)}</ErrorLine> : null}
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Button theme="secondary" onClick={() => navigate("/onboarding")}>
|
<Button theme="secondary" onClick={() => navigate("/onboarding")}>
|
||||||
Back
|
{t("onboarding.proxy.back")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button theme="purple" loading={loading} onClick={test}>
|
<Button theme="purple" loading={loading} onClick={test}>
|
||||||
Submit proxy
|
{t("onboarding.proxy.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
|
|
Loading…
Reference in a new issue