264107e3bf
Layout & Navigation: - Tighten max-width to 6xl, adjust padding, add warm gradient overlay - Add icons to desktop nav links, responsive header height, stronger blur - Active pill highlight on mobile nav icons - Fix middleware blocking static assets (logo.png) behind auth Dashboard restructure: - Merge quick actions into hero area as inline pills - Rename "Coming Up" to "Needs Attention", exclude completed tasks - Promote task cards to #2 with richer card design (2-col grid, colored date badges) - Drop "Your Homes" to #3 with accent bars and larger icons Card redesigns: - Residence cards: accent bar, home icon, warm hover shadow - Contractor cards: letter avatar, text contact links, separator - Document cards: type-colored accent bar, restructured footer - Task cards: warm hover shadow - Empty states: larger icon container, gradient bg, rounded CTA Residence detail page: - Add full kanban board with drag-and-drop for task management - Add "Add Task" button pre-filling residence on task form - Replace broken Users stat with Overdue/Completed stats - Compute task summary from kanban columns (always accurate) Data accuracy fixes: - Fix getMyResidences() to fetch kanban data in parallel and compute real per-residence task counts instead of hardcoding zeros - Task form accepts defaultResidenceId prop for pre-filling - New task page reads residence_id from URL, redirects back after create Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { LogOut, Settings, User } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { NotificationBell } from '@/components/notifications/notification-bell';
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { useDataProvider } from '@/lib/demo/data-provider-context';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import { getNavItems } from './nav-items';
|
|
|
|
export function TopBar() {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const { basePath } = useDataProvider();
|
|
const user = useAuthStore((s) => s.user);
|
|
|
|
const navItems = getNavItems(basePath).filter((item) => item.label !== 'Settings');
|
|
const initials = user
|
|
? `${user.first_name?.[0] ?? ''}${user.last_name?.[0] ?? ''}`.toUpperCase() || 'U'
|
|
: 'U';
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
await fetch('/api/auth/logout', { method: 'POST' });
|
|
} catch {
|
|
// Continue with redirect even if the API call fails
|
|
}
|
|
if (basePath.startsWith('/demo')) {
|
|
router.push('/demo');
|
|
} else {
|
|
router.push('/login');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<header className="sticky top-0 z-30 bg-card/90 backdrop-blur-2xl border-b border-border/40">
|
|
<div className="max-w-6xl mx-auto flex items-center justify-between h-14 sm:h-16 px-4 sm:px-8">
|
|
{/* Logo */}
|
|
<Link href={basePath} className="flex items-center gap-2.5 shrink-0 group">
|
|
<Image
|
|
src="/logo.png"
|
|
alt="Casera"
|
|
width={32}
|
|
height={32}
|
|
className="rounded-lg transition-transform group-hover:scale-105"
|
|
/>
|
|
<span className="font-heading text-xl font-bold text-foreground tracking-tight">
|
|
Casera
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Desktop nav links */}
|
|
<nav className="hidden md:flex items-center gap-0.5" role="navigation" aria-label="Main navigation">
|
|
{navItems.map((item) => {
|
|
const isActive =
|
|
item.href === basePath
|
|
? pathname === basePath || pathname === basePath + '/'
|
|
: pathname.startsWith(item.href);
|
|
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
aria-current={isActive ? 'page' : undefined}
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 px-4 py-1.5 text-sm font-medium rounded-full transition-colors',
|
|
isActive
|
|
? 'bg-primary/10 text-primary'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-accent/50'
|
|
)}
|
|
>
|
|
<item.icon className="size-4" />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
{/* Right section */}
|
|
<div className="flex items-center gap-1.5">
|
|
<NotificationBell />
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<button
|
|
aria-label="User menu"
|
|
className="flex items-center gap-2 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
>
|
|
<Avatar className="size-8 border border-border/60">
|
|
<AvatarFallback className="bg-primary/10 text-primary text-xs font-semibold">
|
|
{initials}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-48">
|
|
<DropdownMenuItem onClick={() => router.push(`${basePath}/settings/profile`)}>
|
|
<User className="size-4" />
|
|
Profile
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => router.push(`${basePath}/settings`)}>
|
|
<Settings className="size-4" />
|
|
Settings
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={handleLogout} variant="destructive">
|
|
<LogOut className="size-4" />
|
|
Sign out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|