Files
prosapp-migration/admin/src/components/sidebar.tsx
T
Lizandro GuarnizoandClaude Sonnet 4.6 7783cac3fe feat: SMS OTP auth, phone verification gate, admin comments/edit/status pages
Backend:
- Add SmsService + SmsModule: send OTP via u-site.app provider, 5-min TTL
- Auth endpoints: POST /auth/send-otp, POST /auth/phone (login by phone+code),
  POST /auth/verify-phone (link), PATCH /auth/change-password
- is_phone_verified included in JWT token response
- GET /comments (admin, JWT-protected) with author/destination names

Admin:
- Users list: link to detail page per row
- User detail: inline edit form (name, city, phone) with PATCH /users/:id
- Services list: link to detail page per row
- Service detail: status change dropdown (PATCH /services/:id/status)
- New Comments page: summary stats + full table with star ratings
- New SMS settings page: configure API key + send test SMS
- Sidebar: added Comments and SMS entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:53:32 -05:00

100 lines
3.2 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useAuth } from '@/lib/auth';
import {
LayoutDashboard,
Users,
Briefcase,
ClipboardList,
MapPin,
Wrench,
Settings,
MessageSquare,
Star,
LogOut,
ChevronLeft,
Menu,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { useState } from 'react';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
const menu = [
{ href: '/', label: 'Dashboard', icon: LayoutDashboard },
{ href: '/users', label: 'Usuarios', icon: Users },
{ href: '/professionals', label: 'Profesionales', icon: Briefcase },
{ href: '/services', label: 'Servicios', icon: ClipboardList },
{ href: '/cities', label: 'Ciudades', icon: MapPin },
{ href: '/professions', label: 'Profesiones', icon: Wrench },
{ href: '/comments', label: 'Comentarios', icon: Star },
{ href: '/sms', label: 'SMS', icon: MessageSquare },
{ href: '/settings', label: 'Configuración', icon: Settings },
];
export default function Sidebar({ children }: { children: React.ReactNode }) {
const path = usePathname();
const { user, logout } = useAuth();
const [collapsed, setCollapsed] = useState(false);
if (path === '/login') return <>{children}</>;
return (
<div className="flex h-screen">
<aside
className={cn(
'flex flex-col border-r bg-card transition-all duration-200',
collapsed ? 'w-16' : 'w-56',
)}
>
<div className="flex h-14 items-center justify-between border-b px-3">
{!collapsed && <span className="font-semibold">ProsApp Admin</span>}
<Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)}>
{collapsed ? <Menu size={18} /> : <ChevronLeft size={18} />}
</Button>
</div>
<nav className="flex-1 space-y-1 p-2">
{menu.map((item) => (
<Link key={item.href} href={item.href}>
<Button
variant={path === item.href ? 'secondary' : 'ghost'}
className={cn('w-full justify-start', collapsed ? 'px-2' : 'px-3')}
>
<item.icon size={18} className={collapsed ? 'mx-auto' : 'mr-2'} />
{!collapsed && item.label}
</Button>
</Link>
))}
</nav>
<div className="border-t p-2">
{collapsed ? (
<Button variant="ghost" size="icon" className="w-full" onClick={logout}>
<LogOut size={18} />
</Button>
) : (
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<AvatarFallback>{user?.name?.charAt(0) || 'A'}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{user?.name}</p>
</div>
<Button variant="ghost" size="icon" onClick={logout}>
<LogOut size={16} />
</Button>
</div>
)}
</div>
</aside>
<main className="flex-1 overflow-auto bg-muted/20">
<div className="mx-auto max-w-7xl p-6">{children}</div>
</main>
</div>
);
}