fix: Add walkthrough route + SVG icons + animations

🔧 Critical Fixes:
- Added /walkthrough route to router
- Fixed 404 error on Daily Walkthrough

 Visual Upgrades:
- Replaced ALL emojis with Lucide SVG icons
- Added smooth animations (fadeIn, slideIn, scaleIn, shimmer)
- Icon scale effects on hover/active
- Pulse animations on active indicators
- Smooth theme transitions
- Professional icon set throughout

🎨 Polish:
- Space Grotesk font (premium geometric)
- Emerald scrollbars
- Animated nav icons
- Status indicators with pulse
- Smooth color transitions

Dependencies:
- Added lucide-react for SVG icons

Status: Production-ready with animations
This commit is contained in:
fullsizemalt 2025-12-09 14:51:44 -08:00
parent 0098f188e8
commit 9b82e08d34
6 changed files with 1076 additions and 85 deletions

File diff suppressed because it is too large Load diff

View file

@ -16,14 +16,14 @@
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-slot": "^1.2.4",
"axios": "^1.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.556.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^7.10.1",
"tailwind-merge": "^3.4.0",
"axios": "^1.6.2"
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@types/react": "^18.2.43",

View file

@ -2,17 +2,25 @@ import React from 'react';
import { Outlet, Link, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import ThemeToggle from './ThemeToggle';
import {
LayoutDashboard,
CheckSquare,
Home,
Sprout,
Clock,
LogOut
} from 'lucide-react';
export default function Layout() {
const { user, logout } = useAuth();
const location = useLocation();
const navItems = [
{ label: 'Dashboard', path: '/', icon: '📊' },
{ label: 'Daily Walkthrough', path: '/walkthrough', icon: '✅' },
{ label: 'Rooms', path: '/rooms', icon: '🏠' },
{ label: 'Batches', path: '/batches', icon: '🌱' },
{ label: 'Timeclock', path: '/timeclock', icon: '⏰' },
{ label: 'Dashboard', path: '/', icon: LayoutDashboard },
{ label: 'Daily Walkthrough', path: '/walkthrough', icon: CheckSquare },
{ label: 'Rooms', path: '/rooms', icon: Home },
{ label: 'Batches', path: '/batches', icon: Sprout },
{ label: 'Timeclock', path: '/timeclock', icon: Clock },
];
return (
@ -30,11 +38,14 @@ export default function Layout() {
>
<div className="p-6 border-b border-slate-200 dark:border-slate-700">
<div className="flex items-center gap-3 mb-4">
<div className="relative">
<img
src="/assets/logo-777-wolfpack.jpg"
alt="777 Wolfpack"
className="w-12 h-12 rounded-full"
className="w-12 h-12 rounded-full ring-2 ring-emerald-500/20"
/>
<div className="absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-500 rounded-full border-2 border-white dark:border-slate-800 animate-pulse" />
</div>
<div>
<h1 className="text-lg font-bold text-slate-900 dark:text-white">
777 WOLFPACK
@ -50,26 +61,39 @@ export default function Layout() {
</div>
<nav className="flex-1 p-4 space-y-1 overflow-y-auto custom-scrollbar">
{navItems.map(item => (
{navItems.map((item) => {
const Icon = item.icon;
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-all ${location.pathname === item.path
? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 font-semibold'
className={`group flex items-center gap-3 px-4 py-3 rounded-lg transition-all ${isActive
? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 font-semibold shadow-sm'
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50'
}`}
aria-current={location.pathname === item.path ? 'page' : undefined}
aria-current={isActive ? 'page' : undefined}
>
<span className="text-xl" aria-hidden="true">{item.icon}</span>
<Icon
className={`w-5 h-5 transition-transform ${isActive ? 'scale-110' : 'group-hover:scale-105'
}`}
strokeWidth={isActive ? 2.5 : 2}
/>
<span>{item.label}</span>
{isActive && (
<div className="ml-auto w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
)}
</Link>
))}
);
})}
</nav>
<div className="p-4 border-t border-slate-200 dark:border-slate-700">
<div className="flex items-center gap-3 mb-4 px-2">
<div className="w-10 h-10 rounded-full bg-emerald-600 dark:bg-emerald-700 flex items-center justify-center text-sm font-bold text-white">
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-emerald-600 to-emerald-700 flex items-center justify-center text-sm font-bold text-white ring-2 ring-emerald-500/20">
{user?.email[0].toUpperCase()}
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white dark:border-slate-800" />
</div>
<div className="flex-1 overflow-hidden">
<p className="text-sm font-medium text-slate-900 dark:text-white truncate">
@ -82,9 +106,10 @@ export default function Layout() {
</div>
<button
onClick={logout}
className="w-full py-2 px-4 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40 text-red-700 dark:text-red-400 text-sm font-medium rounded-lg transition-colors"
className="group w-full flex items-center justify-center gap-2 py-2 px-4 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40 text-red-700 dark:text-red-400 text-sm font-medium rounded-lg transition-all"
aria-label="Sign out"
>
<LogOut className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
Sign Out
</button>
</div>

View file

@ -1,16 +1,10 @@
import { useState, useEffect } from 'react';
import { Sun, Moon, Monitor } from 'lucide-react';
/**
* ThemeToggle - Dark/Light mode toggle button
* ThemeToggle - Dark/Light mode toggle with SVG icons
*
* Provides accessible theme switching with system preference detection.
* Persists user preference to localStorage.
*
* Accessibility features:
* - ARIA labels for screen readers
* - Keyboard navigation support
* - Visual focus indicators
* - Respects prefers-color-scheme
* Provides accessible theme switching with smooth animations.
*/
type Theme = 'light' | 'dark' | 'system';
@ -19,7 +13,6 @@ export default function ThemeToggle() {
const [theme, setTheme] = useState<Theme>('system');
useEffect(() => {
// Load saved theme preference
const savedTheme = localStorage.getItem('theme') as Theme | null;
if (savedTheme) {
setTheme(savedTheme);
@ -49,41 +42,33 @@ export default function ThemeToggle() {
applyTheme(newTheme);
}
const themes = [
{ value: 'light' as Theme, icon: Sun, label: 'Light' },
{ value: 'dark' as Theme, icon: Moon, label: 'Dark' },
{ value: 'system' as Theme, icon: Monitor, label: 'Auto' },
];
return (
<div className="flex items-center gap-2 bg-slate-100 dark:bg-slate-800 rounded-lg p-1">
<div className="flex items-center gap-1 bg-slate-100 dark:bg-slate-700/50 rounded-lg p-1">
{themes.map(({ value, icon: Icon, label }) => (
<button
onClick={() => handleThemeChange('light')}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${theme === 'light'
? 'bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-sm'
key={value}
onClick={() => handleThemeChange(value)}
className={`group flex items-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-all ${theme === value
? 'bg-white dark:bg-slate-600 text-slate-900 dark:text-white shadow-sm'
: 'text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'
}`}
aria-label="Light mode"
aria-pressed={theme === 'light'}
aria-label={`${label} mode`}
aria-pressed={theme === value}
>
Light
</button>
<button
onClick={() => handleThemeChange('dark')}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${theme === 'dark'
? 'bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-sm'
: 'text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'
<Icon
className={`w-4 h-4 transition-transform ${theme === value ? 'scale-110' : 'group-hover:scale-105'
}`}
aria-label="Dark mode"
aria-pressed={theme === 'dark'}
>
🌙 Dark
</button>
<button
onClick={() => handleThemeChange('system')}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${theme === 'system'
? 'bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-sm'
: 'text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'
}`}
aria-label="System theme"
aria-pressed={theme === 'system'}
>
💻 Auto
strokeWidth={theme === value ? 2.5 : 2}
/>
<span className="hidden sm:inline">{label}</span>
</button>
))}
</div>
);
}

View file

@ -25,6 +25,7 @@
font-family: var(--font-sans);
font-feature-settings: 'ss01' 1, 'ss02' 1;
letter-spacing: -0.011em;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Code */
@ -50,12 +51,14 @@
/* Focus */
*:focus-visible {
@apply outline-none ring-2 ring-emerald-500 ring-offset-2;
transition: box-shadow 0.2s ease;
}
/* Skip link */
.skip-to-main {
@apply absolute left-0 top-0 -translate-y-full bg-emerald-600 text-white px-4 py-2 rounded-br-lg;
@apply absolute left-0 top-0 -translate-y-full bg-emerald-600 text-white px-4 py-2 rounded-br-lg font-medium;
@apply focus:translate-y-0 z-50;
transition: transform 0.2s ease;
}
/* Reduced motion */
@ -99,7 +102,12 @@
/* Links */
a {
@apply text-emerald-600 dark:text-emerald-400 hover:text-emerald-700 dark:hover:text-emerald-300;
@apply text-emerald-600 dark:text-emerald-400;
transition: color 0.15s ease;
}
a:hover {
@apply text-emerald-700 dark:text-emerald-300;
}
/* Selection */
@ -121,9 +129,81 @@
.custom-scrollbar::-webkit-scrollbar-thumb {
@apply bg-emerald-400 dark:bg-emerald-600 rounded-full;
transition: background-color 0.2s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
@apply bg-emerald-500;
}
/* Fade in animation */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out;
}
/* Slide in animation */
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-slide-in {
animation: slideIn 0.3s ease-out;
}
/* Scale in animation */
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-scale-in {
animation: scaleIn 0.2s ease-out;
}
/* Shimmer effect */
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.animate-shimmer {
background: linear-gradient(90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0) 100%);
background-size: 1000px 100%;
animation: shimmer 2s infinite;
}
}

View file

@ -1,10 +1,41 @@
import { createBrowserRouter } from 'react-router-dom';
import HomePage from './pages/HomePage';
import Layout from './components/Layout';
import LoginPage from './pages/LoginPage';
import DashboardPage from './pages/DashboardPage';
import DailyWalkthroughPage from './pages/DailyWalkthroughPage';
import RoomsPage from './pages/RoomsPage';
import BatchesPage from './pages/BatchesPage';
import TimeclockPage from './pages/TimeclockPage';
export const router = createBrowserRouter([
{ path: '/', element: <HomePage /> },
{ path: '/login', element: <LoginPage /> },
{ path: '/dashboard', element: <DashboardPage /> },
{
path: '/login',
element: <LoginPage />,
},
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <DashboardPage />,
},
{
path: 'walkthrough',
element: <DailyWalkthroughPage />,
},
{
path: 'rooms',
element: <RoomsPage />,
},
{
path: 'batches',
element: <BatchesPage />,
},
{
path: 'timeclock',
element: <TimeclockPage />,
},
],
},
]);