Files
famapp/docs/tasks/1b-skeleton-tabs-toasts.md
ginnoir a312d4ce39
CI / checks (push) Successful in 12m55s
CI / build (push) Successful in 15m16s
fix: tighten sharing and shadcn composition
2026-06-13 05:20:01 -05:00

184 lines
7.4 KiB
Markdown

# Tier 2: Skeleton loading states, settings Tabs navigation, toast notifications via sonner, form FieldGroup audit, manual separators → <Separator>
## Goal
Standardize loading UX, navigation, and toast patterns across the app using shadcn components. Audit form field consistency. Replace hand-crafted separator borders with the shadcn Separator component.
---
### Task 2a: Skeleton loading states
**Files affected:**
1. `src/app/d/[slug]/page.tsx` line ~120 — inline `<Suspense fallback>` with animate-pulse divs (3 lines). Convert to `<Skeleton>` components.
2. `src/modules/garden/components/plant-detail.tsx` line 55 — `uploadingImage` state during image upload, no visual feedback other than button text. Add a skeleton overlay or shimmer on the gallery card area.
3. `src/modules/garden/components/container-detail.tsx` line 30 — same pattern: `uploadingImage` state without skeleton.
4. Any garden page with loading states that use animate-pulse instead of Skeleton.
**Changes:**
1. **d/[slug]/page.tsx:** Replace inline fallback divs:
```tsx
<Suspense fallback={
<div className="space-y-3">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-4 w-2/3" />
</div>
}>
```
2. **plant-detail.tsx / container-detail.tsx:** When `uploadingImage` is true, wrap the image area with a Skeleton overlay (absolute positioned, full-width). Or add a shimmer effect using the existing skeleton component.
---
### Task 2b: Settings Tabs navigation → `<Tabs>`
**File:** `src/app/settings/page.tsx` + `src/components/settings-section.tsx`
- **Current state:** Uses a sidebar nav (vertical list with URL hash sync). This is fine for desktop but doesn't use `<Tabs>`.
- **What needs to change:** The **garden detail pages** that have manual tab buttons need to use shadcn Tabs, NOT the settings page. The tier says "settings Tabs navigation" — checking the context: plant-detail.tsx line 164 and container-detail.tsx line 139 both have hand-crafted tab buttons with border-bottom active state. These should become `<Tabs>` + `<TabsList>` + `<TabsTrigger>` + `<TabsContent>`.
**Changes:**
1. **plant-detail.tsx (lines 162-180):** Replace manual tab buttons:
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
<Tabs value={tab} onValueChange={(v) => setTab(v as Tab)} className="mt-4">
<TabsList>
<TabsTrigger value="info">Info</TabsTrigger>
<TabsTrigger value="gallery">Gallery</TabsTrigger>
<TabsTrigger value="care">Care</TabsTrigger>
</TabsList>
<TabsContent value="info">{/* existing info section */}</TabsContent>
<TabsContent value="gallery">{/* existing gallery section */}</TabsContent>
<TabsContent value="care">{/* existing care section */}</TabsContent>
</Tabs>;
```
2. **container-detail.tsx (lines 138-155):** Same pattern — info/gallery tabs:
```tsx
<Tabs value={tab} onValueChange={(v) => setTab(v as Tab)} className="mt-4">
<TabsList>
<TabsTrigger value="info">Info</TabsTrigger>
<TabsTrigger value="gallery">Gallery</TabsTrigger>
</TabsList>
<TabsContent value="info">{/* ... */}</TabsContent>
<TabsContent value="gallery">{/* ... */}</TabsContent>
</Tabs>
```
---
### Task 2c: Toast notifications via sonner
**Files affected:** Need to find all manual error/success messages and convert to toast calls.
1. **`src/app/layout.tsx`:** Add `<Toaster />` component to the app layout root so toasts are globally available.
2. **Places that need toast conversion (search for inline `setError` / success messages):**
- `src/modules/garden/components/plant-detail.tsx` — delete confirmation success/error → `toast.success()` / `toast.error()`
- `src/modules/garden/components/container-detail.tsx` — same pattern
- `src/modules/garden/components/care-schedule-editor.tsx` line 192 (`calendarSuccess`) and error states → toast
- `src/components/share-button.tsx` — "Share link created" dialog could be a toast instead (or keep as dialog but add toast on copy)
**Changes:**
```tsx
// In any component that needs toasts:
import { toast } from "sonner";
// On success:
toast.success("Event added to calendar");
// On error:
toast.error("Failed to create share link");
// In layout.tsx (client component or client wrapper):
import { Toaster } from "@/components/ui/sonner";
return <Toaster position="bottom-right" />;
```
**Acceptance for 2c:**
- `<Toaster>` is present in the app shell layout
- All delete confirmations show toasts on success/error
- The share dialog can remain as-is (it's a distinct UX), but copy action adds toast confirmation
---
### Task 2d: Form FieldGroup audit
**Scope:** Audit all form field components across the app. Check for consistency in label-input-error patterns.
**Files to audit:**
- `src/modules/garden/components/container-form.tsx` — garden container form fields
- `src/modules/garden/components/plant-form.tsx` — garden plant form fields
- `src/app/settings/household/*` — household edit forms
- Any other server/action-driven forms
**Audit checklist:**
1. Do all form inputs have `<Label>` from shadcn? (Not just plain text labels)
2. Are error messages rendered consistently below each field?
3. Are `Input` components from shadcn used everywhere? (No native `<input>` tags with manual styles)
4. Is there a FieldGroup wrapper pattern being used, or is it ad-hoc div nesting?
**Deliverable:** A list of inconsistencies found and fixes applied. If no major issues, document the findings. Do not introduce a FieldGroup abstraction unless one already exists in the codebase.
---
### Task 2e: Manual separators → `<Separator>`
**Files affected:**
1. `src/modules/garden/components/plant-detail.tsx` — border-bottom on tabs bar (`border-b border-[var(--ink-faint)]`) → use `<Separator>` instead
2. `src/modules/garden/components/container-detail.tsx` — same pattern for info/gallery tab separator
3. Any other manual `border-b`, `border-t`, or `border-l` borders used purely as visual separators (not structural layout borders)
**Changes:**
```tsx
import { Separator } from "@/components/ui/separator"
// Replace:
<div className="flex gap-6 border-b border-[var(--ink-faint)]">
{/* tab buttons */}
</div>
// With:
<TabsList>
<TabsTrigger value="info">Info</TabsTrigger>
<TabsTrigger value="gallery">Gallery</TabsTrigger>
</TabsList>
<Separator className="my-2" />
// For the garden tab bar:
{tab === "gallery" && (
<Separator className="my-4" orientation="horizontal" />
)}
```
**Note:** The settings sidebar uses a `.nav-divider` class which is handled by CSS — leave that alone. Only convert inline border divs to Separator components.
---
## Acceptance criteria
1. All loading states use `<Skeleton>` (no animate-pulse patterns outside skeleton.tsx)
2. Garden detail page tabs use shadcn Tabs/TabsList/TabsTrigger/TabsContent
3. Toaster component rendered in app layout, delete/cancel actions show toasts
4. Form fields audit complete with findings documented or fixed
5. Manual border separators replaced with `<Separator>` where appropriate
## Steps
1. Add Skeleton imports and replace inline animation patterns (2a)
2. Replace garden detail page tabs with shadcn Tabs (2b)
3. Add Toaster to layout, add toast() calls to action handlers (2c)
4. Audit form fields for consistency (2d)
5. Replace border separators with Separator component (2e)
6. Run `pnpm lint` and `pnpm typecheck`