Role-aware registration: Customer/Employee/Owner/Contractor/Sub-Con/Vendor

- New "Your role" options matching the portal roles
- Customer/Owner = self; Employee shows an Employee ID field;
  Contractor/Sub-Con/Vendor show their ID + property owner name
- Validation adapts per role

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:14:12 +05:30
parent b79d925552
commit f1a7bdc8ce
2 changed files with 40 additions and 26 deletions
+5 -4
View File
@@ -75,11 +75,12 @@ export const countryCodes: CountryCode[] = [
]; ];
export const relationshipOptions = [ export const relationshipOptions = [
"Homeowner", "Customer",
"Employee",
"Owner",
"Contractor", "Contractor",
"Property Manager", "Sub-Con",
"Tenant", "Vendor",
"Authorized Representative",
]; ];
export const sectors = [ export const sectors = [
+35 -22
View File
@@ -27,7 +27,7 @@ export function RegisterFlow() {
const [cc, setCc] = useState("+1"); const [cc, setCc] = useState("+1");
const [phone, setPhone] = useState(""); const [phone, setPhone] = useState("");
const [pw, setPw] = useState(""); const [pw, setPw] = useState("");
const [relationship, setRelationship] = useState("Homeowner"); const [relationship, setRelationship] = useState("Customer");
const [alloeNo, setAlloeNo] = useState(""); const [alloeNo, setAlloeNo] = useState("");
const [alloeFirst, setAlloeFirst] = useState(""); const [alloeFirst, setAlloeFirst] = useState("");
const [alloeLast, setAlloeLast] = useState(""); const [alloeLast, setAlloeLast] = useState("");
@@ -42,15 +42,16 @@ export function RegisterFlow() {
const [emailVerified, setEmailVerified] = useState(false); const [emailVerified, setEmailVerified] = useState(false);
const [phoneVerified, setPhoneVerified] = useState(false); const [phoneVerified, setPhoneVerified] = useState(false);
const isSelf = relationship === "Homeowner"; const isSelf = relationship === "Customer" || relationship === "Owner";
const isEmployee = relationship === "Employee";
const country = countryCodes.find((c) => c.code === cc)!; const country = countryCodes.find((c) => c.code === cc)!;
const phoneOk = phone.replace(/\D/g, "").length === country.digits; const phoneOk = phone.replace(/\D/g, "").length === country.digits;
const pwOk = sso ? true : passwordStrength(pw).score >= 3; const pwOk = sso ? true : passwordStrength(pw).score >= 3;
const alloeIdOk = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/.test(alloeNo); const alloeIdOk = /^(?=.*[a-zA-Z])(?=.*\d).{4,}$/.test(alloeNo);
const step0Valid = const step0Valid =
first.trim() && last.trim() && EMAIL_RE.test(sso?.email || email) && phoneOk && pwOk && first.trim() && last.trim() && EMAIL_RE.test(sso?.email || email) && phoneOk && pwOk &&
termsOk && privacyOk && (isSelf || (alloeIdOk && alloeFirst.trim() && alloeLast.trim())); termsOk && privacyOk && (isSelf || (alloeIdOk && (isEmployee || (alloeFirst.trim() && alloeLast.trim()))));
const step1Valid = /^LUP-\d{6}$/.test(allotment) || plotVerified; const step1Valid = /^LUP-\d{6}$/.test(allotment) || plotVerified;
const step2Valid = emailVerified && phoneVerified; const step2Valid = emailVerified && phoneVerified;
@@ -221,26 +222,38 @@ function StepAccount(p: {
</div> </div>
{p.isSelf ? ( {p.isSelf ? (
<div style={{ marginTop: 12 }}><FlashNote tone="success">Homeowner account you own this property.</FlashNote></div> <div style={{ marginTop: 12 }}><FlashNote tone="success">{p.relationship === "Owner" ? "Owner" : "Customer"} account you own this property.</FlashNote></div>
) : ( ) : (() => {
<div style={{ marginTop: 12 }}> const isEmployee = p.relationship === "Employee";
<FlashNote tone="info">Registering on behalf of the property owner.</FlashNote> const cfg = ({
<div className="dashed-block" style={{ marginTop: 12 }}> Employee: { banner: "Registering as a LynkedUp Pro team member.", title: "Employee details", idLabel: "Employee ID", idPh: "EMP-12345" },
<div className="row between" style={{ marginBottom: 12 }}> Contractor:{ banner: "Registering on behalf of the property owner.", title: "Contractor details", idLabel: "Contractor ID", idPh: "Alphanumeric ID" },
<strong style={{ fontSize: 13.5 }}>Property Owner Details</strong> "Sub-Con": { banner: "Registering on behalf of the property owner.", title: "Sub-contractor details", idLabel: "Sub-contractor ID", idPh: "Alphanumeric ID" },
{p.alloeNo && (p.alloeIdOk ? <Badge tone="green"><Icon name="check" size={12} /> Valid</Badge> : <Badge tone="gray">Checking</Badge>)} Vendor: { banner: "Registering on behalf of the property owner.", title: "Vendor details", idLabel: "Vendor ID", idPh: "Alphanumeric ID" },
</div> } as Record<string, { banner: string; title: string; idLabel: string; idPh: string }>)[p.relationship]
<div className="field"> ?? { banner: "Registering on behalf of the property owner.", title: "Property Owner Details", idLabel: "Owner / Property ID", idPh: "Alphanumeric ID" };
<label className="label">Owner / Property ID</label> return (
<input className="input" value={p.alloeNo} onChange={(e) => p.setAlloeNo(e.target.value.toUpperCase())} placeholder="Alphanumeric ID" /> <div style={{ marginTop: 12 }}>
</div> <FlashNote tone="info">{cfg.banner}</FlashNote>
<div className="row gap-3" style={{ marginTop: 12 }}> <div className="dashed-block" style={{ marginTop: 12 }}>
<div className="field grow"><label className="label">Owner first name</label><input className="input" value={p.alloeFirst} onChange={(e) => p.setAlloeFirst(e.target.value)} /></div> <div className="row between" style={{ marginBottom: 12 }}>
<div className="field grow"><label className="label">Owner last name</label><input className="input" value={p.alloeLast} onChange={(e) => p.setAlloeLast(e.target.value)} /></div> <strong style={{ fontSize: 13.5 }}>{cfg.title}</strong>
{p.alloeNo && (p.alloeIdOk ? <Badge tone="green"><Icon name="check" size={12} /> Valid</Badge> : <Badge tone="gray">Checking</Badge>)}
</div>
<div className="field">
<label className="label">{cfg.idLabel}</label>
<input className="input" value={p.alloeNo} onChange={(e) => p.setAlloeNo(e.target.value.toUpperCase())} placeholder={cfg.idPh} />
</div>
{!isEmployee && (
<div className="row gap-3" style={{ marginTop: 12 }}>
<div className="field grow"><label className="label">Owner first name</label><input className="input" value={p.alloeFirst} onChange={(e) => p.setAlloeFirst(e.target.value)} /></div>
<div className="field grow"><label className="label">Owner last name</label><input className="input" value={p.alloeLast} onChange={(e) => p.setAlloeLast(e.target.value)} /></div>
</div>
)}
</div> </div>
</div> </div>
</div> );
)} })()}
<div className="col gap-2" style={{ marginTop: 16 }}> <div className="col gap-2" style={{ marginTop: 16 }}>
<label className="check-row"> <label className="check-row">