mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-08 13:11:19 +08:00
refactor: use validation on email update modal (@fehmer) (#6272)
This commit is contained in:
parent
14d423e088
commit
09eb1a2b0e
3 changed files with 36 additions and 34 deletions
|
|
@ -35,7 +35,7 @@ import {
|
|||
} from "../utils/simple-modal";
|
||||
import { ShowOptions } from "../utils/animated-modal";
|
||||
import { GenerateDataRequest } from "@monkeytype/contracts/dev";
|
||||
import { UserNameSchema } from "@monkeytype/contracts/users";
|
||||
import { UserEmailSchema, UserNameSchema } from "@monkeytype/contracts/users";
|
||||
import { goToPage } from "../pages/leaderboards";
|
||||
|
||||
type PopupKey =
|
||||
|
|
@ -230,11 +230,20 @@ list.updateEmail = new SimpleModal({
|
|||
type: "text",
|
||||
placeholder: "New email",
|
||||
initVal: "",
|
||||
validation: {
|
||||
schema: UserEmailSchema,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
placeholder: "Confirm new email",
|
||||
initVal: "",
|
||||
validation: {
|
||||
schema: UserEmailSchema,
|
||||
isValid: async (currentValue, thisPopup) =>
|
||||
currentValue === thisPopup.inputs?.[1]?.currentValue() ||
|
||||
"Emails don't match",
|
||||
},
|
||||
},
|
||||
],
|
||||
buttonText: "update",
|
||||
|
|
@ -262,7 +271,7 @@ list.updateEmail = new SimpleModal({
|
|||
|
||||
const response = await Ape.users.updateEmail({
|
||||
body: {
|
||||
newEmail: email.trim(),
|
||||
newEmail: email,
|
||||
previousEmail: reauth.user.email as string,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ type CommonInput<TType, TValue> = {
|
|||
* Custom async validation method.
|
||||
* This is intended to be used for validations that cannot be handled with a Zod schema like server-side validations.
|
||||
* @param value current input value
|
||||
* @param thisPopup the current modal
|
||||
* @returns true if the `value` is valid, an errorMessage as string if it is invalid.
|
||||
*/
|
||||
isValid?: (value: string) => Promise<true | string>;
|
||||
isValid?: (value: string, thisPopup: SimpleModal) => Promise<true | string>;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -88,8 +89,9 @@ export type ExecReturn = {
|
|||
afterHide?: () => void;
|
||||
};
|
||||
|
||||
type CommonInputTypeWithIndicator = CommonInputType & {
|
||||
type FormInput = CommonInputType & {
|
||||
indicator?: InputIndicator;
|
||||
currentValue: () => string;
|
||||
};
|
||||
type SimpleModalOptions = {
|
||||
id: string;
|
||||
|
|
@ -114,7 +116,7 @@ export class SimpleModal {
|
|||
modal: AnimatedModal;
|
||||
id: string;
|
||||
title: string;
|
||||
inputs: CommonInputTypeWithIndicator[];
|
||||
inputs: FormInput[];
|
||||
text?: string;
|
||||
textAllowHtml: boolean;
|
||||
buttonText: string;
|
||||
|
|
@ -130,7 +132,7 @@ export class SimpleModal {
|
|||
this.id = options.id;
|
||||
this.execFn = options.execFn;
|
||||
this.title = options.title;
|
||||
this.inputs = options.inputs ?? [];
|
||||
this.inputs = (options.inputs as FormInput[]) ?? [];
|
||||
this.text = options.text;
|
||||
this.textAllowHtml = options.textAllowHtml ?? false;
|
||||
this.wrapper = modal.getWrapper();
|
||||
|
|
@ -313,9 +315,17 @@ export class SimpleModal {
|
|||
const element = document.querySelector(
|
||||
"#" + attributes["id"]
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (input.oninput !== undefined) {
|
||||
element.oninput = input.oninput;
|
||||
}
|
||||
|
||||
input.currentValue = () => {
|
||||
if (element.type === "checkbox")
|
||||
return element.checked ? "true" : "false";
|
||||
return element.value;
|
||||
};
|
||||
|
||||
if (input.validation !== undefined) {
|
||||
const indicator = new InputIndicator(element, {
|
||||
valid: {
|
||||
|
|
@ -335,7 +345,7 @@ export class SimpleModal {
|
|||
input.indicator = indicator;
|
||||
|
||||
const debouceIsValid = debounce(1000, async (value: string) => {
|
||||
const result = await input.validation?.isValid?.(value);
|
||||
const result = await input.validation?.isValid?.(value, this);
|
||||
|
||||
if (element.value !== value) {
|
||||
//value of the input has changed in the meantime. discard
|
||||
|
|
@ -389,43 +399,24 @@ export class SimpleModal {
|
|||
|
||||
exec(): void {
|
||||
if (!this.canClose) return;
|
||||
const vals: string[] = [];
|
||||
for (const el of $("#simpleModal input, #simpleModal textarea")) {
|
||||
if ($(el).is(":checkbox")) {
|
||||
vals.push($(el).is(":checked") ? "true" : "false");
|
||||
} else {
|
||||
vals.push($(el).val() as string);
|
||||
}
|
||||
}
|
||||
|
||||
type CommonInputWithCurrentValue = CommonInputTypeWithIndicator & {
|
||||
currentValue: string | undefined;
|
||||
};
|
||||
|
||||
const inputsWithCurrentValue: CommonInputWithCurrentValue[] = [];
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
inputsWithCurrentValue.push({
|
||||
...(this.inputs[i] as CommonInputTypeWithIndicator),
|
||||
currentValue: vals[i],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
inputsWithCurrentValue
|
||||
this.inputs
|
||||
.filter((i) => i.hidden !== true && i.optional !== true)
|
||||
.some((v) => v.currentValue === undefined || v.currentValue === "")
|
||||
.some((v) => v.currentValue() === undefined || v.currentValue() === "")
|
||||
) {
|
||||
Notifications.add("Please fill in all fields", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputsWithCurrentValue.some((i) => i.indicator?.get() === "invalid")) {
|
||||
if (this.inputs.some((i) => i.indicator?.get() === "invalid")) {
|
||||
Notifications.add("Please solve all validation errors", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.disableInputs();
|
||||
Loader.show();
|
||||
const vals: string[] = this.inputs.map((it) => it.currentValue());
|
||||
void this.execFn(this, ...vals).then((res) => {
|
||||
Loader.hide();
|
||||
if (res.showNotification ?? true) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import { IdSchema, LanguageSchema, StringNumberSchema } from "./schemas/util";
|
|||
import { CustomThemeColorsSchema } from "./schemas/configs";
|
||||
import { doesNotContainProfanity } from "./validation/validation";
|
||||
|
||||
export const UserEmailSchema = z.string().email();
|
||||
|
||||
export const GetUserResponseSchema = responseWithData(
|
||||
UserSchema.extend({
|
||||
inboxUnreadSize: z.number().int().nonnegative(),
|
||||
|
|
@ -50,7 +52,7 @@ export const UserNameSchema = doesNotContainProfanity(
|
|||
);
|
||||
|
||||
export const CreateUserRequestSchema = z.object({
|
||||
email: z.string().email().optional(),
|
||||
email: UserEmailSchema.optional(),
|
||||
name: UserNameSchema,
|
||||
uid: z.string().optional(), //defined by firebase, no validation should be applied
|
||||
captcha: z.string(), //defined by google recaptcha, no validation should be applied
|
||||
|
|
@ -80,8 +82,8 @@ export type UpdateLeaderboardMemoryRequest = z.infer<
|
|||
>;
|
||||
|
||||
export const UpdateEmailRequestSchema = z.object({
|
||||
newEmail: z.string().email(),
|
||||
previousEmail: z.string().email(),
|
||||
newEmail: UserEmailSchema,
|
||||
previousEmail: UserEmailSchema,
|
||||
});
|
||||
export type UpdateEmailRequestSchema = z.infer<typeof UpdateEmailRequestSchema>;
|
||||
|
||||
|
|
@ -303,7 +305,7 @@ export type ReportUserRequest = z.infer<typeof ReportUserRequestSchema>;
|
|||
|
||||
export const ForgotPasswordEmailRequestSchema = z.object({
|
||||
captcha: z.string(),
|
||||
email: z.string().email(),
|
||||
email: UserEmailSchema,
|
||||
});
|
||||
export type ForgotPasswordEmailRequest = z.infer<
|
||||
typeof ForgotPasswordEmailRequestSchema
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue