diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json index 68491cab5df3..6ae3e1f8e911 100644 --- a/frontend/.oxlintrc.json +++ b/frontend/.oxlintrc.json @@ -21,7 +21,8 @@ "rules": { "explicit-function-return-type": "off", "no-explicit-any": "off", - "no-unsafe-assignment": "off" + "no-unsafe-assignment": "off", + "no-empty-function": "off" } }, { diff --git a/frontend/__tests__/components/common/AsyncContent.spec.tsx b/frontend/__tests__/components/common/AsyncContent.spec.tsx index 5532163a57f9..d23692e1a427 100644 --- a/frontend/__tests__/components/common/AsyncContent.spec.tsx +++ b/frontend/__tests__/components/common/AsyncContent.spec.tsx @@ -10,7 +10,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import AsyncContent, { Props, } from "../../../src/ts/components/common/AsyncContent"; -import * as Notifications from "../../../src/ts/stores/notifications"; +import * as Notifications from "../../../src/ts/states/notifications"; describe("AsyncContent", () => { const notifyErrorMock = vi.spyOn(Notifications, "showErrorNotification"); diff --git a/frontend/__tests__/components/core/Theme.spec.tsx b/frontend/__tests__/components/core/Theme.spec.tsx index 7d40fceba905..5bd1a3b2ce29 100644 --- a/frontend/__tests__/components/core/Theme.spec.tsx +++ b/frontend/__tests__/components/core/Theme.spec.tsx @@ -4,9 +4,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { Theme } from "../../../src/ts/components/core/Theme"; import { ThemeWithName } from "../../../src/ts/constants/themes"; -import * as Loader from "../../../src/ts/signals/loader-bar"; -import * as ThemeSignal from "../../../src/ts/signals/theme"; -import * as Notifications from "../../../src/ts/stores/notifications"; +import * as Loader from "../../../src/ts/states/loader-bar"; +import * as Notifications from "../../../src/ts/states/notifications"; +import * as ThemeSignal from "../../../src/ts/states/theme"; vi.mock("../../../src/ts/constants/themes", () => ({ themes: { diff --git a/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx b/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx index 99b74cb4373d..4b0107f12826 100644 --- a/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx +++ b/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx @@ -3,7 +3,7 @@ import { userEvent } from "@testing-library/user-event"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { ScrollToTop } from "../../../../src/ts/components/layout/footer/ScrollToTop"; -import * as CoreSignals from "../../../../src/ts/signals/core"; +import * as CoreSignals from "../../../../src/ts/states/core"; describe("ScrollToTop", () => { const getActivePageMock = vi.spyOn(CoreSignals, "getActivePage"); diff --git a/frontend/__tests__/components/ui/ValidatedInput.spec.tsx b/frontend/__tests__/components/ui/ValidatedInput.spec.tsx deleted file mode 100644 index 90f48a74ac67..000000000000 --- a/frontend/__tests__/components/ui/ValidatedInput.spec.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { render, waitFor } from "@solidjs/testing-library"; -import userEvent from "@testing-library/user-event"; -import { createSignal } from "solid-js"; -import { describe, expect, it, vi } from "vitest"; -import { z } from "zod"; - -import { ValidatedInput } from "../../../src/ts/components/ui/ValidatedInput"; - -vi.mock("../../../src/ts/config", () => ({})); - -describe("ValidatedInput", () => { - it("renders with valid initial value", async () => { - const schema = z.string().min(4); - const { container } = render(() => ( - - )); - - await waitFor(() => container.querySelector(".inputAndIndicator") !== null); - - const wrapper = container.querySelector(".inputAndIndicator"); - const input = container.querySelector("input"); - console.log(container?.innerHTML); - expect(wrapper).toHaveClass("inputAndIndicator"); - expect(wrapper).toHaveAttribute("data-indicator-status", "success"); - expect(input).toHaveValue("Kevin"); - - const indicator = wrapper?.querySelector("div.indicator:not(.hidden)"); - expect(indicator).toBeInTheDocument(); - expect(indicator).toHaveAttribute("data-option-id", "success"); - expect(indicator?.querySelector("i")).toHaveClass("fa-check"); - }); - - it("renders with invalid initial value", async () => { - const schema = z.string().min(4); - const { container } = render(() => ( - - )); - - await waitFor(() => container.querySelector(".inputAndIndicator") !== null); - - const wrapper = container.querySelector(".inputAndIndicator"); - const input = container.querySelector("input"); - console.log(container?.innerHTML); - expect(wrapper).toHaveClass("inputAndIndicator"); - expect(wrapper).toHaveAttribute("data-indicator-status", "failed"); - expect(input).toHaveValue("Bob"); - - const indicator = wrapper?.querySelector("div.indicator:not(.hidden)"); - expect(indicator).toBeInTheDocument(); - expect(indicator).toHaveAttribute("data-option-id", "failed"); - expect(indicator?.querySelector("i")).toHaveClass("fa-times"); - }); - - it("updates callback", async () => { - const [value, setValue] = createSignal("Bob"); - const schema = z.string().min(4); - const { container } = render(() => ( - - )); - - await waitFor(() => container.querySelector(".inputAndIndicator") !== null); - console.log(container.innerHTML); - const input = container.querySelector("input") as HTMLInputElement; - expect(container.querySelector(".inputAndIndicator")).toHaveAttribute( - "data-indicator-status", - "failed", - ); - - await userEvent.type(input, "ington"); - - expect(value()).toEqual("Bobington"); - expect(container.querySelector(".inputAndIndicator")).toHaveAttribute( - "data-indicator-status", - "success", - ); - }); -}); diff --git a/frontend/__tests__/components/ui/form/Checkbox.spec.tsx b/frontend/__tests__/components/ui/form/Checkbox.spec.tsx new file mode 100644 index 000000000000..71936a160875 --- /dev/null +++ b/frontend/__tests__/components/ui/form/Checkbox.spec.tsx @@ -0,0 +1,91 @@ +import { render, screen, fireEvent } from "@solidjs/testing-library"; +import { describe, it, expect, vi } from "vitest"; + +import { Checkbox } from "../../../../src/ts/components/ui/form/Checkbox"; + +function makeField(name: string, checked = false) { + return { + name, + state: { value: checked }, + handleBlur: vi.fn(), + handleChange: vi.fn(), + } as any; +} + +describe("Checkbox", () => { + it("renders with label text", () => { + const field = makeField("agree"); + render(() => field} label="I agree" />); + + expect(screen.getByText("I agree")).toBeInTheDocument(); + }); + + it("renders checkbox with field name", () => { + const field = makeField("terms"); + render(() => field} />); + + const input = screen.getByRole("checkbox", { hidden: true }); + expect(input).toHaveAttribute("id", "terms"); + expect(input).toHaveAttribute("name", "terms"); + }); + + it("reflects checked state", () => { + const field = makeField("opt", true); + render(() => field} />); + + const input = screen.getByRole("checkbox", { hidden: true }); + expect(input).toBeChecked(); + }); + + it("reflects unchecked state", () => { + const field = makeField("opt", false); + render(() => field} />); + + const input = screen.getByRole("checkbox", { hidden: true }); + expect(input).not.toBeChecked(); + }); + + it("calls handleChange on change", async () => { + const field = makeField("opt"); + render(() => field} />); + + const input = screen.getByRole("checkbox", { hidden: true }); + await fireEvent.change(input, { target: { checked: true } }); + expect(field.handleChange).toHaveBeenCalledWith(true); + }); + + it("calls handleBlur on blur", async () => { + const field = makeField("opt"); + render(() => field} />); + + const input = screen.getByRole("checkbox", { hidden: true }); + await fireEvent.blur(input); + expect(field.handleBlur).toHaveBeenCalled(); + }); + + it("renders disabled checkbox", () => { + const field = makeField("opt"); + render(() => field} disabled />); + + const input = screen.getByRole("checkbox", { hidden: true }); + expect(input).toBeDisabled(); + }); + + it("shows check icon styling when checked", () => { + const field = makeField("opt", true); + const { container } = render(() => field} />); + + const icon = container.querySelector(".fa-check"); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveClass("text-main"); + }); + + it("shows transparent icon styling when unchecked", () => { + const field = makeField("opt", false); + const { container } = render(() => field} />); + + const icon = container.querySelector(".fa-check"); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveClass("text-transparent"); + }); +}); diff --git a/frontend/__tests__/components/ui/form/FieldIndicator.spec.tsx b/frontend/__tests__/components/ui/form/FieldIndicator.spec.tsx new file mode 100644 index 000000000000..5a96d645125c --- /dev/null +++ b/frontend/__tests__/components/ui/form/FieldIndicator.spec.tsx @@ -0,0 +1,88 @@ +import { render } from "@solidjs/testing-library"; +import { describe, it, expect } from "vitest"; + +import { FieldIndicator } from "../../../../src/ts/components/ui/form/FieldIndicator"; + +function makeField(overrides: { + isValidating?: boolean; + isTouched?: boolean; + isValid?: boolean; + isDefaultValue?: boolean; + errors?: string[]; + hasWarning?: boolean; + warnings?: string[]; +}) { + return { + state: { + meta: { + isValidating: overrides.isValidating ?? false, + isTouched: overrides.isTouched ?? false, + isValid: overrides.isValid ?? true, + isDefaultValue: overrides.isDefaultValue ?? true, + errors: overrides.errors ?? [], + }, + }, + getMeta: () => ({ + hasWarning: overrides.hasWarning ?? false, + warnings: overrides.warnings ?? [], + }), + } as any; +} + +describe("FieldIndicator", () => { + it("shows loading spinner when validating", () => { + const { container } = render(() => ( + + )); + expect(container.querySelector(".fa-circle-notch")).toBeInTheDocument(); + }); + + it("shows error icon when touched and invalid", () => { + const { container } = render(() => ( + + )); + expect(container.querySelector(".fa-times")).toBeInTheDocument(); + }); + + it("shows warning icon when has warning", () => { + const { container } = render(() => ( + + )); + expect( + container.querySelector(".fa-exclamation-triangle"), + ).toBeInTheDocument(); + }); + + it("shows success check when touched, valid, and not default", () => { + const { container } = render(() => ( + + )); + expect(container.querySelector(".fa-check")).toBeInTheDocument(); + }); + + it("shows nothing when untouched and not validating", () => { + const { container } = render(() => ( + + )); + expect(container.querySelector(".fa-times")).not.toBeInTheDocument(); + expect(container.querySelector(".fa-check")).not.toBeInTheDocument(); + expect(container.querySelector(".fa-circle-notch")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/ui/form/InputField.spec.tsx b/frontend/__tests__/components/ui/form/InputField.spec.tsx new file mode 100644 index 000000000000..7b5659ad14f1 --- /dev/null +++ b/frontend/__tests__/components/ui/form/InputField.spec.tsx @@ -0,0 +1,118 @@ +import { render, screen, fireEvent } from "@solidjs/testing-library"; +import { describe, it, expect, vi } from "vitest"; + +import { InputField } from "../../../../src/ts/components/ui/form/InputField"; + +function makeField(name: string, value = "") { + return { + name, + state: { + value, + meta: { + isValidating: false, + isTouched: false, + isValid: true, + isDefaultValue: true, + errors: [], + }, + }, + handleBlur: vi.fn(), + handleChange: vi.fn(), + getMeta: () => ({ hasWarning: false, warnings: [] }), + } as any; +} + +describe("InputField", () => { + it("renders input with field name as id", () => { + const field = makeField("email"); + render(() => field} />); + + const input = screen.getByRole("textbox"); + expect(input).toHaveAttribute("id", "email"); + expect(input).toHaveAttribute("name", "email"); + }); + + it("uses field name as default placeholder", () => { + const field = makeField("username"); + render(() => field} />); + + expect(screen.getByPlaceholderText("username")).toBeInTheDocument(); + }); + + it("uses custom placeholder when provided", () => { + const field = makeField("email"); + render(() => field} placeholder="Enter email" />); + + expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument(); + }); + + it("defaults to text type", () => { + const field = makeField("name"); + render(() => field} />); + + expect(screen.getByRole("textbox")).toHaveAttribute("type", "text"); + }); + + it("uses custom type", () => { + const field = makeField("password"); + const { container } = render(() => ( + field} type="password" /> + )); + + expect(container.querySelector("input")).toHaveAttribute( + "type", + "password", + ); + }); + + it("calls handleChange on input", async () => { + const field = makeField("name"); + render(() => field} />); + + await fireEvent.input(screen.getByRole("textbox"), { + target: { value: "test" }, + }); + expect(field.handleChange).toHaveBeenCalledWith("test"); + }); + + it("calls handleBlur on blur", async () => { + const field = makeField("name"); + render(() => field} />); + + await fireEvent.blur(screen.getByRole("textbox")); + expect(field.handleBlur).toHaveBeenCalled(); + }); + + it("calls onFocus callback", async () => { + const field = makeField("name"); + const onFocus = vi.fn(); + render(() => field} onFocus={onFocus} />); + + await fireEvent.focus(screen.getByRole("textbox")); + expect(onFocus).toHaveBeenCalled(); + }); + + it("renders disabled input", () => { + const field = makeField("name"); + render(() => field} disabled />); + + expect(screen.getByRole("textbox")).toBeDisabled(); + }); + + it("shows FieldIndicator when showIndicator is true", () => { + const field = makeField("name"); + field.state.meta.isValidating = true; + const { container } = render(() => ( + field} showIndicator /> + )); + + expect(container.querySelector(".fa-circle-notch")).toBeInTheDocument(); + }); + + it("hides FieldIndicator by default", () => { + const field = makeField("name"); + const { container } = render(() => field} />); + + expect(container.querySelector(".fa-circle-notch")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/ui/form/SubmitButton.spec.tsx b/frontend/__tests__/components/ui/form/SubmitButton.spec.tsx new file mode 100644 index 000000000000..d98a197e4697 --- /dev/null +++ b/frontend/__tests__/components/ui/form/SubmitButton.spec.tsx @@ -0,0 +1,74 @@ +import { render, screen } from "@solidjs/testing-library"; +import { JSXElement } from "solid-js"; +import { describe, it, expect } from "vitest"; + +import { SubmitButton } from "../../../../src/ts/components/ui/form/SubmitButton"; + +type FormState = { + canSubmit: boolean; + isSubmitting: boolean; + isValid: boolean; + isDirty: boolean; +}; + +function makeForm(state: Partial = {}) { + const fullState: FormState = { + canSubmit: true, + isSubmitting: false, + isValid: true, + isDirty: true, + ...state, + }; + + return { + Subscribe: (props: { + selector: (state: FormState) => FormState; + children: (state: () => FormState) => JSXElement; + }) => props.children(() => props.selector(fullState)), + }; +} + +describe("SubmitButton", () => { + it("renders enabled when form is dirty, valid, and can submit", () => { + render(() => ); + expect(screen.getByRole("button", { name: "Save" })).not.toBeDisabled(); + }); + + it("renders as submit type", () => { + render(() => ); + expect(screen.getByRole("button")).toHaveAttribute("type", "submit"); + }); + + it("disables when form is not dirty", () => { + render(() => ( + + )); + expect(screen.getByRole("button")).toBeDisabled(); + }); + + it("disables when form cannot submit", () => { + render(() => ( + + )); + expect(screen.getByRole("button")).toBeDisabled(); + }); + + it("disables when form is submitting", () => { + render(() => ( + + )); + expect(screen.getByRole("button")).toBeDisabled(); + }); + + it("disables when form is not valid", () => { + render(() => ( + + )); + expect(screen.getByRole("button")).toBeDisabled(); + }); + + it("disables when disabled prop is true even if form is ready", () => { + render(() => ); + expect(screen.getByRole("button")).toBeDisabled(); + }); +}); diff --git a/frontend/__tests__/components/ui/form/utils.spec.ts b/frontend/__tests__/components/ui/form/utils.spec.ts new file mode 100644 index 000000000000..1b1447ca31ef --- /dev/null +++ b/frontend/__tests__/components/ui/form/utils.spec.ts @@ -0,0 +1,133 @@ +import { describe, it, expect, vi } from "vitest"; +import { z } from "zod"; + +import { + fromSchema, + handleResult, + allFieldsMandatory, + fieldMandatory, + type ValidationResult, +} from "../../../../src/ts/components/ui/form/utils"; + +describe("fromSchema", () => { + const schema = z.string().min(3, "too short").max(10, "too long"); + const validate = fromSchema(schema); + + it("returns undefined for valid value", () => { + expect(validate({ value: "hello" })).toBeUndefined(); + }); + + it("returns error messages for invalid value", () => { + expect(validate({ value: "ab" })).toEqual(["too short"]); + }); + + it("returns multiple error messages", () => { + const numSchema = z.number().min(5, "too small").max(3, "too big"); + const v = fromSchema(numSchema); + const result = v({ value: 4 }); + // 4 fails min(5) but passes max(3)? Actually 4 > 3, so both fail + // number 4: min(5) fails, max(3) fails + expect(result).toEqual(["too small", "too big"]); + }); +}); + +describe("handleResult", () => { + const mockSetMeta = vi.fn(); + + function makeField() { + mockSetMeta.mockClear(); + return { setMeta: mockSetMeta } as any; + } + + it("returns undefined for undefined results", () => { + expect(handleResult(makeField(), undefined)).toBeUndefined(); + }); + + it("returns undefined for empty results", () => { + expect(handleResult(makeField(), [])).toBeUndefined(); + }); + + it("returns error messages and ignores warnings", () => { + const results: ValidationResult[] = [ + { type: "error", message: "bad email" }, + { type: "error", message: "too short" }, + ]; + expect(handleResult(makeField(), results)).toEqual([ + "bad email", + "too short", + ]); + }); + + it("sets warning meta on field", () => { + const results: ValidationResult[] = [ + { type: "warning", message: "weak password" }, + ]; + const field = makeField(); + const result = handleResult(field, results); + + expect(result).toBeUndefined(); + expect(mockSetMeta).toHaveBeenCalledOnce(); + + const updater = mockSetMeta.mock.calls[0]![0]; + const newMeta = updater({ existing: true }); + expect(newMeta).toEqual({ + existing: true, + hasWarning: true, + warnings: ["weak password"], + }); + }); + + it("handles both errors and warnings", () => { + const results: ValidationResult[] = [ + { type: "warning", message: "not recommended" }, + { type: "error", message: "invalid" }, + ]; + const field = makeField(); + const result = handleResult(field, results); + + expect(mockSetMeta).toHaveBeenCalledOnce(); + expect(result).toEqual(["invalid"]); + }); +}); + +describe("allFieldsMandatory", () => { + const validate = allFieldsMandatory<{ a: string; b: string }>(); + + it("returns undefined when all fields have values", () => { + expect(validate({ value: { a: "x", b: "y" } })).toBeUndefined(); + }); + + it("returns error when a field is empty string", () => { + expect(validate({ value: { a: "x", b: "" } })).toBe( + "all fields are mandatory", + ); + }); + + it("returns error when a field is undefined", () => { + expect(validate({ value: { a: "x", b: undefined } as any })).toBe( + "all fields are mandatory", + ); + }); +}); + +describe("fieldMandatory", () => { + it("returns undefined for non-empty value", () => { + const validate = fieldMandatory(); + expect(validate({ value: "hello" })).toBeUndefined(); + }); + + it("returns default message for empty string", () => { + const validate = fieldMandatory(); + expect(validate({ value: "" })).toBe("mandatory"); + }); + + it("returns default message for undefined", () => { + const validate = fieldMandatory(); + expect(validate({ value: undefined })).toBe("mandatory"); + }); + + it("returns custom message", () => { + const validate = fieldMandatory("required field"); + expect(validate({ value: "" })).toBe("required field"); + }); +}); diff --git a/frontend/__tests__/components/ui/table/DataTable.spec.tsx b/frontend/__tests__/components/ui/table/DataTable.spec.tsx index 17102d8cf5c4..7e42fafb22c0 100644 --- a/frontend/__tests__/components/ui/table/DataTable.spec.tsx +++ b/frontend/__tests__/components/ui/table/DataTable.spec.tsx @@ -19,7 +19,7 @@ const bpSignal = createSignal({ md: true, }); -vi.mock("../../../../src/ts/signals/breakpoints", () => ({ +vi.mock("../../../../src/ts/states/breakpoints", () => ({ bp: () => bpSignal[0](), })); diff --git a/frontend/__tests__/controllers/preset-controller.spec.ts b/frontend/__tests__/controllers/preset-controller.spec.ts index 47b668078564..24f21a517194 100644 --- a/frontend/__tests__/controllers/preset-controller.spec.ts +++ b/frontend/__tests__/controllers/preset-controller.spec.ts @@ -3,7 +3,7 @@ import * as PresetController from "../../src/ts/controllers/preset-controller"; import { Preset } from "@monkeytype/schemas/presets"; import * as DB from "../../src/ts/db"; import * as UpdateConfig from "../../src/ts/config"; -import * as Notifications from "../../src/ts/stores/notifications"; +import * as Notifications from "../../src/ts/states/notifications"; import * as TestLogic from "../../src/ts/test/test-logic"; import * as TagController from "../../src/ts/controllers/tag-controller"; diff --git a/frontend/__tests__/controllers/url-handler.spec.ts b/frontend/__tests__/controllers/url-handler.spec.ts index b6bf72d5bcb6..43681e2713dc 100644 --- a/frontend/__tests__/controllers/url-handler.spec.ts +++ b/frontend/__tests__/controllers/url-handler.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import { Difficulty, Mode, Mode2 } from "@monkeytype/schemas/shared"; import { compressToURI } from "lz-ts"; import * as UpdateConfig from "../../src/ts/config"; -import * as Notifications from "../../src/ts/stores/notifications"; +import * as Notifications from "../../src/ts/states/notifications"; import * as TestLogic from "../../src/ts/test/test-logic"; import * as TestState from "../../src/ts/test/test-state"; import * as Misc from "../../src/ts/utils/misc"; diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts index f6e0c46126ba..fd738d9a9d2e 100644 --- a/frontend/__tests__/root/config.spec.ts +++ b/frontend/__tests__/root/config.spec.ts @@ -11,7 +11,7 @@ import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation"; import * as ConfigValidation from "../../src/ts/config-validation"; import * as ConfigEvent from "../../src/ts/observables/config-event"; import * as ApeConfig from "../../src/ts/ape/config"; -import * as Notifications from "../../src/ts/stores/notifications"; +import * as Notifications from "../../src/ts/states/notifications"; const { replaceConfig, getConfig } = Config.__testing; describe("Config", () => { diff --git a/frontend/__tests__/stores/notifications.spec.ts b/frontend/__tests__/stores/notifications.spec.ts index 8841a7338545..aaf9f8176cef 100644 --- a/frontend/__tests__/stores/notifications.spec.ts +++ b/frontend/__tests__/stores/notifications.spec.ts @@ -10,7 +10,7 @@ import { getNotificationHistory, __testing, AddNotificationOptions, -} from "../../src/ts/stores/notifications"; +} from "../../src/ts/states/notifications"; const { clearNotificationHistory } = __testing; diff --git a/frontend/__tests__/test/funbox/funbox-validation.spec.ts b/frontend/__tests__/test/funbox/funbox-validation.spec.ts index b205702dc867..1f033d3d6d4e 100644 --- a/frontend/__tests__/test/funbox/funbox-validation.spec.ts +++ b/frontend/__tests__/test/funbox/funbox-validation.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect, afterEach, vi } from "vitest"; import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/test/funbox/funbox-validation"; -import * as Notifications from "../../../src/ts/stores/notifications"; +import * as Notifications from "../../../src/ts/states/notifications"; import { FunboxName } from "@monkeytype/schemas/configs"; describe("funbox-validation", () => { describe("canSetConfigWithCurrentFunboxes", () => { diff --git a/frontend/package.json b/frontend/package.json index 438af31ec477..3f446bd6b3b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@date-fns/utc": "1.2.0", - "@leonabcd123/modern-caps-lock": "2.2.2", + "@leonabcd123/modern-caps-lock": "3.0.4", "@monkeytype/contracts": "workspace:*", "@monkeytype/funbox": "workspace:*", "@monkeytype/schemas": "workspace:*", @@ -39,6 +39,7 @@ "@tanstack/pacer-lite": "0.2.1", "@tanstack/query-db-collection": "1.0.27", "@tanstack/solid-db": "0.2.10", + "@tanstack/solid-form": "1.28.4", "@tanstack/solid-query": "5.90.23", "@tanstack/solid-query-devtools": "5.91.3", "@tanstack/solid-table": "8.21.3", @@ -106,7 +107,7 @@ "oxlint": "1.50.0", "oxlint-tsgolint": "0.14.2", "postcss": "8.5.8", - "sass": "1.70.0", + "sass": "1.98.0", "solid-devtools": "0.34.5", "solid-js": "1.9.10", "subset-font": "2.3.0", diff --git a/frontend/scripts/check-assets.ts b/frontend/scripts/check-assets.ts index 996f0334f324..f58b54d49d5c 100644 --- a/frontend/scripts/check-assets.ts +++ b/frontend/scripts/check-assets.ts @@ -1,8 +1,8 @@ /** * Example usage in root or frontend: * pnpm check-assets (npm run check-assets) - * pnpm vaildate-json quotes others(npm run vaildate-json quotes others) - * pnpm check-assets challenges fonts -p (npm run check-assets challenges fonts -- -p) + * pnpm check-assets -- -- quotes others (npm run check-assets -- -- quotes others) + * pnpm check-assets -- -- challenges sound -p (npm run check-assets -- -- challenges sound -p) */ import * as fs from "fs"; @@ -149,28 +149,13 @@ async function validateLayouts(): Promise { } } -async function fetchQuotes(language: string): Promise { - const url = `https://raw.githubusercontent.com/monkeytypegame/monkeytype/refs/heads/master/frontend/static/quotes/${language}.json`; - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Response status: ${response.status}`); - } - - const quoteJsonData = (await response.json()) as QuoteData; - return quoteJsonData; - } catch (error) { - console.error( - `Failed to get quotes: ${error instanceof Error ? error.message : String(error)}`, - ); - return null; - } -} - async function validateQuotes(): Promise { const problems = new Problems("Quotes", {}); + const shortQuotes = JSON.parse( + fs.readFileSync("./scripts/short-quotes.json", "utf8"), + ) as Partial>; + const quotesFiles = fs.readdirSync("./static/quotes/"); for (let quotefilename of quotesFiles) { quotefilename = quotefilename.split(".")[0] as string; @@ -216,19 +201,6 @@ async function validateQuotes(): Promise { ); } - // check if pr added quotes in this language - let addedQuotesToThisLanguage = false; - const currentLanguageData = await fetchQuotes(quotefilename); - - if ( - currentLanguageData !== null && - (currentLanguageData.quotes.length < quoteData.quotes.length || - JSON.stringify(currentLanguageData.quotes) !== - JSON.stringify(quoteData.quotes)) - ) { - addedQuotesToThisLanguage = true; - } - //check quote length quoteData.quotes.forEach((quote) => { if (quote.text.length !== quote.length) { @@ -238,13 +210,7 @@ async function validateQuotes(): Promise { ); } - if ( - addedQuotesToThisLanguage && - currentLanguageData !== null && - !currentLanguageData.quotes.some( - (langQuote) => langQuote.text === quote.text, - ) - ) { + if (!shortQuotes[quoteData.language]?.includes(quote.id)) { if (quote.text.length < 60) { problems.add( quotefilename, diff --git a/frontend/scripts/get-short-quotes.ts b/frontend/scripts/get-short-quotes.ts new file mode 100644 index 000000000000..7098fd7fa4b6 --- /dev/null +++ b/frontend/scripts/get-short-quotes.ts @@ -0,0 +1,27 @@ +import * as fs from "fs"; +import { QuoteData } from "@monkeytype/schemas/quotes"; + +async function getShortQuotes(): Promise { + let shortQuotes: Partial> = {}; + let count = 0; + const quotesFiles = fs.readdirSync("./static/quotes/"); + for (const quotefilename of quotesFiles) { + const lang = quotefilename.split(".")[0] as QuoteData["language"]; + let quoteData: QuoteData; + let quoteJson: string; + quoteJson = fs.readFileSync(`./static/quotes/${lang}.json`, "utf8"); + //quoteJson = await (await fetch(`https://raw.githubusercontent.com/monkeytypegame/monkeytype/refs/heads/master/frontend/static/quotes/${lang}.json`)).json(); + quoteData = JSON.parse(quoteJson) as QuoteData; + for (const quote of quoteData.quotes) { + if (quote.length < 60) { + shortQuotes[lang] ??= []; + shortQuotes[lang].push(quote.id); + count++; + } + } + } + fs.writeFileSync("./scripts/short-quotes.json", JSON.stringify(shortQuotes)); + console.log(`There are ${count} allowed short quotes`); +} + +void getShortQuotes(); diff --git a/frontend/scripts/short-quotes.json b/frontend/scripts/short-quotes.json new file mode 100644 index 000000000000..6caca4959bf4 --- /dev/null +++ b/frontend/scripts/short-quotes.json @@ -0,0 +1,145 @@ +{ + "arabic": [20, 21, 25, 44, 45, 54, 72, 78], + "arabic_egypt": [23, 24, 25, 26, 27, 28, 29, 30, 32, 43, 47, 50, 51], + "azerbaijani": [5, 13, 14, 24], + "bangla": [ + 1, 5, 11, 15, 20, 21, 22, 25, 26, 27, 28, 30, 34, 38, 40, 41, 42, 43, 47, + 50, 51, 53, 54, 63, 66, 72, 73, 76, 77, 81, 86, 87, 88, 89, 92, 93, 98, 101, + 102, 109, 110, 112, 116, 118, 119, 120, 122, 123, 124, 126, 127, 128, 129, + 131, 132, 139, 145, 149, 150, 152, 158, 165, 166, 167, 168, 170, 172, 174, + 175, 179, 180, 181, 182, 184, 188, 191, 193, 198, 199, 200, 202, 203, 206, + 207, 210, 213, 214, 217, 218, 220, 222, 223, 224, 225, 227, 232, 234, 236, + 237, 239, 240, 241, 244, 245, 246, 249, 250, 255, 258, 259, 262, 263, 267, + 270, 272, 273, 275 + ], + "belarusian": [10, 12, 13, 94], + "belarusian_lacinka": [10, 12, 13], + "chinese_simplified": [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, + 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, + 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, + 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, + 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, + 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, + 324, 325, 326, 327, 328, 329, 330 + ], + "code_assembly": [2], + "code_bash": [1], + "code_c++": [4, 10, 11, 17, 18, 20], + "code_c": [23], + "code_css": [1], + "code_java": [3], + "code_jule": [1, 10, 15, 19, 21, 23, 25], + "code_lua": [5, 8], + "code_nim": [ + 1, 2, 3, 8, 9, 15, 17, 19, 20, 25, 31, 33, 35, 38, 39, 41, 45, 48, 51, 56, + 57, 59, 62, 63, 65, 68, 70, 71, 74, 76, 77, 81, 82, 84, 86, 87, 88, 90, 94, + 95, 98, 99, 105 + ], + "code_python": [3], + "code_ruby": [12], + "code_rust": [ + 1, 2, 3, 5, 6, 7, 8, 17, 18, 19, 24, 26, 27, 29, 30, 31, 32, 34, 37, 38, 39, + 40, 41, 42, 48, 56, 57, 58, 59, 62, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, + 78, 81, 83, 84, 85, 86, 88, 89, 91, 98, 100, 101, 102, 103, 104, 106, 109, + 110, 115, 117, 118, 119, 120, 124, 130, 132, 133, 135, 138, 140, 141, 142, + 144, 150, 151, 152, 154, 155, 159, 165, 166, 169, 172, 174, 176, 178, 179, + 180, 183, 185, 186, 187, 188, 189, 190, 192, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 210, 218, 222, 224, 227, 231, 233, 235, 238, 241, + 252, 259, 261, 262, 263, 264, 265, 266, 267, 271, 272, 273, 274, 275, 276, + 277 + ], + "code_yoptascript": [3], + "czech": [1, 3, 4, 6, 7, 8, 10, 13, 17], + "danish": [14, 23], + "docker_file": [5, 9, 10, 17], + "dutch": [16, 22, 29], + "english": [ + 2, 9, 39, 41, 42, 79, 82, 120, 131, 141, 225, 312, 332, 425, 621, 733, 770, + 968, 999, 1154, 1270, 1439, 1471, 1621, 2262, 2299, 2313, 2503, 2923, 3264, + 3814, 4567, 5005, 5015, 5024, 5045, 5049, 5050, 5055, 5074, 5089, 5095, + 5097, 5098, 5130, 5138, 5143, 5169, 5192, 5206, 5209, 5212, 5257, 5268, + 5269, 5282, 5286, 5321, 5341, 5346, 5348, 5398, 5409, 5444, 5452, 5453, + 5561, 5562, 5568, 5574, 5576, 5578, 5579, 5581, 5586, 5589, 5593, 5596, + 5609, 5610, 5616, 5631, 5637, 5639, 5646, 5668, 5669, 5680, 5688, 5690, + 5781, 5795, 6104, 6123, 6210, 6214, 6246, 6731, 6788, 6795, 6842, 6844, + 6847, 6849, 6944, 6945, 6948, 7027, 7643, 7690, 7737 + ], + "estonian": [2, 4, 5, 12], + "filipino": [6], + "french": [15, 36, 46, 47, 51, 52, 59, 61, 85, 86, 87, 88, 89, 90, 104, 106], + "georgian": [3, 4], + "german": [ + 6, 26, 33, 36, 39, 44, 45, 47, 48, 58, 60, 64, 69, 72, 73, 82, 87, 110, 233, + 252, 276, 303, 313, 366, 369, 375, 400, 432, 436, 444, 446, 463, 476 + ], + "hebrew": [ + 3, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 38, 48, + 68, 71, 72, 73 + ], + "hindi": [38], + "hungarian": [9], + "icelandic": [7], + "indonesian": [ + 4, 7, 11, 12, 14, 16, 23, 33, 45, 75, 81, 83, 101, 104, 117, 120, 122, 153, + 200, 204, 205, 207, 211, 212 + ], + "irish": [ + 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 28, 29, 31, 32, 33, 35, 46, 47, 48, 49, 51, 52, 53, 54, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 98, 99, 100, + 101, 102, 103, 105, 106, 107, 108, 109, 110, 111, 112, 114, 115, 117, 118 + ], + "italian": [23, 28, 36, 74, 75, 78, 79, 80, 88, 93, 96, 151], + "kannada": [8, 17, 20, 21, 22, 23, 24, 26, 27], + "kazakh": [18], + "korean": [1, 3, 4, 5, 6, 7, 8, 9, 13, 15, 17], + "lithuanian": [45], + "malagasy": [2], + "mongolian": [25, 44, 58, 59], + "norwegian_bokmal": [ + 6, 10, 13, 17, 22, 24, 28, 30, 33, 39, 47, 49, 114, 126, 127, 128, 129 + ], + "norwegian_nynorsk": [1, 2, 7, 26, 28], + "persian": [4, 8, 15], + "polish": [ + 2, 7, 9, 10, 12, 16, 17, 29, 31, 34, 40, 42, 43, 67, 68, 74, 82, 83, 84, 91, + 119, 121, 123, 134, 145, 155, 161, 206, 208, 212, 218, 236, 237 + ], + "portuguese": [11, 20, 57, 66, 91], + "romanian": [68], + "russian": [ + 1, 5, 14, 15, 26, 60, 68, 71, 74, 80, 91, 95, 101, 116, 130, 137, 150, 152, + 163, 170, 266, 285, 358, 373, 393, 414, 457, 461, 463, 500, 574, 575, 607, + 630, 663, 668, 736, 748, 771, 790, 806, 918, 922, 935, 940, 1009, 1029 + ], + "serbian": [6, 7], + "spanish": [27, 35, 60, 67, 150], + "swedish": [62], + "tamil": [27, 30, 32], + "thai": [3, 14, 15], + "turkish": [ + 1, 8, 9, 10, 11, 13, 14, 15, 16, 17, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 50, 61, 64, 66, 67, 68, 69, 117, 122 + ], + "ukrainian": [ + 9, 119, 120, 121, 122, 123, 124, 126, 127, 131, 132, 133, 135, 136, 137, + 138, 139, 140, 141, 143, 145, 147 + ], + "vietnamese": [18, 25, 33, 46, 63, 66, 78, 88] +} diff --git a/frontend/src/html/pages/login.html b/frontend/src/html/pages/login.html deleted file mode 100644 index f5321e360b8a..000000000000 --- a/frontend/src/html/pages/login.html +++ /dev/null @@ -1,100 +0,0 @@ - diff --git a/frontend/src/index.html b/frontend/src/index.html index c4ea32a36413..26f1c442e032 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -37,7 +37,9 @@ - + + } + else={ +
+ } + /> ); } diff --git a/frontend/src/ts/components/core/Theme.tsx b/frontend/src/ts/components/core/Theme.tsx index fc40a3ddb8d1..c20457d05dd4 100644 --- a/frontend/src/ts/components/core/Theme.tsx +++ b/frontend/src/ts/components/core/Theme.tsx @@ -4,9 +4,9 @@ import { createEffect, createMemo, JSXElement } from "solid-js"; import { themes } from "../../constants/themes"; import { createDebouncedEffectOn } from "../../hooks/effects"; import { useRefWithUtils } from "../../hooks/useRefWithUtils"; -import { hideLoaderBar, showLoaderBar } from "../../signals/loader-bar"; -import { getTheme } from "../../signals/theme"; -import { showNoticeNotification } from "../../stores/notifications"; +import { hideLoaderBar, showLoaderBar } from "../../states/loader-bar"; +import { showNoticeNotification } from "../../states/notifications"; +import { getTheme } from "../../states/theme"; import { FavIcon } from "./FavIcon"; export function Theme(): JSXElement { diff --git a/frontend/src/ts/components/layout/footer/Footer.tsx b/frontend/src/ts/components/layout/footer/Footer.tsx index 77c4e79a2edc..12b2f0265924 100644 --- a/frontend/src/ts/components/layout/footer/Footer.tsx +++ b/frontend/src/ts/components/layout/footer/Footer.tsx @@ -1,7 +1,7 @@ import { JSXElement } from "solid-js"; -import { getFocus, getIsScreenshotting } from "../../../signals/core"; -import { showModal } from "../../../stores/modals"; +import { getFocus, getIsScreenshotting } from "../../../states/core"; +import { showModal } from "../../../states/modals"; import { cn } from "../../../utils/cn"; import { Button } from "../../common/Button"; import { Keytips } from "./Keytips"; diff --git a/frontend/src/ts/components/layout/footer/Keytips.tsx b/frontend/src/ts/components/layout/footer/Keytips.tsx index dd7685b9e34b..071a972bd304 100644 --- a/frontend/src/ts/components/layout/footer/Keytips.tsx +++ b/frontend/src/ts/components/layout/footer/Keytips.tsx @@ -1,7 +1,7 @@ import { JSXElement, Show } from "solid-js"; -import { getConfig } from "../../../signals/config"; -import { getFocus } from "../../../signals/core"; +import { getConfig } from "../../../states/config"; +import { getFocus } from "../../../states/core"; import { Conditional } from "../../common/Conditional"; export function Keytips(): JSXElement { diff --git a/frontend/src/ts/components/layout/footer/ScrollToTop.tsx b/frontend/src/ts/components/layout/footer/ScrollToTop.tsx index 71219605b41e..c737bf914b16 100644 --- a/frontend/src/ts/components/layout/footer/ScrollToTop.tsx +++ b/frontend/src/ts/components/layout/footer/ScrollToTop.tsx @@ -1,6 +1,6 @@ import { JSXElement, createSignal, onMount, onCleanup } from "solid-js"; -import { getActivePage } from "../../../signals/core"; +import { getActivePage } from "../../../states/core"; import { Fa } from "../../common/Fa"; export function ScrollToTop(): JSXElement { diff --git a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx index ae2c81abffeb..58902d1b2ca8 100644 --- a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx +++ b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx @@ -5,10 +5,10 @@ import { isAuthenticated } from "../../../firebase"; import { getThemeIndicator, setCommandlineSubgroup, -} from "../../../signals/core"; -import { showModal } from "../../../stores/modals"; -import { showNoticeNotification } from "../../../stores/notifications"; -import { getSnapshot } from "../../../stores/snapshot"; +} from "../../../states/core"; +import { showModal } from "../../../states/modals"; +import { showNoticeNotification } from "../../../states/notifications"; +import { getSnapshot } from "../../../states/snapshot"; import { Fa } from "../../common/Fa"; export function ThemeIndicator(): JSXElement { diff --git a/frontend/src/ts/components/layout/footer/VersionButton.tsx b/frontend/src/ts/components/layout/footer/VersionButton.tsx index 58cabe9a6256..14bb44947f25 100644 --- a/frontend/src/ts/components/layout/footer/VersionButton.tsx +++ b/frontend/src/ts/components/layout/footer/VersionButton.tsx @@ -3,8 +3,8 @@ import { JSXElement, Show, createSignal } from "solid-js"; import { envConfig } from "virtual:env-config"; import { lastSeenServerCompatibility } from "../../../ape/adapters/ts-rest-adapter"; -import { getVersion } from "../../../signals/core"; -import { showModal } from "../../../stores/modals"; +import { getVersion } from "../../../states/core"; +import { showModal } from "../../../states/modals"; import { isDevEnvironment } from "../../../utils/env"; import { Fa } from "../../common/Fa"; diff --git a/frontend/src/ts/components/layout/header/AccountMenu.tsx b/frontend/src/ts/components/layout/header/AccountMenu.tsx index 3eaed560da7f..8ad9240e2ed0 100644 --- a/frontend/src/ts/components/layout/header/AccountMenu.tsx +++ b/frontend/src/ts/components/layout/header/AccountMenu.tsx @@ -2,7 +2,7 @@ import { JSXElement, Show } from "solid-js"; import { get as getServerConfiguration } from "../../../ape/server-configuration"; import { signOut } from "../../../auth"; -import { getSnapshot } from "../../../stores/snapshot"; +import { getSnapshot } from "../../../states/snapshot"; import { Button } from "../../common/Button"; import { NotificationBubble } from "../../common/NotificationBubble"; diff --git a/frontend/src/ts/components/layout/header/AccountXpBar.tsx b/frontend/src/ts/components/layout/header/AccountXpBar.tsx index de99ab2ed26f..0e95cd5e84e7 100644 --- a/frontend/src/ts/components/layout/header/AccountXpBar.tsx +++ b/frontend/src/ts/components/layout/header/AccountXpBar.tsx @@ -12,12 +12,12 @@ import { import { createEvent } from "../../../hooks/createEvent"; import { createSignalWithSetters } from "../../../hooks/createSignalWithSetters"; import { createEffectOn } from "../../../hooks/effects"; -import { getFocus } from "../../../signals/core"; +import { getFocus } from "../../../states/core"; import { getSkipBreakdownEvent, getXpBarData, setAnimatedLevel, -} from "../../../signals/header"; +} from "../../../states/header"; import { getXpDetails } from "../../../utils/levels"; import { sleep } from "../../../utils/misc"; import { Anime, AnimePresence, AnimeShow } from "../../common/anime"; diff --git a/frontend/src/ts/components/layout/header/Header.tsx b/frontend/src/ts/components/layout/header/Header.tsx index e6bb7b812404..bfc07ae25672 100644 --- a/frontend/src/ts/components/layout/header/Header.tsx +++ b/frontend/src/ts/components/layout/header/Header.tsx @@ -1,6 +1,6 @@ import { JSXElement } from "solid-js"; -import { getFocus, getIsScreenshotting } from "../../../signals/core"; +import { getFocus, getIsScreenshotting } from "../../../states/core"; import { cn } from "../../../utils/cn"; import { Logo } from "./Logo"; import { Nav } from "./Nav"; diff --git a/frontend/src/ts/components/layout/header/Logo.tsx b/frontend/src/ts/components/layout/header/Logo.tsx index f5af622a9425..bc838e360b53 100644 --- a/frontend/src/ts/components/layout/header/Logo.tsx +++ b/frontend/src/ts/components/layout/header/Logo.tsx @@ -4,7 +4,7 @@ import { dispatchRestartTest, getActivePage, getFocus, -} from "../../../signals/core"; +} from "../../../states/core"; import { cn } from "../../../utils/cn"; import { isDevEnvironment } from "../../../utils/env"; diff --git a/frontend/src/ts/components/layout/header/Nav.tsx b/frontend/src/ts/components/layout/header/Nav.tsx index cc09851ea7e7..69c0426e17fb 100644 --- a/frontend/src/ts/components/layout/header/Nav.tsx +++ b/frontend/src/ts/components/layout/header/Nav.tsx @@ -7,14 +7,14 @@ import { dispatchRestartTest, getActivePage, getFocus, -} from "../../../signals/core"; +} from "../../../states/core"; import { getAccountButtonSpinner, getAnimatedLevel, setAnimatedLevel, -} from "../../../signals/header"; -import { showModal } from "../../../stores/modals"; -import { getSnapshot } from "../../../stores/snapshot"; +} from "../../../states/header"; +import { showModal } from "../../../states/modals"; +import { getSnapshot } from "../../../states/snapshot"; import { cn } from "../../../utils/cn"; import { getLevelFromTotalXp } from "../../../utils/levels"; import { AnimeConditional } from "../../common/anime"; diff --git a/frontend/src/ts/components/layout/overlays/Banners.tsx b/frontend/src/ts/components/layout/overlays/Banners.tsx index b350ebfadd4f..26f197532f1e 100644 --- a/frontend/src/ts/components/layout/overlays/Banners.tsx +++ b/frontend/src/ts/components/layout/overlays/Banners.tsx @@ -3,12 +3,12 @@ import { debounce } from "throttle-debounce"; import { createEffectOn } from "../../../hooks/effects"; import { useRefWithUtils } from "../../../hooks/useRefWithUtils"; -import { setGlobalOffsetTop } from "../../../signals/core"; import { Banner as BannerType, getBanners, removeBanner, -} from "../../../stores/banners"; +} from "../../../states/banners"; +import { setGlobalOffsetTop } from "../../../states/core"; import { cn } from "../../../utils/cn"; import { Conditional } from "../../common/Conditional"; import { Fa } from "../../common/Fa"; diff --git a/frontend/src/ts/components/layout/overlays/LoaderBar.tsx b/frontend/src/ts/components/layout/overlays/LoaderBar.tsx index a20234bf0084..887453a83dc4 100644 --- a/frontend/src/ts/components/layout/overlays/LoaderBar.tsx +++ b/frontend/src/ts/components/layout/overlays/LoaderBar.tsx @@ -2,7 +2,7 @@ import { JSX } from "solid-js"; import { useRefWithUtils } from "../../../hooks/useRefWithUtils"; import { useVisibilityAnimation } from "../../../hooks/useVisibilityAnimation"; -import { getLoaderBarSignal } from "../../../signals/loader-bar"; +import { getLoaderBarSignal } from "../../../states/loader-bar"; import { applyReducedMotion } from "../../../utils/misc"; export function LoaderBar(): JSX.Element { diff --git a/frontend/src/ts/components/layout/overlays/MediaQueryDebugger.tsx b/frontend/src/ts/components/layout/overlays/MediaQueryDebugger.tsx index 98454936d2d5..aaa36d50c0c1 100644 --- a/frontend/src/ts/components/layout/overlays/MediaQueryDebugger.tsx +++ b/frontend/src/ts/components/layout/overlays/MediaQueryDebugger.tsx @@ -1,6 +1,6 @@ import { JSXElement, Match, Show, Switch } from "solid-js"; -import { bp } from "../../../signals/breakpoints"; +import { bp } from "../../../states/breakpoints"; import { isDevEnvironment } from "../../../utils/env"; export function MediaQueryDebugger(): JSXElement { diff --git a/frontend/src/ts/components/layout/overlays/Notifications.tsx b/frontend/src/ts/components/layout/overlays/Notifications.tsx index 0621aec45a32..811cad8a6cd7 100644 --- a/frontend/src/ts/components/layout/overlays/Notifications.tsx +++ b/frontend/src/ts/components/layout/overlays/Notifications.tsx @@ -5,13 +5,13 @@ import { getFocus, getGlobalOffsetTop, getIsScreenshotting, -} from "../../../signals/core"; +} from "../../../states/core"; import { Notification, getNotifications, removeNotification, clearAllNotifications, -} from "../../../stores/notifications"; +} from "../../../states/notifications"; import { cn } from "../../../utils/cn"; import { Anime } from "../../common/anime/Anime"; import { AnimePresence } from "../../common/anime/AnimePresence"; diff --git a/frontend/src/ts/components/layout/overlays/Overlays.tsx b/frontend/src/ts/components/layout/overlays/Overlays.tsx index b9e5c5556ac1..851254029316 100644 --- a/frontend/src/ts/components/layout/overlays/Overlays.tsx +++ b/frontend/src/ts/components/layout/overlays/Overlays.tsx @@ -1,8 +1,8 @@ import { JSXElement, Show } from "solid-js"; import { envConfig } from "virtual:env-config"; -import { getIsScreenshotting } from "../../../signals/core"; -import { showModal } from "../../../stores/modals"; +import { getIsScreenshotting } from "../../../states/core"; +import { showModal } from "../../../states/modals"; import { cn } from "../../../utils/cn"; import { isDevEnvironment } from "../../../utils/env"; import { Button } from "../../common/Button"; diff --git a/frontend/src/ts/components/modals/DevOptionsModal.tsx b/frontend/src/ts/components/modals/DevOptionsModal.tsx index 849dfa580945..a0913340d417 100644 --- a/frontend/src/ts/components/modals/DevOptionsModal.tsx +++ b/frontend/src/ts/components/modals/DevOptionsModal.tsx @@ -8,13 +8,13 @@ import { addXp } from "../../db"; import { toggleCaretDebug } from "../../elements/caret"; import { getInputElement } from "../../input/input-element"; import { showPopup } from "../../modals/simple-modals"; -import { showLoaderBar, hideLoaderBar } from "../../signals/loader-bar"; -import { hideModal, showModal } from "../../stores/modals"; +import { showLoaderBar, hideLoaderBar } from "../../states/loader-bar"; +import { hideModal, showModal } from "../../states/modals"; import { showNoticeNotification, showErrorNotification, showSuccessNotification, -} from "../../stores/notifications"; +} from "../../states/notifications"; import { toggleUserFakeChartData } from "../../test/result"; import { disableSlowTimerFail } from "../../test/test-timer"; import { FaSolidIcon } from "../../types/font-awesome"; diff --git a/frontend/src/ts/components/modals/RegisterCaptchaModal.tsx b/frontend/src/ts/components/modals/RegisterCaptchaModal.tsx index eb4bd46b5a16..8fcd76951bdd 100644 --- a/frontend/src/ts/components/modals/RegisterCaptchaModal.tsx +++ b/frontend/src/ts/components/modals/RegisterCaptchaModal.tsx @@ -6,8 +6,8 @@ import { reset as resetCaptcha, } from "../../controllers/captcha-controller"; import { useRef } from "../../hooks/useRef"; -import { hideModal, showModal } from "../../stores/modals"; -import { showErrorNotification } from "../../stores/notifications"; +import { hideModal, showModal } from "../../states/modals"; +import { showErrorNotification } from "../../states/notifications"; import { promiseWithResolvers } from "../../utils/misc"; import { AnimatedModal } from "../common/AnimatedModal"; diff --git a/frontend/src/ts/components/modals/SupportModal.tsx b/frontend/src/ts/components/modals/SupportModal.tsx index 92183d18c799..bb7344190c20 100644 --- a/frontend/src/ts/components/modals/SupportModal.tsx +++ b/frontend/src/ts/components/modals/SupportModal.tsx @@ -1,7 +1,7 @@ import { JSXElement } from "solid-js"; -import { setCommandlineSubgroup } from "../../signals/core"; -import { showModal } from "../../stores/modals"; +import { setCommandlineSubgroup } from "../../states/core"; +import { showModal } from "../../states/modals"; import { AnimatedModal } from "../common/AnimatedModal"; import { Button } from "../common/Button"; import { Fa } from "../common/Fa"; diff --git a/frontend/src/ts/components/modals/VersionHistoryModal.tsx b/frontend/src/ts/components/modals/VersionHistoryModal.tsx index 6fc46d668837..44764bd14717 100644 --- a/frontend/src/ts/components/modals/VersionHistoryModal.tsx +++ b/frontend/src/ts/components/modals/VersionHistoryModal.tsx @@ -2,7 +2,7 @@ import { useInfiniteQuery } from "@tanstack/solid-query"; import { For, JSXElement, Show } from "solid-js"; import { getVersionHistoryQueryOptions } from "../../queries/public"; -import { isModalOpen } from "../../stores/modals"; +import { isModalOpen } from "../../states/modals"; import { AnimatedModal } from "../common/AnimatedModal"; import AsyncContent from "../common/AsyncContent"; import { LoadingCircle } from "../common/LoadingCircle"; diff --git a/frontend/src/ts/components/mount.tsx b/frontend/src/ts/components/mount.tsx index 57da6b278ec3..7e96184bfe23 100644 --- a/frontend/src/ts/components/mount.tsx +++ b/frontend/src/ts/components/mount.tsx @@ -13,6 +13,7 @@ import { Modals } from "./modals/Modals"; import { AboutPage } from "./pages/AboutPage"; import { MyProfile } from "./pages/account/MyProfile"; import { LeaderboardPage } from "./pages/leaderboard/LeaderboardPage"; +import { LoginPage } from "./pages/login/LoginPage"; import { ProfilePage } from "./pages/profile/ProfilePage"; import { ProfileSearchPage } from "./pages/profile/ProfileSearchPage"; import { Popups } from "./popups/Popups"; @@ -20,6 +21,7 @@ import { Popups } from "./popups/Popups"; const components: Record JSXElement> = { footer: () =>