From a438931648e1e4afb723a7075d21ae273a8e6897 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti Date: Fri, 3 Jul 2026 18:23:43 +0200 Subject: [PATCH] refactor(frontend): consolidate Home card options into a single state object --- apps/frontend/src/models/Stage.ts | 12 +- apps/frontend/src/pages/Home/Home.tsx | 205 ++++------------ .../src/pages/Home/buildCardUrl.test.ts | 28 +-- apps/frontend/src/pages/Home/buildCardUrl.ts | 72 ++---- apps/frontend/src/pages/Home/cardOptions.ts | 53 ++++ .../src/pages/Home/stages/Customize.tsx | 232 +++++++++--------- 6 files changed, 267 insertions(+), 335 deletions(-) create mode 100644 apps/frontend/src/pages/Home/cardOptions.ts diff --git a/apps/frontend/src/models/Stage.ts b/apps/frontend/src/models/Stage.ts index c1a102c1a78d3..f76755ec421c5 100644 --- a/apps/frontend/src/models/Stage.ts +++ b/apps/frontend/src/models/Stage.ts @@ -2,24 +2,34 @@ export const STAGE_LABELS = [ { title: "Login", shortTitle: "Login", + description: "", }, { title: "Select a Card", shortTitle: "Select Card", + description: "You will be able to customize your card in future steps.", }, { title: "Modify Card Parameters", shortTitle: "Modify Parameters", + description: "", }, { title: "Choose a Theme", shortTitle: "Select Theme", + description: "", }, { title: "Display your Card", shortTitle: "Display Card", + description: + "Display the finished card on GitHub, Twitter/X, LinkedIn, or anywhere else!", }, -] as const satisfies Array<{ title: string; shortTitle: string }>; +] as const satisfies Array<{ + title: string; + shortTitle: string; + description: string; +}>; export type StageIndex = { [K in keyof typeof STAGE_LABELS]: K extends `${infer N extends number}` diff --git a/apps/frontend/src/pages/Home/Home.tsx b/apps/frontend/src/pages/Home/Home.tsx index b475fa2b69c59..7c22adef91d09 100644 --- a/apps/frontend/src/pages/Home/Home.tsx +++ b/apps/frontend/src/pages/Home/Home.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { JSX } from "react"; import { useDispatch } from "react-redux"; import { BounceLoader } from "react-spinners"; @@ -6,15 +6,8 @@ import { v4 as uuidv4 } from "uuid"; import { authenticate } from "../../api/user"; import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; -import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; -import { - DEMO_GIST, - DEMO_REPO, - DEMO_USER, - DEMO_WAKATIME_USER, - HOST, -} from "../../constants"; +import { DEMO_USER } from "../../constants"; import { CardType } from "../../models/CardType"; import { STAGE_LABELS } from "../../models/Stage"; import type { StageIndex } from "../../models/Stage"; @@ -27,6 +20,8 @@ import { import { login } from "../../redux/slices/user"; import { buildCardUrl } from "./buildCardUrl"; +import { getDefaultCardOptions } from "./cardOptions"; +import type { CardOptions } from "./cardOptions"; import { CustomizeStage } from "./stages/Customize"; import { DisplayStage } from "./stages/Display"; import { LoginStage } from "./stages/Login/Login"; @@ -48,57 +43,44 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { const dispatch = useDispatch(); - // for stage two - const [selectedUserId, setSelectedUserId] = useState(userId); - const [repo, setRepo] = useState(DEMO_REPO); - const [gist, setGist] = useState(DEMO_GIST); - const [wakatimeUser, setWakatimeUser] = useState(DEMO_WAKATIME_USER); - const [selectedCard, setSelectedCard] = useState(CardType.STATS); + // for stages two and three + const [cardOptions, setCardOptions] = useState(() => + getDefaultCardOptions(userId), + ); + + const setCardOption = useCallback< + (key: K, value: CardOptions[K]) => void + >((key, value) => { + setCardOptions((prev) => ({ ...prev, [key]: value })); + }, []); + // Reset the selected user to the resolved account id when it changes, // adjusting state during render rather than in an effect: // https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes const [prevUserId, setPrevUserId] = useState(userId); if (userId !== prevUserId) { setPrevUserId(userId); - setSelectedUserId(userId); + setCardOptions((prev) => ({ ...prev, selectedUserId: userId })); } - // for stage three - const [selectedStatsRank, setSelectedStatsRank] = - useState(STATS_DEFAULT_RANK); - const [selectedLanguagesLayout, setSelectedLanguagesLayout] = useState( - LANGUAGES_DEFAULT_LAYOUT, - ); - const [selectedWakatimeLayout, setSelectedWakatimeLayout] = useState( - WAKATIME_DEFAULT_LAYOUT, - ); - - const [showTitle, setShowTitle] = useState(true); - const [showOwner, setShowOwner] = useState(false); - const [descriptionLines, setDescriptionLines] = useState< - number | undefined - >(); - const [customTitle, setCustomTitle] = useState(""); - const [langsCount, setLangsCount] = useState(); - const [hideValues, setHideValues] = useState(false); - const [showAllStats, setShowAllStats] = useState(false); - const [showIcons, setShowIcons] = useState(false); - const [includeAllCommits, setIncludeAllCommits] = useState(true); - const [enableAnimations, setEnableAnimations] = useState(true); - const [usePercent, setUsePercent] = useState(false); - const { isDark } = useTheme(); const [theme, setTheme] = useState(isDark ? "dark" : "default"); const handleCardTypeChange = (cardType: CardType) => { if (cardType === CardType.TOP_LANGS) { - setLangsCount(4); - setSelectedWakatimeLayout(WAKATIME_DEFAULT_LAYOUT); + setCardOptions((prev) => ({ + ...prev, + langsCount: 4, + selectedWakatimeLayout: WAKATIME_DEFAULT_LAYOUT, + })); } else if (cardType === CardType.WAKATIME) { - setLangsCount(6); - setSelectedLanguagesLayout(LANGUAGES_DEFAULT_LAYOUT); + setCardOptions((prev) => ({ + ...prev, + langsCount: 6, + selectedLanguagesLayout: LANGUAGES_DEFAULT_LAYOUT, + })); } if (theme === "default" || theme === "default_repocard") { @@ -119,51 +101,8 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { // string), but memoizing keeps it safe to pass as a prop if any of them are // ever wrapped in React.memo. const cardBuilder = useMemo( - () => - buildCardUrl({ - userId, - selectedCard, - selectedUserId, - repo, - gist, - wakatimeUser, - selectedStatsRank, - selectedLanguagesLayout, - selectedWakatimeLayout, - showTitle, - showOwner, - descriptionLines, - customTitle, - langsCount, - hideValues, - showAllStats, - showIcons, - includeAllCommits, - enableAnimations, - usePercent, - }), - [ - userId, - selectedCard, - selectedUserId, - repo, - gist, - wakatimeUser, - selectedStatsRank, - selectedLanguagesLayout, - selectedWakatimeLayout, - showTitle, - showOwner, - descriptionLines, - customTitle, - langsCount, - hideValues, - showAllStats, - showIcons, - includeAllCommits, - enableAnimations, - usePercent, - ], + () => buildCardUrl(userId, selectedCard, cardOptions), + [userId, selectedCard, cardOptions], ); // Preview builder for the customize stage, dark-themed to match the surroundings. @@ -187,10 +126,10 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { const cardDescriptor = useCardDescriptor({ selectedCard, themeBuilder, - repo, + repo: cardOptions.repo, userId, - wakatimeUser, - gist, + wakatimeUser: cardOptions.wakatimeUser, + gist: cardOptions.gist, }); const contentSectionRef = useRef(null); @@ -208,31 +147,31 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { }, [stage]); useEffect(() => { - async function redirectCode() { - // After requesting GitHub access, GitHub redirects back to your app with a code parameter - const url = window.location.href; + // After requesting GitHub access, GitHub redirects back to the app with a + // code parameter appended to the redirect URI (which carries mode=private|public). + const url = new URL(window.location.href); + const code = url.searchParams.get("code"); + if (code === null) { + return; + } - // If GitHub API returns the code parameter - if (url.includes("code=")) { - const tempPrivateAccess = url.includes("private"); - const newUrl = url.split("code=", 2) as [string, string]; - const redirect = `${url.split(HOST)[0] as string}${HOST}/frontend`; - window.history.pushState({}, "", redirect); - setIsLoading(true); - const userKey = uuidv4(); - const newUserId = await authenticate( - newUrl[1], - tempPrivateAccess, - userKey, - ); + const tempPrivateAccess = url.searchParams.get("mode") === "private"; + window.history.pushState({}, "", `${url.origin}/frontend`); + async function exchangeCode(code: string) { + setIsLoading(true); + try { + const userKey = uuidv4(); + const newUserId = await authenticate(code, tempPrivateAccess, userKey); dispatch(login({ userId: newUserId, userKey })); - + } catch (error) { + console.error(error); + } finally { setIsLoading(false); } } - void redirectCode(); + void exchangeCode(code); }, [dispatch]); if (isLoading) { @@ -283,13 +222,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {

) : ( - [ - "", - "You will be able to customize your card in future steps.", - "", - "", - "Display the finished card on GitHub, Twitter/X, LinkedIn, or anywhere else!", - ][stage] + STAGE_LABELS[stage].description )} @@ -309,42 +242,8 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { {stage === 2 && ( diff --git a/apps/frontend/src/pages/Home/buildCardUrl.test.ts b/apps/frontend/src/pages/Home/buildCardUrl.test.ts index 2f36b4ad0c979..350eecd12c848 100644 --- a/apps/frontend/src/pages/Home/buildCardUrl.test.ts +++ b/apps/frontend/src/pages/Home/buildCardUrl.test.ts @@ -6,11 +6,14 @@ import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home import { CardType } from "../../models/CardType"; import { buildCardUrl } from "./buildCardUrl"; +import type { CardOptions } from "./cardOptions"; -const baseOptions = { - selectedCard: CardType.STATS, +const USER_ID = "john-github"; + +// Built inline instead of via `getDefaultCardOptions`, which imports +// `constants.ts` and needs a `window` global these node-based tests lack. +const baseOptions: CardOptions = { selectedUserId: "john", - userId: "john-github", repo: "repo1", gist: "gist1", wakatimeUser: "wakaUser", @@ -32,13 +35,13 @@ const baseOptions = { describe("buildCardUrl", () => { it("builds stats suffix with defaults", () => { - const result = buildCardUrl(baseOptions); + const result = buildCardUrl(USER_ID, CardType.STATS, baseOptions); expect(result.toString()).toBe("?username=john"); }); it("adds stats options", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.STATS, { ...baseOptions, showIcons: true, includeAllCommits: true, @@ -57,9 +60,8 @@ describe("buildCardUrl", () => { }); it("builds top-langs suffix", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.TOP_LANGS, { ...baseOptions, - selectedCard: CardType.TOP_LANGS, langsCount: 5, showTitle: false, }); @@ -70,9 +72,8 @@ describe("buildCardUrl", () => { }); it("builds pin suffix using userId not selectedUserId", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.PIN, { ...baseOptions, - selectedCard: CardType.PIN, showOwner: true, descriptionLines: 3, }); @@ -83,9 +84,8 @@ describe("buildCardUrl", () => { }); it("builds gist suffix", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.GIST, { ...baseOptions, - selectedCard: CardType.GIST, showOwner: true, }); @@ -93,9 +93,8 @@ describe("buildCardUrl", () => { }); it("builds wakatime suffix with percent and custom title", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.WAKATIME, { ...baseOptions, - selectedCard: CardType.WAKATIME, wakatimeUser: "waka", usePercent: true, customTitle: "My Stats", @@ -108,9 +107,8 @@ describe("buildCardUrl", () => { }); it("adds non-default layouts", () => { - const result = buildCardUrl({ + const result = buildCardUrl(USER_ID, CardType.TOP_LANGS, { ...baseOptions, - selectedCard: CardType.TOP_LANGS, selectedLanguagesLayout: { id: 2, value: "compact", label: "Compact" }, }); diff --git a/apps/frontend/src/pages/Home/buildCardUrl.ts b/apps/frontend/src/pages/Home/buildCardUrl.ts index b129930c71547..abab84e067010 100644 --- a/apps/frontend/src/pages/Home/buildCardUrl.ts +++ b/apps/frontend/src/pages/Home/buildCardUrl.ts @@ -1,4 +1,3 @@ -import type { SelectOption } from "../../components/Generic/Select"; import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; @@ -6,51 +5,34 @@ import { CardType } from "../../models/CardType"; import { cardUrl } from "../../models/CardUrl"; import type { CardUrlBuilder } from "../../models/CardUrl"; -interface Options { - userId: string; - selectedUserId: string; - selectedCard: CardType; - repo: string; - gist: string; - wakatimeUser: string; - selectedStatsRank: SelectOption; - selectedLanguagesLayout: SelectOption; - selectedWakatimeLayout: SelectOption; - showTitle: boolean; - showOwner: boolean; - descriptionLines: number | undefined; - customTitle: string; - langsCount: number | undefined; - hideValues: boolean; - showAllStats: boolean; - showIcons: boolean; - includeAllCommits: boolean; - enableAnimations: boolean; - usePercent: boolean; -} +import type { CardOptions } from "./cardOptions"; + +export function buildCardUrl( + userId: string, + selectedCard: CardType, + options: CardOptions, +): CardUrlBuilder { + const { + selectedUserId, + repo, + gist, + wakatimeUser, + selectedStatsRank, + selectedLanguagesLayout, + selectedWakatimeLayout, + showTitle, + showOwner, + descriptionLines, + customTitle, + langsCount, + hideValues, + showAllStats, + showIcons, + includeAllCommits, + enableAnimations, + usePercent, + } = options; -export function buildCardUrl({ - userId, - selectedCard, - selectedUserId, - repo, - gist, - wakatimeUser, - selectedStatsRank, - selectedLanguagesLayout, - selectedWakatimeLayout, - showTitle, - showOwner, - descriptionLines, - customTitle, - langsCount, - hideValues, - showAllStats, - showIcons, - includeAllCommits, - enableAnimations, - usePercent, -}: Options): CardUrlBuilder { switch (selectedCard) { case CardType.STATS: { let url = cardUrl(CardType.STATS); diff --git a/apps/frontend/src/pages/Home/cardOptions.ts b/apps/frontend/src/pages/Home/cardOptions.ts new file mode 100644 index 0000000000000..7e9a3650a60e1 --- /dev/null +++ b/apps/frontend/src/pages/Home/cardOptions.ts @@ -0,0 +1,53 @@ +import type { SelectOption } from "../../components/Generic/Select"; +import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; +import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; +import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; +import { DEMO_GIST, DEMO_REPO, DEMO_WAKATIME_USER } from "../../constants"; + +/** + * All user-tunable card parameters collected during the customize stage. + * Held as a single state object in `HomeScreen` and updated one key at a time. + */ +export interface CardOptions { + selectedUserId: string; + repo: string; + gist: string; + wakatimeUser: string; + selectedStatsRank: SelectOption; + selectedLanguagesLayout: SelectOption; + selectedWakatimeLayout: SelectOption; + showTitle: boolean; + showOwner: boolean; + descriptionLines: number | undefined; + customTitle: string; + langsCount: number | undefined; + hideValues: boolean; + showAllStats: boolean; + showIcons: boolean; + includeAllCommits: boolean; + enableAnimations: boolean; + usePercent: boolean; +} + +export function getDefaultCardOptions(userId: string): CardOptions { + return { + selectedUserId: userId, + repo: DEMO_REPO, + gist: DEMO_GIST, + wakatimeUser: DEMO_WAKATIME_USER, + selectedStatsRank: STATS_DEFAULT_RANK, + selectedLanguagesLayout: LANGUAGES_DEFAULT_LAYOUT, + selectedWakatimeLayout: WAKATIME_DEFAULT_LAYOUT, + showTitle: true, + showOwner: false, + descriptionLines: undefined, + customTitle: "", + langsCount: undefined, + hideValues: false, + showAllStats: false, + showIcons: false, + includeAllCommits: true, + enableAnimations: true, + usePercent: false, + }; +} diff --git a/apps/frontend/src/pages/Home/stages/Customize.tsx b/apps/frontend/src/pages/Home/stages/Customize.tsx index 414231c0dae2d..50c828ec55d04 100644 --- a/apps/frontend/src/pages/Home/stages/Customize.tsx +++ b/apps/frontend/src/pages/Home/stages/Customize.tsx @@ -1,7 +1,6 @@ -import type { JSX, default as React } from "react"; +import type { JSX } from "react"; import { CardImage } from "../../../components/Card/CardImage"; -import type { SelectOption } from "../../../components/Generic/Select"; import { CheckboxSection } from "../../../components/Home/CheckboxSection"; import { LanguagesLayoutSection } from "../../../components/Home/LanguagesLayoutSection"; import { NumericSection } from "../../../components/Home/NumericSection"; @@ -18,96 +17,66 @@ import { CardType } from "../../../models/CardType"; import type { CardUrlBuilder } from "../../../models/CardUrl"; import type { StageIndex } from "../../../models/Stage"; import { useIsAuthenticated } from "../../../redux/selectors/userSelectors"; +import type { CardOptions } from "../cardOptions"; -type Updater = React.Dispatch>; +/** + * Extract the trailing path segments from pasted text, so pasting a full + * GitHub URL yields just the username (1 segment), owner/repo (2), or Gist id (1). + */ +function pastedPathTail(text: string, segments: number): string { + let value = text; + if (value.endsWith("/")) { + value = value.slice(0, -1); + } + const parts = value.split("/"); + if (parts.length > segments) { + value = parts.slice(-segments).join("/"); + } + return value; +} -/** @todo todo consider using React context API to avoid prop drilling */ interface CustomizeStageProps { selectedCard: CardType; - selectedStatsRank: SelectOption; - setSelectedStatsRank: Updater; - selectedLanguagesLayout: SelectOption; - setSelectedLanguagesLayout: Updater; - selectedWakatimeLayout: SelectOption; - setSelectedWakatimeLayout: Updater; - selectedUserId: string; - setSelectedUserId: Updater; - repo: string; - setRepo: Updater; - gist: string; - setGist: Updater; - wakatimeUser: string; - setWakatimeUser: Updater; - showTitle: boolean; - setShowTitle: Updater; - descriptionLines: number | undefined; - setDescriptionLines: Updater; - showOwner: boolean; - setShowOwner: Updater; - customTitle: string; - setCustomTitle: Updater; - langsCount: number | undefined; - setLangsCount: Updater; - hideValues: boolean; - setHideValues: Updater; - showIcons: boolean; - setShowIcons: Updater; - showAllStats: boolean; - setShowAllStats: Updater; - includeAllCommits: boolean; - setIncludeAllCommits: Updater; - enableAnimations: boolean; - setEnableAnimations: Updater; - usePercent: boolean; - setUsePercent: Updater; + options: CardOptions; + onOptionChange: ( + key: K, + value: CardOptions[K], + ) => void; card: CardUrlBuilder; setStage: (stageIndex: StageIndex) => void; } export function CustomizeStage({ selectedCard, - selectedStatsRank, - setSelectedStatsRank, - selectedLanguagesLayout, - setSelectedLanguagesLayout, - selectedWakatimeLayout, - setSelectedWakatimeLayout, - selectedUserId, - setSelectedUserId, - repo, - setRepo, - gist, - setGist, - wakatimeUser, - setWakatimeUser, - showTitle, - setShowTitle, - showOwner, - setShowOwner, - descriptionLines, - setDescriptionLines, - customTitle, - setCustomTitle, - langsCount, - setLangsCount, - hideValues, - setHideValues, - showIcons, - setShowIcons, - showAllStats, - setShowAllStats, - includeAllCommits, - setIncludeAllCommits, - enableAnimations, - setEnableAnimations, - usePercent, - setUsePercent, + options, + onOptionChange, card, setStage, }: CustomizeStageProps): JSX.Element { const cardType = selectedCard; const isAuthenticated = useIsAuthenticated(); + const { + selectedUserId, + repo, + gist, + wakatimeUser, + selectedStatsRank, + selectedLanguagesLayout, + selectedWakatimeLayout, + showTitle, + showOwner, + descriptionLines, + customTitle, + langsCount, + hideValues, + showAllStats, + showIcons, + includeAllCommits, + enableAnimations, + usePercent, + } = options; + return (
@@ -138,19 +107,16 @@ export function CustomizeStage({ } placeholder={`e.g. "${DEMO_USER}"`} value={selectedUserId} - onValueChange={setSelectedUserId} + onValueChange={(value) => { + onOptionChange("selectedUserId", value); + }} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract username - if (newValue.endsWith("/")) { - newValue = newValue.slice(0, -1); - } - const parts = newValue.split("/"); - if (parts.length > 1) { - newValue = parts.slice(-1).join("/"); - } - setSelectedUserId(newValue); + onOptionChange( + "selectedUserId", + pastedPathTail(e.clipboardData.getData("text"), 1), + ); }} disabled={!isAuthenticated} /> @@ -182,19 +148,16 @@ export function CustomizeStage({ } placeholder={`e.g. "${DEMO_REPO}"`} value={repo} - onValueChange={setRepo} + onValueChange={(value) => { + onOptionChange("repo", value); + }} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract owner/repo - if (newValue.endsWith("/")) { - newValue = newValue.slice(0, -1); - } - const parts = newValue.split("/"); - if (parts.length > 2) { - newValue = parts.slice(-2).join("/"); - } - setRepo(newValue); + onOptionChange( + "repo", + pastedPathTail(e.clipboardData.getData("text"), 2), + ); }} disabled={!isAuthenticated} /> @@ -226,19 +189,16 @@ export function CustomizeStage({ } placeholder={`e.g. "${DEMO_GIST}"`} value={gist} - onValueChange={setGist} + onValueChange={(value) => { + onOptionChange("gist", value); + }} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract Gist ID - if (newValue.endsWith("/")) { - newValue = newValue.slice(0, -1); - } - const parts = newValue.split("/"); - if (parts.length > 1) { - newValue = parts.slice(-1).join("/"); - } - setGist(newValue); + onOptionChange( + "gist", + pastedPathTail(e.clipboardData.getData("text"), 1), + ); }} disabled={!isAuthenticated} /> @@ -261,7 +221,9 @@ export function CustomizeStage({ } placeholder={`e.g. "${DEMO_WAKATIME_USER}"`} value={wakatimeUser} - onValueChange={setWakatimeUser} + onValueChange={(value) => { + onOptionChange("wakatimeUser", value); + }} /> )} {cardType === CardType.STATS && ( @@ -270,13 +232,17 @@ export function CustomizeStage({ text="Show all available statistics." question="Show all stats?" checked={showAllStats} - onCheckedChange={setShowAllStats} + onCheckedChange={(checked) => { + onOptionChange("showAllStats", checked); + }} /> )} {cardType === CardType.STATS && ( { + onOptionChange("selectedStatsRank", option); + }} /> )} {cardType === CardType.STATS && ( @@ -285,7 +251,9 @@ export function CustomizeStage({ text="Show icons next to all stats." question="Show icons?" checked={showIcons} - onCheckedChange={setShowIcons} + onCheckedChange={(checked) => { + onOptionChange("showIcons", checked); + }} /> )} {cardType === CardType.STATS && ( @@ -294,19 +262,25 @@ export function CustomizeStage({ text="Count total commits or just commits of the last 365 days." question="Include all commits?" checked={includeAllCommits} - onCheckedChange={setIncludeAllCommits} + onCheckedChange={(checked) => { + onOptionChange("includeAllCommits", checked); + }} /> )} {cardType === CardType.TOP_LANGS && ( { + onOptionChange("selectedLanguagesLayout", option); + }} /> )} {cardType === CardType.WAKATIME && ( { + onOptionChange("selectedWakatimeLayout", option); + }} /> )} {(cardType === CardType.TOP_LANGS || @@ -321,7 +295,9 @@ export function CustomizeStage({ } value={langsCount} - onValueChange={setLangsCount} + onValueChange={(value) => { + onOptionChange("langsCount", value); + }} min={1} max={20} /> @@ -332,7 +308,9 @@ export function CustomizeStage({ text="Hide language percentages or bytes while keeping the selected layout visible." question="Hide values?" checked={hideValues} - onCheckedChange={setHideValues} + onCheckedChange={(checked) => { + onOptionChange("hideValues", checked); + }} /> )} {cardType === CardType.WAKATIME && ( @@ -341,7 +319,9 @@ export function CustomizeStage({ text="Show time spent in hours or percentages." question="Show percentages?" checked={usePercent} - onCheckedChange={setUsePercent} + onCheckedChange={(checked) => { + onOptionChange("usePercent", checked); + }} /> )} {(cardType === CardType.STATS || @@ -352,7 +332,9 @@ export function CustomizeStage({ text="Shows a title at the top of the card." question="Show title?" checked={showTitle} - onCheckedChange={setShowTitle} + onCheckedChange={(checked) => { + onOptionChange("showTitle", checked); + }} /> )} {(cardType === CardType.STATS || cardType === CardType.WAKATIME) && ( @@ -367,7 +349,9 @@ export function CustomizeStage({ } placeholder='e.g. "My GitHub Stats"' value={customTitle} - onValueChange={setCustomTitle} + onValueChange={(value) => { + onOptionChange("customTitle", value); + }} /> )} {(cardType === CardType.STATS || @@ -378,7 +362,9 @@ export function CustomizeStage({ // text="Enable Animations." question="enable animations?" checked={enableAnimations} - onCheckedChange={setEnableAnimations} + onCheckedChange={(checked) => { + onOptionChange("enableAnimations", checked); + }} /> )} {(cardType === CardType.PIN || cardType === CardType.GIST) && ( @@ -387,7 +373,9 @@ export function CustomizeStage({ text="Shows the repo owner's name next to the repo name." question="Show owner?" checked={showOwner} - onCheckedChange={setShowOwner} + onCheckedChange={(checked) => { + onOptionChange("showOwner", checked); + }} /> )} {cardType === CardType.PIN && ( @@ -402,7 +390,9 @@ export function CustomizeStage({ } value={descriptionLines} - onValueChange={setDescriptionLines} + onValueChange={(value) => { + onOptionChange("descriptionLines", value); + }} min={1} max={3} />