Style onboarding pages

Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
mrjvs 2024-01-20 11:18:16 +01:00
parent bb147a1367
commit 9ec273e78c
7 changed files with 123 additions and 35 deletions

View File

@ -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}>

View File

@ -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}

View 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>
);
}

View File

@ -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>
); );

View File

@ -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&apos;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">
{value === "success" ? "Continue" : "Check extension"}{" "} Back
</Button> </Button>
<Button onClick={() => exec(true)} theme="purple">
{value === "success" ? "Continue" : "Check extension"}{" "}
</Button>
</div>
</CenterContainer> </CenterContainer>
</MinimalPageLayout> </MinimalPageLayout>
); );

View File

@ -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");
const res = await singularProxiedFetch(url, testUrl, {}); try {
if (res.url !== testUrl) throw new Error("Not a proxy"); const res = await singularProxiedFetch(url, testUrl, {});
setProxySet([url]); if (res.url !== testUrl) throw new Error("Not a proxy");
completeAndRedirect(); setProxySet([url]);
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&apos;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>
</Button> <Link>Learn how to make a custom proxy</Link>
<Button onClick={test}>Submit proxy</Button> <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 theme="purple" loading={loading} onClick={test}>
Submit proxy
</Button>
</div>
</CenterContainer> </CenterContainer>
</MinimalPageLayout> </MinimalPageLayout>
); );

View File

@ -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}