diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..705d734 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "shadcn": { + "command": "shadcn", + "args": [ + "mcp" + ] + } + } +} \ No newline at end of file diff --git a/.cursor/rules/architecture.mdc b/.cursor/rules/architecture.mdc new file mode 100644 index 0000000..6566476 --- /dev/null +++ b/.cursor/rules/architecture.mdc @@ -0,0 +1,59 @@ +--- +description: Every time a new feature, file or folder has to be created +alwaysApply: false +--- +# Architecture Rules + +## Folder Structure + +Follow this exact structure for consistency: + +``` +src/ +├── app/ # Next.js App Router (routes only) +│ └── [locale]/ # Locale-based routing +│ ├── (auth)/ # Route groups with parentheses +│ ├── (dashboard)/ +│ └── layout.tsx # Locale-specific layout +├── components/ # Shared UI components (not route-specific) +│ ├── ui/ # Shadcn components +│ ├── layout/ # Layout components (Header, Footer, Sidebar) +│ ├── forms/ # Reusable form components +├── features/ # Feature modules (feature-sliced architecture) +│ └── {feature-name}/ +│ ├── api/ # API client functions +│ ├── components/ # Feature-specific UI components +│ ├── hooks/ # Feature-specific hooks +│ ├── lib/ # Feature-specific utilities +│ └── types/ # Feature-specific types +├── lib/ # Core utilities and infrastructure +│ ├── api/ # API client (centralized) +│ ├── i18n/ # Internationalization config +│ ├── react-query/ # React Query provider +│ ├── validation/ # Zod schemas and form helpers +│ └── pwa/ # PWA utilities +├── hooks/ # Shared React hooks (not feature-specific) +├── store/ # Zustand stores (global state) +├── types/ # Global TypeScript types +└── locales/ # Translation files (next-intl) +``` + +## Naming Conventions + +- **Folders**: camelCase for features/lib (`auth`, `apiClient`), kebab-case for routes +- **Components**: PascalCase (`UserProfile.tsx`) +- **Files**: camelCase for utilities/hooks (`useAuth.ts`), PascalCase for components +- **Route groups**: Use parentheses `(auth)`, `(dashboard)` to organize without affecting URLs +- **NO underscore prefixes** - Next.js App Router distinguishes routes via `(folderName)` syntax + +## Feature-Sliced Architecture + +Each feature in `features/` must be self-contained: + +- `api/` - API client functions for this feature +- `components/` - Feature-specific UI components +- `hooks/` - Feature-specific React hooks +- `lib/` - Feature-specific utilities/logic +- `types/` - Feature-specific TypeScript types + +**Principle**: Features should be independent and reusable. Import shared utilities from `lib/`, not from other features. diff --git a/.cursor/rules/data-fetching.mdc b/.cursor/rules/data-fetching.mdc new file mode 100644 index 0000000..0b41a06 --- /dev/null +++ b/.cursor/rules/data-fetching.mdc @@ -0,0 +1,129 @@ +--- +description: When working on anything related to API calls / data fetching +alwaysApply: false +--- +# Data Fetching & API Rules + +## Centralized API Client + +**ALWAYS use the centralized API client** from `@/lib/api/client`: + +```typescript +// ✅ CORRECT: Use centralized client +import { apiClient } from "@/lib/api"; + +const response = await apiClient.post("/endpoint", data); +``` + +### API Client Features + +The `apiClient` automatically handles: +- Authentication tokens (from Zustand auth store) +- Error handling and 401 redirects +- Request/response interceptors +- Timeout handling +- Base URL configuration + +### API Functions Location + +- **Feature-specific API functions**: `features/{feature}/api/` +- **Use centralized client**: Import `apiClient` in feature API files +- **Type safety**: Use TypeScript generics for request/response types + +```typescript +// ✅ CORRECT: Feature API function +// features/users/api/users.ts +import { apiClient } from "@/lib/api"; +import type { User, CreateUserRequest } from "../types"; + +export async function createUser(data: CreateUserRequest) { + return apiClient.post("/users", data); +} +``` + +### Environment Variables + +- Use `NEXT_PUBLIC_API_BASE_URL` for API base URL +- Defaults to `http://localhost:3001/api` if not set +- API client reads from `API_BASE_URL` constant in `lib/constants/` + +## Type Generation + +- **Auto-generate types** from OpenAPI/GraphQL schema when available +- Generated types go in `lib/api/generated/` +- See `scripts/generate-api-types.md` for setup +- **DO NOT manually write API types** if schema is available - generate them + +## Data Fetching Strategy + +Use a **hybrid approach** based on context: + +### Server Components (Default) + +**Use Server Components for:** +- Initial page data +- SEO-critical content +- Public data that doesn't change frequently +- Data that benefits from SSR + +```typescript +// ✅ CORRECT: Fetch in Server Components for initial data +export default async function Page() { + const posts = await apiClient.get("/posts"); + return ; +} +``` + +### TanStack Query (Client Components) + +**Use TanStack Query for:** +- User-specific data after hydration +- Real-time updates +- Optimistic updates +- Data that needs caching/refetching +- Interactive features + +```typescript +// ✅ CORRECT: Use TanStack Query for dynamic/interactive data +"use client"; +import { useQuery } from "@tanstack/react-query"; +import { apiClient } from "@/lib/api"; + +export function UserProfile({ userId }: { userId: string }) { + const { data: user } = useQuery({ + queryKey: ["user", userId], + queryFn: () => apiClient.get(`/users/${userId}`), + }); + return
{user?.data?.name}
; +} +``` + +**Decision Tree:** +``` +Need data? +├─ Initial page load? → Server Component +├─ User-specific after login? → TanStack Query +├─ Real-time updates? → TanStack Query +├─ SEO-critical? → Server Component +└─ Needs caching/refetching? → TanStack Query +``` + +**Implementation guides:** +- Server Components: See `nextjs` skill +- TanStack Query: See `tanstack-query` skill + +## Error Handling + +- **API functions**: Return structured `APIResponse` with `success` and `error` fields +- **Components/hooks**: Display errors, retry logic, fallback UI +- Always check `response.success` before using `response.data` + +```typescript +// ✅ CORRECT: Error handling pattern +const response = await apiClient.get("/user/me"); +if (response.success && response.data) { + // Use response.data +} else { + // Handle response.error +} +``` \ No newline at end of file diff --git a/.cursor/rules/general.mdc b/.cursor/rules/general.mdc new file mode 100644 index 0000000..5d41587 --- /dev/null +++ b/.cursor/rules/general.mdc @@ -0,0 +1,53 @@ +--- +alwaysApply: true +--- + +# General Rules & Best Practices + +## Simplicity First + +Before implementing any solution, ask yourself: + +1. **Is there a simpler way?** - Can this be done with less code, fewer components, or fewer dependencies? +2. **Can this be server-side?** - Server Components are simpler, faster, and better for SEO +3. **Do I really need this?** - Is the complexity justified, or is there a built-in solution? + +**For complete problem-solving methodology**, see `problem-solving` skill. + +## Tech Stack + +- **Framework**: Next.js 16 with App Router +- **Language**: TypeScript (strict mode), see `coding-standards` skill for best practices +- **Package Manager**: pnpm +- **UI Library**: Shadcn/ui components +- **Styling**: Tailwind CSS v4 + +## Core Libraries + +- **i18n**: next-intl +- **Server State**: TanStack Query v5 +- **Client State**: Zustand +- **Forms & Validation**: React Hook Form with Zod + +## File Organization + +- **Avoid barrel files** - Don't use `index.ts` that re-exports from multiple files +- **Feature ownership** - Each feature in `features/` is self-contained + +### Manual Types + +- Global types in `src/types/` folder +- Feature-specific types in `features/{feature}/types/` + +## Error Handling + +- Handle errors gracefully +- Provide fallback UI for errors +- Use error boundaries where appropriate +- Log errors for debugging (use consistent logging strategy) + +## Testing + +- Use jest for unit and integration tests +- Use React Testing Library for component tests +- Use MSW for API mocking in tests diff --git a/.cursor/rules/i18n.mdc b/.cursor/rules/i18n.mdc new file mode 100644 index 0000000..6574668 --- /dev/null +++ b/.cursor/rules/i18n.mdc @@ -0,0 +1,66 @@ +--- +description: When adding or changing visible texts in the UI +alwaysApply: false +--- +# Internationalization Rules + +## i18n Library + +Use **next-intl** for all internationalization: + +## Locale Structure + +- **Locales folder**: `src/locales/{locale}/` +- **Share common texts** Texts used across features live in `src/locales/{locale}/common.json` +- **Organized by feature**: One translation file per language per feature, e.g. `src/locales/{locale}/{feature}.json` +- **Supported locales**: defined in `lib/i18n/routing.ts` +- **Route structure**: All routes under `app/[locale]/` + +## Usage Patterns + +### Server Components (Preferred) + +```typescript +// ✅ CORRECT: Use getTranslations in Server Components +import { getTranslations } from "next-intl/server"; + +export default async function Page() { + const t = await getTranslations(); + return

