# Task 8-C — Admin Modules (Leads, Settings, Media Library)

Agent: full-stack-developer
Task ID: 8-C

## Files Created

### Leads Management
- `src/app/api/admin/leads/route.ts` — GET list with optional `type` & `status` query filters (whitelist-validated), ordered by `createdAt desc`. Uses `requireAdminSession`.
- `src/app/api/admin/leads/[id]/route.ts` — PUT (status update, whitelist NEW/READ/RESPONDED/CLOSED) + DELETE. Uses `requireAdminSession`.
- `src/app/admin/(dashboard)/leads/page.tsx` — Server list page. Reads `searchParams` Promise for `type`/`status`. Renders colored type & status badges, name, phone, service, message snippet, date, count, and links to detail. `export const dynamic = "force-dynamic"`.
- `src/app/admin/(dashboard)/leads/[id]/page.tsx` — Server detail page. Full lead info + sidebar with metadata, quick contact (call/WhatsApp/email). `export const dynamic = "force-dynamic"`.
- `src/components/admin/leads-filters.tsx` — Client toolbar: two `Select` dropdowns (Type / Status) updating URL searchParams + CSV export button (uses already-fetched leads passed as props; builds Blob, temporary anchor download).
- `src/components/admin/lead-status-manager.tsx` — Client. Status buttons (NEW/READ/RESPONDED/CLOSED) calling PUT `/api/admin/leads/[id]`. Delete button calling DELETE then `router.push('/admin/leads')`. On mount, if status is NEW, auto-marks as READ (silent toast). Uses sonner toast.

### Settings
- `src/app/api/admin/settings/route.ts` — GET (upsert singleton) + PUT (upsert with id `"singleton"`; strips `undefined` fields so unspecified fields aren't nulled). Uses `requireAdminSession`.
- `src/app/admin/(dashboard)/settings/page.tsx` — Server page that loads singleton settings (upsert create defaults if missing) and renders `<SettingsForm>`. `export const dynamic = "force-dynamic"`.
- `src/components/admin/settings-form.tsx` — Client form with 7 Card sections (Company Info, Logo & Favicon, Contact Info, Map, Legal, Social Media, Default SEO). Uses `MediaPickerButton` for logo/favicon. Sticky save bar at bottom with brand-gradient Save button → PUT `/api/admin/settings`. Sonner toast "Settings saved".

### Media Library Page
- `src/app/admin/(dashboard)/media/page.tsx` — Server page rendering `<MediaLibrary>` client component. `export const dynamic = "force-dynamic"`.
- `src/components/admin/media-library.tsx` — Full-page MediaLibrary (no Dialog wrapper). Features:
  - Multi-file upload (hidden input + POST `/api/media`)
  - Tabs filter (All/Images/Videos/Documents) + search input
  - Grid of items (image thumb / video preview / file icon)
  - Detail Dialog on click: large preview, originalName, mimeType, dimensions, size, URL with copy button, editable altText (PATCH `/api/media/[id]`)
  - Delete with `AlertDialog` confirm (DELETE `/api/media/[id]`)
  - Loading + empty states
  - Sonner toast feedback

## Conventions Followed
- All admin API routes use `requireAdminSession()` + return `unauthorized()` if no session; use `ok()`/`fail()` helpers from `src/lib/admin-api.ts`.
- Server pages have `export const dynamic = "force-dynamic"`.
- Save/action buttons use `className="brand-gradient text-white"`.
- shadcn/ui components reused (Card, Button, Input, Textarea, Label, Select, Tabs, Dialog, AlertDialog, Badge).
- Brand colors / utility classes (`brand-gradient`, `navy-bg`, `royal-text`, `shadow-soft`, `var(--brand-royal)`) match existing admin pages.
- Rounded-2xl Cards, consistent `p-6` padding, sticky save bar.
- Did NOT modify any existing files (media-picker.tsx, existing media API routes, public leads route, services list pattern).

## Lint Result
- All 4 errors and remaining warnings are in pre-existing files (`src/components/site/before-after-slider.tsx` ref-during-render errors, `media-picker.tsx` unused eslint-disable warnings). None of my created files produce lint errors or warnings.
- Confirmed via filtered grep: no lint output for any of the 8 new files.

## Notes for Next Agents
- `Lead` and `SiteSetting` Prisma models already exist in `prisma/schema.prisma` (no migration needed).
- Site settings use the singleton id pattern (`id = "singleton"`).
- The leads public POST route (`/api/leads`) is unchanged — admin only manages existing leads.
- The MediaLibrary component reuses the existing `/api/media` (GET/POST) and `/api/media/[id]` (DELETE/PATCH) routes — no new media API was created.
- The leads filters and CSV export both work client-side without extra endpoints; filters use URL searchParams so they're shareable/back-button friendly.
