mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: implement password requirements validation and UI component
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
export * from "./lib/input/BilingualInput";
|
||||
export * from "./lib/input/CountrySelect";
|
||||
export * from "./lib/input/PasswordRequirements";
|
||||
export * from "./lib/feedback/ConfirmModal";
|
||||
export * from "./lib/feedback/ApiErrorAlert";
|
||||
export * from "./lib/feedback/notify";
|
||||
@@ -13,3 +14,4 @@ export * from "./lib/layout/PageHeader";
|
||||
export * from "./lib/data/AdvancedTable";
|
||||
export * from "./lib/data/useServerTable";
|
||||
export * from "./lib/components/MaritimeLoader"
|
||||
|
||||
|
||||
56
libs/ui/src/lib/input/PasswordRequirements.tsx
Normal file
56
libs/ui/src/lib/input/PasswordRequirements.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Stack, Text, Group } from '@mantine/core';
|
||||
import { IconCheck, IconX } from '@tabler/icons-react';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function passwordRules(minLength: number) {
|
||||
return [
|
||||
{ label: `At least ${minLength} characters`, test: (p: string) => p.length >= minLength },
|
||||
{ label: 'One lowercase letter', test: (p: string) => /[a-z]/.test(p) },
|
||||
{ label: 'One uppercase letter', test: (p: string) => /[A-Z]/.test(p) },
|
||||
{ label: 'One number', test: (p: string) => /\d/.test(p) },
|
||||
{ label: 'One special character', test: (p: string) => /[^A-Za-z0-9]/.test(p) },
|
||||
];
|
||||
}
|
||||
|
||||
/** Zod field schema enforcing every rule; unmet rules surface as separate issues. */
|
||||
export const passwordSchema = (minLength: number) =>
|
||||
z.string().superRefine((val, ctx) => {
|
||||
for (const rule of passwordRules(minLength)) {
|
||||
if (!rule.test(val)) {
|
||||
ctx.addIssue({ code: 'custom', message: rule.label });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const passwordMeetsAll = (password: string, minLength: number) =>
|
||||
passwordRules(minLength).every((rule) => rule.test(password));
|
||||
|
||||
interface PasswordRequirementsProps {
|
||||
password: string;
|
||||
minLength: number;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
/** Live checklist of password requirements, ticking off as the user types. */
|
||||
export function PasswordRequirements({ password, minLength, labels }: PasswordRequirementsProps) {
|
||||
const rules = passwordRules(minLength);
|
||||
return (
|
||||
<Stack gap={6} mt={6}>
|
||||
{rules.map((rule, i) => {
|
||||
const met = rule.test(password);
|
||||
return (
|
||||
<Group key={rule.label} gap={6} wrap="nowrap">
|
||||
{met ? (
|
||||
<IconCheck size={14} color="var(--mantine-color-teal-6)" />
|
||||
) : (
|
||||
<IconX size={14} color="var(--mantine-color-gray-5)" />
|
||||
)}
|
||||
<Text fz="xs" c={met ? 'teal' : 'dimmed'}>
|
||||
{labels?.[i] ?? rule.label}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user