{t("common.welcome")}

; +} +``` + +### Client Components + +```typescript +// ✅ CORRECT: Use useTranslations in Client Components +"use client"; +import { useTranslations } from "next-intl"; + +export function Component() { + const t = useTranslations(); + return ; +} +``` + +### Navigation + +```typescript +// ✅ CORRECT: Use next-intl Link for locale-aware navigation +import { Link } from "@/lib/i18n/routing"; + +Dashboard +``` + +## Middleware + +- Middleware handles locale detection and routing +- Located at project root: `middleware.ts` +- Automatically redirects to appropriate locale + +## Rules + +- **NEVER use Next.js `Link` directly** - use `@/lib/i18n/routing` Link +- **Always use translation functions** - don't hardcode strings +- **Add translations** - for all supported locales +- **Use nested keys** - organize translations logically (`common.*`, `navigation.*`) diff --git a/.cursor/rules/state-management.mdc b/.cursor/rules/state-management.mdc new file mode 100644 index 0000000..07ffba4 --- /dev/null +++ b/.cursor/rules/state-management.mdc @@ -0,0 +1,60 @@ +--- +description: When working on components or otherwise dealing with state +alwaysApply: false +--- +# State Management Rules + +## State Management Strategy + +Use this hybrid approach for all state: + +### 1. TanStack Query (React Query) - Server State +- Use for server/API data +- Automatic caching, refetching, synchronization +- Use in Client Components only +- Preferred for user-specific data after hydration + +```typescript +// ✅ CORRECT: Use TanStack Query for API data +import { useQuery, useMutation } from "@tanstack/react-query"; + +const { data } = useQuery({ + queryKey: ["users", userId], + queryFn: () => fetchUser(userId), +}); +``` + +### 2. Zustand - Global Client State +- Use for auth state, UI preferences, real-time features +- Persist state when needed (use `persist` middleware) + +```typescript +// ✅ CORRECT: Use Zustand for global client state +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +export const useAuthStore = create()( + persist((set) => ({ /* ... */ }), { name: "auth-storage" }) +); +``` + +### 3. React Context - App-Wide Providers Only +- Use ONLY for truly app-wide providers (theme, i18n context) +- Avoid for feature-specific state - use Zustand instead + +### 4. useState - Component-Scoped State +- Use for component-only state that doesn't need sharing +- Simple form state, UI toggles, etc. + +## Rules + +- **Server state goes in TanStack Query** - don't duplicate in Zustand +- **Zustand stores must be in `src/store/` folder** - named as `{name}Store.ts` +- **Use TanStack Query for optimistic updates** - not Zustand +- **Persist auth state** - use Zustand's `persist` middleware +- **Clear TanStack Query cache on logout** - use `queryClient.clear()` + +## Implementation Guides + +- **TanStack Query**: See `tanstack-query` skill for setup, patterns, and best practices +- **Zustand**: See `zustand-state-management` skill for store creation, middleware, and Next.js SSR handling \ No newline at end of file diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 0000000..6e9f014 --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -0,0 +1,37 @@ +--- +globs: **/*.test.* +alwaysApply: false +--- +# Testing Guidelines + +## Test Best Practices +- Write clear, focused tests that verify one behavior at a time +- Tests should fail for the right reason - verify they catch the bugs they're meant to catch +- Avoid testing implementation details such as the presence of a CSS class or DOM structure; focus on behavior and outcomes. +- Trivial test cases such as the component being rendered should be avoided. + +## Test Structure +- Follow Arrange-Act-Assert pattern: set up test data, execute the code under test, verify results +- Keep tests independent - each test should run in isolation without depending on other tests +- Use a `describe` block to group related tests for a module or component. +- Use an `it` block for each individual test case, clearly describing the expected behavior. + +## Test Data and Environment +- Mock external dependencies to isolate the unit under test. +- Use setup and teardown functions (`beforeEach`, `afterEach`) to prepare test environments. +- Write tests that are deterministic and do not rely on external state or timing. + +## DOM Queries & Interactions +- Elements should be queried like the user would see them on screen (e.g., use `getByRole`, `getByLabelText` from Testing Library). +- If querying like a user is not possible, the default solution should be to improve the accessibility of the element (e.g., adding ARIA attributes). +- Querying by test-id attribute should be a last resort. Avoid completely if possible. +- `userEvent` should be used to simulate user interactions instead of `fireEvent` for more realistic behavior. + +## Test-Driven Development + +For TDD methodology and process, see `test-driven-development` skill. + +**Relationship:** +- This rule covers **how** to write tests (tools, patterns, quality) +- The TDD skill covers **when** to write tests (before code, always) +- Both apply: Use TDD process with testing rule patterns \ No newline at end of file diff --git a/.cursor/skills/accessibility/.claude-plugin/plugin.json b/.cursor/skills/accessibility/.claude-plugin/plugin.json new file mode 100644 index 0000000..7fc3045 --- /dev/null +++ b/.cursor/skills/accessibility/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "accessibility", + "description": "Build WCAG 2.1 AA compliant websites with semantic HTML, proper ARIA, focus management, and screen reader support. Includes color contrast (4.5:1 text), keyboard navigation, form labels, and live regions. Use when implementing accessible interfaces, fixing screen reader issues, keyboard navigation, or troubleshooting focus outline missing, aria-label required, insufficient contrast.", + "version": "1.0.0", + "author": { + "name": "Jeremy Dawes", + "email": "jeremy@jezweb.net" + }, + "license": "MIT", + "repository": "https://github.com/jezweb/claude-skills", + "keywords": [] +} diff --git a/.cursor/skills/accessibility/SKILL.md b/.cursor/skills/accessibility/SKILL.md new file mode 100644 index 0000000..f7db070 --- /dev/null +++ b/.cursor/skills/accessibility/SKILL.md @@ -0,0 +1,882 @@ +--- +name: accessibility +description: | + Build WCAG 2.1 AA compliant websites with semantic HTML, proper ARIA, focus management, and screen reader support. Includes color contrast (4.5:1 text), keyboard navigation, form labels, and live regions. + + Use when implementing accessible interfaces, fixing screen reader issues, keyboard navigation, or troubleshooting "focus outline missing", "aria-label required", "insufficient contrast". +--- + +# Web Accessibility (WCAG 2.1 AA) + +**Status**: Production Ready ✅ +**Last Updated**: 2026-01-14 +**Dependencies**: None (framework-agnostic) +**Standards**: WCAG 2.1 Level AA + +--- + +## Quick Start (5 Minutes) + +### 1. Semantic HTML Foundation + +Choose the right element - don't use `div` for everything: + +```html + +
Submit
+
Next page
+ + + +Next page +``` + +**Why this matters:** +- Semantic elements have built-in keyboard support +- Screen readers announce role automatically +- Browser provides default accessible behaviors + +### 2. Focus Management + +Make interactive elements keyboard-accessible: + +```css +/* ❌ WRONG - removes focus outline */ +button:focus { outline: none; } + +/* ✅ CORRECT - custom accessible outline */ +button:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} +``` + +**CRITICAL:** +- Never remove focus outlines without replacement +- Use `:focus-visible` to show only on keyboard focus +- Ensure 3:1 contrast ratio for focus indicators + +### 3. Text Alternatives + +Every non-text element needs a text alternative: + +```html + + + + + +Company Name + +``` + +--- + +## The 5-Step Accessibility Process + +### Step 1: Choose Semantic HTML + +**Decision tree for element selection:** + +``` +Need clickable element? +├─ Navigates to another page? → +├─ Submits form? → + Forgot password? + +``` + +### Contact Form + +```html +
+

