fix(portal): redirect unauthenticated users to login — never show portal chrome (GRO-309) #191
@@ -39,6 +39,9 @@ export function CustomerPortal() {
|
|||||||
const [sessionExtended, setSessionExtended] = useState(false);
|
const [sessionExtended, setSessionExtended] = useState(false);
|
||||||
const [clientName, setClientName] = useState<string>("");
|
const [clientName, setClientName] = useState<string>("");
|
||||||
const [initComplete, setInitComplete] = useState(false);
|
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 { branding } = useBranding();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ export function CustomerPortal() {
|
|||||||
.then((s) => {
|
.then((s) => {
|
||||||
if (s && s.status === "active") {
|
if (s && s.status === "active") {
|
||||||
setSession(s);
|
setSession(s);
|
||||||
|
setSessionAttempted(true);
|
||||||
fetch(`/api/portal/me`, { headers: { "X-Impersonation-Session-Id": s.id } })
|
fetch(`/api/portal/me`, { headers: { "X-Impersonation-Session-Id": s.id } })
|
||||||
.then(r => r.ok ? r.json() : null)
|
.then(r => r.ok ? r.json() : null)
|
||||||
.then(data => { if (data?.name) setClientName(data.name); })
|
.then(data => { if (data?.name) setClientName(data.name); })
|
||||||
@@ -68,6 +72,7 @@ export function CustomerPortal() {
|
|||||||
setSearchParams({}, { replace: true });
|
setSearchParams({}, { replace: true });
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
setSessionAttempted(true);
|
||||||
setSearchParams({}, { replace: true });
|
setSearchParams({}, { replace: true });
|
||||||
})
|
})
|
||||||
.finally(() => setInitComplete(true));
|
.finally(() => setInitComplete(true));
|
||||||
@@ -89,10 +94,11 @@ export function CustomerPortal() {
|
|||||||
.then((s) => {
|
.then((s) => {
|
||||||
if (s && s.id) {
|
if (s && s.id) {
|
||||||
setSession(s);
|
setSession(s);
|
||||||
|
setSessionAttempted(true);
|
||||||
setClientName(devUser.name);
|
setClientName(devUser.name);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => { setSessionAttempted(true); })
|
||||||
.finally(() => setInitComplete(true));
|
.finally(() => setInitComplete(true));
|
||||||
} else {
|
} else {
|
||||||
// No valid session: staff dev users and unauthenticated users fall through here
|
// 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();
|
const avatarInitials = (clientName.split(" ")[0] || "G").charAt(0).toUpperCase();
|
||||||
|
|
||||||
// After init completes, redirect unauthenticated users to /login and staff to /admin
|
// 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
|
// The portal chrome must NEVER be visible to users without a valid client session.
|
||||||
if (initComplete && !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();
|
const devUser = getDevUser();
|
||||||
if (devUser && devUser.type === "staff") {
|
if (devUser && devUser.type === "staff") {
|
||||||
return <Navigate to="/admin" replace />;
|
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">
|
<div className="hidden md:flex items-center gap-3 px-6 py-5 border-b border-stone-100">
|
||||||
{branding.logoBase64 && branding.logoMimeType ? (
|
{branding.logoBase64 && branding.logoMimeType ? (
|
||||||
<img
|
<img
|
||||||
src={`data:${branding.logoMimeType};base64,${branding.logoBase64}`}
|
src={`data:${branding.logoMimeType};base64,${branding.logoBase64}}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-10 h-10 rounded-xl object-contain"
|
className="w-10 h-10 rounded-xl object-contain"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user