mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2026-01-09 08:54:05 +08:00
impr: handle backend unavailable in remote validations (@fehmer) (#7105)
This commit is contained in:
parent
05afcc51f9
commit
0e4b9c4687
8 changed files with 62 additions and 47 deletions
|
|
@ -14,6 +14,8 @@ export type ValidationResult = {
|
|||
errorMessage?: string;
|
||||
};
|
||||
|
||||
export type IsValidResponse = true | string | { warning: string };
|
||||
|
||||
export type Validation<T> = {
|
||||
/**
|
||||
* Zod schema to validate the input value against.
|
||||
|
|
@ -28,7 +30,7 @@ export type Validation<T> = {
|
|||
* @param thisPopup the current modal
|
||||
* @returns true if the `value` is valid, an errorMessage as string if it is invalid.
|
||||
*/
|
||||
isValid?: (value: T) => Promise<true | string | { warning: string }>;
|
||||
isValid?: (value: T) => Promise<IsValidResponse>;
|
||||
|
||||
/** custom debounce delay for `isValid` call. defaults to 100 */
|
||||
debounceDelay?: number;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import Ape from "../ape";
|
||||
import * as DB from "../db";
|
||||
import { IsValidResponse } from "../elements/input-validation";
|
||||
import * as Settings from "../pages/settings";
|
||||
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
|
||||
import { SimpleModal, TextInput } from "../utils/simple-modal";
|
||||
import { TagNameSchema } from "@monkeytype/schemas/users";
|
||||
|
||||
const cleanTagName = (tagName: string): string => tagName.replaceAll(" ", "_");
|
||||
const tagNameValidation = async (tagName: string): Promise<true | string> => {
|
||||
const tagNameValidation = async (tagName: string): Promise<IsValidResponse> => {
|
||||
const validationResult = TagNameSchema.safeParse(cleanTagName(tagName));
|
||||
if (validationResult.success) return true;
|
||||
return validationResult.error.errors.map((err) => err.message).join(", ");
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import AnimatedModal from "../utils/animated-modal";
|
|||
import { resetIgnoreAuthCallback } from "../firebase";
|
||||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { UserNameSchema } from "@monkeytype/schemas/users";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
||||
let signedInUser: UserCredential | undefined = undefined;
|
||||
|
||||
|
|
@ -154,17 +155,10 @@ function disableInput(): void {
|
|||
|
||||
validateWithIndicator(nameInputEl, {
|
||||
schema: UserNameSchema,
|
||||
isValid: async (name: string) => {
|
||||
const checkNameResponse = await Ape.users.getNameAvailability({
|
||||
params: { name: name },
|
||||
});
|
||||
|
||||
return (
|
||||
(checkNameResponse.status === 200 &&
|
||||
checkNameResponse.body.data.available) ||
|
||||
"Name not available"
|
||||
);
|
||||
},
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
{ check: (data) => data.available || "Name not available" }
|
||||
),
|
||||
debounceDelay: 1000,
|
||||
callback: (result) => {
|
||||
if (result.status === "success") {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import {
|
|||
import { goToPage } from "../pages/leaderboards";
|
||||
import FileStorage from "../utils/file-storage";
|
||||
import { z } from "zod";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
||||
type PopupKey =
|
||||
| "updateEmail"
|
||||
|
|
@ -479,17 +480,10 @@ list.updateName = new SimpleModal({
|
|||
initVal: "",
|
||||
validation: {
|
||||
schema: UserNameSchema,
|
||||
isValid: async (newName: string) => {
|
||||
const checkNameResponse = await Ape.users.getNameAvailability({
|
||||
params: { name: newName },
|
||||
});
|
||||
|
||||
return (
|
||||
(checkNameResponse.status === 200 &&
|
||||
checkNameResponse.body.data.available) ||
|
||||
"Name not available"
|
||||
);
|
||||
},
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
{ check: (data) => data.available || "Name not available" }
|
||||
),
|
||||
debounceDelay: 1000,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { Connection } from "@monkeytype/schemas/connections";
|
|||
import { Friend, UserNameSchema } from "@monkeytype/schemas/users";
|
||||
import * as Loader from "../elements/loader";
|
||||
import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
||||
const pageElement = $(".page.pageFriends");
|
||||
|
||||
|
|
@ -75,17 +76,10 @@ const addFriendModal = new SimpleModal({
|
|||
initVal: "",
|
||||
validation: {
|
||||
schema: UserNameSchema,
|
||||
isValid: async (name: string) => {
|
||||
const checkNameResponse = await Ape.users.getNameAvailability({
|
||||
params: { name: name },
|
||||
});
|
||||
|
||||
return (
|
||||
(checkNameResponse.status === 200 &&
|
||||
!checkNameResponse.body.data.available) ||
|
||||
"Unknown user"
|
||||
);
|
||||
},
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
{ check: (data) => !data.available || "Unknown user" }
|
||||
),
|
||||
debounceDelay: 1000,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { validateWithIndicator } from "../elements/input-validation";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
import { z } from "zod";
|
||||
import { remoteValidation } from "../utils/remote-validation";
|
||||
|
||||
let registerForm: {
|
||||
name?: string;
|
||||
|
|
@ -73,17 +74,10 @@ const nameInputEl = document.querySelector(
|
|||
) as HTMLInputElement;
|
||||
validateWithIndicator(nameInputEl, {
|
||||
schema: UserNameSchema,
|
||||
isValid: async (name: string) => {
|
||||
const checkNameResponse = await Ape.users.getNameAvailability({
|
||||
params: { name: name },
|
||||
});
|
||||
|
||||
return (
|
||||
(checkNameResponse.status === 200 &&
|
||||
checkNameResponse.body.data.available) ||
|
||||
"Name not available"
|
||||
);
|
||||
},
|
||||
isValid: remoteValidation(
|
||||
async (name) => Ape.users.getNameAvailability({ params: { name } }),
|
||||
{ check: (data) => data.available || "Name not available" }
|
||||
),
|
||||
debounceDelay: 1000,
|
||||
callback: (result) => {
|
||||
registerForm.name =
|
||||
|
|
|
|||
32
frontend/src/ts/utils/remote-validation.ts
Normal file
32
frontend/src/ts/utils/remote-validation.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { IsValidResponse } from "../elements/input-validation";
|
||||
|
||||
type IsValidResonseOrFunction =
|
||||
| ((message: string) => IsValidResponse)
|
||||
| IsValidResponse;
|
||||
export function remoteValidation<V, T>(
|
||||
call: (
|
||||
val: V
|
||||
) => Promise<{ status: number; body: { data?: T; message: string } }>,
|
||||
options?: {
|
||||
check?: (data: T) => IsValidResponse;
|
||||
on4xx?: IsValidResonseOrFunction;
|
||||
on5xx?: IsValidResonseOrFunction;
|
||||
}
|
||||
): (val: V) => Promise<IsValidResponse> {
|
||||
return async (val) => {
|
||||
const result = await call(val);
|
||||
if (result.status <= 299) {
|
||||
return options?.check?.(result.body.data as T) ?? true;
|
||||
}
|
||||
|
||||
let handler: IsValidResonseOrFunction | undefined;
|
||||
if (result.status <= 499) {
|
||||
handler = options?.on4xx ?? ((message) => message);
|
||||
} else {
|
||||
handler = options?.on5xx ?? "Server unavailable. Please try again later.";
|
||||
}
|
||||
|
||||
if (typeof handler === "function") return handler(result.body.message);
|
||||
return handler;
|
||||
};
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import * as Loader from "../elements/loader";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as ConnectionState from "../states/connection";
|
||||
import {
|
||||
IsValidResponse,
|
||||
Validation,
|
||||
ValidationOptions,
|
||||
ValidationResult,
|
||||
|
|
@ -33,7 +34,10 @@ type CommonInput<TType, TValue> = {
|
|||
* @param thisPopup the current modal
|
||||
* @returns true if the `value` is valid, an errorMessage as string if it is invalid.
|
||||
*/
|
||||
isValid?: (value: string, thisPopup: SimpleModal) => Promise<true | string>;
|
||||
isValid?: (
|
||||
value: string,
|
||||
thisPopup: SimpleModal
|
||||
) => Promise<IsValidResponse>;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue