From 40e35735dc54ef9d290a36243224be918b2bd026 Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Thu, 4 Dec 2025 09:27:14 +0100 Subject: [PATCH] Add improved full screen loader --- apps/web/src/pages/_app.tsx | 43 +---------- .../ui/src/components/atoms/IconSpinner.tsx | 74 +++++++++++++++++++ packages/ui/src/components/atoms/Loader.tsx | 54 ++++++++++++++ packages/ui/src/components/atoms/index.ts | 2 + 4 files changed, 133 insertions(+), 40 deletions(-) create mode 100644 packages/ui/src/components/atoms/IconSpinner.tsx create mode 100644 packages/ui/src/components/atoms/Loader.tsx diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx index 2d5e75a..3397519 100644 --- a/apps/web/src/pages/_app.tsx +++ b/apps/web/src/pages/_app.tsx @@ -5,6 +5,7 @@ import React, {useEffect} from 'react'; import {Toaster} from 'sonner'; import {SWRConfig} from 'swr'; import {DefaultSeo} from 'next-seo'; +import {Loader} from '@plunk/ui'; import {ActiveProjectProvider} from '../lib/contexts/ActiveProjectProvider'; import {useProjects} from '../lib/hooks/useProject'; import {useUser} from '../lib/hooks/useUser'; @@ -43,26 +44,7 @@ function AuthGuard({children}: {children: React.ReactNode}) { // Show loading state while checking authentication (only for protected routes) if (isLoading && !isPublicRoute) { - return ( -
-
- - - - -

Loading...

-
-
- ); + return ; } // Don't render protected content if redirecting @@ -87,26 +69,7 @@ function ProjectGuard({children}: {children: React.ReactNode}) { // Show loading state while checking projects (only for routes that need a project) if (isLoading && !isNoProjectRoute) { - return ( -
-
- - - - -

Loading...

-
-
- ); + return ; } // Don't render protected content if redirecting diff --git a/packages/ui/src/components/atoms/IconSpinner.tsx b/packages/ui/src/components/atoms/IconSpinner.tsx new file mode 100644 index 0000000..0519dc9 --- /dev/null +++ b/packages/ui/src/components/atoms/IconSpinner.tsx @@ -0,0 +1,74 @@ +import {cn} from '../../lib'; + +export interface IconSpinnerProps { + size?: 'sm' | 'md' | 'lg' | 'xl'; + className?: string; +} + +const sizeConfig = { + sm: { + container: 'w-4 h-4', + border: 'border-2', + innerInset: 'inset-0.5', + dot: 'w-1 h-1', + }, + md: { + container: 'w-8 h-8', + border: 'border-[2.5px]', + innerInset: 'inset-1', + dot: 'w-1.5 h-1.5', + }, + lg: { + container: 'w-12 h-12', + border: 'border-[3px]', + innerInset: 'inset-1.5', + dot: 'w-2 h-2', + }, + xl: { + container: 'w-16 h-16', + border: 'border-[3px]', + innerInset: 'inset-2', + dot: 'w-2.5 h-2.5', + }, +}; + +/** + * Animated spinner icon component with dual rotating rings + */ +export function IconSpinner({size = 'md', className}: IconSpinnerProps) { + const config = sizeConfig[size]; + + return ( +
+ {/* Outer glow effect */} +
+ + {/* Base circle */} +
+ + {/* Primary animated ring */} +
+ + {/* Secondary animated ring (counter-rotating) */} +
+ + {/* Center dot */} +
+
+
+
+ ); +} diff --git a/packages/ui/src/components/atoms/Loader.tsx b/packages/ui/src/components/atoms/Loader.tsx new file mode 100644 index 0000000..77f205b --- /dev/null +++ b/packages/ui/src/components/atoms/Loader.tsx @@ -0,0 +1,54 @@ +import Image from 'next/image'; + +export interface LoaderProps { + message?: string; + showLogo?: boolean; +} + +/** + * Full-screen loader component with animated logo and spinner + */ +export function Loader({message = 'Loading...', showLogo = true}: LoaderProps) { + return ( +
+
+ {showLogo && ( +
+
+ Plunk +
+
+ )} + + {/* Animated spinner */} +
+ {/* Outer glow effect */} +
+ + {/* Base circle */} +
+ + {/* Primary animated ring */} +
+ + {/* Secondary animated ring (counter-rotating) */} +
+ + {/* Center dot */} +
+
+
+
+ + {/* Loading message */} + {message &&

{message}

} +
+
+ ); +} diff --git a/packages/ui/src/components/atoms/index.ts b/packages/ui/src/components/atoms/index.ts index 9fb081f..b81cf71 100644 --- a/packages/ui/src/components/atoms/index.ts +++ b/packages/ui/src/components/atoms/index.ts @@ -9,8 +9,10 @@ export * from './Command'; export * from './Dialog'; export * from './DropdownMenu'; export * from './Form'; +export * from './IconSpinner'; export * from './Input'; export * from './Label'; +export * from './Loader'; export * from './Popover'; export * from './Progress'; export * from './RadioGroup';