feat: setup forget password to the backoffice

This commit is contained in:
Nathnael
2026-07-20 06:19:12 +00:00
parent 5702933870
commit 3d4996e4df
10 changed files with 708 additions and 23 deletions

View File

@@ -0,0 +1,22 @@
/** Normalise Ethiopian local phone (09…/07…) to E.164; pass email through unchanged. */
export function normaliseIdentifier(raw: string): string {
const v = raw.trim();
const digits = v.replace(/\D/g, "");
if (digits.length >= 9 && (v.startsWith("0") || v.startsWith("+251"))) {
const local = digits.startsWith("251") ? digits.slice(3) : digits.replace(/^0/, "");
return `+251${local}`;
}
return v.toLowerCase();
}
/** Mask all but the first 7 chars of an E.164 phone for display. */
export const maskPhone = (p: string) =>
p.length > 4 ? `${p.slice(0, 7)}${"*".repeat(Math.max(0, p.length - 7))}` : p;
/** Mask the local part of an email for display (j***e@example.com). */
export const maskEmail = (email: string) => {
const [local, domain] = email.split("@");
if (!local || !domain) return email;
if (local.length <= 2) return `${local[0] ?? ""}***@${domain}`;
return `${local[0]}***${local[local.length - 1]}@${domain}`;
};

View File

@@ -0,0 +1,38 @@
import { z } from "zod";
/** Live checklist shown under the password field. Mirrors {@link passwordField}. */
export const passwordRequirements = [
{ label: "At least 8 characters", test: (v: string) => v.length >= 8 },
{ label: "One uppercase letter", test: (v: string) => /[A-Z]/.test(v) },
{ label: "One lowercase letter", test: (v: string) => /[a-z]/.test(v) },
{ label: "One number", test: (v: string) => /\d/.test(v) },
{
label: "One special character",
test: (v: string) => /[^A-Za-z0-9]/.test(v),
},
] as const;
/**
* Must stay in step with IAM's `@IsStrongPassword()` on `InitialResetPasswordDto`
* — a password this accepts but the API rejects surfaces as an opaque 400.
*/
export const passwordField = z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must include an uppercase letter")
.regex(/[a-z]/, "Password must include a lowercase letter")
.regex(/\d/, "Password must include a number")
.regex(/[^A-Za-z0-9]/, "Password must include a special character");
export const confirmPasswordField = z
.string()
.min(1, "Please confirm your password");
export const samePassword = (data: {
password: string;
confirmPassword: string;
}) => data.password === data.confirmPassword;
/** Every requirement in {@link passwordRequirements} is satisfied. */
export const meetsAllRequirements = (value: string) =>
passwordRequirements.every((r) => r.test(value));