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