diff --git a/AGENTS.md b/AGENTS.md index 8bd0e39..84494f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,3 +3,267 @@ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + +# Agent Instructions + +## Architecture Overview + +This is a **Next.js 16 + Convex (self-hosted) + Better Auth** SaaS template. + +- **Frontend**: Next.js 16 App Router, React 19, TypeScript 5, Tailwind CSS 4 +- **Backend**: Convex self-hosted (Coolify) with Better Auth for authentication +- **UI**: shadcn/ui (radix-nova style), @hugeicons/react icons +- **i18n**: next-intl v4 with locale-based routing (`/en`, `/pl`) +- **State**: React Server Components + Client Components hybrid. No global state library. +- **Auth**: Better Auth with email/password, HIBP plugin, Convex adapter + +### Key Files + +| File | Purpose | +|------|---------| +| `src/app/[locale]/layout.tsx` | Locale layout — validates locale, enables static rendering | +| `src/app/layout.tsx` | Root layout — theme provider, i18n provider, dynamic `lang` | +| `src/lib/auth-server.ts` | Server-side auth helpers (`isAuthenticated`, `getToken`) | +| `src/lib/auth-client.ts` | Client-side auth client (`authClient.signIn.email(...)`) | +| `src/i18n/routing.ts` | next-intl routing config (locales, defaultLocale, localePrefix) | +| `src/i18n/request.ts` | Request config — reads locale from middleware, loads messages | +| `src/proxy.ts` | next-intl middleware (Next.js 16 convention — was `middleware.ts`) | +| `convex/auth.ts` | Better Auth configuration (plugins, password policy, HIBP) | +| `convex/convex.config.ts` | Convex app definition | + +### Directory Structure + +``` +src/ + app/[locale]/ # All pages live under locale segment + layout.tsx # Locale validation + setRequestLocale + page.tsx # Home page + dashboard/page.tsx # Protected dashboard (server page + isAuthenticated) + settings/page.tsx # Protected settings (server page + isAuthenticated) + sign-in/page.tsx # Auth form with callbackURL support + sign-up/page.tsx # Auth form with callbackURL support + api/auth/[...all]/ # Better Auth API route + components/ + ui/ # shadcn/ui primitives (button, card, field, etc.) + auth/ # Auth-specific components + settings/ # Settings-specific components + core/ # App-wide components (ThemeChanger) + lib/ + auth-server.ts # Server auth helpers + auth-client.ts # Client auth client + routes.ts # Route constants + utils.ts # cn() and utilities + env.ts # Validated environment variables (Zod) + i18n/ + routing.ts # next-intl routing config + request.ts # next-intl request config +convex/ + auth.ts # Better Auth setup + auth.config.ts # Convex auth config provider + convex.config.ts # Convex app definition + http.ts # Convex HTTP actions + betterAuth/ # Better Auth Convex component +messages/ + en.json # English translations + pl.json # Polish translations +``` + +## Conventions + +### File Naming + +- **Components**: PascalCase (`PasswordChangeCard.tsx`, `AuthForm.tsx`) +- **Pages**: `page.tsx` inside kebab-case directory (`sign-in/page.tsx`) +- **Layouts**: `layout.tsx` +- **Utilities**: camelCase (`auth-server.ts`, `utils.ts`) +- **Constants**: `UPPER_SNAKE_CASE` for values, camelCase for files (`constants.ts`) + +### Imports + +```ts +// Good +import { Button } from '@/components/ui/button'; +import { routes } from '@/lib/routes'; + +// Bad — never use @/src/components/ +import { Card } from '@/src/components/ui/card'; +``` + +### Auth Patterns + +#### Server Component (check auth) + +```tsx +import { isAuthenticated } from '@/lib/auth-server'; +import { routes } from '@/lib/routes'; +import { redirect } from 'next/navigation'; + +export default async function ProtectedPage() { + const authenticated = await isAuthenticated(); + if (!authenticated) { + const searchParams = new URLSearchParams({ callbackURL: '/dashboard' }); + redirect(`${routes.public.signIn}?${searchParams.toString()}`); + } + // ... render protected content +} +``` + +#### Client Component (auth actions) + +```tsx +'use client'; +import { authClient } from '@/lib/auth-client'; + +async function handleSignIn(email: string, password: string) { + const result = await authClient.signIn.email({ + email, + password, + callbackURL: '/dashboard', + }); + if (result.error) { + toast.error(result.error.message); + } +} +``` + +#### Password Change + +```tsx +const result = await authClient.changePassword({ + currentPassword: values.currentPassword, + newPassword: values.newPassword, + revokeOtherSessions: true, +}); +``` + +### i18n Patterns + +#### Server Component + +```tsx +import { getTranslations } from 'next-intl/server'; + +export default async function Page() { + const t = await getTranslations('Namespace'); + return