From 38ee8ac718e32eb8c0db75fab0466a73f7993aa0 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:08:05 -0500 Subject: [PATCH 1/4] Add change password ui --- src/@types/api/pokerogue-account-api.ts | 7 ++ src/enums/ui-mode.ts | 3 +- src/plugins/api/pokerogue-account-api.ts | 16 +++ src/ui/change-password-form-ui-handler.ts | 115 ++++++++++++++++++++++ src/ui/menu-ui-handler.ts | 11 +++ src/ui/ui.ts | 3 + 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/ui/change-password-form-ui-handler.ts diff --git a/src/@types/api/pokerogue-account-api.ts b/src/@types/api/pokerogue-account-api.ts index 7bcdc766664..779592483fb 100644 --- a/src/@types/api/pokerogue-account-api.ts +++ b/src/@types/api/pokerogue-account-api.ts @@ -15,3 +15,10 @@ export interface AccountRegisterRequest { username: string; password: string; } + +export interface AccountChangePwRequest { + password: string; +} +export interface AccountChangePwResponse { + success: boolean; +} diff --git a/src/enums/ui-mode.ts b/src/enums/ui-mode.ts index dcf6bd2a238..bc93e747be2 100644 --- a/src/enums/ui-mode.ts +++ b/src/enums/ui-mode.ts @@ -43,5 +43,6 @@ export enum UiMode { TEST_DIALOGUE, AUTO_COMPLETE, ADMIN, - MYSTERY_ENCOUNTER + MYSTERY_ENCOUNTER, + CHANGE_PASSWORD_FORM, } diff --git a/src/plugins/api/pokerogue-account-api.ts b/src/plugins/api/pokerogue-account-api.ts index 03f522e8dac..22f86413618 100644 --- a/src/plugins/api/pokerogue-account-api.ts +++ b/src/plugins/api/pokerogue-account-api.ts @@ -1,6 +1,7 @@ import { ApiBase } from "#api/api-base"; import { SESSION_ID_COOKIE_NAME } from "#app/constants"; import type { + AccountChangePwRequest, AccountInfoResponse, AccountLoginRequest, AccountLoginResponse, @@ -95,4 +96,19 @@ export class PokerogueAccountApi extends ApiBase { removeCookie(SESSION_ID_COOKIE_NAME); // we are always clearing the cookie. } + + public async changePassword(changePwData: AccountChangePwRequest) { + try { + const response = await this.doPost("/account/changepw", changePwData, "form-urlencoded"); + if (response.ok) { + return null; + } + console.warn("Change password failed!", response.status, response.statusText); + return response.text(); + } catch (err) { + console.warn("Change password failed!", err); + } + + return "Unknown error!"; + } } diff --git a/src/ui/change-password-form-ui-handler.ts b/src/ui/change-password-form-ui-handler.ts new file mode 100644 index 00000000000..dc0fd55d98d --- /dev/null +++ b/src/ui/change-password-form-ui-handler.ts @@ -0,0 +1,115 @@ +import { globalScene } from "#app/global-scene"; +// import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { UiMode } from "#enums/ui-mode"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import i18next from "i18next"; + +export class ChangePasswordFormUiHandler extends FormModalUiHandler { + private readonly ERR_PASSWORD: string = "invalid password"; + private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist"; + private readonly ERR_PASSWORD_MISMATCH: string = "password doesn't match"; + + constructor(mode: UiMode | null = null) { + super(mode); + } + + setup(): void { + super.setup(); + } + + override getModalTitle(_config?: ModalConfig): string { + return i18next.t("menu:changePassword"); + } + + override getWidth(_config?: ModalConfig): number { + return 160; + } + + override getMargin(_config?: ModalConfig): [number, number, number, number] { + return [0, 0, 48, 0]; + } + + override getButtonLabels(_config?: ModalConfig): string[] { + return [i18next.t("settings:buttonSubmit"), i18next.t("menu:cancel")]; + } + + override getReadableErrorMessage(error: string): string { + const colonIndex = error?.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + switch (error) { + case this.ERR_PASSWORD: + return i18next.t("menu:invalidRegisterPassword"); + case this.ERR_ACCOUNT_EXIST: + return i18next.t("menu:accountNonExistent"); + case this.ERR_PASSWORD_MISMATCH: + return i18next.t("menu:passwordNotMatchingConfirmPassword"); + } + + return super.getReadableErrorMessage(error); + } + + override getInputFieldConfigs(): InputFieldConfig[] { + const inputFieldConfigs: InputFieldConfig[] = []; + inputFieldConfigs.push({ + label: i18next.t("menu:password"), + isPassword: true, + }); + inputFieldConfigs.push({ + label: i18next.t("menu:confirmPassword"), + isPassword: true, + }); + return inputFieldConfigs; + } + + override show(args: any[]): boolean { + console.log("=========================1======================="); + if (super.show(args)) { + // Forces the modal to show above the others + // this.modalContainer.parentContainer?.bringToTop(this.modalContainer); + console.log("========================2========================"); + const config = args[0] as ModalConfig; + const originalSubmitAction = this.submitAction; + this.submitAction = () => { + if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { + // Prevent overlapping overrides on action modification + this.submitAction = originalSubmitAction; + this.sanitizeInputs(); + globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); + const onFail = (error: string | null) => { + globalScene.ui.setMode(UiMode.CHANGE_PASSWORD_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); + }; + const [passwordInput, confirmPasswordInput] = this.inputs; + if (!passwordInput?.text) { + return onFail(this.getReadableErrorMessage("invalid password")); + } + if (passwordInput.text !== confirmPasswordInput.text) { + return onFail(this.ERR_PASSWORD_MISMATCH); + } + + pokerogueApi.account.changePassword({ password: passwordInput.text }).then(error => { + if (!error && originalSubmitAction) { + originalSubmitAction(); + } else { + onFail(error); + } + }); + } + }; + + return true; + } + + return false; + } + + override clear() { + super.clear(); + this.setMouseCursorStyle("default"); //reset cursor + } +} diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 4e45dfedcb3..ff48da01ef7 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -310,6 +310,17 @@ export class MenuUiHandler extends MessageUiHandler { }, keepOpen: true, }, + { + // Note: i18n key is under `menu`, not `menuUiHandler` to avoid duplication + label: i18next.t("menu:changePassword"), + handler: () => { + ui.setOverlayMode(UiMode.CHANGE_PASSWORD_FORM, { + buttonActions: [() => ui.revertMode(), () => ui.revertMode()], + }); + return true; + }, + keepOpen: true, + }, { label: i18next.t("menuUiHandler:consentPreferences"), handler: () => { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index e9798e6350d..16a271e908c 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -12,6 +12,7 @@ import { BallUiHandler } from "#ui/ball-ui-handler"; import { BattleMessageUiHandler } from "#ui/battle-message-ui-handler"; import type { BgmBar } from "#ui/bgm-bar"; import { GameChallengesUiHandler } from "#ui/challenges-select-ui-handler"; +import { ChangePasswordFormUiHandler } from "#ui/change-password-form-ui-handler"; import { CommandUiHandler } from "#ui/command-ui-handler"; import { ConfirmUiHandler } from "#ui/confirm-ui-handler"; import { EggGachaUiHandler } from "#ui/egg-gacha-ui-handler"; @@ -101,6 +102,7 @@ const noTransitionModes = [ UiMode.ADMIN, UiMode.MYSTERY_ENCOUNTER, UiMode.RUN_INFO, + UiMode.CHANGE_PASSWORD_FORM, ]; export class UI extends Phaser.GameObjects.Container { @@ -171,6 +173,7 @@ export class UI extends Phaser.GameObjects.Container { new AutoCompleteUiHandler(), new AdminUiHandler(), new MysteryEncounterUiHandler(), + new ChangePasswordFormUiHandler(), ]; } From 7b1eb66e1cfd0b68d691f34e1f206bc3ad8e123f Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:51:37 -0500 Subject: [PATCH 2/4] Ensure input fields are cleared after submit or cancel --- src/ui/change-password-form-ui-handler.ts | 21 ++++++++++++----- src/ui/form-modal-ui-handler.ts | 28 ++++++++++++++++++----- src/ui/modal-ui-handler.ts | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/ui/change-password-form-ui-handler.ts b/src/ui/change-password-form-ui-handler.ts index dc0fd55d98d..12a01bbb722 100644 --- a/src/ui/change-password-form-ui-handler.ts +++ b/src/ui/change-password-form-ui-handler.ts @@ -66,13 +66,9 @@ export class ChangePasswordFormUiHandler extends FormModalUiHandler { return inputFieldConfigs; } - override show(args: any[]): boolean { - console.log("=========================1======================="); + override show(args: [ModalConfig, ...any]): boolean { if (super.show(args)) { - // Forces the modal to show above the others - // this.modalContainer.parentContainer?.bringToTop(this.modalContainer); - console.log("========================2========================"); - const config = args[0] as ModalConfig; + const config = args[0]; const originalSubmitAction = this.submitAction; this.submitAction = () => { if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { @@ -95,12 +91,25 @@ export class ChangePasswordFormUiHandler extends FormModalUiHandler { pokerogueApi.account.changePassword({ password: passwordInput.text }).then(error => { if (!error && originalSubmitAction) { originalSubmitAction(); + // Only clear inputs if the action was successful + for (const input of this.inputs) { + input.setText(""); + } } else { onFail(error); } }); } }; + // Upon pressing cancel, the inputs should be cleared + const originalCancelAction = this.cancelAction; + this.cancelAction = () => { + console.log("Change password form cancelled"); + for (const input of this.inputs) { + input.setText(""); + } + originalCancelAction?.(); + }; return true; } diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts index 35965b09b80..0857ae6ba6e 100644 --- a/src/ui/form-modal-ui-handler.ts +++ b/src/ui/form-modal-ui-handler.ts @@ -18,6 +18,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { protected inputs: InputText[]; protected errorMessage: Phaser.GameObjects.Text; protected submitAction: Function | null; + protected cancelAction: (() => void) | null; protected tween: Phaser.Tweens.Tween; protected formLabels: Phaser.GameObjects.Text[]; @@ -113,22 +114,37 @@ export abstract class FormModalUiHandler extends ModalUiHandler { }); } - show(args: any[]): boolean { + override show(args: any[]): boolean { if (super.show(args)) { this.inputContainers.map(ic => ic.setVisible(true)); const config = args[0] as FormModalConfig; this.submitAction = config.buttonActions.length ? config.buttonActions[0] : null; + this.cancelAction = config.buttonActions[1] ?? null; - if (this.buttonBgs.length) { - this.buttonBgs[0].off("pointerdown"); - this.buttonBgs[0].on("pointerdown", () => { - if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { - this.submitAction(); + // #region: Override button pointerDown + // Override the pointerDown event for the buttonBgs to call the `submitAction` and `cancelAction` + // properties that we set above, allowing their behavior to change after this method terminates + // Some subclasses use this to add behavior to the submit and cancel action + + this.buttonBgs[0].off("pointerdown"); + this.buttonBgs[0].on("pointerdown", () => { + if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { + this.submitAction(); + } + }); + const cancelBg = this.buttonBgs[1]; + if (cancelBg) { + cancelBg.off("pointerdown"); + cancelBg.on("pointerdown", () => { + // The seemingly redundant cancelAction check is intentionally left in as a defensive programming measure + if (this.cancelAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { + this.cancelAction(); } }); } + //#endregion: Override pointerDown events this.modalContainer.y += 24; this.modalContainer.setAlpha(0); diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts index 844f7f43930..ffeca039dcd 100644 --- a/src/ui/modal-ui-handler.ts +++ b/src/ui/modal-ui-handler.ts @@ -6,7 +6,7 @@ import { UiHandler } from "#ui/ui-handler"; import { addWindow, WindowVariant } from "#ui/ui-theme"; export interface ModalConfig { - buttonActions: Function[]; + buttonActions: ((...args: any[]) => any)[]; } export abstract class ModalUiHandler extends UiHandler { From 283fc1b4e409c66d09b06ee58a6ea1664e1e48c8 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:30:35 -0500 Subject: [PATCH 3/4] Play select sound on successful submission or cancel --- src/ui/change-password-form-ui-handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/change-password-form-ui-handler.ts b/src/ui/change-password-form-ui-handler.ts index 12a01bbb722..8e4bededc12 100644 --- a/src/ui/change-password-form-ui-handler.ts +++ b/src/ui/change-password-form-ui-handler.ts @@ -90,6 +90,7 @@ export class ChangePasswordFormUiHandler extends FormModalUiHandler { pokerogueApi.account.changePassword({ password: passwordInput.text }).then(error => { if (!error && originalSubmitAction) { + globalScene.ui.playSelect(); originalSubmitAction(); // Only clear inputs if the action was successful for (const input of this.inputs) { @@ -104,7 +105,7 @@ export class ChangePasswordFormUiHandler extends FormModalUiHandler { // Upon pressing cancel, the inputs should be cleared const originalCancelAction = this.cancelAction; this.cancelAction = () => { - console.log("Change password form cancelled"); + globalScene.ui.playSelect(); for (const input of this.inputs) { input.setText(""); } From 943cbeb89f20748e9a505626170b9ce9d610dfe6 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:07:06 -0600 Subject: [PATCH 4/4] Update src/ui/change-password-form-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/ui/change-password-form-ui-handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/change-password-form-ui-handler.ts b/src/ui/change-password-form-ui-handler.ts index 8e4bededc12..eccc67ffb04 100644 --- a/src/ui/change-password-form-ui-handler.ts +++ b/src/ui/change-password-form-ui-handler.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -// import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { UiMode } from "#enums/ui-mode"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler";