Style the settings onboarding card

This commit is contained in:
mrjvs 2024-01-20 13:09:42 +01:00
parent de5d47a730
commit f96a0de373
4 changed files with 145 additions and 33 deletions

View File

@ -2,7 +2,10 @@ import classNames from "classnames";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { StatusCircle } from "@/components/player/internals/StatusCircle"; import {
StatusCircle,
StatusCircleProps,
} from "@/components/player/internals/StatusCircle";
import { Transition } from "@/components/utils/Transition"; import { Transition } from "@/components/utils/Transition";
export interface ScrapeItemProps { export interface ScrapeItemProps {
@ -23,13 +26,14 @@ const statusTextMap: Partial<Record<ScrapeCardProps["status"], string>> = {
pending: "player.scraping.items.pending", pending: "player.scraping.items.pending",
}; };
const statusMap: Record<ScrapeCardProps["status"], StatusCircle["type"]> = { const statusMap: Record<ScrapeCardProps["status"], StatusCircleProps["type"]> =
{
failure: "error", failure: "error",
notfound: "noresult", notfound: "noresult",
pending: "loading", pending: "loading",
success: "success", success: "success",
waiting: "waiting", waiting: "waiting",
}; };
export function ScrapeItem(props: ScrapeItemProps) { export function ScrapeItem(props: ScrapeItemProps) {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -4,23 +4,24 @@ import classNames from "classnames";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { Transition } from "@/components/utils/Transition"; import { Transition } from "@/components/utils/Transition";
export interface StatusCircle { export interface StatusCircleProps {
type: "loading" | "success" | "error" | "noresult" | "waiting"; type: "loading" | "success" | "error" | "noresult" | "waiting";
percentage?: number; percentage?: number;
className?: string;
} }
export interface StatusCircleLoading extends StatusCircle { export interface StatusCircleLoading extends StatusCircleProps {
type: "loading"; type: "loading";
percentage: number; percentage: number;
} }
function statusIsLoading( function statusIsLoading(
props: StatusCircle | StatusCircleLoading, props: StatusCircleProps | StatusCircleLoading,
): props is StatusCircleLoading { ): props is StatusCircleLoading {
return props.type === "loading"; return props.type === "loading";
} }
export function StatusCircle(props: StatusCircle | StatusCircleLoading) { export function StatusCircle(props: StatusCircleProps | StatusCircleLoading) {
const [spring] = useSpring( const [spring] = useSpring(
() => ({ () => ({
percentage: statusIsLoading(props) ? props.percentage : 0, percentage: statusIsLoading(props) ? props.percentage : 0,
@ -30,7 +31,8 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) {
return ( return (
<div <div
className={classNames({ className={classNames(
{
"p-0.5 border-current border-[3px] rounded-full h-6 w-6 relative transition-colors": "p-0.5 border-current border-[3px] rounded-full h-6 w-6 relative transition-colors":
true, true,
"text-video-scraping-loading": props.type === "loading", "text-video-scraping-loading": props.type === "loading",
@ -41,7 +43,9 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) {
"text-green-500 bg-green-500": props.type === "success", "text-green-500 bg-green-500": props.type === "success",
"text-video-scraping-noresult bg-video-scraping-noresult": "text-video-scraping-noresult bg-video-scraping-noresult":
props.type === "noresult", props.type === "noresult",
})} },
props.className,
)}
> >
<Transition animation="fade" show={statusIsLoading(props)}> <Transition animation="fade" show={statusIsLoading(props)}>
<svg <svg
@ -65,13 +69,13 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) {
</Transition> </Transition>
<Transition animation="fade" show={props.type === "error"}> <Transition animation="fade" show={props.type === "error"}>
<Icon <Icon
className="absolute inset-0 flex items-center justify-center text-white" className="absolute inset-0 flex items-center justify-center text-background-main"
icon={Icons.X} icon={Icons.X}
/> />
</Transition> </Transition>
<Transition animation="fade" show={props.type === "success"}> <Transition animation="fade" show={props.type === "success"}>
<Icon <Icon
className="absolute inset-0 flex items-center text-xs justify-center text-white" className="absolute inset-0 flex items-center text-sm justify-center text-background-main"
icon={Icons.CHECKMARK} icon={Icons.CHECKMARK}
/> />
</Transition> </Transition>

View File

@ -1,9 +1,19 @@
import classNames from "classnames";
import { t } from "i18next";
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useAsync } from "react-use"; import { useAsync } from "react-use";
import { isExtensionActive } from "@/backend/extension/messaging"; import { isExtensionActive } from "@/backend/extension/messaging";
import { singularProxiedFetch } from "@/backend/helpers/fetch"; import { singularProxiedFetch } from "@/backend/helpers/fetch";
import { Button } from "@/components/buttons/Button"; import { Button } from "@/components/buttons/Button";
import { Icon, Icons } from "@/components/Icon";
import { SettingsCard } from "@/components/layout/SettingsCard";
import {
StatusCircle,
StatusCircleProps,
} from "@/components/player/internals/StatusCircle";
import { Heading3 } from "@/components/utils/Text";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
const testUrl = "https://postman-echo.com/get"; const testUrl = "https://postman-echo.com/get";
@ -63,17 +73,110 @@ function useIsSetup() {
}; };
} }
function SetupCheckList(props: {
status: Status;
grey?: boolean;
children?: ReactNode;
}) {
const statusMap: Record<Status, StatusCircleProps["type"]> = {
error: "error",
success: "success",
unset: "noresult",
};
return (
<div className="flex items-start text-type-dimmed my-4">
<StatusCircle
type={statusMap[props.status]}
className={classNames({
"!text-video-scraping-noresult !bg-video-scraping-noresult opacity-50":
props.grey,
"scale-90 mr-3": true,
})}
/>
<div>
<p
className={classNames({
"!text-type-dimmed opacity-75": props.grey,
"text-type-danger": props.status === "error",
"text-white": props.status === "success",
})}
>
{props.children}
</p>
{props.status === "error" ? (
<p className="max-w-96">
There is something wrong with this setting. Go through setup again
to fix it.
</p>
) : null}
</div>
</div>
);
}
export function SetupPart() { export function SetupPart() {
const navigate = useNavigate(); const navigate = useNavigate();
const { loading, setupStates, globalState } = useIsSetup(); const { loading, setupStates, globalState } = useIsSetup();
if (loading || !setupStates) return <p>Loading states...</p>; if (loading || !setupStates) return <p>Loading states...</p>;
const textLookupMap: Record<Status, { title: string; desc: string }> = {
error: {
title: "err1",
desc: "err2",
},
success: {
title: "success1",
desc: "success2",
},
unset: {
title: "unset1",
desc: "unset2",
},
};
return ( return (
<SettingsCard>
<div className="flex items-start gap-4">
<div> <div>
<p className="font-bold text-white">state: {globalState}</p> <div
<p>extension: {setupStates.extension}</p> className={classNames({
<p>proxy: {setupStates.proxy}</p> "rounded-full h-12 w-12 flex bg-opacity-15 justify-center items-center":
<p>defaults: {setupStates.defaultProxy}</p> true,
<Button onClick={() => navigate("/onboarding")}>Do setup</Button> "text-type-success bg-type-success": globalState === "success",
"text-type-danger bg-type-danger":
globalState === "error" || globalState === "unset",
})}
>
<Icon
icon={globalState === "success" ? Icons.CHECKMARK : Icons.X}
className="text-xl"
/>
</div> </div>
</div>
<div className="flex-1">
<Heading3 className="!mb-3">
{t(textLookupMap[globalState].title)}
</Heading3>
<p className="max-w-[20rem] font-medium mb-6">
{t(textLookupMap[globalState].desc)}
</p>
<SetupCheckList status={setupStates.extension}>
Extension
</SetupCheckList>
<SetupCheckList status={setupStates.proxy}>
Custom proxy
</SetupCheckList>
<SetupCheckList grey status={setupStates.defaultProxy}>
Default setup
</SetupCheckList>
</div>
<div className="mt-5">
<Button theme="purple" onClick={() => navigate("/onboarding")}>
Do setup
</Button>
</div>
</div>
</SettingsCard>
); );
} }

View File

@ -152,6 +152,7 @@ export const defaultTheme = {
divider: tokens.ash.c500, divider: tokens.ash.c500,
secondary: tokens.ash.c100, secondary: tokens.ash.c100,
danger: tokens.semantic.red.c100, danger: tokens.semantic.red.c100,
success: tokens.semantic.green.c100,
link: tokens.purple.c100, link: tokens.purple.c100,
linkHover: tokens.purple.c50, linkHover: tokens.purple.c50,
}, },