From 10fcd31c0145bec997c1c686288dcbb9a7032683 Mon Sep 17 00:00:00 2001 From: Vishal27alpha <120443254+Vishal27alpha@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:55:32 +0530 Subject: [PATCH 1/3] feat(settings): add result saving toggle to account settings and practice alias --- backend/src/api/controllers/user.ts | 2 +- frontend/src/html/pages/account-settings.html | 19 ++++++ frontend/src/styles/account-settings.scss | 66 +++++++++++++++++++ .../src/ts/ape/adapters/ts-rest-adapter.ts | 11 +++- .../src/ts/commandline/lists/result-saving.ts | 2 +- frontend/src/ts/firebase.ts | 1 + frontend/src/ts/modals/simple-modals-base.ts | 2 + frontend/src/ts/modals/simple-modals.ts | 22 +++++++ frontend/src/ts/pages/account-settings.ts | 34 ++++++++++ 9 files changed, 154 insertions(+), 5 deletions(-) diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index 964602852f10..816242cab515 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -270,7 +270,7 @@ export async function deleteUser(req: MonkeyRequest): Promise { if (error) { if (error instanceof MonkeyError && error.status === 404) { - //userinfo was already deleted. We ignore this and still try to remove the other data + //userinfo was already deleted. We ignore and still try to remove the other data } else { throw error; } diff --git a/frontend/src/html/pages/account-settings.html b/frontend/src/html/pages/account-settings.html index 6ef41fa45ad2..847d1a2ebd96 100644 --- a/frontend/src/html/pages/account-settings.html +++ b/frontend/src/html/pages/account-settings.html @@ -133,6 +133,25 @@ +
+
+ + result saving +
+
+ Toggle result saving. When disabled, results will not be saved ( + practice mode + ). +
+
+ +
+
+
- + + + + on
diff --git a/frontend/src/styles/account-settings.scss b/frontend/src/styles/account-settings.scss index 32e5fdc7e04c..445ee309dca5 100644 --- a/frontend/src/styles/account-settings.scss +++ b/frontend/src/styles/account-settings.scss @@ -117,68 +117,27 @@ } &.resultSaving { .buttons { - justify-items: center; - } - .toggleSwitch { - display: inline-flex; - align-items: center; - gap: 0.75rem; - cursor: pointer; - user-select: none; + grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); + gap: 0.5rem; } - .toggleSwitch input { + #toggleResultSaving { position: absolute; opacity: 0; width: 0; height: 0; } - .toggleSwitch .track { - width: 3rem; - height: 1.6rem; - border-radius: 999px; - background: var(--sub-alt-color); - border: 1px solid var(--sub-color); - position: relative; - transition: - background 0.2s ease, - border-color 0.2s ease; - } - - .toggleSwitch .track::after { - content: ""; + .toggleLabel { position: absolute; - top: 2px; - left: 2px; - width: 1.2rem; - height: 1.2rem; - border-radius: 999px; - background: var(--text-color); - transition: - transform 0.2s ease, - background 0.2s ease; - } - - .toggleSwitch input:checked + .track { - background: var(--main-color); - border-color: var(--main-color); - } - - .toggleSwitch input:checked + .track::after { - transform: translateX(1.4rem); - background: var(--bg-color); - } - - .toggleSwitch .toggleLabel { - color: var(--sub-color); - text-transform: uppercase; - letter-spacing: 0.08em; - font-size: 0.75rem; - } - - .toggleSwitch input:checked ~ .toggleLabel { - color: var(--main-color); + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } } } diff --git a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts index 78cb4ae7717f..54d6d5ed3bc9 100644 --- a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts +++ b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts @@ -29,14 +29,9 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{ }> { return async (request: ApiFetcherArgs) => { try { - // ===== DEV AUTH (LOCAL ONLY) ===== - if (import.meta.env.DEV) { - request.headers["Authorization"] = "Uid localuser|vishal@test.com"; - } else { - const token = await getIdToken(); - if (token !== null) { - request.headers["Authorization"] = `Bearer ${token}`; - } + const token = await getIdToken(); + if (token !== null) { + request.headers["Authorization"] = `Bearer ${token}`; } const usePolyfill = AbortSignal?.timeout === undefined; diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index 90ec3c78c7b3..9e4c4dbc73be 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -7,8 +7,6 @@ import * as Settings from "../pages/settings"; import * as ThemePicker from "../elements/settings/theme-picker"; import * as CustomText from "../test/custom-text"; import { FirebaseError } from "firebase/app"; -import * as TestState from "../test/test-state"; -import * as ModesNotice from "../elements/modes-notice"; import { isAuthenticated, @@ -819,25 +817,6 @@ list.optOutOfLeaderboards = new SimpleModal({ }, }); -list.toggleResultSaving = new SimpleModal({ - id: "toggleResultSaving", - title: "Result saving", - text: "Toggle result saving. When disabled, results will not be saved (practice mode).", - buttonText: "toggle", - execFn: async (): Promise => { - const current = TestState.savingEnabled; - TestState.setSaving(!current); - await ModesNotice.update(); - - return { - status: 1, - message: !current - ? "Result saving enabled" - : "Result saving disabled (practice mode)", - }; - }, -}); - list.applyCustomFont = new SimpleModal({ id: "applyCustomFont", title: "Custom font", diff --git a/frontend/src/ts/pages/account-settings.ts b/frontend/src/ts/pages/account-settings.ts index aed1a77faac9..176100c81f03 100644 --- a/frontend/src/ts/pages/account-settings.ts +++ b/frontend/src/ts/pages/account-settings.ts @@ -36,6 +36,26 @@ const state: State = { tab: "account", }; +function setResultSaving(checked: boolean): void { + TestState.setSaving(checked); + void ModesNotice.update(); + + pageElement.qs("#toggleResultSaving")?.setChecked(checked); + pageElement + .qs(".section.resultSaving .toggleLabel") + ?.setText(checked ? "on" : "off"); + pageElement + .qsa(".section.resultSaving .resultSavingToggle") + ?.removeClass("active"); + pageElement + .qs( + `.section.resultSaving .resultSavingToggle[data-value="${ + checked ? "on" : "off" + }"]`, + ) + ?.addClass("active"); +} + function updateAuthenticationSections(): void { pageElement.qsa(".section.passwordAuthSettings button")?.addClass("hidden"); pageElement.qsa(".section.googleAuthSettings button")?.addClass("hidden"); @@ -124,8 +144,8 @@ function updateTabs(): void { pageElement.qs(`.tab[data-tab="${state.tab}"]`)?.addClass("active"); }, ); - pageElement.qsa("button")?.removeClass("active"); - pageElement.qs(`button[data-tab="${state.tab}"]`)?.addClass("active"); + pageElement.qsa(".tabs button")?.removeClass("active"); + pageElement.qs(`.tabs button[data-tab="${state.tab}"]`)?.addClass("active"); } function updateAccountSections(): void { @@ -149,24 +169,14 @@ function updateAccountSections(): void { } } -function updateResultSavingToggle(): void { - const toggle = pageElement.qs( - "#toggleResultSaving", - ) as HTMLInputElement | null; - if (!toggle) return; - - toggle.checked = TestState.savingEnabled; - pageElement - .qs(".section.resultSaving .toggleLabel") - ?.setText(TestState.savingEnabled ? "on" : "off"); -} - export function updateUI(): void { if (getActivePage() !== "accountSettings") return; updateAuthenticationSections(); updateIntegrationSections(); updateAccountSections(); - updateResultSavingToggle(); + + setResultSaving(TestState.savingEnabled); + void ApeKeyTable.update(updateUI); void BlockedUserTable.update(); updateTabs(); @@ -256,23 +266,26 @@ qs(".pageAccountSettings")?.onChild( showPopup("resetPersonalBests"); }, ); + qs(".pageAccountSettings")?.onChild( "change", "#toggleResultSaving", (event) => { - const target = event.target as HTMLInputElement | null; - if (!target) return; - - TestState.setSaving(target.checked); - void ModesNotice.update(); - updateResultSavingToggle(); - - Notifications.add( - target.checked - ? "Result saving enabled" - : "Result saving disabled (practice mode)", - 1, - ); + const checked = (event.target as HTMLInputElement).checked; + setResultSaving(checked); + }, +); + +qs(".pageAccountSettings")?.onChild( + "click", + ".section.resultSaving .resultSavingToggle", + (event) => { + const value = (event.childTarget as HTMLElement).getAttribute("data-value"); + if (value === "on") { + setResultSaving(true); + } else if (value === "off") { + setResultSaving(false); + } }, ); diff --git a/frontend/src/ts/test/test-state.ts b/frontend/src/ts/test/test-state.ts index 1b5079d9d377..6312a941e0f7 100644 --- a/frontend/src/ts/test/test-state.ts +++ b/frontend/src/ts/test/test-state.ts @@ -5,7 +5,22 @@ export let isRepeated = false; export let isPaceRepeat = false; export let isActive = false; export let activeChallenge: null | Challenge = null; -export let savingEnabled = true; +const savingEnabledStorageKey = "resultSavingEnabled"; + +function getInitialSavingEnabled(): boolean { + try { + if (typeof window === "undefined" || !("localStorage" in window)) { + return true; + } + const stored = window.localStorage.getItem(savingEnabledStorageKey); + if (stored === null) return true; + return stored === "true"; + } catch { + return true; + } +} + +export let savingEnabled = getInitialSavingEnabled(); export let bailedOut = false; export let selectedQuoteId = 1; export let activeWordIndex = 0; @@ -33,6 +48,14 @@ export function setActiveChallenge(val: null | Challenge): void { export function setSaving(val: boolean): void { savingEnabled = val; + try { + if (typeof window === "undefined" || !("localStorage" in window)) { + return; + } + window.localStorage.setItem(savingEnabledStorageKey, String(val)); + } catch { + // ignore storage failures (e.g., private mode) + } } export function setBailedOut(tf: boolean): void { From 5405393583dab68ba4ef9f6ec11cf05d2cab098a Mon Sep 17 00:00:00 2001 From: Vishal27alpha <120443254+Vishal27alpha@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:19:59 +0530 Subject: [PATCH 3/3] fix(settings): address review feedback (move setting location and add command active state) --- frontend/src/html/pages/account-settings.html | 21 --------- frontend/src/html/pages/settings.html | 27 +++++++++++ frontend/src/styles/account-settings.scss | 25 ---------- frontend/src/styles/settings.scss | 25 ++++++++++ .../src/ts/commandline/lists/result-saving.ts | 2 + frontend/src/ts/pages/account-settings.ts | 46 ------------------- frontend/src/ts/pages/settings.ts | 38 +++++++++++++++ 7 files changed, 92 insertions(+), 92 deletions(-) diff --git a/frontend/src/html/pages/account-settings.html b/frontend/src/html/pages/account-settings.html index 833b0c607d74..c731bd519630 100644 --- a/frontend/src/html/pages/account-settings.html +++ b/frontend/src/html/pages/account-settings.html @@ -133,27 +133,6 @@ -
-
- - result saving -
-
- Toggle result saving. When disabled, results will not be saved ( - practice mode - ). -
-
- - - - on -
-
diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index d33167a8e55e..2ead73e8192c 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -106,6 +106,33 @@ +
+
+ + result saving + +
+
+ Toggle result saving. When disabled, results will not be saved ( + practice mode + ). +
+
+ + + + on +
+
diff --git a/frontend/src/styles/account-settings.scss b/frontend/src/styles/account-settings.scss index 445ee309dca5..68eb21dee253 100644 --- a/frontend/src/styles/account-settings.scss +++ b/frontend/src/styles/account-settings.scss @@ -115,31 +115,6 @@ color: var(--error-color); } } - &.resultSaving { - .buttons { - grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); - gap: 0.5rem; - } - - #toggleResultSaving { - position: absolute; - opacity: 0; - width: 0; - height: 0; - } - - .toggleLabel { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - } } &[data-tab="apeKeys"], &[data-tab="blockedUsers"] { diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index 6aa5b86db954..7581b6e6cd2a 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -197,6 +197,31 @@ } } } + &.resultSaving { + .buttons { + grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); + gap: 0.5rem; + } + + #toggleResultSaving { + position: absolute; + opacity: 0; + width: 0; + height: 0; + } + + .toggleLabel { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + } &[data-config-name="fontFamily"] { grid-template-areas: diff --git a/frontend/src/ts/commandline/lists/result-saving.ts b/frontend/src/ts/commandline/lists/result-saving.ts index 4619684b5a08..70e8748fd523 100644 --- a/frontend/src/ts/commandline/lists/result-saving.ts +++ b/frontend/src/ts/commandline/lists/result-saving.ts @@ -13,6 +13,7 @@ const subgroup: CommandsSubgroup = { TestState.setSaving(false); void ModesNotice.update(); }, + active: () => !TestState.savingEnabled, }, { id: "setResultSavingOn", @@ -22,6 +23,7 @@ const subgroup: CommandsSubgroup = { TestState.setSaving(true); void ModesNotice.update(); }, + active: () => TestState.savingEnabled, }, ], }; diff --git a/frontend/src/ts/pages/account-settings.ts b/frontend/src/ts/pages/account-settings.ts index 176100c81f03..4a9ba3dcb5b0 100644 --- a/frontend/src/ts/pages/account-settings.ts +++ b/frontend/src/ts/pages/account-settings.ts @@ -5,13 +5,11 @@ import { getActivePage } from "../signals/core"; import { swapElements } from "../utils/misc"; import { getSnapshot } from "../db"; import Ape from "../ape"; -import * as TestState from "../test/test-state"; import * as StreakHourOffsetModal from "../modals/streak-hour-offset"; import { showLoaderBar } from "../signals/loader-bar"; import * as ApeKeyTable from "../elements/account-settings/ape-key-table"; import * as BlockedUserTable from "../elements/account-settings/blocked-user-table"; import * as Notifications from "../elements/notifications"; -import * as ModesNotice from "../elements/modes-notice"; import { z } from "zod"; import * as AuthEvent from "../observables/auth-event"; import { qs, qsa, qsr, onDOMReady } from "../utils/dom"; @@ -36,26 +34,6 @@ const state: State = { tab: "account", }; -function setResultSaving(checked: boolean): void { - TestState.setSaving(checked); - void ModesNotice.update(); - - pageElement.qs("#toggleResultSaving")?.setChecked(checked); - pageElement - .qs(".section.resultSaving .toggleLabel") - ?.setText(checked ? "on" : "off"); - pageElement - .qsa(".section.resultSaving .resultSavingToggle") - ?.removeClass("active"); - pageElement - .qs( - `.section.resultSaving .resultSavingToggle[data-value="${ - checked ? "on" : "off" - }"]`, - ) - ?.addClass("active"); -} - function updateAuthenticationSections(): void { pageElement.qsa(".section.passwordAuthSettings button")?.addClass("hidden"); pageElement.qsa(".section.googleAuthSettings button")?.addClass("hidden"); @@ -175,8 +153,6 @@ export function updateUI(): void { updateIntegrationSections(); updateAccountSections(); - setResultSaving(TestState.savingEnabled); - void ApeKeyTable.update(updateUI); void BlockedUserTable.update(); updateTabs(); @@ -267,28 +243,6 @@ qs(".pageAccountSettings")?.onChild( }, ); -qs(".pageAccountSettings")?.onChild( - "change", - "#toggleResultSaving", - (event) => { - const checked = (event.target as HTMLInputElement).checked; - setResultSaving(checked); - }, -); - -qs(".pageAccountSettings")?.onChild( - "click", - ".section.resultSaving .resultSavingToggle", - (event) => { - const value = (event.childTarget as HTMLElement).getAttribute("data-value"); - if (value === "on") { - setResultSaving(true); - } else if (value === "off") { - setResultSaving(false); - } - }, -); - qs(".pageAccountSettings")?.onChild("click", "#updateAccountName", () => { showPopup("updateName"); }); diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 72fff7a2da59..026b8ad676c7 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -11,6 +11,7 @@ import * as ThemePicker from "../elements/settings/theme-picker"; import * as Notifications from "../elements/notifications"; import * as ImportExportSettingsModal from "../modals/import-export-settings"; import * as ConfigEvent from "../observables/config-event"; +import * as ModesNotice from "../elements/modes-notice"; import { getActivePage } from "../signals/core"; import { PageWithUrlParams } from "./page"; import { isAuthenticated } from "../firebase"; @@ -18,6 +19,7 @@ import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; import SlimSelect from "slim-select"; import * as Skeleton from "../utils/skeleton"; import * as CustomBackgroundFilter from "../elements/custom-background-filter"; +import * as TestState from "../test/test-state"; import { ThemeName, CustomLayoutFluid, @@ -481,6 +483,23 @@ function showAccountSection(): void { qsa(`.pageSettings .section.needsAccount`)?.show(); refreshTagsSettingsSection(); refreshPresetsSettingsSection(); + updateResultSavingUI(TestState.savingEnabled); +} + +function updateResultSavingUI(checked: boolean): void { + const section = qs(".pageSettings .section.resultSaving"); + section?.qs("#toggleResultSaving")?.setChecked(checked); + section?.qs(".toggleLabel")?.setText(checked ? "on" : "off"); + section?.qsa(".resultSavingToggle")?.removeClass("active"); + section + ?.qs(`.resultSavingToggle[data-value="${checked ? "on" : "off"}"]`) + ?.addClass("active"); +} + +function setResultSaving(checked: boolean): void { + TestState.setSaving(checked); + void ModesNotice.update(); + updateResultSavingUI(checked); } function setActiveFunboxButton(): void { @@ -806,6 +825,24 @@ qs(".pageSettings .section.presets")?.onChild( }, ); +qs(".pageSettings")?.onChild("change", "#toggleResultSaving", (event) => { + const checked = (event.target as HTMLInputElement).checked; + setResultSaving(checked); +}); + +qs(".pageSettings")?.onChild( + "click", + ".section.resultSaving .resultSavingToggle", + (event) => { + const value = (event.childTarget as HTMLElement).getAttribute("data-value"); + if (value === "on") { + setResultSaving(true); + } else if (value === "off") { + setResultSaving(false); + } + }, +); + qs("#importSettingsButton")?.on("click", () => { ImportExportSettingsModal.show("import"); }); @@ -1069,6 +1106,7 @@ export const page = new PageWithUrlParams({ await update(); // theme UI updates manually to avoid duplication await ThemePicker.updateThemeUI(); + updateResultSavingUI(TestState.savingEnabled); handleHighlightSection(options.urlParams?.highlight); },