184 lines
7.4 KiB
Markdown
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`
|