Skip to content

Commit 7747db8

Browse files
authored
impr: use zod schema for url parameters on leaderboard (@fehmer) (monkeytypegame#6305)
!nuf
1 parent 942faec commit 7747db8

File tree

3 files changed

+69
-47
lines changed

3 files changed

+69
-47
lines changed

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"slim-select": "2.9.2",
9797
"stemmer": "2.0.1",
9898
"throttle-debounce": "5.0.2",
99-
"zod": "3.23.8"
99+
"zod": "3.23.8",
100+
"zod-urlsearchparams": "0.0.16"
100101
}
101102
}

frontend/src/ts/pages/leaderboards.ts

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ import {
66
} from "@monkeytype/contracts/schemas/leaderboards";
77
import { capitalizeFirstLetter } from "../utils/strings";
88
import Ape from "../ape";
9-
import {
10-
Mode,
11-
Mode2Schema,
12-
ModeSchema,
13-
} from "@monkeytype/contracts/schemas/shared";
9+
import { Mode } from "@monkeytype/contracts/schemas/shared";
1410
import * as Notifications from "../elements/notifications";
1511
import Format from "../utils/format";
1612
import { Auth, isAuthenticated } from "../firebase";
@@ -34,7 +30,10 @@ import {
3430
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
3531
import { z } from "zod";
3632
import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
37-
import { LanguageSchema } from "@monkeytype/contracts/schemas/util";
33+
import {
34+
safeParse as parseUrlSearchParams,
35+
serialize as serializeUrlSearchParams,
36+
} from "zod-urlsearchparams";
3837
// import * as ServerConfiguration from "../ape/server-configuration";
3938

4039
const LeaderboardTypeSchema = z.enum(["allTime", "weekly", "daily"]);
@@ -97,17 +96,20 @@ const state = {
9796

9897
const SelectorSchema = z.object({
9998
type: LeaderboardTypeSchema,
100-
mode: ModeSchema.optional(),
101-
mode2: Mode2Schema.optional(),
102-
language: LanguageSchema.optional(),
99+
mode2: z.enum(["15", "60"]).optional(),
100+
language: z.string().optional(),
103101
yesterday: z.boolean().optional(),
104102
lastWeek: z.boolean().optional(),
105103
});
104+
const UrlParameterSchema = SelectorSchema.extend({
105+
page: z.number(),
106+
}).partial();
107+
type UrlParameter = z.infer<typeof UrlParameterSchema>;
106108

107109
const selectorLS = new LocalStorageWithSchema({
108110
key: "leaderboardSelector",
109111
schema: SelectorSchema,
110-
fallback: { type: "allTime", mode: "time", mode2: "15" },
112+
fallback: { type: "allTime", mode2: "15" },
111113
});
112114

113115
function updateTitle(): void {
@@ -1138,76 +1140,82 @@ function handleYesterdayLastWeekButton(action: string): void {
11381140
}
11391141

11401142
function updateGetParameters(): void {
1141-
const params = new URLSearchParams();
1143+
const params: UrlParameter = {};
11421144

1143-
params.set("type", state.type);
1145+
params.type = state.type;
11441146
if (state.type === "allTime") {
1145-
params.set("mode2", state.mode2);
1147+
params.mode2 = state.mode2;
11461148
} else if (state.type === "daily") {
1147-
params.set("language", state.language);
1148-
params.set("mode2", state.mode2);
1149+
params.language = state.language;
1150+
params.mode2 = state.mode2;
11491151
if (state.yesterday) {
1150-
params.set("yesterday", "true");
1151-
} else {
1152-
params.delete("yesterday");
1152+
params.yesterday = true;
11531153
}
11541154
} else if (state.type === "weekly") {
11551155
if (state.lastWeek) {
1156-
params.set("lastWeek", "true");
1157-
} else {
1158-
params.delete("lastWeek");
1156+
params.lastWeek = true;
11591157
}
11601158
}
11611159

1162-
params.set("page", (state.page + 1).toString());
1160+
params.page = state.page + 1;
11631161

1164-
const newUrl = `${window.location.pathname}?${params.toString()}`;
1162+
const urlParams = serializeUrlSearchParams({
1163+
schema: UrlParameterSchema,
1164+
data: params,
1165+
});
1166+
1167+
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
11651168
window.history.replaceState({}, "", newUrl);
11661169

11671170
selectorLS.set(state);
11681171
}
11691172

11701173
function readGetParameters(): void {
1171-
const params = new URLSearchParams(window.location.search);
1174+
const urlParams = new URLSearchParams(window.location.search);
11721175

1173-
if (params.size == 0) {
1176+
if (urlParams.size == 0) {
11741177
Object.assign(state, selectorLS.get());
11751178
return;
11761179
}
11771180

1178-
const type = params.get("type") as "allTime" | "weekly" | "daily";
1179-
if (type) {
1180-
state.type = type;
1181+
const parsed = parseUrlSearchParams({
1182+
schema: UrlParameterSchema,
1183+
input: urlParams,
1184+
});
1185+
if (!parsed.success) {
1186+
return;
1187+
}
1188+
const params = parsed.data;
1189+
1190+
if (params.type !== undefined) {
1191+
state.type = params.type;
11811192
}
11821193

11831194
if (state.type === "allTime") {
1184-
const mode = params.get("mode2") as "15" | "60";
1185-
if (mode) {
1186-
state.mode2 = mode;
1195+
if (params.mode2) {
1196+
state.mode2 = params.mode2;
11871197
}
11881198
} else if (state.type === "daily") {
1189-
const language = params.get("language");
1190-
const dailyMode = params.get("mode2") as "15" | "60";
1191-
const yesterday = params.get("yesterday") as string;
1192-
if (language !== null) {
1193-
state.language = language;
1199+
if (params.language !== undefined) {
1200+
state.language = params.language;
1201+
}
1202+
if (state.language === undefined) {
1203+
state.language = "english";
11941204
}
1195-
if (dailyMode) {
1196-
state.mode2 = dailyMode;
1205+
if (params.mode2 !== undefined) {
1206+
state.mode2 = params.mode2;
11971207
}
1198-
if (yesterday !== null && yesterday === "true") {
1199-
state.yesterday = true;
1208+
if (params.yesterday !== undefined) {
1209+
state.yesterday = params.yesterday;
12001210
}
12011211
} else if (state.type === "weekly") {
1202-
const lastWeek = params.get("lastWeek") as string;
1203-
if (lastWeek !== null && lastWeek === "true") {
1204-
state.lastWeek = true;
1212+
if (params.lastWeek !== undefined) {
1213+
state.lastWeek = params.lastWeek;
12051214
}
12061215
}
12071216

1208-
const page = params.get("page");
1209-
if (page !== null) {
1210-
state.page = parseInt(page, 10) - 1;
1217+
if (params.page !== undefined) {
1218+
state.page = params.page - 1;
12111219

12121220
if (state.page < 0) {
12131221
state.page = 0;
@@ -1264,6 +1272,7 @@ $(".page.pageLeaderboards .buttonGroup.secondary").on(
12641272
) {
12651273
if (state.mode2 === mode) return;
12661274
state.mode2 = mode;
1275+
state.page = 0;
12671276
} else if (language !== undefined && state.type === "daily") {
12681277
if (state.language === language) return;
12691278
state.language = language;

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)