Merge pull request 'feat(register): full registration → crm.account.register' (#2) from feat/appshell-integration into goutamnextflow

This commit was merged in pull request #2.
This commit is contained in:
2026-07-11 00:50:08 +00:00
+45 -10
View File
@@ -11,18 +11,26 @@ import {
countryCodes, relationshipOptions, addressCountries,
TERMS, PRIVACY, passwordStrength, type AddrCountry,
} from "./data";
import { useAuth } from "@abe-kap/appshell-sdk/react";
import { useAuth, useAppShell } from "@abe-kap/appshell-sdk/react";
import { isShellConfigured } from "@/lib/appshell";
type Addr = { line1?: string; line2?: string; city?: string; state?: string; postalCode?: string; country?: string; locality?: string };
const STEPS = ["Account", "Verify", "Address"];
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export function RegisterFlow() {
const router = useRouter();
const { register } = useAuth();
const { sdk } = useAppShell();
const [step, setStep] = useState(0);
const [submitErr, setSubmitErr] = useState("");
// address (lifted from StepAddress/AddressBlock so finish() can persist it)
const [regAddr, setRegAddr] = useState<Addr>({});
const [mailAddr, setMailAddr] = useState<Addr>({});
const [mailingSame, setMailingSame] = useState(true);
// shared form state
const [sso, setSso] = useState<{ provider: string; email: string } | null>(null);
const [email, setEmail] = useState("");
@@ -65,13 +73,34 @@ export function RegisterFlow() {
allotteeNames: isSelf ? null : `${alloeFirst} ${alloeLast}`.trim(),
};
try { localStorage.setItem("lup_profile", JSON.stringify(profile)); } catch { /* ignore */ }
// With the Shell live, create the real account through appshell → BFF. SSO users
// already have a session from OAuth, so only email/password sign-ups register here.
if (isShellConfigured() && !sso) {
if (isShellConfigured()) {
setSubmitErr("");
// 1) Auth account — appshell → Supabase (SSO users already have a session).
if (!sso) {
try { await register(finalEmail, pw); }
catch { setSubmitErr("We couldn't create that account. The email may already be registered."); return; }
}
// 2) Persist the full CRM registration payload to be-crm (name, phone, persona,
// allottee, both addresses, consent). Non-fatal: the auth account exists either way.
try {
const mailing = mailingSame ? regAddr : mailAddr;
await sdk.command("crm.account.register", {
email: finalEmail,
firstName: first, lastName: last,
phoneCc: cc, phoneNumber: phone.replace(/\D/g, ""),
persona: relationship,
isAllottee: isSelf,
...(isSelf ? {} : { allotteeId: alloeNo, allotteeFirstName: alloeFirst, allotteeLastName: alloeLast }),
registeredAddress: regAddr,
mailingAddress: mailing,
mailingSameAsRegistered: mailingSame,
consentTerms: termsOk, consentPrivacy: privacyOk,
});
} catch (e) {
console.warn("crm.account.register failed (continuing):", e);
}
}
router.push("/dashboard");
}
@@ -106,7 +135,7 @@ export function RegisterFlow() {
{step === 2 && (
<>
{submitErr && <div style={{ marginBottom: 14 }}><FlashNote tone="error">{submitErr}</FlashNote></div>}
<StepAddress onBack={() => setStep(1)} onFinish={finish} />
<StepAddress sameAs={mailingSame} setSameAs={setMailingSame} onRegAddr={setRegAddr} onMailAddr={setMailAddr} onBack={() => setStep(1)} onFinish={finish} />
</>
)}
</div>
@@ -391,8 +420,7 @@ function VerifyChannel({ kind, initial, country, cc, initialPhone, verified, onV
}
/* ====================== STEP 3 — ADDRESS ====================== */
function StepAddress({ onBack, onFinish }: { onBack: () => void; onFinish: () => void }) {
const [sameAs, setSameAs] = useState(true);
function StepAddress({ sameAs, setSameAs, onRegAddr, onMailAddr, onBack, onFinish }: { sameAs: boolean; setSameAs: (v: boolean) => void; onRegAddr: (a: Addr) => void; onMailAddr: (a: Addr) => void; onBack: () => void; onFinish: () => void }) {
return (
<div>
<StepBack onClick={onBack} />
@@ -400,7 +428,7 @@ function StepAddress({ onBack, onFinish }: { onBack: () => void; onFinish: () =>
<p className="sub">Add your registered address and where we should send mail.</p>
<AddrSectionHead icon="home" tone="amber" title="Registered address" sub="Where your property is registered" />
<AddressBlock idPrefix="reg" />
<AddressBlock idPrefix="reg" onChange={onRegAddr} />
<label className="check-row" style={{ marginTop: 16 }}>
<input type="checkbox" checked={sameAs} onChange={(e) => setSameAs(e.target.checked)} />
@@ -410,7 +438,7 @@ function StepAddress({ onBack, onFinish }: { onBack: () => void; onFinish: () =>
{!sameAs && (
<>
<AddrSectionHead icon="mail" tone="blue" title="Mailing / correspondence address" sub="Where we send physical mail" />
<AddressBlock idPrefix="mail" />
<AddressBlock idPrefix="mail" onChange={onMailAddr} />
</>
)}
@@ -435,7 +463,7 @@ function AddrSectionHead({ icon, title, sub, tone }: { icon: string; title: stri
);
}
function AddressBlock({ idPrefix }: { idPrefix: string }) {
function AddressBlock({ idPrefix, onChange }: { idPrefix: string; onChange?: (a: Addr) => void }) {
const [country, setCountry] = useState<AddrCountry>(addressCountries[0]);
const [pin, setPin] = useState("");
const [city, setCity] = useState("");
@@ -448,6 +476,13 @@ function AddressBlock({ idPrefix }: { idPrefix: string }) {
const [suggestions, setSuggestions] = useState<{ line1: string; line2: string; city: string; state: string; postal: string }[]>([]);
const debounce = useRef<ReturnType<typeof setTimeout> | null>(null);
// Report the composed address up so the parent can persist it (postalCode is the
// PIN for India, the postal field elsewhere).
useEffect(() => {
onChange?.({ line1, line2, city, state: stateName, postalCode: postal || pin, country: country.code, locality });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [line1, line2, city, stateName, postal, pin, country, locality]);
// India: PIN auto-fill
useEffect(() => {
if (country.lookup !== "pincode" || pin.length !== 6) return;