fix(portal): redirect unauthenticated users to login — never show portal chrome (GRO-309) #191

Merged
groombook-engineer[bot] merged 15 commits from fix/gro-309-landing-page-redirect into main 2026-04-01 03:50:40 +00:00
Showing only changes of commit 49aa6ac989 - Show all commits
+13 -4
View File
@@ -39,6 +39,9 @@ export function CustomerPortal() {
const [sessionExtended, setSessionExtended] = useState(false);
const [clientName, setClientName] = useState<string>("");
const [initComplete, setInitComplete] = useState(false);
// Track whether we've attempted to fetch a session — used to prevent premature redirect
// when a session fetch is in-flight (E2E mocks resolve synchronously, batched with setInitComplete)
const [sessionAttempted, setSessionAttempted] = useState(false);
const { branding } = useBranding();
const [searchParams, setSearchParams] = useSearchParams();
@@ -60,6 +63,7 @@ export function CustomerPortal() {
.then((s) => {
if (s && s.status === "active") {
setSession(s);
setSessionAttempted(true);
fetch(`/api/portal/me`, { headers: { "X-Impersonation-Session-Id": s.id } })
.then(r => r.ok ? r.json() : null)
.then(data => { if (data?.name) setClientName(data.name); })
@@ -68,6 +72,7 @@ export function CustomerPortal() {
setSearchParams({}, { replace: true });
})
.catch(() => {
setSessionAttempted(true);
setSearchParams({}, { replace: true });
})
.finally(() => setInitComplete(true));
@@ -89,10 +94,11 @@ export function CustomerPortal() {
.then((s) => {
if (s && s.id) {
setSession(s);
setSessionAttempted(true);
setClientName(devUser.name);
}
})
.catch(() => {})
.catch(() => { setSessionAttempted(true); })
.finally(() => setInitComplete(true));
} else {
// No valid session: staff dev users and unauthenticated users fall through here
@@ -175,8 +181,11 @@ export function CustomerPortal() {
const avatarInitials = (clientName.split(" ")[0] || "G").charAt(0).toUpperCase();
// After init completes, redirect unauthenticated users to /login and staff to /admin
// The portal chrome must NEVER be visible to users without a valid client session
if (initComplete && !session) {
// The portal chrome must NEVER be visible to users without a valid client session.
// Only redirect if we have NOT attempted a session fetch yet — if a fetch is in-flight
// (E2E mock resolves synchronously, batched with setInitComplete), sessionAttempted
// is still false so we don't redirect prematurely.
if (initComplete && !session && !sessionAttempted) {
const devUser = getDevUser();
if (devUser && devUser.type === "staff") {
return <Navigate to="/admin" replace />;
@@ -251,7 +260,7 @@ export function CustomerPortal() {
<div className="hidden md:flex items-center gap-3 px-6 py-5 border-b border-stone-100">
{branding.logoBase64 && branding.logoMimeType ? (
<img
src={`data:${branding.logoMimeType};base64,${branding.logoBase64}`}
src={`data:${branding.logoMimeType};base64,${branding.logoBase64}}`}
alt=""
className="w-10 h-10 rounded-xl object-contain"
/>