united-tattoo/docs/ui-architecture.md

13 KiB

United Tattoo — Frontend Architecture Document

Version: 1.0
Date: 2025-09-17
Template: .bmad-core/templates/front-end-architecture-tmpl.yaml (frontend-architecture-template-v2)
Basis: docs/PRD.md, docs/Architecture.md, repo config (Next.js App Router, Tailwind, ShadCN, Lenis, React Query), clinerules

Template and Framework Selection

Starter/Existing Project Decision:

  • Existing project using Next.js 14 App Router with ShadCN UI, Tailwind, and OpenNext (Cloudflare).
  • Dependencies of note: next 14.2.16, react 18, @tanstack/react-query, react-hook-form + zod resolver, shadcn primitives (Radix), tailwind v4, lenis (smooth scroll), embla carousel.
  • Folder structure and conventions already aligned to App Router: app/, components/, lib/, hooks/, styles/, public/, sql/.

Change Log

Date Version Description Author
2025-09-17 1.0 Initial frontend architecture document Architect

Frontend Tech Stack

Technology Stack Table (synchronized with Backend Architecture)

Category Technology Version Purpose Rationale
Framework Next.js (App Router) 14.2.16 Routing, SSR/ISR, layouts, server comps Unified stack, Cloudflare via OpenNext, matches backend architecture
UI Library React 18.x Component model Ecosystem, SSR-friendly
Component Library ShadCN UI (+ Radix Primitives) latest (registry-based) Accessible primitives, consistent UI Standardized components, theming, accessibility
Styling Tailwind CSS 4.x Utility-first styling Rapid iteration, design consistency
State Management React Query (+ Zustand planned) ^5.89.0 (Query) Server state (RQ), local UI state (Z) RQ for async/cache; add Zustand for local UI flows where needed
Routing Next.js App Router 14.2.16 File-based routing Built-in layouts, loading/error boundaries
Forms react-hook-form + zod ^7.60.0 / ^3.10.0 Typed form handling + validation Lightweight, integrates with zod and ShadCN
Testing Vitest + RTL + JSDOM ^3.2.4 / latest Unit/component tests Fast TS-native testing
Animation tailwindcss-animate + Lenis latest Motion primitives & smooth scroll Simple, perf-conscious; embla for carousel
Dev Tools ESLint/Prettier + TS + RQ Devtools latest Lint/format/types/diagnostics Quality gates + developer experience

Project Structure

app/                         # App Router segments, server components by default
  (marketing)/               # Optional grouping for public pages
  (admin)/                   # Admin area (guarded by middleware)
  api/                       # Route handlers (REST endpoints)
  layout.tsx                 # Root layout (ThemeProvider, font, metadata)
  page.tsx                   # Home
  error.tsx                  # Root error boundary
  loading.tsx                # Root loading state

components/
  ui/                        # ShadCN primitives (registry-managed)
  shared/                    # Reusable presentational components
  composite/                 # Complex composed components (forms, galleries)
  layouts/                   # Layout wrappers (page sections)
  charts/                    # Visualization components if needed

hooks/
  use-mobile.ts              # Existing
  use-toast.ts               # Existing
  use-file-upload.ts         # Existing
  use-query-keys.ts          # Centralized React Query keys
  use-zustand-...ts          # Future local state slices

lib/
  utils.ts                   # cn(), formatting helpers
  api-client.ts              # fetch wrappers, error mapping, tags
  query-client.ts            # React Query client and config (if using RQ Provider)
  validations.ts             # zod schemas (shared)
  types/                     # Shared FE-only types if needed

styles/
  globals.css                # Tailwind base, variables, themes

public/
  ...                        # Static assets

tests/
  components/                # RTL component tests
  e2e/                       # Playwright (planned)

docs/
  ui-architecture.md         # This document

Component Standards

Component Template (ShadCN-style, typed, with cn)

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center rounded-md border px-2 py-1 text-xs font-medium",
  {
    variants: {
      variant: {
        default: "bg-neutral-900 text-white border-transparent",
        outline: "bg-transparent border-neutral-200 text-neutral-900",
        destructive: "bg-red-600 text-white border-transparent",
      },
      size: {
        sm: "text-[10px] px-1.5 py-0.5",
        md: "text-xs px-2 py-1",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  }
)

export interface BadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof badgeVariants> {}

export function Badge({ className, variant, size, ...props }: BadgeProps) {
  return <span className={cn(badgeVariants({ variant, size }), className)} {...props} />
}

Naming Conventions

  • File names: kebab-case (e.g., booking-form.tsx), directories kebab-case.
  • Component names: PascalCase (BookingForm).
  • Hooks: use-*.ts, exports useCamelCase.
  • Variant utilities via cva(), className merged with cn().
  • Group page-level components under components/composite or components/layouts.

State Management

Store Structure (Zustand planned)

hooks/
  state/
    ui/
      use-sidebar.ts          # UI toggles
      use-artist-filter.ts    # Local filter state
    booking/
      use-booking-step.ts     # Stepper state (client-only)

Zustand Slice Example

import { create } from "zustand"

interface BookingStepState {
  step: number
  setStep: (n: number) => void
  next: () => void
  prev: () => void
}

export const useBookingStep = create<BookingStepState>((set) => ({
  step: 1,
  setStep: (n) => set({ step: n }),
  next: () => set((s) => ({ step: s.step + 1 })),
  prev: () => set((s) => ({ step: Math.max(1, s.step - 1) })),
}))

React Query Patterns

  • Use query keys from a central module (hooks/use-query-keys.ts).
  • Server components fetch on the server; client components use React Query for hydration and client mutations.
  • Mutations return zod-validated payloads; invalidate by tag where feasible.

API Integration

Service Template (fetch wrapper + zod)

import { z } from "zod"

const Appointment = z.object({
  id: z.string(),
  artistId: z.string(),
  clientId: z.string(),
  title: z.string(),
  startTime: z.string(),
  endTime: z.string(),
  status: z.string(),
})

export type Appointment = z.infer<typeof Appointment>

async function apiFetch<T>(input: RequestInfo, init?: RequestInit): Promise<T> {
  const res = await fetch(input, {
    ...init,
    headers: { "Content-Type": "application/json", ...(init?.headers || {}) },
  })
  if (!res.ok) {
    // Map standard error shape here
    throw new Error(`API ${res.status}`)
  }
  return (await res.json()) as T
}

export const AppointmentsApi = {
  list: async () => {
    const data = await apiFetch<unknown>("/api/appointments")
    return z.array(Appointment).parse(data)
  },
}

API Client Config (React Query)

import { QueryClient } from "@tanstack/react-query"

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60_000,
      retry: 1,
    },
    mutations: {
      retry: 0,
    },
  },
})

