refactor: Remove AccountRouteGuard and adjust loading state handling in AppShell and views
This commit is contained in:
parent
69e07ecef2
commit
8d9c954230
@ -1,31 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
|
||||||
import { useAuthStore } from "@/features/auth/stores/auth.store";
|
|
||||||
|
|
||||||
export function AccountRouteGuard() {
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const isAuthenticated = useAuthStore(state => state.isAuthenticated);
|
|
||||||
const hasCheckedAuth = useAuthStore(state => state.hasCheckedAuth);
|
|
||||||
const loading = useAuthStore(state => state.loading);
|
|
||||||
const checkAuth = useAuthStore(state => state.checkAuth);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasCheckedAuth) {
|
|
||||||
void checkAuth();
|
|
||||||
}
|
|
||||||
}, [checkAuth, hasCheckedAuth]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasCheckedAuth || loading || isAuthenticated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const destination = pathname || "/account";
|
|
||||||
router.replace(`/auth/login?redirect=${encodeURIComponent(destination)}`);
|
|
||||||
}, [hasCheckedAuth, isAuthenticated, loading, pathname, router]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@ -2,12 +2,10 @@ import type { ReactNode } from "react";
|
|||||||
import { AppShell } from "@/components/organisms";
|
import { AppShell } from "@/components/organisms";
|
||||||
import { ErrorBoundary, PageErrorFallback } from "@/components/molecules";
|
import { ErrorBoundary, PageErrorFallback } from "@/components/molecules";
|
||||||
import { AccountEventsListener } from "@/features/realtime/components/AccountEventsListener";
|
import { AccountEventsListener } from "@/features/realtime/components/AccountEventsListener";
|
||||||
import { AccountRouteGuard } from "./AccountRouteGuard";
|
|
||||||
|
|
||||||
export default function AccountLayout({ children }: { children: ReactNode }) {
|
export default function AccountLayout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<AccountRouteGuard />
|
|
||||||
<AccountEventsListener />
|
<AccountEventsListener />
|
||||||
<ErrorBoundary fallback={<PageErrorFallback />}>{children}</ErrorBoundary>
|
<ErrorBoundary fallback={<PageErrorFallback />}>{children}</ErrorBoundary>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
|
|||||||
@ -147,7 +147,10 @@ export function AppShell({ children }: AppShellProps) {
|
|||||||
}
|
}
|
||||||
}, [navigation, router]);
|
}, [navigation, router]);
|
||||||
|
|
||||||
// Do not block the shell; sidebar/header remain mounted. The content area can handle its own loading.
|
// Determine if we should show loading state in content area
|
||||||
|
// Auth must be checked before rendering protected content
|
||||||
|
const isAuthReady = hasCheckedAuth && isAuthenticated;
|
||||||
|
const isCheckingAuth = !hasCheckedAuth || loading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -194,7 +197,25 @@ export function AppShell({ children }: AppShellProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
<main className="flex-1 relative overflow-y-auto focus:outline-none">{children}</main>
|
<main className="flex-1 relative overflow-y-auto focus:outline-none">
|
||||||
|
{isCheckingAuth ? (
|
||||||
|
<div className="p-6 md:p-8 lg:p-10">
|
||||||
|
<div className="space-y-6 animate-pulse">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="h-4 bg-muted rounded w-24" />
|
||||||
|
<div className="h-8 bg-muted rounded w-48" />
|
||||||
|
</div>
|
||||||
|
<div className="h-32 bg-muted rounded-xl" />
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div className="h-40 bg-muted rounded-xl" />
|
||||||
|
<div className="h-40 bg-muted rounded-xl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : isAuthReady ? (
|
||||||
|
children
|
||||||
|
) : null}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -145,7 +145,7 @@ export const useAuthStore = create<AuthState>()((set, get) => {
|
|||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new Error(parsed.error.issues?.[0]?.message ?? "Login failed");
|
throw new Error(parsed.error.issues?.[0]?.message ?? "Login failed");
|
||||||
}
|
}
|
||||||
applyAuthResponse(parsed.data, true); // Keep loading for redirect
|
applyAuthResponse(parsed.data); // Let destination page handle loading
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const parsed = parseError(error);
|
const parsed = parseError(error);
|
||||||
set({ loading: false, error: parsed.message, isAuthenticated: false });
|
set({ loading: false, error: parsed.message, isAuthenticated: false });
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Suspense, useEffect, useMemo, useState } from "react";
|
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
import { AuthLayout } from "../components";
|
import { AuthLayout } from "../components";
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
resolveLogoutMessage,
|
resolveLogoutMessage,
|
||||||
type LogoutReason,
|
type LogoutReason,
|
||||||
} from "@/features/auth/utils/logout-reason";
|
} from "@/features/auth/utils/logout-reason";
|
||||||
|
import { getPostLoginRedirect } from "@/features/auth/utils/route-protection";
|
||||||
|
|
||||||
function LoginContent() {
|
function LoginContent() {
|
||||||
const { loading, isAuthenticated } = useAuthStore();
|
const { loading, isAuthenticated } = useAuthStore();
|
||||||
@ -42,6 +43,19 @@ function LoginContent() {
|
|||||||
}
|
}
|
||||||
}, [logoutReason]);
|
}, [logoutReason]);
|
||||||
|
|
||||||
|
// Track if redirect has been initiated to prevent multiple attempts
|
||||||
|
const hasRedirected = useRef(false);
|
||||||
|
|
||||||
|
// Redirect authenticated users away from login page
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated && !loading && !hasRedirected.current) {
|
||||||
|
hasRedirected.current = true;
|
||||||
|
const redirectTo = searchParams ? getPostLoginRedirect(searchParams) : "/account";
|
||||||
|
// Force hard navigation - auth state is persisted via cookies
|
||||||
|
window.location.assign(redirectTo);
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, loading, searchParams]);
|
||||||
|
|
||||||
const logoutMessage = logoutReason ? resolveLogoutMessage(logoutReason) : null;
|
const logoutMessage = logoutReason ? resolveLogoutMessage(logoutReason) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -30,8 +30,8 @@ export function DashboardView() {
|
|||||||
const { tasks, isLoading: tasksLoading, taskCount } = useDashboardTasks();
|
const { tasks, isLoading: tasksLoading, taskCount } = useDashboardTasks();
|
||||||
const { data: eligibility } = useInternetEligibility({ enabled: isAuthenticated });
|
const { data: eligibility } = useInternetEligibility({ enabled: isAuthenticated });
|
||||||
|
|
||||||
// Combined loading state
|
// Combined loading state - AppShell handles unauthenticated redirect
|
||||||
const isLoading = authLoading || summaryLoading || !isAuthenticated;
|
const isLoading = authLoading || summaryLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthenticated || !user?.id) return;
|
if (!isAuthenticated || !user?.id) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user