diff --git a/src/components/portal/bits.tsx b/src/components/portal/bits.tsx index 9014cc1..c2efe8b 100644 --- a/src/components/portal/bits.tsx +++ b/src/components/portal/bits.tsx @@ -145,18 +145,13 @@ export function startSocial(provider: "google" | "microsoft" | "apple"): boolean if (url) { window.location.href = url; return true; } return false; } -export function SocialButtons({ onPick, verb = "Continue" }: { onPick: (p: "google" | "microsoft" | "apple") => void; verb?: string }) { +export function SocialButtons({ onPick, verb = "Continue" }: { onPick: (p: "google") => void; verb?: string }) { + // Only Google is offered (Microsoft/Apple intentionally hidden). return (
- -
); } diff --git a/src/components/portal/login-flow.tsx b/src/components/portal/login-flow.tsx index 96ced16..6186507 100644 --- a/src/components/portal/login-flow.tsx +++ b/src/components/portal/login-flow.tsx @@ -31,6 +31,18 @@ export function LoginFlow() { const [provider, setProvider] = useState(""); const [remember, setRemember] = useState(false); const [flash, setFlash] = useState(""); + const [otpChannel, setOtpChannel] = useState<"email" | "sms">("email"); + + // Minimal account stand-in for passwordless entry points (Shell mode). + function blankAccount(): Account { + return { firstName: "", name: "", initials: "", hasPasskey: false, hasTotp: false, hasPush: false, maskedEmail: maskEmail(email || ""), maskedPhone: "", maskedWa: "" }; + } + // Direct "sign in with phone" → the one-time-code screen, SMS preselected. + function startPhoneLogin() { + setAccount(blankAccount()); + setOtpChannel("sms"); + replace("otp"); + } /* ---- history hash sync ---- */ function push(s: Step) { @@ -100,6 +112,7 @@ export function LoginFlow() { hasPasskey: false, hasTotp: false, hasPush: false, maskedEmail: maskEmail(email), maskedPhone: "", maskedWa: "", }); + setOtpChannel("email"); replace("password"); return; } @@ -116,7 +129,7 @@ export function LoginFlow() { /* =================================================================== */ return (
- {step === "identify" && router.push("/portal/register")} />} + {step === "identify" && router.push("/portal/register")} />} {step === "connecting" && (
@@ -162,11 +175,11 @@ export function LoginFlow() { )} {step === "password" && account && ( - router.replace("/dashboard")} flash={flash} onBack={back} onForgot={() => push("fp_confirm")} onOtp={() => replace("otp")} onLocked={() => setFlash("This account is temporarily locked.")} onOk={() => afterAuth("password")} /> + router.replace("/dashboard")} flash={flash} onBack={back} onForgot={() => push("fp_confirm")} onOtp={() => { setOtpChannel("email"); replace("otp"); }} onLocked={() => setFlash("This account is temporarily locked.")} onOk={() => afterAuth("password")} /> )} {step === "otp" && account && ( - afterAuth("otp")} onAuthenticated={() => router.replace("/dashboard")} /> + afterAuth("otp")} onAuthenticated={() => router.replace("/dashboard")} /> )} {step === "another" && account && ( @@ -230,9 +243,9 @@ export function LoginFlow() { /* ============================ screens ============================ */ -function Identify({ email, setEmail, onSocial, onEmail, toRegister }: { +function Identify({ email, setEmail, onSocial, onEmail, onPhone, toRegister }: { email: string; setEmail: (v: string) => void; - onSocial: (p: "google" | "microsoft" | "apple") => void; onEmail: () => void; toRegister: () => void; + onSocial: (p: "google") => void; onEmail: () => void; onPhone: () => void; toRegister: () => void; }) { const [showEmail, setShowEmail] = useState(false); const valid = EMAIL_RE.test(email); @@ -263,6 +276,9 @@ function Identify({ email, setEmail, onSocial, onEmail, toRegister }: { )} + {isShellConfigured() && ( + + )}

New homeowner or contractor?

@@ -357,20 +373,20 @@ function Password({ account, email, login, onAuthenticated, flash, onBack, onFor
- +
{!isShellConfigured() &&

Demo: any password works; type “wrong” to see the lockout. Password always needs 2-step next.

}
); } -function OtpVerify({ account, email, remember, setRemember, onBack, onVerified, onAuthenticated }: { - account: Account; email: string; remember: boolean; setRemember: (v: boolean) => void; onBack: () => void; onVerified: () => void; onAuthenticated: () => void; +function OtpVerify({ account, email, initialChannel = "email", remember, setRemember, onBack, onVerified, onAuthenticated }: { + account: Account; email: string; initialChannel?: "email" | "sms"; remember: boolean; setRemember: (v: boolean) => void; onBack: () => void; onVerified: () => void; onAuthenticated: () => void; }) { const { sendEmailOtp, verifyEmailOtp, sendPhoneOtp, verifyPhoneOtp } = useAuth(); const shell = isShellConfigured(); // In Shell mode only email + SMS are real Supabase OTP channels; hide WhatsApp. - const [channel, setChannel] = useState<"email" | "sms" | "wa">("email"); + const [channel, setChannel] = useState<"email" | "sms" | "wa">(initialChannel); const [phone, setPhone] = useState(""); const [sent, setSent] = useState(false); const [busy, setBusy] = useState(false); diff --git a/src/components/portal/register-flow.tsx b/src/components/portal/register-flow.tsx index c9829e4..e1bc222 100644 --- a/src/components/portal/register-flow.tsx +++ b/src/components/portal/register-flow.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation"; import { Icon, Avatar } from "./icons"; import { StepBack, FlashNote, Badge, OtpBoxes, ResendLink, RememberDevice, - SocialButtons, startSocial, PasswordStrength, LegalModal, + PasswordStrength, LegalModal, } from "./bits"; import { countryCodes, relationshipOptions, addressCountries, @@ -158,23 +158,13 @@ function StepAccount(p: { const [phase, setPhase] = useState<"sso" | "profile">("sso"); const [modal, setModal] = useState(null); - function pickSso(provider: "google" | "microsoft" | "apple") { - if (startSocial(provider)) return; - const mockEmail = `you@${provider === "microsoft" ? "outlook.com" : provider + ".com"}`; - p.setSso({ provider, email: mockEmail }); - p.setEmail(mockEmail); - setPhase("profile"); - } - if (phase === "sso") { const valid = EMAIL_RE.test(p.email); return (

Create your account

-

Sign up with a provider or your email to get started.

+

Enter your email to get started.

- -
or continue with email
{ e.preventDefault(); if (valid) { p.setSso(null); setPhase("profile"); } }}>