feat: make frontend fully responsive with mobile-first layout
Add mobile-responsive design with full feature parity: - Bottom sheet (vaul) with 3 snap points for map+list coexistence - Swipeable property cards with horizontal scroll-snap - Hamburger menu with health, tasks, user info - Full-screen map with repositioned legend (top-left on mobile) - Filter FAB opening Sheet drawer - TaskProgressDrawer from bottom on mobile - All changes gated behind useIsMobile() hook (768px breakpoint) - Desktop layout completely untouched New components: MobileBottomSheet, SwipeableCardRow, PropertyCardCompact, MobileMenu Also fixes: idempotent longitude migration, React hooks order
This commit is contained in:
parent
8f068a581e
commit
a744b33578
14 changed files with 1768 additions and 152 deletions
|
|
@ -2,11 +2,13 @@ import type { AuthUser } from '@/auth/types';
|
|||
import type { TaskState } from '@/types';
|
||||
import { Button } from './ui/button';
|
||||
import { Separator } from './ui/separator';
|
||||
import { LogOut, Home, Filter } from 'lucide-react';
|
||||
import { LogOut, Home } from 'lucide-react';
|
||||
import { logout } from '@/auth/authService';
|
||||
import { clearPasskeyUser } from '@/auth/passkeyService';
|
||||
import { HealthIndicator } from './HealthIndicator';
|
||||
import { TaskIndicator } from './TaskIndicator';
|
||||
import { MobileMenu } from './MobileMenu';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
|
||||
interface HeaderProps {
|
||||
user: AuthUser;
|
||||
|
|
@ -25,9 +27,6 @@ interface HeaderProps {
|
|||
|
||||
export function Header({
|
||||
user,
|
||||
activeFilterCount = 0,
|
||||
onToggleFilters,
|
||||
showFilterToggle = false,
|
||||
tasks,
|
||||
activeTaskId,
|
||||
isConnected,
|
||||
|
|
@ -35,6 +34,8 @@ export function Header({
|
|||
onClearAllTasks,
|
||||
onTaskCompleted,
|
||||
}: HeaderProps) {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleLogout = async () => {
|
||||
if (user.provider === 'passkey') {
|
||||
clearPasskeyUser();
|
||||
|
|
@ -45,63 +46,62 @@ export function Header({
|
|||
};
|
||||
|
||||
return (
|
||||
<header className="flex h-14 shrink-0 items-center gap-3 border-b bg-background px-4">
|
||||
<header className={`flex shrink-0 items-center gap-3 border-b bg-background px-4 ${isMobile ? 'h-12' : 'h-14'}`}>
|
||||
{/* Logo / Brand */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Home className="h-5 w-5 text-primary" />
|
||||
<span className="font-semibold text-lg hidden sm:inline">Wrongmove</span>
|
||||
</div>
|
||||
|
||||
<Separator orientation="vertical" className="h-6" />
|
||||
|
||||
{/* Health Indicator */}
|
||||
<HealthIndicator />
|
||||
|
||||
{/* Task Indicator */}
|
||||
<TaskIndicator
|
||||
tasks={tasks}
|
||||
activeTaskId={activeTaskId}
|
||||
isConnected={isConnected}
|
||||
onCancelTask={onCancelTask}
|
||||
onClearAllTasks={onClearAllTasks}
|
||||
onTaskCompleted={onTaskCompleted}
|
||||
/>
|
||||
|
||||
{/* Filter Toggle (mobile) */}
|
||||
{showFilterToggle && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="sm:hidden"
|
||||
onClick={onToggleFilters}
|
||||
>
|
||||
<Filter className="h-4 w-4" />
|
||||
{activeFilterCount > 0 && (
|
||||
<span className="ml-1 bg-primary text-primary-foreground text-xs px-1.5 py-0.5 rounded-full">
|
||||
{activeFilterCount}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
{/* Desktop-only items */}
|
||||
{!isMobile && (
|
||||
<>
|
||||
<Separator orientation="vertical" className="h-6" />
|
||||
<HealthIndicator />
|
||||
<TaskIndicator
|
||||
tasks={tasks}
|
||||
activeTaskId={activeTaskId}
|
||||
isConnected={isConnected}
|
||||
onCancelTask={onCancelTask}
|
||||
onClearAllTasks={onClearAllTasks}
|
||||
onTaskCompleted={onTaskCompleted}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* User Menu */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-muted-foreground hidden md:inline">
|
||||
{user.email}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleLogout}
|
||||
className="gap-2"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Logout</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/* Mobile: hamburger menu */}
|
||||
{isMobile && (
|
||||
<MobileMenu
|
||||
user={user}
|
||||
tasks={tasks}
|
||||
activeTaskId={activeTaskId}
|
||||
isConnected={isConnected}
|
||||
onCancelTask={onCancelTask}
|
||||
onClearAllTasks={onClearAllTasks}
|
||||
onTaskCompleted={onTaskCompleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Desktop: user menu */}
|
||||
{!isMobile && (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{user.email}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleLogout}
|
||||
className="gap-2"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue