| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 1 | 27 | 0% |
| Commands | 0 | 0 | 10 | 0% |
| Section tags | 1 | 0 | 10 | 9% |
What each file covers
Sections
0 shared · 1 only in A · 27 only in B- − Backend Rules
- + CLAUDE.md
- + Standard Worflow
- + Workflow when working on a new task:
- + Development Commands
- + Core Development
- + Code Quality
- + Database Operations
- + Architecture Overview
- + Tech Stack
- + Key Architectural Patterns
- + Important Development Rules
- + Environment Variables
- + Import Conventions
- + File Naming
- + Code Quality Requirements
- + Coding Standards
- + Syntax & Structure
- + Functional Programming Rules
- + Type Safety & Error Handling
- + React Component Standards
- + Component Granularity & Organization
- + Advanced Component Architecture Patterns
- + Application Guidelines
- + Database Schema
- + Current Tables
- + Schema Location
- + Testing and Deployment
Commands
0 shared · 0 only in A · 10 only in B- + yarn dev
- + yarn build
- + yarn lint
- + yarn type-check
- + yarn clean
- + yarn lint:fix
- + yarn format:write
- + yarn db:push
- + yarn db:generate
- + yarn db:migrate
Section tags
1 shared · 0 only in A · 10 only in B- + setup
- + test
- + lint-format
- + code-style
- + architecture
- + types
- + security
- + database
- + ui
- + agent-behaviour
- do-not
Line diff
sportiz91/vibe-template · .cursor/rules/backend.mdc
@@ −1 @@
1---
2description: Follow these rules when working on the backend.
3globs:
4alwaysApply: false
5---
6### Backend Rules
7
8Follow these rules when working on the backend.
9
10It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
11
12#### General Rules
13
14- Never generate migrations. You do not have to do anything in the `db/migrations` folder including migrations and metadata. Ignore it.
15
16#### Organization
17
18#### Schemas
19
20- When importing schemas, use `@/db/schema`
21- Name files like `example-schema.ts`
22- All schemas should go in `db/schema`
23- Make sure to export the schema in `db/schema/index.ts`
24- Make sure to add the schema to the `schema` object in `db/db.ts`
25- If using a userId, always use `userId: text("user_id").notNull()`
26- Always include createdAt and updatedAt columns in all tables
27- Make sure to cascade delete when necessary
28- Use enums for columns that have a limited set of possible values such as:
29
30```ts
31import { pgEnum } from "drizzle-orm/pg-core"
32
33export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
34 "membership",
35 MEMBERSHIP_VALUES
36)
37
38membership: MEMBERSHIP("membership").notNull().default("free")
39```
40
41Example of a schema:
42
43`db/schema/todos-schema.ts`
44
45```ts
46import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
47
48export const todosTable = pgTable("todos", {
49 id: uuid("id").defaultRandom().primaryKey(),
50 userId: text("user_id").notNull(),
51 content: text("content").notNull(),
52 completed: boolean("completed").default(false).notNull(),
53 createdAt: timestamp("created_at").defaultNow().notNull(),
54 updatedAt: timestamp("updated_at")
55 .defaultNow()
56 .notNull()
57 .$onUpdate(() => new Date())
58})
59
60export type InsertTodo = typeof todosTable.$inferInsert
61export type SelectTodo = typeof todosTable.$inferSelect
62```
63
64And exporting it:
65
66`db/schema/index.ts`
67
68```ts
69export * from "./todos-schema"
70```
71
72And adding it to the schema in `db/db.ts`:
73
74`db/db.ts`
75
76```ts
77import { todosTable } from "@/db/schema"
78
79const schema = {
80 todos: todosTable
81}
82```
83
84And a more complex schema:
85
86```ts
87import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
88
89export const chatsTable = pgTable("chats", {
90 id: uuid("id").defaultRandom().primaryKey(),
91 userId: text("user_id").notNull(),
92 name: text("name").notNull(),
93 createdAt: timestamp("created_at").defaultNow().notNull(),
94 updatedAt: timestamp("updated_at")
95 .defaultNow()
96 .notNull()
97 .$onUpdate(() => new Date())
98})
99
100export type InsertChat = typeof chatsTable.$inferInsert
101export type SelectChat = typeof chatsTable.$inferSelect
102```
103
104```ts
105import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
106import { chatsTable } from "./chats-schema"
107
108export const roleEnum = pgEnum("role", ["assistant", "user"])
109
110export const messagesTable = pgTable("messages", {
111 id: uuid("id").defaultRandom().primaryKey(),
112 chatId: uuid("chat_id")
113 .references(() => chatsTable.id, { onDelete: "cascade" })
114 .notNull(),
115 content: text("content").notNull(),
116 role: roleEnum("role").notNull(),
117 createdAt: timestamp("created_at").defaultNow().notNull(),
118 updatedAt: timestamp("updated_at")
119 .defaultNow()
120 .notNull()
121 .$onUpdate(() => new Date())
122})
123
124export type InsertMessage = typeof messagesTable.$inferInsert
125export type SelectMessage = typeof messagesTable.$inferSelect
126```
127
128And exporting it:
129
130`db/schema/index.ts`
131
132```ts
133export * from "./chats-schema"
134export * from "./messages-schema"
135```
136
137And adding it to the schema in `db/db.ts`:
138
139`db/db.ts`
140
141```ts
142import { chatsTable, messagesTable } from "@/db/schema"
143
144const schema = {
145 chats: chatsTable,
146 messages: messagesTable
147}
148```
149
150#### Server Actions
151
152- When importing actions, use `@/actions` or `@/actions/db` if db related
153- DB related actions should go in the `actions/db` folder
154- Other actions should go in the `actions` folder
155- Name files like `example-actions.ts`
156- All actions should go in the `actions` folder
157- Only write the needed actions
158- Return an ActionState with the needed data type from actions
159- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
160- Actions should return a Promise<ActionState<T>>
161- Sort in CRUD order: Create, Read, Update, Delete
162- Make sure to return undefined as the data type if the action is not supposed to return any data
163- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
164
165```ts
166export type ActionState<T> =
167 | { isSuccess: true; message: string; data: T }
168 | { isSuccess: false; message: string; data?: never }
169```
170
171Example of an action:
172
173`actions/db/todos-actions.ts`
174
175```ts
176"use server"
177
178import { db } from "@/db/db"
179import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
180import { ActionState } from "@/types"
181import { eq } from "drizzle-orm"
182
183export async function createTodoAction(
184 todo: InsertTodo
185): Promise<ActionState<SelectTodo>> {
186 try {
187 const [newTodo] = await db.insert(todosTable).values(todo).returning()
188 return {
189 isSuccess: true,
190 message: "Todo created successfully",
191 data: newTodo
192 }
193 } catch (error) {
194 console.error("Error creating todo:", error)
195 return { isSuccess: false, message: "Failed to create todo" }
196 }
197}
198
199export async function getTodosAction(
200 userId: string
201): Promise<ActionState<SelectTodo[]>> {
202 try {
203 const todos = await db.query.todos.findMany({
204 where: eq(todosTable.userId, userId)
205 })
206 return {
207 isSuccess: true,
208 message: "Todos retrieved successfully",
209 data: todos
210 }
211 } catch (error) {
212 console.error("Error getting todos:", error)
213 return { isSuccess: false, message: "Failed to get todos" }
214 }
215}
216
217export async function updateTodoAction(
218 id: string,
219 data: Partial<InsertTodo>
220): Promise<ActionState<SelectTodo>> {
221 try {
222 const [updatedTodo] = await db
223 .update(todosTable)
224 .set(data)
225 .where(eq(todosTable.id, id))
226 .returning()
227
228 return {
229 isSuccess: true,
230 message: "Todo updated successfully",
231 data: updatedTodo
232 }
233 } catch (error) {
234 console.error("Error updating todo:", error)
235 return { isSuccess: false, message: "Failed to update todo" }
236 }
237}
238
239export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
240 try {
241 await db.delete(todosTable).where(eq(todosTable.id, id))
242 return {
243 isSuccess: true,
244 message: "Todo deleted successfully",
245 data: undefined
246 }
247 } catch (error) {
248 console.error("Error deleting todo:", error)
249 return { isSuccess: false, message: "Failed to delete todo" }
250 }
251}
252```
253
254#### Services
255
256- When importing services, use `@/lib/services`
257- Name files like `example-service.ts`
258- All services should go in the `lib/services` folder
259- Services handle complex business logic that would otherwise make server actions too large
260- Services should be pure functions that take inputs and return outputs
261- Services should not directly handle HTTP requests or database operations
262- Use services for external API integrations, complex calculations, and domain-specific logic
263- Follow the data flow: Components → Actions → Services
264- Export functions using named exports
265
266Example of a service:
267
268`lib/services/grammar-correction.ts`
269
270```ts
271import OpenAI from "openai"
272import { getCompletion } from "@/lib/services/open-ai"
273
274const SYSTEM_MESSAGE: string = `You are a grammar correction assistant...`
275
276export const getPunchyText = async (
277 userMessage: string
278): Promise<string | undefined> => {
279 const completion: OpenAI.Chat.Completions.ChatCompletion =
280 await getCompletion(userMessage, {
281 systemMessage: SYSTEM_MESSAGE,
282 maxTokens: 280,
283 temperature: 0.7
284 })
285
286 return completion?.choices?.[0]?.message?.content?.trim()
287}
288```
sportiz91/vibe-template · CLAUDE.md
@@ +1 @@
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5## Standard Worflow
6
7Use this workflow when working on a new task:
8
9### Workflow when working on a new task:
10
111. First, think through the problem, read the codebase for relevant files, and
12 write a plan to tasks/todo.md.
132. The plan should have a list of todo items that you can check off as you complete them.
143. Before you begin to work, check in with me and I verify the plan.
154. Then, begin working on the todo items, marking them as complete as you go.
165. Finally, add a review section to the todo.md file with a summary of the changes
17 you made and any other relevant information.
18
19Periodically make sure to commit when it makes sense to do so.
20
21## Development Commands
22
23**Important: This project uses yarn, not npm. Always use yarn commands.**
24
25**Node.js Version**: This project uses Node.js version 20.12.2 (see .nvmrc). Always ensure you're using the correct Node.js version before running any commands, especially linting and code quality tools.
26
27**To activate the correct Node.js version, run these commands first:**
28```bash
29export NVM_DIR="$HOME/.nvm"
30[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
31nvm use
32```
33
34**Note**: These commands load nvm and switch to the project's Node.js version. You need to run them in each new terminal session.
35
36### Core Development
37
38- `yarn dev` - Start development server
39- `yarn build` - Build for production
40- `yarn lint` - Run ESLint
41- `yarn type-check` - TypeScript type checking
42
43### Code Quality
44
45- `yarn clean` - Fix linting and format code (recommended after changes)
46- `yarn lint:fix` - Auto-fix linting issues
47- `yarn format:write` - Format code with Prettier
48
49### Database Operations
50
51- `yarn db:push` - Push schema changes to database
52- `yarn db:generate` - Generate new migrations
53- `yarn db:migrate` - Run pending migrations
54
55## Architecture Overview
56
57This is a full-stack Next.js application template with the following architecture:
58
59### Tech Stack
60
61- **Frontend**: Next.js 15 with App Router, React 19, TypeScript, Tailwind CSS, Shadcn/UI
62- **Backend**: PostgreSQL with Supabase, Drizzle ORM, Next.js Server Actions
63- **Auth**: Clerk authentication
64- **Payments**: Stripe integration
65- **Analytics**: PostHog
66- **AI**: OpenAI integration
67
68### Key Architectural Patterns
69
70#### Route Organization
71
72- **Route Groups**: `(auth)` for authentication pages, `(marketing)` for public pages
73- **Route-specific Components**: Use `_components` folder within routes for one-off components
74- **Layouts**: Separate layouts for different route groups
75
76#### Data Layer
77
78- **Server Actions**: Located in `/actions/` directory, organized by functionality
79- **Services**: Located in `/lib/services/` directory for complex business logic
80- **Database Schema**: Drizzle ORM schemas in `/db/schema/`
81- **Type Safety**: Use schema-generated types like InsertProfile and SelectProfile from your database schemas
82
83#### Data Flow Architecture
84
85Follow this layered architecture pattern:
86
87- **React Components** → **Server Actions** → **Services** (when complex logic is needed)
88- Services handle domain-specific logic, external API integrations, and complex business rules
89- Keep Server Actions lightweight and focused on data validation and orchestration
90
91#### Component Architecture
92
93- **UI Components**: Shadcn/UI components in `/components/ui/` (don't modify unless specified)
94- **Shared Components**: Reusable components in `/components/`
95- **Route-specific**: Components in `app/route/_components/`
96
97## Important Development Rules
98
99### Environment Variables
100
101- Always use centralized config: `serverConfig` and `publicEnv` from `@/lib/config`
102- Never use `process.env` directly in application code
103- Update `.env.example` when adding new environment variables
104
105### Import Conventions
106
107- Use `@/` for all imports from the app root
108- Import types from `@/types`
109- Import database types from `@/db/schema`
110- Import services from `@/lib/services`
111
112### File Naming
113
114- Use kebab-case for all files and folders
115- Type files: `example-types.ts` in `/types/` directory
116- Export all types in `types/index.ts`
117
118### Code Quality Requirements
119
120- **Always use the correct Node.js version (20.12.2)** before running any code quality commands
121- **Load nvm first**: Run the nvm commands above if you're in a new terminal session
122- Run `yarn clean` after making changes to ensure code quality
123- Use TypeScript interfaces over type aliases when possible
124- Follow the existing patterns in the codebase
125
126## Coding Standards
127
128All code must adhere to these strict formatting and quality guidelines:
129
130### Syntax & Structure
131
132- File names must be dash-case (word-cloud.service.ts) unless an existing pattern differs
133- Group imports: node/standard → npm packages → internal paths. No unused imports
134- Use arrow functions everywhere except inside class bodies, where concise method syntax is allowed
135- Prefer early returns; nested if/else blocks deeper than two levels are disallowed
136- Early returns must use block format with braces (e.g., `if (!value) { return }`) for readability
137- Extract function call results as scope variables before using in conditions (e.g., `const trimmedText = text.trim(); if (!trimmedText) {...}` instead of `if (!text.trim()) {...}`)
138- Use async/await—never chain .then()
139- No .forEach for side effects; use for (const x of arr) instead
140- Array combinators (map, reduce, filter) are allowed only when you return their result
141- Identifiers must be English
142- No commented code allowed
143
144### Functional Programming Rules
145
146Each function must:
147
148- Be ≤ 50 lines (preferably; extract helpers if longer)
149- Take ≤ 4 parameters (optional ones last)
150- Have a single responsibility
151- Be pure unless it is an intentional I/O wrapper (e.g. DB write); such wrappers must be ≤ 15 lines
152- Name functions with camelCase imperative verbs (calculateTotals, getUserById)
153
154### Type Safety & Error Handling
155
156- Explicitly type all function parameters, return types, and exported constants
157- Type all local variables inside a function
158- **Special attention for async operations**: Variables from awaited functions (e.g., `const { userId } = await auth()`) must be explicitly typed, especially in Next.js components where auth results should use proper domain types
159- No any; if an external library forces it, wrap and narrow
160- Error handling in catch blocks:
161 - If the error variable is not used, use `catch {}` (no parameter)
162 - If the error is used, type it as `unknown` and handle it safely within the catch block
163
164### React Component Standards
165
166- Always define props with interfaces, never inline types
167- Place interfaces directly above component definitions
168- Use const arrow functions for component definitions
169- Use implicit return syntax when components only return JSX (no logic before return)
170- Export components using export default pattern (required for Next.js pages/layouts)
171- Handler functions inside components must be ≤ 20 lines and have a single, clear responsibility. Extract helper functions for complex logic.
172
173 **Wrong (~50 lines in one handler):**
174
175 ```tsx
176 const handleFormSubmit = async (): Promise<void> => {
177 const trimmedName: string = formData.name.trim()
178 const trimmedEmail: string = formData.email.trim()
179 const trimmedMessage: string = formData.message.trim()
180
181 if (!trimmedName) {
182 setErrors({ ...errors, name: "Name is required" })
183 toast({
184 title: "Error",
185 description: "Name is required",
186 variant: "destructive"
187 })
188 return
189 }
190
191 if (!trimmedEmail || !trimmedEmail.includes("@")) {
192 setErrors({ ...errors, email: "Valid email is required" })
193 toast({
194 title: "Error",
195 description: "Valid email is required",
196 variant: "destructive"
197 })
198 return
199 }
200
201 if (!trimmedMessage || trimmedMessage.length < 10) {
202 setErrors({
203 ...errors,
204 message: "Message must be at least 10 characters"
205 })
206 toast({
207 title: "Error",
208 description: "Message too short",
209 variant: "destructive"
210 })
211 return
212 }
213
214 setIsSubmitting(true)
215 setErrors({})
216
217 try {
218 const payload: FormPayload = {
219 name: trimmedName,
220 email: trimmedEmail,
221 message: trimmedMessage,
222 timestamp: new Date().toISOString()
223 }
224
225 const response: Response = await fetch("/api/contact", {
226 method: "POST",
227 headers: { "Content-Type": "application/json" },
228 body: JSON.stringify(payload)
229 })
230
231 if (!response.ok) {
232 throw new Error("Failed to submit")
233 }
234
235 const result: SubmissionResult = await response.json()
236
237 setFormData({ name: "", email: "", message: "" })
238 setSubmissionCount((prev) => prev + 1)
239
240 toast({ title: "Success", description: "Message sent successfully!" })
241
242 if (onSuccess) {
243 onSuccess(result)
244 }
245 } catch (error: unknown) {
246 const errorMessage: string =
247 error instanceof Error ? error.message : "Unknown error"
248 console.error("Submission error:", errorMessage)
249 setErrors({ submit: "Failed to send message" })
250 toast({
251 title: "Error",
252 description: "Failed to send message",
253 variant: "destructive"
254 })
255 } finally {
256 setIsSubmitting(false)
257 }
258 }
259 ```
260
261 **Good (broken into focused helpers ≤ 20 lines each):**
262
263 ```tsx
264 const validateForm = (): boolean => {
265 const trimmedName: string = formData.name.trim()
266 const trimmedEmail: string = formData.email.trim()
267 const trimmedMessage: string = formData.message.trim()
268
269 if (!trimmedName) {
270 setErrors({ ...errors, name: "Name is required" })
271 toast({
272 title: "Error",
273 description: "Name is required",
274 variant: "destructive"
275 })
276 return false
277 }
278
279 if (!trimmedEmail || !trimmedEmail.includes("@")) {
280 setErrors({ ...errors, email: "Valid email is required" })
281 toast({
282 title: "Error",
283 description: "Valid email is required",
284 variant: "destructive"
285 })
286 return false
287 }
288
289 if (!trimmedMessage || trimmedMessage.length < 10) {
290 setErrors({
291 ...errors,
292 message: "Message must be at least 10 characters"
293 })
294 toast({
295 title: "Error",
296 description: "Message too short",
297 variant: "destructive"
298 })
299 return false
300 }
301
302 return true
303 }
304
305 const submitForm = async (): Promise<SubmissionResult> => {
306 const payload: FormPayload = {
307 name: formData.name.trim(),
308 email: formData.email.trim(),
309 message: formData.message.trim(),
310 timestamp: new Date().toISOString()
311 }
312
313 const response: Response = await fetch("/api/contact", {
314 method: "POST",
315 headers: { "Content-Type": "application/json" },
316 body: JSON.stringify(payload)
317 })
318
319 if (!response.ok) {
320 throw new Error("Failed to submit")
321 }
322
323 return response.json()
324 }
325
326 const handleSuccess = (result: SubmissionResult): void => {
327 setFormData({ name: "", email: "", message: "" })
328 setSubmissionCount((prev: number) => prev + 1)
329 toast({ title: "Success", description: "Message sent successfully!" })
330
331 if (onSuccess) {
332 onSuccess(result)
333 }
334 }
335
336 const handleError = (error: unknown): void => {
337 const errorMessage: string =
338 error instanceof Error ? error.message : "Unknown error"
339 console.error("Submission error:", errorMessage)
340 setErrors({ submit: "Failed to send message" })
341 toast({
342 title: "Error",
343 description: "Failed to send message",
344 variant: "destructive"
345 })
346 }
347
348 const handleFormSubmit = async (): Promise<void> => {
349 const isValid: boolean = validateForm()
350
351 if (!isValid) {
352 return
353 }
354
355 setIsSubmitting(true)
356 setErrors({})
357
358 try {
359 const result: SubmissionResult = await submitForm()
360 handleSuccess(result)
361 } catch (error: unknown) {
362 handleError(error)
363 } finally {
364 setIsSubmitting(false)
365 }
366 }
367 ```
368
369- Example with implicit return:
370
371 ```tsx
372 interface MyComponentProps {
373 title: string
374 children: React.ReactNode
375 }
376
377 const MyComponent = ({ title, children }: MyComponentProps) => (
378 <div>
379 {title}
380 {children}
381 </div>
382 )
383
384 export default MyComponent
385 ```
386
387- Example with explicit return (when logic is present):
388
389 ```tsx
390 interface MyComponentProps {
391 title: string
392 children: React.ReactNode
393 }
394
395 const MyComponent = ({ title, children }: MyComponentProps) => {
396 const processedTitle = title.toUpperCase()
397
398 return (
399 <div>
400 {processedTitle}
401 {children}
402 </div>
403 )
404 }
405
406 export default MyComponent
407 ```
408
409- Normal components that are not Next.js pages/layouts should be exported
410 using export const pattern
411- Example with implicit return:
412
413 ```tsx
414 interface NotAPageOrLayoutComponentProps {
415 title: string
416 children: React.ReactNode
417 }
418
419 export const NotAPageOrLayoutComponent = ({
420 title,
421 children
422 }: NotAPageOrLayoutComponentProps) => (
423 <div>
424 {title}
425 {children}
426 </div>
427 )
428 ```
429
430### Component Granularity & Organization
431
432- Break down large components into smaller, focused components for better maintainability
433- When a component contains multiple logical sections (e.g., Card with CardHeader + CardContent), extract each section into separate components
434- Create dedicated folders for related component groups:
435 - Use kebab-case folder names matching the main component concept
436 - Place related sub-components within the same folder using kebab-case file names
437 - Example structure: `component-name/component-name-header.tsx`, `component-name/component-name-content.tsx`
438- Each sub-component should have a single, clear responsibility
439- Maintain the parent component as a composition wrapper that orchestrates child components
440- Follow this pattern when refactoring existing components or creating new feature components
441
442### Advanced Component Architecture Patterns
443
444#### Pure Functions and Constants Organization
445
446- **Pure functions** (no side effects, deterministic output) must be extracted outside components:
447 - Place above the component definition
448 - Examples: `getGreeting()`, `getMembershipBadgeColor()`, `formatDate()`
449- **Constants and static data** must be moved outside components:
450 - Place after imports and interfaces, before pure functions
451 - Use SCREAMING_SNAKE_CASE for naming (e.g., `TEMPLATE_FEATURES`, `TECH_STACK`) constants
452 - **Always explicitly type constants** with appropriate type annotations
453 - Examples: `const API_URL: string = "..."`, `const MAX_RETRIES: number = 3`
454 - Use `as const` for immutable values when type inference is sufficient
455 - Group related constants together
456
457#### Custom Hooks Organization
458
459- **Custom hooks** must be extracted to separate files in the `/hooks/` directory:
460 - Use kebab-case file naming: `use-scroll-detection.ts`, `use-local-storage.ts`
461 - Start hook names with `use` prefix following React conventions
462 - Place hooks in `/hooks/` folder at project root level
463 - Export hooks using named exports: `export const useScrollDetection = () => {}`
464 - **Always explicitly type hook return values** and parameters
465 - Examples: `useScrollDetection(): boolean`, `useLocalStorage<T>(key: string): [T, (value: T) => void]`
466 - Group related hooks in the same file only if they're tightly coupled
467
468#### Whitespace and Formatting Rules
469
470- **Component variable organization**: Maintain consistent whitespace between different types of declarations:
471 - Add a blank line between React state declarations and custom hook calls
472 - Add a blank line between custom hook calls and other variable declarations
473 - Example:
474
475 ```tsx
476 const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
477 const [isVisible, setIsVisible] = useState<boolean>(true)
478
479 const isScrolled: boolean = useScrollDetection()
480 const userData: UserData = useUserData()
481
482 const processedData: ProcessedData = processUserData(userData)
483 ```
484
485#### Nested Component Structure for Complex Components
486
487When a component has multiple distinct sections, create nested folder structure:
488
489```
490dashboard-welcome/
491├── dashboard-welcome.tsx // Main orchestrator component
492├── greeting.tsx // Self-contained greeting section
493├── whats-included/ // Folder for multi-part section
494│ ├── whats-included.tsx // Section orchestrator
495│ ├── whats-included-title.tsx // Title sub-component
496│ └── whats-included-features.tsx // Features list sub-component
497├── core-technologies/ // Folder for multi-part section
498│ ├── core-technologies.tsx // Section orchestrator
499│ ├── core-technologies-title.tsx // Title sub-component
500│ └── core-technologies-list.tsx // Tech list sub-component
501└── get-started/ // Folder for multi-part section
502 ├── get-started.tsx // Section orchestrator
503 ├── get-started-title.tsx // Title sub-component
504 ├── get-started-features.tsx // Features grid sub-component
505 └── get-started-feature-2.tsx // Individual feature card
506```
507
508#### Component Organization Rules
509
5101. **Main orchestrator**: Composition only, minimal logic, imports and renders sub-components
5112. **Section orchestrators**: Handle section-specific logic, render related sub-components
5123. **Leaf components**: Single responsibility, pure presentation, accept props only
5134. **Shared constants**: Extract to file level, use proper naming conventions
5145. **Pure functions**: Extract above component definitions, properly typed
5156. **File structure**: Mirror logical component hierarchy in folder structure
516
517### Application Guidelines
518
5191. Apply these standards to all new code and when refactoring existing code
5202. When making any code changes, ensure they conform to these guidelines
5213. If existing code doesn't follow these standards, update it to comply when modifying those files
5224. Use these standards as a checklist when reviewing code changes
5235. Prefer extracting helper functions over writing long, complex functions
5246. Always prioritize code readability and maintainability
525
526## Database Schema
527
528### Current Tables
529
530- **Profiles**: User profiles with Stripe integration and membership tiers
531
532### Schema Location
533
534- Schemas: `/db/schema/`
535- Migrations: `/db/migrations/`
536- Database connection: `/db/db.ts`
537
538## Testing and Deployment
539
540- The project uses Vercel for deployment
541- No specific test framework is configured - check with user if testing is needed
542- Always run `yarn build` and `yarn type-check` before considering work complete
543
@@ −1 +1 @@
1−---
2−description: Follow these rules when working on the backend.
3−globs:
4−alwaysApply: false
5−---
6−### Backend Rules
1+# CLAUDE.md
72
8−Follow these rules when working on the backend.
3+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
94
10−It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
5+## Standard Worflow
116
12−#### General Rules
7+Use this workflow when working on a new task:
138
14−- Never generate migrations. You do not have to do anything in the `db/migrations` folder including migrations and metadata. Ignore it.
9+### Workflow when working on a new task:
1510
16−#### Organization
11+1. First, think through the problem, read the codebase for relevant files, and
12+ write a plan to tasks/todo.md.
13+2. The plan should have a list of todo items that you can check off as you complete them.
14+3. Before you begin to work, check in with me and I verify the plan.
15+4. Then, begin working on the todo items, marking them as complete as you go.
16+5. Finally, add a review section to the todo.md file with a summary of the changes
17+ you made and any other relevant information.
1718
18−#### Schemas
19+Periodically make sure to commit when it makes sense to do so.
1920
20−- When importing schemas, use `@/db/schema`
21−- Name files like `example-schema.ts`
22−- All schemas should go in `db/schema`
23−- Make sure to export the schema in `db/schema/index.ts`
24−- Make sure to add the schema to the `schema` object in `db/db.ts`
25−- If using a userId, always use `userId: text("user_id").notNull()`
26−- Always include createdAt and updatedAt columns in all tables
27−- Make sure to cascade delete when necessary
28−- Use enums for columns that have a limited set of possible values such as:
21+## Development Commands
2922
30−```ts
31−import { pgEnum } from "drizzle-orm/pg-core"
23+**Important: This project uses yarn, not npm. Always use yarn commands.**
3224
33−export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
34− "membership",
35− MEMBERSHIP_VALUES
36−)
25+**Node.js Version**: This project uses Node.js version 20.12.2 (see .nvmrc). Always ensure you're using the correct Node.js version before running any commands, especially linting and code quality tools.
3726
38−membership: MEMBERSHIP("membership").notNull().default("free")
27+**To activate the correct Node.js version, run these commands first:**
28+```bash
29+export NVM_DIR="$HOME/.nvm"
30+[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
31+nvm use
3932 ```
4033
41−Example of a schema:
34+**Note**: These commands load nvm and switch to the project's Node.js version. You need to run them in each new terminal session.
4235
43−`db/schema/todos-schema.ts`
36+### Core Development
4437
45−```ts
46−import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
38+- `yarn dev` - Start development server
39+- `yarn build` - Build for production
40+- `yarn lint` - Run ESLint
41+- `yarn type-check` - TypeScript type checking
4742
48−export const todosTable = pgTable("todos", {
49− id: uuid("id").defaultRandom().primaryKey(),
50− userId: text("user_id").notNull(),
51− content: text("content").notNull(),
52− completed: boolean("completed").default(false).notNull(),
53− createdAt: timestamp("created_at").defaultNow().notNull(),
54− updatedAt: timestamp("updated_at")
55− .defaultNow()
56− .notNull()
57− .$onUpdate(() => new Date())
58−})
43+### Code Quality
5944
60−export type InsertTodo = typeof todosTable.$inferInsert
61−export type SelectTodo = typeof todosTable.$inferSelect
62−```
45+- `yarn clean` - Fix linting and format code (recommended after changes)
46+- `yarn lint:fix` - Auto-fix linting issues
47+- `yarn format:write` - Format code with Prettier
6348
64−And exporting it:
49+### Database Operations
6550
66−`db/schema/index.ts`
51+- `yarn db:push` - Push schema changes to database
52+- `yarn db:generate` - Generate new migrations
53+- `yarn db:migrate` - Run pending migrations
6754
68−```ts
69−export * from "./todos-schema"
70−```
55+## Architecture Overview
7156
72−And adding it to the schema in `db/db.ts`:
57+This is a full-stack Next.js application template with the following architecture:
7358
74−`db/db.ts`
59+### Tech Stack
7560
76−```ts
77−import { todosTable } from "@/db/schema"
61+- **Frontend**: Next.js 15 with App Router, React 19, TypeScript, Tailwind CSS, Shadcn/UI
62+- **Backend**: PostgreSQL with Supabase, Drizzle ORM, Next.js Server Actions
63+- **Auth**: Clerk authentication
64+- **Payments**: Stripe integration
65+- **Analytics**: PostHog
66+- **AI**: OpenAI integration
7867
79−const schema = {
80− todos: todosTable
81−}
82−```
68+### Key Architectural Patterns
8369
84−And a more complex schema:
70+#### Route Organization
8571
86−```ts
87−import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
72+- **Route Groups**: `(auth)` for authentication pages, `(marketing)` for public pages
73+- **Route-specific Components**: Use `_components` folder within routes for one-off components
74+- **Layouts**: Separate layouts for different route groups
8875
89−export const chatsTable = pgTable("chats", {
90− id: uuid("id").defaultRandom().primaryKey(),
91− userId: text("user_id").notNull(),
92− name: text("name").notNull(),
93− createdAt: timestamp("created_at").defaultNow().notNull(),
94− updatedAt: timestamp("updated_at")
95− .defaultNow()
96− .notNull()
97− .$onUpdate(() => new Date())
98−})
76+#### Data Layer
9977
100−export type InsertChat = typeof chatsTable.$inferInsert
101−export type SelectChat = typeof chatsTable.$inferSelect
102−```
78+- **Server Actions**: Located in `/actions/` directory, organized by functionality
79+- **Services**: Located in `/lib/services/` directory for complex business logic
80+- **Database Schema**: Drizzle ORM schemas in `/db/schema/`
81+- **Type Safety**: Use schema-generated types like InsertProfile and SelectProfile from your database schemas
10382
104−```ts
105−import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
106−import { chatsTable } from "./chats-schema"
83+#### Data Flow Architecture
10784
108−export const roleEnum = pgEnum("role", ["assistant", "user"])
85+Follow this layered architecture pattern:
10986
110−export const messagesTable = pgTable("messages", {
111− id: uuid("id").defaultRandom().primaryKey(),
112− chatId: uuid("chat_id")
113− .references(() => chatsTable.id, { onDelete: "cascade" })
114− .notNull(),
115− content: text("content").notNull(),
116− role: roleEnum("role").notNull(),
117− createdAt: timestamp("created_at").defaultNow().notNull(),
118− updatedAt: timestamp("updated_at")
119− .defaultNow()
120− .notNull()
121− .$onUpdate(() => new Date())
122−})
87+- **React Components** → **Server Actions** → **Services** (when complex logic is needed)
88+- Services handle domain-specific logic, external API integrations, and complex business rules
89+- Keep Server Actions lightweight and focused on data validation and orchestration
12390
124−export type InsertMessage = typeof messagesTable.$inferInsert
125−export type SelectMessage = typeof messagesTable.$inferSelect
126−```
91+#### Component Architecture
12792
128−And exporting it:
93+- **UI Components**: Shadcn/UI components in `/components/ui/` (don't modify unless specified)
94+- **Shared Components**: Reusable components in `/components/`
95+- **Route-specific**: Components in `app/route/_components/`
12996
130−`db/schema/index.ts`
97+## Important Development Rules
13198
132−```ts
133−export * from "./chats-schema"
134−export * from "./messages-schema"
135−```
99+### Environment Variables
136100
137−And adding it to the schema in `db/db.ts`:
101+- Always use centralized config: `serverConfig` and `publicEnv` from `@/lib/config`
102+- Never use `process.env` directly in application code
103+- Update `.env.example` when adding new environment variables
138104
139−`db/db.ts`
105+### Import Conventions
140106
141−```ts
142−import { chatsTable, messagesTable } from "@/db/schema"
107+- Use `@/` for all imports from the app root
108+- Import types from `@/types`
109+- Import database types from `@/db/schema`
110+- Import services from `@/lib/services`
143111
144−const schema = {
145− chats: chatsTable,
146− messages: messagesTable
147−}
148−```
112+### File Naming
149113
150−#### Server Actions
114+- Use kebab-case for all files and folders
115+- Type files: `example-types.ts` in `/types/` directory
116+- Export all types in `types/index.ts`
151117
152−- When importing actions, use `@/actions` or `@/actions/db` if db related
153−- DB related actions should go in the `actions/db` folder
154−- Other actions should go in the `actions` folder
155−- Name files like `example-actions.ts`
156−- All actions should go in the `actions` folder
157−- Only write the needed actions
158−- Return an ActionState with the needed data type from actions
159−- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
160−- Actions should return a Promise<ActionState<T>>
161−- Sort in CRUD order: Create, Read, Update, Delete
162−- Make sure to return undefined as the data type if the action is not supposed to return any data
163−- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
118+### Code Quality Requirements
164119
165−```ts
166−export type ActionState<T> =
167− | { isSuccess: true; message: string; data: T }
168− | { isSuccess: false; message: string; data?: never }
169−```
120+- **Always use the correct Node.js version (20.12.2)** before running any code quality commands
121+- **Load nvm first**: Run the nvm commands above if you're in a new terminal session
122+- Run `yarn clean` after making changes to ensure code quality
123+- Use TypeScript interfaces over type aliases when possible
124+- Follow the existing patterns in the codebase
170125
171−Example of an action:
126+## Coding Standards
172127
173−`actions/db/todos-actions.ts`
128+All code must adhere to these strict formatting and quality guidelines:
174129
175−```ts
176−"use server"
130+### Syntax & Structure
177131
178−import { db } from "@/db/db"
179−import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
180−import { ActionState } from "@/types"
181−import { eq } from "drizzle-orm"
132+- File names must be dash-case (word-cloud.service.ts) unless an existing pattern differs
133+- Group imports: node/standard → npm packages → internal paths. No unused imports
134+- Use arrow functions everywhere except inside class bodies, where concise method syntax is allowed
135+- Prefer early returns; nested if/else blocks deeper than two levels are disallowed
136+- Early returns must use block format with braces (e.g., `if (!value) { return }`) for readability
137+- Extract function call results as scope variables before using in conditions (e.g., `const trimmedText = text.trim(); if (!trimmedText) {...}` instead of `if (!text.trim()) {...}`)
138+- Use async/await—never chain .then()
139+- No .forEach for side effects; use for (const x of arr) instead
140+- Array combinators (map, reduce, filter) are allowed only when you return their result
141+- Identifiers must be English
142+- No commented code allowed
182143
183−export async function createTodoAction(
184− todo: InsertTodo
185−): Promise<ActionState<SelectTodo>> {
186− try {
187− const [newTodo] = await db.insert(todosTable).values(todo).returning()
188− return {
189− isSuccess: true,
190− message: "Todo created successfully",
191− data: newTodo
144+### Functional Programming Rules
145+
146+Each function must:
147+
148+- Be ≤ 50 lines (preferably; extract helpers if longer)
149+- Take ≤ 4 parameters (optional ones last)
150+- Have a single responsibility
151+- Be pure unless it is an intentional I/O wrapper (e.g. DB write); such wrappers must be ≤ 15 lines
152+- Name functions with camelCase imperative verbs (calculateTotals, getUserById)
153+
154+### Type Safety & Error Handling
155+
156+- Explicitly type all function parameters, return types, and exported constants
157+- Type all local variables inside a function
158+- **Special attention for async operations**: Variables from awaited functions (e.g., `const { userId } = await auth()`) must be explicitly typed, especially in Next.js components where auth results should use proper domain types
159+- No any; if an external library forces it, wrap and narrow
160+- Error handling in catch blocks:
161+ - If the error variable is not used, use `catch {}` (no parameter)
162+ - If the error is used, type it as `unknown` and handle it safely within the catch block
163+
164+### React Component Standards
165+
166+- Always define props with interfaces, never inline types
167+- Place interfaces directly above component definitions
168+- Use const arrow functions for component definitions
169+- Use implicit return syntax when components only return JSX (no logic before return)
170+- Export components using export default pattern (required for Next.js pages/layouts)
171+- Handler functions inside components must be ≤ 20 lines and have a single, clear responsibility. Extract helper functions for complex logic.
172+
173+ **Wrong (~50 lines in one handler):**
174+
175+ ```tsx
176+ const handleFormSubmit = async (): Promise<void> => {
177+ const trimmedName: string = formData.name.trim()
178+ const trimmedEmail: string = formData.email.trim()
179+ const trimmedMessage: string = formData.message.trim()
180+
181+ if (!trimmedName) {
182+ setErrors({ ...errors, name: "Name is required" })
183+ toast({
184+ title: "Error",
185+ description: "Name is required",
186+ variant: "destructive"
187+ })
188+ return
192189 }
193− } catch (error) {
194− console.error("Error creating todo:", error)
195− return { isSuccess: false, message: "Failed to create todo" }
190+
191+ if (!trimmedEmail || !trimmedEmail.includes("@")) {
192+ setErrors({ ...errors, email: "Valid email is required" })
193+ toast({
194+ title: "Error",
195+ description: "Valid email is required",
196+ variant: "destructive"
197+ })
198+ return
199+ }
200+
201+ if (!trimmedMessage || trimmedMessage.length < 10) {
202+ setErrors({
203+ ...errors,
204+ message: "Message must be at least 10 characters"
205+ })
206+ toast({
207+ title: "Error",
208+ description: "Message too short",
209+ variant: "destructive"
210+ })
211+ return
212+ }
213+
214+ setIsSubmitting(true)
215+ setErrors({})
216+
217+ try {
218+ const payload: FormPayload = {
219+ name: trimmedName,
220+ email: trimmedEmail,
221+ message: trimmedMessage,
222+ timestamp: new Date().toISOString()
223+ }
224+
225+ const response: Response = await fetch("/api/contact", {
226+ method: "POST",
227+ headers: { "Content-Type": "application/json" },
228+ body: JSON.stringify(payload)
229+ })
230+
231+ if (!response.ok) {
232+ throw new Error("Failed to submit")
233+ }
234+
235+ const result: SubmissionResult = await response.json()
236+
237+ setFormData({ name: "", email: "", message: "" })
238+ setSubmissionCount((prev) => prev + 1)
239+
240+ toast({ title: "Success", description: "Message sent successfully!" })
241+
242+ if (onSuccess) {
243+ onSuccess(result)
244+ }
245+ } catch (error: unknown) {
246+ const errorMessage: string =
247+ error instanceof Error ? error.message : "Unknown error"
248+ console.error("Submission error:", errorMessage)
249+ setErrors({ submit: "Failed to send message" })
250+ toast({
251+ title: "Error",
252+ description: "Failed to send message",
253+ variant: "destructive"
254+ })
255+ } finally {
256+ setIsSubmitting(false)
257+ }
196258 }
197−}
259+ ```
198260
199−export async function getTodosAction(
200− userId: string
201−): Promise<ActionState<SelectTodo[]>> {
202− try {
203− const todos = await db.query.todos.findMany({
204− where: eq(todosTable.userId, userId)
261+ **Good (broken into focused helpers ≤ 20 lines each):**
262+
263+ ```tsx
264+ const validateForm = (): boolean => {
265+ const trimmedName: string = formData.name.trim()
266+ const trimmedEmail: string = formData.email.trim()
267+ const trimmedMessage: string = formData.message.trim()
268+
269+ if (!trimmedName) {
270+ setErrors({ ...errors, name: "Name is required" })
271+ toast({
272+ title: "Error",
273+ description: "Name is required",
274+ variant: "destructive"
275+ })
276+ return false
277+ }
278+
279+ if (!trimmedEmail || !trimmedEmail.includes("@")) {
280+ setErrors({ ...errors, email: "Valid email is required" })
281+ toast({
282+ title: "Error",
283+ description: "Valid email is required",
284+ variant: "destructive"
285+ })
286+ return false
287+ }
288+
289+ if (!trimmedMessage || trimmedMessage.length < 10) {
290+ setErrors({
291+ ...errors,
292+ message: "Message must be at least 10 characters"
293+ })
294+ toast({
295+ title: "Error",
296+ description: "Message too short",
297+ variant: "destructive"
298+ })
299+ return false
300+ }
301+
302+ return true
303+ }
304+
305+ const submitForm = async (): Promise<SubmissionResult> => {
306+ const payload: FormPayload = {
307+ name: formData.name.trim(),
308+ email: formData.email.trim(),
309+ message: formData.message.trim(),
310+ timestamp: new Date().toISOString()
311+ }
312+
313+ const response: Response = await fetch("/api/contact", {
314+ method: "POST",
315+ headers: { "Content-Type": "application/json" },
316+ body: JSON.stringify(payload)
205317 })
206− return {
207− isSuccess: true,
208− message: "Todos retrieved successfully",
209− data: todos
318+
319+ if (!response.ok) {
320+ throw new Error("Failed to submit")
210321 }
211− } catch (error) {
212− console.error("Error getting todos:", error)
213− return { isSuccess: false, message: "Failed to get todos" }
322+
323+ return response.json()
214324 }
215−}
216325
217−export async function updateTodoAction(
218− id: string,
219− data: Partial<InsertTodo>
220−): Promise<ActionState<SelectTodo>> {
221− try {
222− const [updatedTodo] = await db
223− .update(todosTable)
224− .set(data)
225− .where(eq(todosTable.id, id))
226− .returning()
326+ const handleSuccess = (result: SubmissionResult): void => {
327+ setFormData({ name: "", email: "", message: "" })
328+ setSubmissionCount((prev: number) => prev + 1)
329+ toast({ title: "Success", description: "Message sent successfully!" })
227330
228− return {
229− isSuccess: true,
230− message: "Todo updated successfully",
231− data: updatedTodo
331+ if (onSuccess) {
332+ onSuccess(result)
232333 }
233− } catch (error) {
234− console.error("Error updating todo:", error)
235− return { isSuccess: false, message: "Failed to update todo" }
236334 }
237−}
238335
239−export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
240− try {
241− await db.delete(todosTable).where(eq(todosTable.id, id))
242− return {
243− isSuccess: true,
244− message: "Todo deleted successfully",
245− data: undefined
336+ const handleError = (error: unknown): void => {
337+ const errorMessage: string =
338+ error instanceof Error ? error.message : "Unknown error"
339+ console.error("Submission error:", errorMessage)
340+ setErrors({ submit: "Failed to send message" })
341+ toast({
342+ title: "Error",
343+ description: "Failed to send message",
344+ variant: "destructive"
345+ })
346+ }
347+
348+ const handleFormSubmit = async (): Promise<void> => {
349+ const isValid: boolean = validateForm()
350+
351+ if (!isValid) {
352+ return
246353 }
247− } catch (error) {
248− console.error("Error deleting todo:", error)
249− return { isSuccess: false, message: "Failed to delete todo" }
354+
355+ setIsSubmitting(true)
356+ setErrors({})
357+
358+ try {
359+ const result: SubmissionResult = await submitForm()
360+ handleSuccess(result)
361+ } catch (error: unknown) {
362+ handleError(error)
363+ } finally {
364+ setIsSubmitting(false)
365+ }
250366 }
251−}
252−```
367+ ```
253368
254−#### Services
369+- Example with implicit return:
255370
256−- When importing services, use `@/lib/services`
257−- Name files like `example-service.ts`
258−- All services should go in the `lib/services` folder
259−- Services handle complex business logic that would otherwise make server actions too large
260−- Services should be pure functions that take inputs and return outputs
261−- Services should not directly handle HTTP requests or database operations
262−- Use services for external API integrations, complex calculations, and domain-specific logic
263−- Follow the data flow: Components → Actions → Services
264−- Export functions using named exports
371+ ```tsx
372+ interface MyComponentProps {
373+ title: string
374+ children: React.ReactNode
375+ }
265376
266−Example of a service:
377+ const MyComponent = ({ title, children }: MyComponentProps) => (
378+ <div>
379+ {title}
380+ {children}
381+ </div>
382+ )
267383
268−`lib/services/grammar-correction.ts`
384+ export default MyComponent
385+ ```
269386
270−```ts
271−import OpenAI from "openai"
272−import { getCompletion } from "@/lib/services/open-ai"
387+- Example with explicit return (when logic is present):
273388
274−const SYSTEM_MESSAGE: string = `You are a grammar correction assistant...`
389+ ```tsx
390+ interface MyComponentProps {
391+ title: string
392+ children: React.ReactNode
393+ }
275394
276−export const getPunchyText = async (
277− userMessage: string
278−): Promise<string | undefined> => {
279− const completion: OpenAI.Chat.Completions.ChatCompletion =
280− await getCompletion(userMessage, {
281− systemMessage: SYSTEM_MESSAGE,
282− maxTokens: 280,
283− temperature: 0.7
284− })
395+ const MyComponent = ({ title, children }: MyComponentProps) => {
396+ const processedTitle = title.toUpperCase()
285397
286− return completion?.choices?.[0]?.message?.content?.trim()
287−}
398+ return (
399+ <div>
400+ {processedTitle}
401+ {children}
402+ </div>
403+ )
404+ }
405+
406+ export default MyComponent
407+ ```
408+
409+- Normal components that are not Next.js pages/layouts should be exported
410+ using export const pattern
411+- Example with implicit return:
412+
413+ ```tsx
414+ interface NotAPageOrLayoutComponentProps {
415+ title: string
416+ children: React.ReactNode
417+ }
418+
419+ export const NotAPageOrLayoutComponent = ({
420+ title,
421+ children
422+ }: NotAPageOrLayoutComponentProps) => (
423+ <div>
424+ {title}
425+ {children}
426+ </div>
427+ )
428+ ```
429+
430+### Component Granularity & Organization
431+
432+- Break down large components into smaller, focused components for better maintainability
433+- When a component contains multiple logical sections (e.g., Card with CardHeader + CardContent), extract each section into separate components
434+- Create dedicated folders for related component groups:
435+ - Use kebab-case folder names matching the main component concept
436+ - Place related sub-components within the same folder using kebab-case file names
437+ - Example structure: `component-name/component-name-header.tsx`, `component-name/component-name-content.tsx`
438+- Each sub-component should have a single, clear responsibility
439+- Maintain the parent component as a composition wrapper that orchestrates child components
440+- Follow this pattern when refactoring existing components or creating new feature components
441+
442+### Advanced Component Architecture Patterns
443+
444+#### Pure Functions and Constants Organization
445+
446+- **Pure functions** (no side effects, deterministic output) must be extracted outside components:
447+ - Place above the component definition
448+ - Examples: `getGreeting()`, `getMembershipBadgeColor()`, `formatDate()`
449+- **Constants and static data** must be moved outside components:
450+ - Place after imports and interfaces, before pure functions
451+ - Use SCREAMING_SNAKE_CASE for naming (e.g., `TEMPLATE_FEATURES`, `TECH_STACK`) constants
452+ - **Always explicitly type constants** with appropriate type annotations
453+ - Examples: `const API_URL: string = "..."`, `const MAX_RETRIES: number = 3`
454+ - Use `as const` for immutable values when type inference is sufficient
455+ - Group related constants together
456+
457+#### Custom Hooks Organization
458+
459+- **Custom hooks** must be extracted to separate files in the `/hooks/` directory:
460+ - Use kebab-case file naming: `use-scroll-detection.ts`, `use-local-storage.ts`
461+ - Start hook names with `use` prefix following React conventions
462+ - Place hooks in `/hooks/` folder at project root level
463+ - Export hooks using named exports: `export const useScrollDetection = () => {}`
464+ - **Always explicitly type hook return values** and parameters
465+ - Examples: `useScrollDetection(): boolean`, `useLocalStorage<T>(key: string): [T, (value: T) => void]`
466+ - Group related hooks in the same file only if they're tightly coupled
467+
468+#### Whitespace and Formatting Rules
469+
470+- **Component variable organization**: Maintain consistent whitespace between different types of declarations:
471+ - Add a blank line between React state declarations and custom hook calls
472+ - Add a blank line between custom hook calls and other variable declarations
473+ - Example:
474+
475+ ```tsx
476+ const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
477+ const [isVisible, setIsVisible] = useState<boolean>(true)
478+
479+ const isScrolled: boolean = useScrollDetection()
480+ const userData: UserData = useUserData()
481+
482+ const processedData: ProcessedData = processUserData(userData)
483+ ```
484+
485+#### Nested Component Structure for Complex Components
486+
487+When a component has multiple distinct sections, create nested folder structure:
488+
288489 ```
490+dashboard-welcome/
491+├── dashboard-welcome.tsx // Main orchestrator component
492+├── greeting.tsx // Self-contained greeting section
493+├── whats-included/ // Folder for multi-part section
494+│ ├── whats-included.tsx // Section orchestrator
495+│ ├── whats-included-title.tsx // Title sub-component
496+│ └── whats-included-features.tsx // Features list sub-component
497+├── core-technologies/ // Folder for multi-part section
498+│ ├── core-technologies.tsx // Section orchestrator
499+│ ├── core-technologies-title.tsx // Title sub-component
500+│ └── core-technologies-list.tsx // Tech list sub-component
501+└── get-started/ // Folder for multi-part section
502+ ├── get-started.tsx // Section orchestrator
503+ ├── get-started-title.tsx // Title sub-component
504+ ├── get-started-features.tsx // Features grid sub-component
505+ └── get-started-feature-2.tsx // Individual feature card
506+```
507+
508+#### Component Organization Rules
509+
510+1. **Main orchestrator**: Composition only, minimal logic, imports and renders sub-components
511+2. **Section orchestrators**: Handle section-specific logic, render related sub-components
512+3. **Leaf components**: Single responsibility, pure presentation, accept props only
513+4. **Shared constants**: Extract to file level, use proper naming conventions
514+5. **Pure functions**: Extract above component definitions, properly typed
515+6. **File structure**: Mirror logical component hierarchy in folder structure
516+
517+### Application Guidelines
518+
519+1. Apply these standards to all new code and when refactoring existing code
520+2. When making any code changes, ensure they conform to these guidelines
521+3. If existing code doesn't follow these standards, update it to comply when modifying those files
522+4. Use these standards as a checklist when reviewing code changes
523+5. Prefer extracting helper functions over writing long, complex functions
524+6. Always prioritize code readability and maintainability
525+
526+## Database Schema
527+
528+### Current Tables
529+
530+- **Profiles**: User profiles with Stripe integration and membership tiers
531+
532+### Schema Location
533+
534+- Schemas: `/db/schema/`
535+- Migrations: `/db/migrations/`
536+- Database connection: `/db/db.ts`
537+
538+## Testing and Deployment
539+
540+- The project uses Vercel for deployment
541+- No specific test framework is configured - check with user if testing is needed
542+- Always run `yarn build` and `yarn type-check` before considering work complete
543+
