diff --git a/src/components/portal/login-flow.tsx b/src/components/portal/login-flow.tsx index 3f7d7cb..b899a25 100644 --- a/src/components/portal/login-flow.tsx +++ b/src/components/portal/login-flow.tsx @@ -11,6 +11,7 @@ import { import { lookupAccount, maskEmail, type Account } from "./data"; import { useAuth, useAppShell } from "@abe-kap/appshell-sdk/react"; import { isShellConfigured } from "@/lib/appshell"; +import { MOCK_OTP } from "@/lib/otp"; // Map the portal's social button ids to Supabase OAuth provider ids. const OAUTH_PROVIDER: Record = { google: "google", microsoft: "azure", apple: "apple" }; @@ -42,7 +43,7 @@ export function LoginFlow() { function startPhoneLogin() { setAccount(blankAccount()); setOtpChannel("sms"); - replace("otp"); + push("otp"); // push (not replace) so Back returns to the identify screen } /* ---- history hash sync ---- */ @@ -134,7 +135,7 @@ export function LoginFlow() { maskedEmail: maskEmail(email), maskedPhone: "", maskedWa: "", }); setOtpChannel("email"); - replace("password"); + push("password"); // push (not replace) so Back returns to the email screen, not off-page return; } push("connecting"); @@ -301,7 +302,10 @@ function Identify({ email, setEmail, onSocial, onEmail, onPhone, toRegister }: { )} - {isShellConfigured() && ( + {/* Phone (SMS) sign-in needs a deliverable one-time code — hidden while SMS is + mocked, since a faked code can't mint a real session. Email + password + Google + all work without SMS. */} + {isShellConfigured() && !MOCK_OTP && ( )} @@ -450,12 +454,22 @@ function OtpVerify({ account, email, initialChannel = "email", remember, setReme

{shell ? "One-time passcode" : "2-step verification"}

-

{shell ? "We'll text or email you a 6-digit code to sign in." : "Enter the 6-digit code we sent you to finish signing in."}

-
- - - {!shell && } -
+

+ {!shell + ? "Enter the 6-digit code we sent you to finish signing in." + : channel === "sms" + ? "We'll text a 6-digit code to sign in." + : "We'll email you a 6-digit code to sign in."} +

+ {/* In Shell mode the channel is fixed by how you signed in (email vs phone) — + no toggle, so an email sign-in never shows a stray SMS option. */} + {!shell && ( +
+ + + +
+ )} {shell && channel === "sms" && !sent ? (
{ e.preventDefault(); void send(); }}> diff --git a/src/components/portal/register-flow.tsx b/src/components/portal/register-flow.tsx index 1703b53..6dd6318 100644 --- a/src/components/portal/register-flow.tsx +++ b/src/components/portal/register-flow.tsx @@ -13,18 +13,13 @@ import { } from "./data"; import { useAuth, useAppShell } from "@abe-kap/appshell-sdk/react"; import { isShellConfigured } from "@/lib/appshell"; +import { MOCK_OTP, DEMO_OTP } from "@/lib/otp"; 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@]+$/; -// Demo fallback for phone OTP while the Twilio sender is down. When on, the Verify -// step skips Supabase/Twilio and accepts a fixed code. Flip NEXT_PUBLIC_MOCK_OTP to -// "false" (or remove it) to restore real SMS verification — no other change needed. -const MOCK_OTP = process.env.NEXT_PUBLIC_MOCK_OTP === "true"; -const DEMO_OTP = "123456"; - export function RegisterFlow({ mode = "register" }: { mode?: "register" | "onboard" }) { // "onboard" = an already-authenticated OAuth (Google) user completing their CRM // profile: email is skipped (Google-verified), the auth account already exists so @@ -214,6 +209,7 @@ function StepAccount(p: { const valid = EMAIL_RE.test(p.email); return (
+

Create your account

Enter your email to get started.

@@ -235,6 +231,9 @@ function StepAccount(p: { return (
+ {/* Non-onboarding users can step back to the email screen; onboarding starts + here (email came from Google), so there's nothing to go back to. */} + {!p.onboard && setPhase("sso")} />}

Complete your profile

Tell us a bit about you to set up your account.

diff --git a/src/lib/otp.ts b/src/lib/otp.ts new file mode 100644 index 0000000..d42ba19 --- /dev/null +++ b/src/lib/otp.ts @@ -0,0 +1,16 @@ +/** + * Demo OTP fallback, shared by the login and registration flows. + * + * While the Twilio SMS sender is down we can't deliver real one-time codes. When + * MOCK_OTP is on, phone verification steps skip Supabase/Twilio and accept a fixed + * DEMO_OTP instead. Flip NEXT_PUBLIC_MOCK_OTP to "false" (or remove it) to restore + * real SMS — no other change needed. + * + * IMPORTANT: this only substitutes for a *secondary* verification (e.g. confirming a + * phone during registration/onboarding, where the session already exists). It cannot + * mock a *login* whose sole credential is the OTP — there the OTP verification is what + * mints the session, and a faked code produces no session (the dashboard's AuthGate + * would bounce the user straight back). Passwordless login therefore stays real. + */ +export const MOCK_OTP = process.env.NEXT_PUBLIC_MOCK_OTP === "true"; +export const DEMO_OTP = "123456";