7.4 KiB
Tier 2: Skeleton loading states, settings Tabs navigation, toast notifications via sonner, form FieldGroup audit, manual separators →
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:
src/app/d/[slug]/page.tsxline ~120 — inline<Suspense fallback>with animate-pulse divs (3 lines). Convert to<Skeleton>components.src/modules/garden/components/plant-detail.tsxline 55 —uploadingImagestate during image upload, no visual feedback other than button text. Add a skeleton overlay or shimmer on the gallery card area.src/modules/garden/components/container-detail.tsxline 30 — same pattern:uploadingImagestate without skeleton.- Any garden page with loading states that use animate-pulse instead of Skeleton.
Changes:
- d/[slug]/page.tsx: Replace inline fallback divs:
<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> }> - plant-detail.tsx / container-detail.tsx: When
uploadingImageis 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:
-
plant-detail.tsx (lines 162-180): Replace manual tab buttons:
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>; -
container-detail.tsx (lines 138-155): Same pattern — info/gallery tabs:
<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.
src/app/layout.tsx: Add<Toaster />component to the app layout root so toasts are globally available.- 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 patternsrc/modules/garden/components/care-schedule-editor.tsxline 192 (calendarSuccess) and error states → toastsrc/components/share-button.tsx— "Share link created" dialog could be a toast instead (or keep as dialog but add toast on copy)
Changes:
// 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 fieldssrc/modules/garden/components/plant-form.tsx— garden plant form fieldssrc/app/settings/household/*— household edit forms- Any other server/action-driven forms
Audit checklist:
- Do all form inputs have
<Label>from shadcn? (Not just plain text labels) - Are error messages rendered consistently below each field?
- Are
Inputcomponents from shadcn used everywhere? (No native<input>tags with manual styles) - 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:
src/modules/garden/components/plant-detail.tsx— border-bottom on tabs bar (border-b border-[var(--ink-faint)]) → use<Separator>insteadsrc/modules/garden/components/container-detail.tsx— same pattern for info/gallery tab separator- Any other manual
border-b,border-t, orborder-lborders used purely as visual separators (not structural layout borders)
Changes:
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
- All loading states use
<Skeleton>(no animate-pulse patterns outside skeleton.tsx) - Garden detail page tabs use shadcn Tabs/TabsList/TabsTrigger/TabsContent
- Toaster component rendered in app layout, delete/cancel actions show toasts
- Form fields audit complete with findings documented or fixed
- Manual border separators replaced with
<Separator>where appropriate
Steps
- Add Skeleton imports and replace inline animation patterns (2a)
- Replace garden detail page tabs with shadcn Tabs (2b)
- Add Toaster to layout, add toast() calls to action handlers (2c)
- Audit form fields for consistency (2d)
- Replace border separators with Separator component (2e)
- Run
pnpm lintandpnpm typecheck