Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion apps/frontend/src/models/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down
205 changes: 52 additions & 153 deletions apps/frontend/src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
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";
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";
Expand All @@ -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";
Expand All @@ -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>(CardType.STATS);

// for stages two and three
const [cardOptions, setCardOptions] = useState(() =>
getDefaultCardOptions(userId),
);

const setCardOption = useCallback<
<K extends keyof CardOptions>(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<number | undefined>();
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") {
Expand All @@ -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.
Expand All @@ -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<HTMLDivElement | null>(null);
Expand All @@ -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) {
Expand Down Expand Up @@ -283,13 +222,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
</p>
</div>
) : (
[
"",
"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
)}
</div>
</div>
Expand All @@ -309,42 +242,8 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
{stage === 2 && (
<CustomizeStage
selectedCard={selectedCard}
selectedStatsRank={selectedStatsRank}
setSelectedStatsRank={setSelectedStatsRank}
selectedLanguagesLayout={selectedLanguagesLayout}
setSelectedLanguagesLayout={setSelectedLanguagesLayout}
selectedWakatimeLayout={selectedWakatimeLayout}
setSelectedWakatimeLayout={setSelectedWakatimeLayout}
showTitle={showTitle}
setShowTitle={setShowTitle}
showOwner={showOwner}
setShowOwner={setShowOwner}
descriptionLines={descriptionLines}
setDescriptionLines={setDescriptionLines}
customTitle={customTitle}
setCustomTitle={setCustomTitle}
langsCount={langsCount}
setLangsCount={setLangsCount}
hideValues={hideValues}
setHideValues={setHideValues}
showAllStats={showAllStats}
setShowAllStats={setShowAllStats}
showIcons={showIcons}
setShowIcons={setShowIcons}
includeAllCommits={includeAllCommits}
setIncludeAllCommits={setIncludeAllCommits}
enableAnimations={enableAnimations}
setEnableAnimations={setEnableAnimations}
selectedUserId={selectedUserId}
setSelectedUserId={setSelectedUserId}
repo={repo}
setRepo={setRepo}
gist={gist}
setGist={setGist}
wakatimeUser={wakatimeUser}
setWakatimeUser={setWakatimeUser}
usePercent={usePercent}
setUsePercent={setUsePercent}
options={cardOptions}
onOptionChange={setCardOption}
card={customizeCardBuilder}
setStage={setStage}
/>
Expand Down
Loading
Loading