refactor: Remove AccountRouteGuard and adjust loading state handling in AppShell and views

This commit is contained in:
barsa 2026-01-19 18:32:31 +09:00
parent 69e07ecef2
commit 8d9c954230
6 changed files with 41 additions and 39 deletions

View File

@ -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;
}

View File

@ -2,12 +2,10 @@ import type { ReactNode } from "react";
import { AppShell } from "@/components/organisms";
import { ErrorBoundary, PageErrorFallback } from "@/components/molecules";
import { AccountEventsListener } from "@/features/realtime/components/AccountEventsListener";
import { AccountRouteGuard } from "./AccountRouteGuard";
export default function AccountLayout({ children }: { children: ReactNode }) {
return (
<AppShell>
<AccountRouteGuard />
<AccountEventsListener />
<ErrorBoundary fallback={<PageErrorFallback />}>{children}</ErrorBoundary>
</AppShell>

View File

@ -147,7 +147,10 @@ export function AppShell({ children }: AppShellProps) {
}
}, [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 (
<>
@ -194,7 +197,25 @@ export function AppShell({ children }: AppShellProps) {
/>
{/* 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>

View File

@ -145,7 +145,7 @@ export const useAuthStore = create<AuthState>()((set, get) => {
if (!parsed.success) {
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) {
const parsed = parseError(error);
set({ loading: false, error: parsed.message, isAuthenticated: false });

View File

@ -1,6 +1,6 @@
"use client";
import { Suspense, useEffect, useMemo, useState } from "react";
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "next/navigation";
import { AuthLayout } from "../components";
@ -14,6 +14,7 @@ import {
resolveLogoutMessage,
type LogoutReason,
} from "@/features/auth/utils/logout-reason";
import { getPostLoginRedirect } from "@/features/auth/utils/route-protection";
function LoginContent() {
const { loading, isAuthenticated } = useAuthStore();
@ -42,6 +43,19 @@ function LoginContent() {
}
}, [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;
return (

View File

@ -30,8 +30,8 @@ export function DashboardView() {
const { tasks, isLoading: tasksLoading, taskCount } = useDashboardTasks();
const { data: eligibility } = useInternetEligibility({ enabled: isAuthenticated });
// Combined loading state
const isLoading = authLoading || summaryLoading || !isAuthenticated;
// Combined loading state - AppShell handles unauthenticated redirect
const isLoading = authLoading || summaryLoading;
useEffect(() => {
if (!isAuthenticated || !user?.id) return;