Routing

Route Configuration (App Router patterns)

  • Public segments: app/(marketing)/page.tsx, etc.
  • Admin segments under app/admin/* (guarded by middleware.ts).
  • Provide loading.tsx and error.tsx for key segments.
  • Use route groups to separate concerns: (marketing), (catalog), (admin).
  • Link client forms to server actions or route handlers as appropriate.

Styling Guidelines

Styling Approach

  • Tailwind CSS utilities as primary styling; minimal custom CSS.
  • Use cva() for component variants; cn() for class merging.
  • Follow ShadCN spacing/typography scales and tokens for consistency.

Global Theme Variables (CSS)

:root {
  --bg: #0a0a0a;
  --fg: #fafafa;
  --muted: #a3a3a3;
  --primary: #111;
  --accent: #b91c1c; /* brand accent */
  --radius: 0.5rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a0a;
    --fg: #fafafa;
  }
}

html, body {
  background: var(--bg);
  color: var(--fg);
}

Testing Requirements

Component Test Template (Vitest + RTL)

import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Badge } from "@/components/shared/badge"

describe("Badge", () => {
  it("renders with text", () => {
    render(<Badge>New</Badge>)
    expect(screen.getByText("New")).toBeInTheDocument()
  })

  it("applies variant classes", async () => {
    render(<Badge variant="destructive">Danger</Badge>)
    expect(screen.getByText("Danger")).toHaveClass("bg-red-600")
  })
})

Testing Best Practices

  1. Unit Tests: Test individual components in isolation.
  2. Integration Tests: Compose components and verify interactions.
  3. E2E Tests: Playwright for booking flow, admin CRUD, and uploads (on CF preview).
  4. Coverage: Aim for 80% on component libs; critical flows prioritized.
  5. Test Structure: Arrange-Act-Assert; deterministic tests.
  6. Mock External: Network, router, and provider hooks.

Environment Configuration

Frontend-safe variables (prefix NEXT_PUBLIC_):

  • NEXT_PUBLIC_SITE_URL (computed at runtime if not set)
  • NEXT_PUBLIC_R2_ASSETS_BASE (if public asset base is needed)
  • NEXT_PUBLIC_ANALYTICS_ID (optional) Guidance:
  • Do not expose secrets. Only NEXT_PUBLIC_* keys are readable by the browser.
  • Server-only configuration (NEXTAUTH_URL, secrets, Stripe keys, etc.) remains in Wrangler secrets.

Frontend Developer Standards

Critical Coding Rules

  • Do not bypass ShadCN patterns; use cva() and cn() for variants/classes.
  • Validate all form inputs with zod schemas; surface user-friendly errors.
  • Avoid direct window/document access in server components; use “use client” as needed.
  • Use React Query for async client-side data; do not roll bespoke caching.
  • Keep animations subtle; prefer tailwindcss-animate and Lenis; avoid heavy JS animation libraries unless justified.
  • Keep components pure/presentational where possible; contain side effects in hooks.

Quick Reference

  • Dev: npm run dev
  • Preview (Cloudflare runtime): npm run pages:build && npm run preview
  • Build: npm run build
  • Test: npm run test / npm run test:ui
  • File naming: kebab-case files, PascalCase components
  • Import patterns: "@/components/..", "@/lib/..", "@/hooks/.."
  • UI composition: Follow ShadCN examples and registry patterns before custom

Typography Ramp

Authoritative site-wide type scale to enforce consistency across public pages:

  • Headings
    • h1: font-playfair text-5xl lg:text-7xl tracking-tight
    • h2: text-4xl md:text-5xl font-semibold
    • h3: text-2xl md:text-3xl font-semibold
  • Body
    • Base: text-base leading-relaxed
    • Large: text-xl leading-relaxed (hero/intro copy)
  • Muted/Secondary: text-muted-foreground for supporting text and CardContent
  • Spacing Patterns:
    • Paragraph stacks: space-y-3
    • Grid gaps: gap-6 (default), escalate by breakpoint as needed (md:gap-8)
    • Section padding: standardized via SectionWrapper (px-8 lg:px-16)

Application

  • Apply to: /aftercare, /deposit, /terms, /privacy, /book, /artists (incl. nested)
  • Prefer ShadCN token classes over ad-hoc color utilities (e.g., bg-background, text-foreground)
  • Decorative icons should omit color overrides and include aria-hidden="true" when appropriate

Audit

  • RTL tests should assert:
    • Presence of heading/body classes defined above on representative pages
    • Use of text-muted-foreground for secondary content
    • Standard section paddings via SectionWrapper