From f96a0de373c81539f0db8ecf9a3fcb2598a5c6e3 Mon Sep 17 00:00:00 2001 From: mrjvs <jellevs@gmail.com> Date: Sat, 20 Jan 2024 13:09:42 +0100 Subject: [PATCH] Style the settings onboarding card --- .../player/internals/ScrapeCard.tsx | 20 +-- .../player/internals/StatusCircle.tsx | 40 +++--- src/pages/parts/settings/SetupPart.tsx | 117 ++++++++++++++++-- themes/default.ts | 1 + 4 files changed, 145 insertions(+), 33 deletions(-) diff --git a/src/components/player/internals/ScrapeCard.tsx b/src/components/player/internals/ScrapeCard.tsx index 479bf2b1..dc057901 100644 --- a/src/components/player/internals/ScrapeCard.tsx +++ b/src/components/player/internals/ScrapeCard.tsx @@ -2,7 +2,10 @@ import classNames from "classnames"; import { ReactNode } from "react"; 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"; export interface ScrapeItemProps { @@ -23,13 +26,14 @@ const statusTextMap: Partial<Record<ScrapeCardProps["status"], string>> = { pending: "player.scraping.items.pending", }; -const statusMap: Record<ScrapeCardProps["status"], StatusCircle["type"]> = { - failure: "error", - notfound: "noresult", - pending: "loading", - success: "success", - waiting: "waiting", -}; +const statusMap: Record<ScrapeCardProps["status"], StatusCircleProps["type"]> = + { + failure: "error", + notfound: "noresult", + pending: "loading", + success: "success", + waiting: "waiting", + }; export function ScrapeItem(props: ScrapeItemProps) { const { t } = useTranslation(); diff --git a/src/components/player/internals/StatusCircle.tsx b/src/components/player/internals/StatusCircle.tsx index 32855321..ede7392e 100644 --- a/src/components/player/internals/StatusCircle.tsx +++ b/src/components/player/internals/StatusCircle.tsx @@ -4,23 +4,24 @@ import classNames from "classnames"; import { Icon, Icons } from "@/components/Icon"; import { Transition } from "@/components/utils/Transition"; -export interface StatusCircle { +export interface StatusCircleProps { type: "loading" | "success" | "error" | "noresult" | "waiting"; percentage?: number; + className?: string; } -export interface StatusCircleLoading extends StatusCircle { +export interface StatusCircleLoading extends StatusCircleProps { type: "loading"; percentage: number; } function statusIsLoading( - props: StatusCircle | StatusCircleLoading, + props: StatusCircleProps | StatusCircleLoading, ): props is StatusCircleLoading { return props.type === "loading"; } -export function StatusCircle(props: StatusCircle | StatusCircleLoading) { +export function StatusCircle(props: StatusCircleProps | StatusCircleLoading) { const [spring] = useSpring( () => ({ percentage: statusIsLoading(props) ? props.percentage : 0, @@ -30,18 +31,21 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) { return ( <div - className={classNames({ - "p-0.5 border-current border-[3px] rounded-full h-6 w-6 relative transition-colors": - true, - "text-video-scraping-loading": props.type === "loading", - "text-video-scraping-noresult text-opacity-50": - props.type === "waiting", - "text-video-scraping-error bg-video-scraping-error": - props.type === "error", - "text-green-500 bg-green-500": props.type === "success", - "text-video-scraping-noresult bg-video-scraping-noresult": - props.type === "noresult", - })} + className={classNames( + { + "p-0.5 border-current border-[3px] rounded-full h-6 w-6 relative transition-colors": + true, + "text-video-scraping-loading": props.type === "loading", + "text-video-scraping-noresult text-opacity-50": + props.type === "waiting", + "text-video-scraping-error bg-video-scraping-error": + props.type === "error", + "text-green-500 bg-green-500": props.type === "success", + "text-video-scraping-noresult bg-video-scraping-noresult": + props.type === "noresult", + }, + props.className, + )} > <Transition animation="fade" show={statusIsLoading(props)}> <svg @@ -65,13 +69,13 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) { </Transition> <Transition animation="fade" show={props.type === "error"}> <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} /> </Transition> <Transition animation="fade" show={props.type === "success"}> <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} /> </Transition> diff --git a/src/pages/parts/settings/SetupPart.tsx b/src/pages/parts/settings/SetupPart.tsx index f639b43b..478722c6 100644 --- a/src/pages/parts/settings/SetupPart.tsx +++ b/src/pages/parts/settings/SetupPart.tsx @@ -1,9 +1,19 @@ +import classNames from "classnames"; +import { t } from "i18next"; +import { ReactNode } from "react"; import { useNavigate } from "react-router-dom"; import { useAsync } from "react-use"; import { isExtensionActive } from "@/backend/extension/messaging"; import { singularProxiedFetch } from "@/backend/helpers/fetch"; 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"; 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() { const navigate = useNavigate(); const { loading, setupStates, globalState } = useIsSetup(); 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 ( - <div> - <p className="font-bold text-white">state: {globalState}</p> - <p>extension: {setupStates.extension}</p> - <p>proxy: {setupStates.proxy}</p> - <p>defaults: {setupStates.defaultProxy}</p> - <Button onClick={() => navigate("/onboarding")}>Do setup</Button> - </div> + <SettingsCard> + <div className="flex items-start gap-4"> + <div> + <div + className={classNames({ + "rounded-full h-12 w-12 flex bg-opacity-15 justify-center items-center": + true, + "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 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> ); } diff --git a/themes/default.ts b/themes/default.ts index dfda2d86..bd31b3ff 100644 --- a/themes/default.ts +++ b/themes/default.ts @@ -152,6 +152,7 @@ export const defaultTheme = { divider: tokens.ash.c500, secondary: tokens.ash.c100, danger: tokens.semantic.red.c100, + success: tokens.semantic.green.c100, link: tokens.purple.c100, linkHover: tokens.purple.c50, },