import { state, property, customElement } from "lit/decorators.js"; import { str, msg, localized } from "@lit/localize"; import debounce from "lodash/fp/debounce"; import { when } from "lit/directives/when.js"; import type { ZxcvbnResult } from "@zxcvbn-ts/core"; import type { ViewState } from "@/utils/APIRouter"; import LiteElement, { html } from "@/utils/LiteElement"; import PasswordService from "@/utils/PasswordService"; import type { Input as BtrixInput } from "@/components/ui/input"; const { PASSWORD_MINLENGTH, PASSWORD_MAXLENGTH, PASSWORD_MIN_SCORE } = PasswordService; @localized() @customElement("btrix-reset-password") export class ResetPassword extends LiteElement { @property({ type: Object }) viewState!: ViewState; @state() private pwStrengthResults: null | ZxcvbnResult = null; @state() private serverError?: string; @state() private isSubmitting: boolean = false; protected firstUpdated() { PasswordService.setOptions(); } render() { let formError; if (this.serverError) { formError = html`
${this.serverError}
`; } return html`

${msg( str`Choose a strong password between ${PASSWORD_MINLENGTH}-${PASSWORD_MAXLENGTH} characters.` )}

${when(this.pwStrengthResults, this.renderPasswordStrength)}
${formError} ${msg("Change Password")}
${msg("Resend password reset email?")}
`; } private renderPasswordStrength = () => html`
`; private onPasswordInput = debounce(150)(async (e: InputEvent) => { const { value } = e.target as BtrixInput; if (!value || value.length < 4) { this.pwStrengthResults = null; return; } this.pwStrengthResults = await PasswordService.checkStrength(value); }) as any; async onSubmit(event: SubmitEvent) { event.preventDefault(); this.isSubmitting = true; const formData = new FormData(event.target as HTMLFormElement); const password = formData.get("password") as string; const resp = await fetch("/api/auth/reset-password", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ token: this.viewState.params.token, password, }), }); switch (resp.status) { case 200: // TODO show toast notification this.navTo("/log-in"); break; case 400: case 422: const { detail } = await resp.json(); if (detail === "reset_password_bad_token") { // TODO password validation details this.serverError = msg( "Password reset email is not valid. Request a new password reset email" ); } else if (detail.code && detail.code === "invalid_password") { this.serverError = msg( "Invalid password. Must be between 8 and 64 characters" ); } break; default: this.serverError = msg("Something unexpected went wrong"); break; } this.isSubmitting = false; } }