import React, { useEffect, useRef, useState } from "react"; import { EyeIcon, EyeOffIcon, Trash2Icon } from "lucide-react"; import { Button } from "./button"; import { Field, FieldError, FieldLabel } from "./field"; import { Input } from "./input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select"; import { PASSWORD_STRENGTH_RULES, PasswordStrengthTooltip, } from "./password-strength-tooltip"; // --------------------------------------------------------------------------- // SectionHeading // --------------------------------------------------------------------------- export interface SectionHeadingProps { children: React.ReactNode; } export function SectionHeading({ children }: SectionHeadingProps) { return
{children}
; } // --------------------------------------------------------------------------- // FormField — label + required marker wrapper // --------------------------------------------------------------------------- export interface FormFieldProps { label: string; required?: boolean; children: React.ReactNode; } export function FormField({ label, required, children }: FormFieldProps) { return ( {label} {required && *} {children} ); } // --------------------------------------------------------------------------- // PasswordField — input with show/hide toggle + optional strength popover // --------------------------------------------------------------------------- export interface PasswordFieldProps { label: string; placeholder: string; required?: boolean; showStrengthPopover?: boolean; onValueChange?: (v: string) => void; } export function PasswordField({ label, placeholder, required, showStrengthPopover, onValueChange, }: PasswordFieldProps) { const [show, setShow] = useState(false); const [error, setError] = useState(""); const [touched, setTouched] = useState(false); const [value, setValue] = useState(""); const [dismissed, setDismissed] = useState(false); const wrapperRef = useRef(null); const allRulesPass = PASSWORD_STRENGTH_RULES.every((r) => r.test(value)); const isPopoverOpen = !!showStrengthPopover && !dismissed && value.length > 0 && !allRulesPass; const validate = (v: string) => { if (!v) return ""; if (v.length < 8) return "Min. 8 characters required"; if (!PASSWORD_STRENGTH_RULES.every((r) => r.test(v))) return "Password doesn't meet all requirements"; return ""; }; useEffect(() => { if (!isPopoverOpen) return; const onMouseDown = (e: MouseEvent) => { if (!wrapperRef.current?.contains(e.target as Node)) setDismissed(true); }; document.addEventListener("mousedown", onMouseDown, true); return () => document.removeEventListener("mousedown", onMouseDown, true); }, [isPopoverOpen]); const inputEl = (
{ const v = e.target.value; setValue(v); onValueChange?.(v); if (touched) setError(validate(v)); setDismissed(false); }} onFocus={() => setDismissed(false)} onKeyDown={(e: React.KeyboardEvent) => { if (e.key === "Tab") { setTouched(true); setError(validate(value)); setDismissed(true); } }} onBlur={() => { setTouched(true); setError(validate(value)); }} />
); return ( {label} {required && *} {showStrengthPopover ? ( {inputEl} ) : ( inputEl )}
{touched && error && !isPopoverOpen && {error}}
); } // --------------------------------------------------------------------------- // StaffRowItem — email + role select row inside InviteStaffView // --------------------------------------------------------------------------- export interface StaffMember { id: string; email: string; role: string; } export interface StaffRowItemProps { member: StaffMember; onRemove: (id: string) => void; onChange: (id: string, field: "email" | "role", value: string) => void; showRemove: boolean; roleOptions?: string[]; } const DEFAULT_ROLE_OPTIONS = ["Admin", "Broker", "Support"]; export function StaffRowItem({ member, onRemove, onChange, showRemove, roleOptions = DEFAULT_ROLE_OPTIONS, }: StaffRowItemProps) { return (
onChange(member.id, "email", e.target.value)} /> {showRemove && ( )}
); }