Skip to content

Commit

Permalink
finish account page
Browse files Browse the repository at this point in the history
  • Loading branch information
beebls committed Dec 26, 2024
1 parent e85e9cd commit 4416431
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 44 deletions.
50 changes: 50 additions & 0 deletions src/backend/state/theme-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface CSSLoaderStateActions {
request?: RequestInit,
requiresAuth?: boolean | string
) => Promise<Return>;
logInWithShortToken: (newToken?: string) => Promise<void>;
logOut: () => void;
getThemes: () => Promise<void>;
changePreset: (presetName: string) => Promise<void>;
testBackend: () => Promise<void>;
Expand Down Expand Up @@ -171,6 +173,10 @@ export const createCSSLoaderStore = (backend: Backend) =>
const hiddenMotd = await backend.storeRead("hiddenMotd");
set({ hiddenMotdId: hiddenMotd ?? "" });

if (shortToken) {
await get().logInWithShortToken();
}

const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get();
await bulkThemeUpdateCheck();
scheduleBulkThemeUpdateCheck();
Expand Down Expand Up @@ -212,6 +218,50 @@ export const createCSSLoaderStore = (backend: Backend) =>
console.error("Error Reloading Themes", error);
}
},
logInWithShortToken: async (newToken?: string) => {
try {
const token = newToken ?? get().apiShortToken;
if (!token) {
throw new Error("No Token Provided");
}
// This can't use apiFetch because it doesn't use header based auth
const json = await backend.fetch<{ token: string }>(`${apiUrl}/auth/authenticate_token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ token }),
});
if (!json.token) {
throw new FetchError(
"Token Authentication Failed",
`${apiUrl}/auth/authenticate_token`,
"No Token in Response"
);
}
backend.storeWrite("shortToken", token);
set({
apiShortToken: token,
apiFullToken: json.token,
apiTokenExpireDate: new Date().valueOf() + 1000 * 10 * 60,
});
const meJson = await apiFetch<FullAccountData>("/auth/me", undefined, true);
if (meJson) {
set({ apiMeData: meJson });
}
} catch (error) {
backend.toast("CSSLoader", "Failed to log in");
}
},
logOut: () => {
set({
apiShortToken: "",
apiFullToken: "",
apiMeData: undefined,
apiTokenExpireDate: undefined,
});
backend.storeWrite("shortToken", "");
},
refreshToken: async (): Promise<string | undefined> => {
const { apiFullToken, apiTokenExpireDate } = get();
if (!apiFullToken) {
Expand Down
69 changes: 69 additions & 0 deletions src/modules/theme-store/pages/AccountPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend";
import { DialogButton, Focusable, TextField } from "@decky/ui";
import { useState } from "react";
import { FaArrowRightToBracket } from "react-icons/fa6";

export function AccountPage() {
const apiFullToken = useCSSLoaderValue("apiFullToken");

return (
<div>
<h1 className="cl_fullscrenroute_title">{apiFullToken ? "Your Account" : "Log In"}</h1>
{apiFullToken ? <LoggedInSection /> : <LoggedOutSection />}
<p>
Logging in gives you access to star themes, saving them to their own page where you can
quickly find them.
<br />
To get started, create an account on deckthemes.com and generate an account key from your
profile page.
<br />
</p>
</div>
);
}
function LoggedInSection() {
const apiMeData = useCSSLoaderValue("apiMeData");
const logOut = useCSSLoaderAction("logOut");
return (
<Focusable className="flex items-center justify-between">
<span className="font-bold">
{apiMeData ? `Logged in as ${apiMeData.username}` : "Loading..."}
</span>
<DialogButton className="cl_accountpage_actionbutton" onClick={logOut}>
<span>Unlink My Deck</span>
</DialogButton>
</Focusable>
);
}

function LoggedOutSection() {
const apiFullToken = useCSSLoaderValue("apiFullToken");
const logInWithShortToken = useCSSLoaderAction("logInWithShortToken");
const apiShortToken = useCSSLoaderValue("apiShortToken");

const [shortTokenInterimValue, setShortTokenIntValue] = useState(apiShortToken);

return (
<Focusable className="flex items-center justify-between gap-2">
<div className="flex-1">
<TextField
disabled={!!apiFullToken}
label="DeckThemes Account Key"
bIsPassword
value={shortTokenInterimValue}
onChange={(e) => setShortTokenIntValue(e.target.value)}
/>
</div>
<DialogButton
className="cl_accountpage_actionbutton"
disabled={shortTokenInterimValue.length !== 12}
onClick={() => {
logInWithShortToken(shortTokenInterimValue);
}}
>
<FaArrowRightToBracket />
<span>Log In</span>
</DialogButton>
</Focusable>
);
}
109 changes: 75 additions & 34 deletions src/modules/theme-store/pages/ThemeStoreRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,90 @@ import {
useThemeBrowserSharedAction,
useThemeBrowserSharedValue,
} from "../context";
import { AccountPage } from "./AccountPage";
import { useCSSLoaderValue } from "@/backend";
import { Permissions } from "@/types";

// TODO: Make the tab definition a constant so that it isn't re-generated every page load

export function ThemeStoreRouter() {
const currentTab = useThemeBrowserSharedValue("currentTab");
const setCurrentTab = useThemeBrowserSharedAction("setCurrentTab");

const apiMeData = useCSSLoaderValue("apiMeData");

const tabs = [
{
id: "bpm-themes",
title: "Deck UI Themes",
content: (
<ThemeBrowserStoreProvider
filterPath="/themes/filters"
themePath="/themes"
themeType="BPM"
requiresAuth={false}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
},
{
id: "desktop-themes",
title: "Desktop Themes",
content: (
<ThemeBrowserStoreProvider
filterPath="/themes/filters"
themePath="/themes"
themeType="DESKTOP"
requiresAuth={false}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
},
{
id: "account",
title: "Account",
content: <AccountPage />,
},
];

apiMeData?.permissions?.includes(Permissions.viewSubs) &&
tabs.splice(2, 0, {
id: "submissions",
title: "Submissions",
content: (
<ThemeBrowserStoreProvider
filterPath="/themes/awaiting_approval/filters"
themePath="/themes/awaiting_approval"
themeType="ALL"
requiresAuth={true}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
});

apiMeData?.username &&
tabs.splice(2, 0, {
id: "starred-themes",
title: "Starred Themes",
content: (
<ThemeBrowserStoreProvider
filterPath="/users/me/stars/filters"
themePath="/users/me/stars"
themeType="ALL"
requiresAuth={true}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
});

return (
<div className="cl_fullscreenroute_container">
<ThemeCardCSSVariableProvider />
<Tabs
activeTab={currentTab}
onShowTab={(tab) => setCurrentTab(tab)}
tabs={[
{
id: "bpm-themes",
title: "BPM Themes",
content: (
<ThemeBrowserStoreProvider
filterPath="/themes/filters"
themePath="/themes"
themeType="BPM"
requiresAuth={false}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
},
{
id: "desktop-themes",
title: "Desktop Themes",
content: (
<ThemeBrowserStoreProvider
filterPath="/themes/filters"
themePath="/themes"
themeType="DESKTOP"
requiresAuth={false}
>
<ThemeBrowserPage />
</ThemeBrowserStoreProvider>
),
},
]}
></Tabs>
<Tabs activeTab={currentTab} onShowTab={(tab) => setCurrentTab(tab)} tabs={tabs}></Tabs>
</div>
);
}
50 changes: 45 additions & 5 deletions src/styles/styles-as-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export const styles = `
/* THAT IS NEEDED FOR STATIC CLASS INJECTION */
/* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */
/*
MARK: TAILWIND
*/
.flex {
display: flex !important;
}
Expand All @@ -18,6 +22,10 @@ export const styles = `
flex-wrap: wrap !important;
}
.flex-1 {
flex: 1 1 0% !important;
}
.gap-1 {
gap: 0.25rem !important;
}
Expand Down Expand Up @@ -93,15 +101,25 @@ export const styles = `
transform: translate(-50%, -50%) !important;
}
/* Fullscreen Routes */
/*
MARK: Fullscreen Routes
*/
.cl_fullscreenroute_container {
margin-top: 40px !important;
height: calc(100% - 40px) !important;
background: #0e141b !important;
}
/* TitleView */
.cl_fullscrenroute_title {
font-size: 2rem !important;
font-weight: bold !important;
}
/*
MARK: TitleView
*/
.cl-title-view-button {
height: 28px !important;
Expand Down Expand Up @@ -131,7 +149,10 @@ export const styles = `
animation: onboardingButton 1s infinite ease-in-out !important;
}
/* QAM Tab */
/*
MARK: QAM Tab
*/
.cl-qam-collapse-button-container > div > div > div > div > button {
height: 10px !important;
Expand Down Expand Up @@ -190,7 +211,10 @@ export const styles = `
white-space: nowrap !important;
}
/* Theme Store */
/*
MARK: Store
*/
.cl-store-filter-field-container {
display: flex !important;
Expand Down Expand Up @@ -249,6 +273,7 @@ export const styles = `
/* The variables should be injected wherever needed */
/* This module actually is based on font-size, so EM makes sense over REM */
/* TODO: For some reason I made half of these classes with dashes and the other half with underscores, standardize it!!! */
.cl_storeitem_notifbubble {
position: absolute !important;
background: linear-gradient(135deg, #fca904 50%, transparent 51%) !important;
Expand Down Expand Up @@ -342,7 +367,10 @@ export const styles = `
font-size: 0.75em !important;
}
/* Expanded View */
/*
MARK: Expanded View
*/
@keyframes cl_spin {
to {
Expand Down Expand Up @@ -505,4 +533,16 @@ export const styles = `
min-width: 1rem !important;
position: relative;
}
/*
MARK: Account Page
*/
.cl_accountpage_actionbutton {
max-width: 30% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 0.5rem !important;
}
`;
Loading

0 comments on commit 4416431

Please sign in to comment.