fix(invite): look up the invitation and route correctly (register vs sign in)
The invite link is token-only, so the landing page couldn't tell the invited email or whether an account existed — it dumped everyone on an empty register screen. Now it calls a public lookup (/api/invite/lookup → be-crm) and: - first-time invitee → register with the email prefilled + locked; - email already registered → sign in with the email prefilled; - signed-in + registered → accept immediately; signed-in + no profile → onboarding. Login now redeems the pending invite after sign-in (password/OTP/OAuth), so an existing user's invite is accepted on login (not only when already logged in).
This commit is contained in:
@@ -53,6 +53,13 @@ export function RegisterFlow({ mode = "register" }: { mode?: "register" | "onboa
|
||||
|
||||
// verify step (email is trusted without an OTP — see verifyValid below)
|
||||
const [phoneVerified, setPhoneVerified] = useState(false);
|
||||
// When arriving from a team invite, the email is fixed to the invited address.
|
||||
const [invitedEmail, setInvitedEmail] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (onboard) return;
|
||||
try { const e = sessionStorage.getItem("invite_email"); if (e) { setEmail(e); setInvitedEmail(e); } } catch { /* ignore */ }
|
||||
}, [onboard]);
|
||||
|
||||
// Onboarding: prefill the verified email — from the session-storage hint the login
|
||||
// page stashed at OAuth time, then confirmed via the live Supabase session.
|
||||
@@ -138,6 +145,7 @@ export function RegisterFlow({ mode = "register" }: { mode?: "register" | "onboa
|
||||
if (inviteToken) {
|
||||
await sdk.command("crm.team.invitation.accept", { token: inviteToken });
|
||||
sessionStorage.removeItem("invite_token");
|
||||
sessionStorage.removeItem("invite_email");
|
||||
}
|
||||
} catch { /* invitation expired/used — they can still be invited again */ }
|
||||
}
|
||||
@@ -154,7 +162,7 @@ export function RegisterFlow({ mode = "register" }: { mode?: "register" | "onboa
|
||||
<>
|
||||
{submitErr && <div style={{ marginBottom: 14 }}><FlashNote tone="error">{submitErr}</FlashNote></div>}
|
||||
<StepAccount
|
||||
onboard={onboard} creating={creating}
|
||||
onboard={onboard} creating={creating} invited={!!invitedEmail}
|
||||
sso={sso} setSso={setSso} email={email} setEmail={setEmail}
|
||||
first={first} setFirst={setFirst} last={last} setLast={setLast}
|
||||
cc={cc} setCc={setCc} phone={phone} setPhone={setPhone} phoneOk={phoneOk} country={country}
|
||||
@@ -192,7 +200,7 @@ function StepAccount(p: {
|
||||
cc: string; setCc: (v: string) => void; phone: string; setPhone: (v: string) => void; phoneOk: boolean; country: typeof countryCodes[number];
|
||||
pw: string; setPw: (v: string) => void;
|
||||
termsOk: boolean; setTermsOk: (v: boolean) => void; privacyOk: boolean; setPrivacyOk: (v: boolean) => void;
|
||||
valid: boolean; onContinue: () => void; toLogin: () => void; onboard?: boolean; creating?: boolean;
|
||||
valid: boolean; onContinue: () => void; toLogin: () => void; onboard?: boolean; creating?: boolean; invited?: boolean;
|
||||
}) {
|
||||
// Onboarding (OAuth) starts on the profile step — email is already known + verified.
|
||||
const [phase, setPhase] = useState<"sso" | "profile">(p.onboard ? "profile" : "sso");
|
||||
@@ -203,21 +211,22 @@ function StepAccount(p: {
|
||||
return (
|
||||
<div>
|
||||
<StepBack onClick={p.toLogin} />
|
||||
<h1>Create your account</h1>
|
||||
<p className="sub">Enter your email to get started.</p>
|
||||
<h1>{p.invited ? "Accept your invitation" : "Create your account"}</h1>
|
||||
<p className="sub">{p.invited ? "You've been invited to the team. Set up your account to join." : "Enter your email to get started."}</p>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<form onSubmit={(e) => { e.preventDefault(); if (valid) { p.setSso(null); setPhase("profile"); } }}>
|
||||
<div className="field">
|
||||
<label className="label">Email address</label>
|
||||
<div className="input-wrap">
|
||||
<span className="input-ico"><Icon name="mail" size={17} /></span>
|
||||
<input className="input" type="email" value={p.email} onChange={(e) => p.setEmail(e.target.value)} placeholder="you@example.com" autoFocus />
|
||||
<input className="input" type="email" value={p.email} onChange={(e) => p.setEmail(e.target.value)} placeholder="you@example.com" autoFocus={!p.invited} disabled={p.invited} readOnly={p.invited} />
|
||||
</div>
|
||||
{p.invited && <span className="faint" style={{ fontSize: 12 }}>This is the address you were invited with.</span>}
|
||||
</div>
|
||||
<button className="btn btn-primary" style={{ marginTop: 12 }} disabled={!valid}>Continue <Icon name="arrowR" size={16} /></button>
|
||||
</form>
|
||||
</div>
|
||||
<p className="foot-note">Already registered? <button className="link" onClick={p.toLogin}>Sign in</button></p>
|
||||
{!p.invited && <p className="foot-note">Already registered? <button className="link" onClick={p.toLogin}>Sign in</button></p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user