Contact us

+ + + + + + + + + + + + +
+ +
+
+``` + +--- + +## Testing Checklist + +- [ ] All inputs have associated labels +- [ ] Required fields marked (visual + programmatic) +- [ ] Error messages linked with aria-describedby +- [ ] aria-invalid set on fields with errors +- [ ] Error summary at top of form with role="alert" +- [ ] Success/error messages use live regions +- [ ] Correct input types and autocomplete +- [ ] Tab order is logical +- [ ] Can submit with keyboard (Enter key) +- [ ] Focus visible on all form controls +- [ ] Tested with screen reader + +--- + +**Key Takeaway**: Accessible forms require visible labels, clear error messages, and proper ARIA attributes. Test with keyboard and screen reader. diff --git a/.cursor/skills/accessibility/references/semantic-html.md b/.cursor/skills/accessibility/references/semantic-html.md new file mode 100644 index 0000000..116c0dd --- /dev/null +++ b/.cursor/skills/accessibility/references/semantic-html.md @@ -0,0 +1,686 @@ +# Semantic HTML Element Selection Guide + +Choosing the right HTML element is the foundation of web accessibility. This guide helps you select the correct semantic element for every use case. + +--- + +## Decision Trees + +### Interactive Elements + +``` +Need user interaction? +│ +├─ Navigates to another page/section? +│ └─ Link text +│ +├─ Performs action (submit, open, toggle)? +│ ├─ Submits form? +│ │ └─ +│ │ +│ ├─ Resets form? +│ │ └─ +│ │ +│ └─ Other action? +│ └─ +│ +└─ Do nothing (not interactive)? + └─ or
(no onclick!) +``` + +### Content Structure + +``` +Grouping content? +│ +├─ Self-contained, reusable content? +│ └─
+│ Examples: Blog post, news article, product card, comment +│ +├─ Thematic grouping? +│ └─
+│ Examples: Chapter, tab panel, part of page +│ +├─ Navigation links? +│ └─