From 54c32bf97faf609777e74e32c0b355a6545bcee7 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Sun, 2 Nov 2025 16:56:23 +0100 Subject: [PATCH] Introduce `AuthLoadingSkeleton` and `HeaderSkeleton` for smoother loading, replace spinner in `AuthGuard`, update ReactQueryDevtools toggle, enable Docker ports for local development. --- docker-compose.yml | 6 +++ frontend/next.config.ts | 7 +-- frontend/src/app/providers.tsx | 6 ++- frontend/src/components/auth/AuthGuard.tsx | 47 ++++++++++++------- .../components/layout/AuthLoadingSkeleton.tsx | 33 +++++++++++++ .../src/components/layout/HeaderSkeleton.tsx | 32 +++++++++++++ frontend/src/components/layout/index.ts | 2 + 7 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/layout/AuthLoadingSkeleton.tsx create mode 100644 frontend/src/components/layout/HeaderSkeleton.tsx diff --git a/docker-compose.yml b/docker-compose.yml index 2185f75..dbfaa12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ services: image: postgres:17-alpine volumes: - postgres_data:/var/lib/postgresql/data/ + ports: + - "5432:5432" environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} @@ -21,6 +23,8 @@ services: context: ./backend dockerfile: Dockerfile target: production + ports: + - "8000:8000" env_file: - .env environment: @@ -43,6 +47,8 @@ services: target: runner args: - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} + ports: + - "3000:3000" environment: - NODE_ENV=production - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 5c9b62e..837863a 100755 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -21,9 +21,4 @@ const nextConfig: NextConfig = { // Note: swcMinify is default in Next.js 15 }; -// Enable bundle analyzer when ANALYZE=true -const withBundleAnalyzer = require('@next/bundle-analyzer')({ - enabled: process.env.ANALYZE === 'true', -}); - -export default withBundleAnalyzer(nextConfig); \ No newline at end of file +export default nextConfig; \ No newline at end of file diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx index 51a0207..905a550 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/app/providers.tsx @@ -5,9 +5,11 @@ import { lazy, Suspense, useState } from 'react'; import { ThemeProvider } from '@/components/theme'; import { AuthInitializer } from '@/components/auth'; -// Lazy load devtools - only in development, never in production +// Lazy load devtools - only in local development (not in Docker), never in production +// Set NEXT_PUBLIC_ENABLE_DEVTOOLS=true in .env.local to enable const ReactQueryDevtools = - process.env.NODE_ENV === 'development' + process.env.NODE_ENV === 'development' && + process.env.NEXT_PUBLIC_ENABLE_DEVTOOLS === 'true' ? lazy(() => import('@tanstack/react-query-devtools').then((mod) => ({ default: mod.ReactQueryDevtools, diff --git a/frontend/src/components/auth/AuthGuard.tsx b/frontend/src/components/auth/AuthGuard.tsx index 6b5326a..43b4878 100644 --- a/frontend/src/components/auth/AuthGuard.tsx +++ b/frontend/src/components/auth/AuthGuard.tsx @@ -6,10 +6,11 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import { useAuthStore } from '@/lib/stores/authStore'; import { useMe } from '@/lib/api/hooks/useAuth'; +import { AuthLoadingSkeleton } from '@/components/layout'; import config from '@/config/app.config'; interface AuthGuardProps { @@ -18,20 +19,6 @@ interface AuthGuardProps { fallback?: React.ReactNode; } -/** - * Loading spinner component - */ -function LoadingSpinner() { - return ( -
-
-
-

Loading...

-
-
- ); -} - /** * AuthGuard - Client component for route protection * @@ -65,12 +52,33 @@ export function AuthGuard({ children, requireAdmin = false, fallback }: AuthGuar const pathname = usePathname(); const { isAuthenticated, isLoading: authLoading, user } = useAuthStore(); + // Delayed loading state - only show skeleton after 150ms to avoid flicker on fast loads + const [showLoading, setShowLoading] = useState(false); + // Fetch user data if authenticated but user not loaded const { isLoading: userLoading } = useMe(); // Determine overall loading state const isLoading = authLoading || (isAuthenticated && !user && userLoading); + // Delayed loading effect - wait 150ms before showing skeleton + useEffect(() => { + if (!isLoading) { + // Reset immediately when loading completes + setShowLoading(false); + return; + } + + // Set a timer to show loading skeleton after 150ms + const timer = setTimeout(() => { + if (isLoading) { + setShowLoading(true); + } + }, 150); + + return () => clearTimeout(timer); + }, [isLoading]); + useEffect(() => { // If not loading and not authenticated, redirect to login if (!isLoading && !isAuthenticated) { @@ -94,9 +102,14 @@ export function AuthGuard({ children, requireAdmin = false, fallback }: AuthGuar } }, [requireAdmin, isAuthenticated, user, router]); - // Show loading state + // Show loading skeleton only after delay (prevents flicker on fast loads) + if (isLoading && showLoading) { + return fallback ? <>{fallback} : ; + } + + // Show nothing while loading but before delay threshold (prevents flicker) if (isLoading) { - return fallback ? <>{fallback} : ; + return null; } // Show nothing if redirecting diff --git a/frontend/src/components/layout/AuthLoadingSkeleton.tsx b/frontend/src/components/layout/AuthLoadingSkeleton.tsx new file mode 100644 index 0000000..e25466e --- /dev/null +++ b/frontend/src/components/layout/AuthLoadingSkeleton.tsx @@ -0,0 +1,33 @@ +/** + * Auth Loading Skeleton + * Loading placeholder shown during authentication check + * Mimics the authenticated layout structure for smooth loading experience + */ + +import { HeaderSkeleton } from './HeaderSkeleton'; +import { Footer } from './Footer'; + +export function AuthLoadingSkeleton() { + return ( +
+ +
+
+ {/* Page title skeleton */} +
+
+
+
+ + {/* Content skeleton */} +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/frontend/src/components/layout/HeaderSkeleton.tsx b/frontend/src/components/layout/HeaderSkeleton.tsx new file mode 100644 index 0000000..6633025 --- /dev/null +++ b/frontend/src/components/layout/HeaderSkeleton.tsx @@ -0,0 +1,32 @@ +/** + * Header Skeleton Component + * Loading placeholder for Header during authentication check + * Matches the structure of the actual Header component + */ + +export function HeaderSkeleton() { + return ( +
+
+ {/* Logo skeleton */} +
+
+
+
+ + {/* Navigation links skeleton */} + +
+ + {/* Right side - Theme toggle and user menu skeleton */} +
+
+
+
+
+
+ ); +} diff --git a/frontend/src/components/layout/index.ts b/frontend/src/components/layout/index.ts index 31d5973..b9cfdb0 100755 --- a/frontend/src/components/layout/index.ts +++ b/frontend/src/components/layout/index.ts @@ -5,3 +5,5 @@ export { Header } from './Header'; export { Footer } from './Footer'; +export { HeaderSkeleton } from './HeaderSkeleton'; +export { AuthLoadingSkeleton } from './AuthLoadingSkeleton';