TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import { authClient } from "@/lib/auth-client";4import { recordAuditFromClient } from "@/actions/account";5import { PasswordInput, PasswordRequirements, passwordStrong } from "@/components/auth/password-input";6import { Button } from "@/components/ui/button";7import { Field, FieldError, Hint, Label } from "@/components/ui/label";8import { Alert } from "@/components/ui/alert";910export function ChangePasswordForm() {11 const [current, setCurrent] = React.useState("");12 const [next, setNext] = React.useState("");13 const [confirm, setConfirm] = React.useState("");14 const [revokeOthers, setRevokeOthers] = React.useState(true);15 const [error, setError] = React.useState<string | null>(null);16 const [done, setDone] = React.useState(false);17 const [loading, setLoading] = React.useState(false);1819 const mismatch = confirm.length > 0 && confirm !== next;20 const canSubmit = current.length > 0 && passwordStrong(next) && next === confirm && next !== current;2122 async function submit(e: React.FormEvent) {23 e.preventDefault();24 setError(null);25 setDone(false);26 setLoading(true);27 const { error } = await authClient.changePassword({ currentPassword: current, newPassword: next, revokeOtherSessions: revokeOthers });28 if (error) {29 setLoading(false);30 setError(error.status === 400 || error.status === 401 ? "Your current password is incorrect." : error.message ?? "Could not change the password. Try again.");31 return;32 }33 await recordAuditFromClient("password.changed", { revokedOtherSessions: revokeOthers }).catch(() => {});34 setLoading(false);35 setDone(true);36 setCurrent("");37 setNext("");38 setConfirm("");39 }4041 return (42 <form onSubmit={submit} className="grid gap-4 sm:max-w-md" noValidate>43 {error ? <Alert variant="danger">{error}</Alert> : null}44 {done ? <Alert variant="success">Password changed.{revokeOthers ? " Other devices have been signed out." : ""}</Alert> : null}45 <Field>46 <Label htmlFor="pw-current">Current password</Label>47 <PasswordInput id="pw-current" autoComplete="current-password" value={current} onChange={(e) => setCurrent(e.target.value)} required />48 </Field>49 <Field>50 <Label htmlFor="pw-new">New password</Label>51 <PasswordInput id="pw-new" autoComplete="new-password" value={next} onChange={(e) => setNext(e.target.value)} required />52 <PasswordRequirements password={next} />53 {next && current && next === current ? <FieldError>Choose a password different from the current one.</FieldError> : null}54 </Field>55 <Field>56 <Label htmlFor="pw-confirm">Confirm new password</Label>57 <PasswordInput id="pw-confirm" autoComplete="new-password" value={confirm} onChange={(e) => setConfirm(e.target.value)} required aria-invalid={mismatch} />58 <FieldError>{mismatch ? "Passwords do not match." : null}</FieldError>59 </Field>60 <label className="flex items-start gap-2 text-[13px] text-fg-muted">61 <input type="checkbox" checked={revokeOthers} onChange={(e) => setRevokeOthers(e.target.checked)} className="mt-0.5 size-4 rounded border-border accent-[var(--accent)]" />62 <span>63 Sign out all other devices64 <Hint>Recommended if you suspect your password leaked. This session stays active.</Hint>65 </span>66 </label>67 <div>68 <Button type="submit" variant="primary" loading={loading} disabled={!canSubmit}>69 Update password70 </Button>71 </div>72 </form>73 );74}75