mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-30 16:17:41 +01:00
Style onboarding pages
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
bb147a1367
commit
9ec273e78c
7 changed files with 123 additions and 35 deletions
|
@ -5,7 +5,7 @@ export interface StepperProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Stepper(props: StepperProps) {
|
export function Stepper(props: StepperProps) {
|
||||||
const percentage = (props.current / (props.steps + 1)) * 100;
|
const percentage = (props.current / props.steps) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={props.className}>
|
<div className={props.className}>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { TextInputControl } from "./TextInputControl";
|
import { TextInputControl } from "./TextInputControl";
|
||||||
|
|
||||||
export function AuthInputBox(props: {
|
export function AuthInputBox(props: {
|
||||||
|
@ -8,9 +10,10 @@ export function AuthInputBox(props: {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange?: (data: string) => void;
|
onChange?: (data: string) => void;
|
||||||
passwordToggleable?: boolean;
|
passwordToggleable?: boolean;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className={classNames("space-y-3", props.className)}>
|
||||||
{props.label ? (
|
{props.label ? (
|
||||||
<p className="font-bold text-white">{props.label}</p>
|
<p className="font-bold text-white">{props.label}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
18
src/components/utils/ErrorLine.tsx
Normal file
18
src/components/utils/ErrorLine.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
||||||
|
export function ErrorLine(props: { children?: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={classNames(
|
||||||
|
"inline-flex items-center text-type-danger",
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon icon={Icons.WARNING} className="text-xl mr-4" />
|
||||||
|
{props.children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
|
@ -89,14 +89,6 @@ export function OnboardingPage() {
|
||||||
use the default setup
|
use the default setup
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* <Button onClick={() => navigate("/onboarding/proxy")}>
|
|
||||||
Custom proxy
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => navigate("/onboarding/extension")}>
|
|
||||||
Extension
|
|
||||||
</Button>
|
|
||||||
<Button onClick={skipModal.show}>Default</Button> */}
|
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,11 +5,13 @@ import { useAsyncFn, useInterval } from "react-use";
|
||||||
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
|
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
|
||||||
import { extensionInfo } from "@/backend/extension/messaging";
|
import { extensionInfo } from "@/backend/extension/messaging";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
|
import { Loading } from "@/components/layout/Loading";
|
||||||
import { Stepper } from "@/components/layout/Stepper";
|
import { Stepper } from "@/components/layout/Stepper";
|
||||||
import { CenterContainer } from "@/components/layout/ThinContainer";
|
import { CenterContainer } from "@/components/layout/ThinContainer";
|
||||||
import { Heading2, Paragraph } from "@/components/utils/Text";
|
import { Heading2, Paragraph } from "@/components/utils/Text";
|
||||||
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
||||||
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
||||||
|
import { Card, Link } from "@/pages/onboarding/utils";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
|
|
||||||
type ExtensionStatus =
|
type ExtensionStatus =
|
||||||
|
@ -36,14 +38,25 @@ export function ExtensionStatus(props: {
|
||||||
}) {
|
}) {
|
||||||
let content: ReactNode = null;
|
let content: ReactNode = null;
|
||||||
if (props.loading || props.status === "unknown")
|
if (props.loading || props.status === "unknown")
|
||||||
content = <p>waiting on extension...</p>;
|
content = (
|
||||||
|
<>
|
||||||
|
<Loading />
|
||||||
|
<p>waiting on extension</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
if (props.status === "disallowed")
|
if (props.status === "disallowed")
|
||||||
content = <p>Extension disabled for this page</p>;
|
content = <p>Extension disabled for this page</p>;
|
||||||
else if (props.status === "failed") content = <p>Failed to request status</p>;
|
else if (props.status === "failed") content = <p>Failed to request status</p>;
|
||||||
else if (props.status === "outdated") content = <p>Extension too old</p>;
|
else if (props.status === "outdated") content = <p>Extension too old</p>;
|
||||||
else if (props.status === "noperms") content = <p>No permissions to act</p>;
|
else if (props.status === "noperms") content = <p>No permissions to act</p>;
|
||||||
else if (props.status === "success") content = <p>Extension is working!</p>;
|
else if (props.status === "success") content = <p>Extension is working!</p>;
|
||||||
return <div>{content}</div>;
|
return (
|
||||||
|
<Card>
|
||||||
|
<div className="flex py-6 flex-col space-y-2 items-center justify-center">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingExtensionPage() {
|
export function OnboardingExtensionPage() {
|
||||||
|
@ -65,13 +78,26 @@ export function OnboardingExtensionPage() {
|
||||||
<PageTitle subpage k="global.pages.about" />
|
<PageTitle subpage k="global.pages.about" />
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
Let's start with an extension
|
||||||
|
</Heading2>
|
||||||
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
|
Using the browser extension, you can get the best streams we have to
|
||||||
|
offer. With just a simple install.
|
||||||
|
</Paragraph>
|
||||||
|
<Link href="https://google.com" target="_blank" className="mb-12">
|
||||||
|
Install extension
|
||||||
|
</Link>
|
||||||
|
|
||||||
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
||||||
<Button onClick={() => navigate("/onboarding")}>Back</Button>
|
<div className="flex justify-between items-center mt-8">
|
||||||
<Button onClick={() => exec(true)}>
|
<Button onClick={() => navigate("/onboarding")} theme="secondary">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => exec(true)} theme="purple">
|
||||||
{value === "success" ? "Continue" : "Check extension"}{" "}
|
{value === "success" ? "Continue" : "Check extension"}{" "}
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,9 +7,12 @@ import { Button } from "@/components/buttons/Button";
|
||||||
import { Stepper } from "@/components/layout/Stepper";
|
import { Stepper } from "@/components/layout/Stepper";
|
||||||
import { CenterContainer } from "@/components/layout/ThinContainer";
|
import { CenterContainer } from "@/components/layout/ThinContainer";
|
||||||
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||||
|
import { Divider } from "@/components/utils/Divider";
|
||||||
|
import { ErrorLine } from "@/components/utils/ErrorLine";
|
||||||
import { Heading2, Paragraph } from "@/components/utils/Text";
|
import { Heading2, Paragraph } from "@/components/utils/Text";
|
||||||
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
||||||
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
|
||||||
|
import { Link } from "@/pages/onboarding/utils";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
@ -23,10 +26,14 @@ export function OnboardingProxyPage() {
|
||||||
|
|
||||||
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("Not a valid URL");
|
||||||
|
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("Not a proxy");
|
||||||
setProxySet([url]);
|
setProxySet([url]);
|
||||||
completeAndRedirect();
|
completeAndRedirect();
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Could not connect to proxy");
|
||||||
|
}
|
||||||
}, [url, completeAndRedirect, setProxySet]);
|
}, [url, completeAndRedirect, setProxySet]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,14 +41,32 @@ export function OnboardingProxyPage() {
|
||||||
<PageTitle subpage k="global.pages.about" />
|
<PageTitle subpage k="global.pages.about" />
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0">Lorem ipsum</Heading2>
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
<Paragraph>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</Paragraph>
|
Let's setup a custom proxy
|
||||||
<AuthInputBox value={url} onChange={setUrl} placeholder="lorem ipsum" />
|
</Heading2>
|
||||||
{error ? <p>url invalid</p> : null}
|
<Paragraph className="max-w-[320px] !mb-5">
|
||||||
<Button onClick={() => navigate("/onboarding")} loading={loading}>
|
Using a custom proxy, you can get great quality streams!
|
||||||
Backagd
|
</Paragraph>
|
||||||
|
<Link>Learn how to make a custom proxy</Link>
|
||||||
|
<div className="w-[400px] max-w-full mt-14 mb-28">
|
||||||
|
<AuthInputBox
|
||||||
|
label="Proxy URL"
|
||||||
|
value={url}
|
||||||
|
onChange={setUrl}
|
||||||
|
placeholder="lorem ipsum"
|
||||||
|
className="mb-4"
|
||||||
|
/>
|
||||||
|
{error ? <ErrorLine>{error.message}</ErrorLine> : null}
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button theme="secondary" onClick={() => navigate("/onboarding")}>
|
||||||
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={test}>Submit proxy</Button>
|
<Button theme="purple" loading={loading} onClick={test}>
|
||||||
|
Submit proxy
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { Heading2, Heading3, Paragraph } from "@/components/utils/Text";
|
import { Heading2, Heading3, Paragraph } from "@/components/utils/Text";
|
||||||
|
|
||||||
export function Card(props: {
|
export function Card(props: {
|
||||||
children: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClick: () => void;
|
onClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bg-onboarding-card hover:bg-onboarding-cardHover transition-colors duration-300 border border-onboarding-border rounded-lg p-7 cursor-pointer "
|
className={classNames({
|
||||||
|
"bg-onboarding-card duration-300 border border-onboarding-border rounded-lg p-7":
|
||||||
|
true,
|
||||||
|
"hover:bg-onboarding-cardHover transition-colors cursor-pointer":
|
||||||
|
!!props.onClick,
|
||||||
|
})}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -50,9 +56,27 @@ export function CardContent(props: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Link(props: { children: React.ReactNode }) {
|
export function Link(props: {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
to?: string;
|
||||||
|
href?: string;
|
||||||
|
className?: string;
|
||||||
|
target?: "_blank";
|
||||||
|
}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<a className="text-onboarding-link cursor-pointer flex gap-2 items-center group hover:opacity-75 transition-opacity">
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
if (props.to) navigate(props.to);
|
||||||
|
}}
|
||||||
|
href={props.href}
|
||||||
|
target={props.target}
|
||||||
|
className={classNames(
|
||||||
|
"text-onboarding-link cursor-pointer inline-flex gap-2 items-center group hover:opacity-75 transition-opacity",
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
<Icon
|
<Icon
|
||||||
icon={Icons.ARROW_RIGHT}
|
icon={Icons.ARROW_RIGHT}
|
||||||
|
|
Loading…
Reference in a new issue