Skip to content

Commit 0a4043d

Browse files
authored
impr: validate username on profile search page (@fehmer) (monkeytypegame#7132)
fixes monkeytypegame#7131
1 parent b7ddb26 commit 0a4043d

File tree

5 files changed

+93
-81
lines changed

5 files changed

+93
-81
lines changed

frontend/src/ts/controllers/profile-search-controller.ts

Lines changed: 0 additions & 79 deletions
This file was deleted.

frontend/src/ts/controllers/route-controller.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { isFunboxActive } from "../test/funbox/list";
66
import * as TestState from "../test/test-state";
77
import * as Notifications from "../elements/notifications";
88
import { LoadingOptions } from "../pages/page";
9+
import * as NavigationEvent from "../observables/navigation-event";
910

1011
//source: https://www.youtube.com/watch?v=OstALBk-jTc
1112
// https://www.youtube.com/watch?v=OstALBk-jTc
@@ -249,3 +250,7 @@ document.addEventListener("DOMContentLoaded", () => {
249250
}
250251
});
251252
});
253+
254+
NavigationEvent.subscribe((it) => {
255+
void navigate(it.url, { data: it.data });
256+
});

frontend/src/ts/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { egVideoListener } from "./popups/video-ad-popup";
3737
import "./states/connection";
3838
import "./test/tts";
3939
import "./elements/fps-counter";
40-
import "./controllers/profile-search-controller";
4140
import { isDevEnvironment, addToGlobal } from "./utils/misc";
4241
import * as VersionButton from "./elements/version-button";
4342
import * as Focus from "./test/focus";
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
type NavigationEvent = {
2+
url: string;
3+
data?: unknown;
4+
};
5+
6+
type SubscribeFunction = (event: NavigationEvent) => void;
7+
8+
const subscribers: SubscribeFunction[] = [];
9+
10+
export function subscribe(fn: SubscribeFunction): void {
11+
subscribers.push(fn);
12+
}
13+
14+
export function dispatch(event: NavigationEvent): void {
15+
subscribers.forEach((fn) => {
16+
try {
17+
fn(event);
18+
} catch (e) {
19+
console.error("Navigation event subscriber threw an error");
20+
console.error(e);
21+
}
22+
});
23+
}

frontend/src/ts/pages/profile-search.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
import Page from "./page";
22
import * as Skeleton from "../utils/skeleton";
3+
import Ape from "../ape";
4+
import {
5+
ValidatedHtmlInputElement,
6+
validateWithIndicator,
7+
} from "../elements/input-validation";
8+
import { UserNameSchema, UserProfile } from "@monkeytype/schemas/users";
9+
import { remoteValidation } from "../utils/remote-validation";
10+
import * as NavigationEvent from "../observables/navigation-event";
11+
12+
let nameInputEl: ValidatedHtmlInputElement | null = null;
13+
let lastProfile: UserProfile | null = null;
14+
15+
function enableButton(): void {
16+
$('.page.pageProfileSearch button[type="submit"]').prop("disabled", false);
17+
}
18+
19+
function disableButton(): void {
20+
$('.page.pageProfileSearch button[type="submit"]').prop("disabled", true);
21+
}
322

423
export const page = new Page({
524
id: "profileSearch",
@@ -10,9 +29,54 @@ export const page = new Page({
1029
},
1130
beforeShow: async (): Promise<void> => {
1231
Skeleton.append("pageProfileSearch", "main");
13-
$(".page.pageProfileSearch input").val("");
32+
33+
if (nameInputEl === null) {
34+
nameInputEl = validateWithIndicator(
35+
document.querySelector(
36+
".page.pageProfileSearch input"
37+
) as HTMLInputElement,
38+
{
39+
schema: UserNameSchema,
40+
isValid: remoteValidation(
41+
async (name) =>
42+
Ape.users.getProfile({ params: { uidOrName: name } }),
43+
{
44+
check: (data) => {
45+
lastProfile = data;
46+
return true;
47+
},
48+
on4xx: () => "Unknown user",
49+
}
50+
),
51+
callback: (result) => {
52+
if (result.status === "success") {
53+
enableButton();
54+
} else {
55+
disableButton();
56+
lastProfile = null;
57+
}
58+
},
59+
}
60+
);
61+
}
62+
63+
nameInputEl.setValue(null);
64+
disableButton();
1465
},
1566
afterShow: async (): Promise<void> => {
1667
$(".page.pageProfileSearch input").trigger("focus");
1768
},
1869
});
70+
71+
$(".page.pageProfileSearch form").on("submit", (e) => {
72+
e.preventDefault();
73+
if (lastProfile === null) return;
74+
NavigationEvent.dispatch({
75+
url: `/profile/${lastProfile.name}`,
76+
data: lastProfile,
77+
});
78+
});
79+
80+
$(() => {
81+
Skeleton.save("pageProfileSearch");
82+
});

0 commit comments

Comments
 (0)