Architecture
Project architecture overview, file organization, and code structure patterns.
Architecture
Project Structure
Next.js App Router Architecture
The project follows Next.js 15's App Router convention with a clear separation of concerns:
app/
├── (app)/ # Protected routes group
│ ├── layout.tsx # Authenticated user layout with sidebar
│ ├── dashboard/ # Dashboard and analytics
│ ├── inventory/ # Inventory management pages
│ ├── products/ # Product catalog management
│ ├── purchasing/ # Purchase order management
│ ├── sales/ # Sales and order management
│ ├── warehouse/ # Warehouse operations
│ ├── analytics/ # Reports and data visualization
│ └── admin/ # System administration
├── api/ # API routes and endpoints
│ ├── auth/ # Authentication endpoints
│ ├── inventory/ # Inventory API routes
│ ├── products/ # Product management API
│ ├── orders/ # Order management API
│ └── analytics/ # Analytics and reporting API
├── auth/ # Authentication pages (login, register)
├── layout.tsx # Root application layout
├── page.tsx # Landing/home page
├── loading.tsx # Global loading UI
├── error.tsx # Global error boundary
├── not-found.tsx # 404 page
└── globals.css # Global styles and CSS variables
Component Architecture
components/
├── ui/ # Base UI components (shadcn/ui)
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── form.tsx
│ ├── table.tsx
│ └── ...
├── layout/ # Layout components
│ ├── navbar.tsx
│ ├── sidebar.tsx
│ └── footer.tsx
├── dashboard/ # Dashboard-specific components
│ ├── stats-cards.tsx
│ ├── recent-orders.tsx
│ └── analytics-chart.tsx
├── inventory/ # Inventory management components
│ ├── inventory-table.tsx
│ ├── stock-level-badge.tsx
│ └── reorder-alert.tsx
├── products/ # Product management components
│ ├── product-form.tsx
│ ├── product-card.tsx
│ └── category-selector.tsx
├── barcode/ # Barcode scanning components
│ ├── barcode-scanner.tsx
│ └── scanner-controls.tsx
├── auth/ # Authentication components
│ ├── login-form.tsx
│ ├── register-form.tsx
│ └── auth-guard.tsx
└── shared/ # Shared utility components
├── data-table.tsx
├── loading-spinner.tsx
├── error-boundary.tsx
└── confirmation-dialog.tsx
Service Layer Architecture
lib/
├── supabase/ # Database client configuration
│ ├── server.ts # Server-side Supabase client
│ ├── client.ts # Client-side Supabase client
│ └── middleware.ts # Authentication middleware
├── services/ # Business logic services
│ ├── dashboard.ts # Dashboard data aggregation
│ ├── inventory.ts # Inventory operations
│ ├── products.ts # Product management
│ ├── orders.ts # Order processing
│ ├── analytics.ts # Analytics and reporting
│ └── warehouse.ts # Warehouse operations
├── actions/ # Server actions
│ ├── auth.ts # Authentication actions
│ ├── inventory.ts # Inventory actions
│ └── products.ts # Product actions
├── hooks/ # Custom React hooks
│ ├── use-current-user.ts
│ ├── use-inventory.ts
│ └── use-products.ts
├── types/ # TypeScript type definitions
│ ├── database.ts # Database schema types
│ ├── api.ts # API response types
│ └── auth.ts # Authentication types
├── validations/ # Zod validation schemas
│ ├── product.ts
│ ├── inventory.ts
│ └── auth.ts
├── utils/ # Utility functions
│ ├── cn.ts # Class name utility
│ ├── format.ts # Formatting utilities
│ └── date.ts # Date utilities
└── constants/ # Application constants
├── routes.ts # Route definitions
├── permissions.ts # Permission constants
└── status.ts # Status constants
Code Organization Principles
File Naming Conventions
Components
// Use PascalCase for component files
ProductCard.tsx // ✅ Good
product-card.tsx // ❌ Avoid
productCard.tsx // ❌ Avoid
// Use kebab-case for pages and layouts
page.tsx // ✅ Good (Next.js convention)
layout.tsx // ✅ Good (Next.js convention)
inventory-list/page.tsx // ✅ Good (nested routes)
API Routes
// Use kebab-case for API route files
route.ts // ✅ Good (Next.js convention)
products/route.ts // ✅ Good
purchase-orders/route.ts // ✅ Good
Services and Utilities
// Use camelCase for service files
inventoryService.ts // ✅ Good
authUtils.ts // ✅ Good
databaseHelpers.ts // ✅ Good
// Or kebab-case for consistency
inventory-service.ts // ✅ Also good
auth-utils.ts // ✅ Also good
database-helpers.ts // ✅ Also good
Import Organization
Follow a consistent import order for better readability:
// 1. React and Next.js imports
import React, { useState, useEffect } from 'react'
import { NextRequest, NextResponse } from 'next/server'
import { redirect } from 'next/navigation'
// 2. Third-party libraries
import { createClient } from '@supabase/supabase-js'
import { z } from 'zod'
import { clsx } from 'clsx'
// 3. Internal utilities and services
import { createClient } from '@/lib/supabase/server'
import { getInventoryStats } from '@/lib/services/inventory'
import { formatCurrency } from '@/lib/utils/format'
// 4. UI Components (external first, then internal)
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { InventoryTable } from '@/components/inventory/inventory-table'
// 5. Types and interfaces
import type { Database } from '@/lib/types/database'
import type { InventoryItem, Product } from '@/lib/types/inventory'
// 6. Constants and configurations
import { ROUTES } from '@/lib/constants/routes'
import { PERMISSIONS } from '@/lib/constants/permissions'
Code Style Guidelines
TypeScript Best Practices
// Use interfaces for object shapes
interface ProductFormData {
name: string
sku: string
category_id: string
cost_price: number
selling_price: number
description?: string
}
// Use type aliases for unions and primitives
type ProductStatus = 'active' | 'inactive' | 'discontinued'
type ProductId = string
// Use const assertions for immutable data
const PRODUCT_CATEGORIES = [
'electronics',
'clothing',
'books',
'home'
] as const
type ProductCategory = typeof PRODUCT_CATEGORIES[number]
// Prefer async/await over promises
export async function getProducts(): Promise<Product[]> {
try {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
.select('*')
.eq('is_active', true)
.order('name')
if (error) throw error
return data || []
} catch (error) {
console.error('Error fetching products:', error)
return []
}
}
Component Patterns
// Server Component (default - no 'use client')
export default async function ProductsPage() {
const products = await getProducts()
return (
<div className="space-y-6">
<PageHeader title="Products" />
<ProductTable products={products} />
</div>
)
}
// Client Component (when needed)
'use client'
interface InteractiveComponentProps {
initialData: any[]
}
export function InteractiveComponent({ initialData }: InteractiveComponentProps) {
const [data, setData] = useState(initialData)
// Client-side logic here
return <div>{/* Interactive content */}</div>
}
Architectural Patterns
Service Layer Pattern
Centralize business logic in service modules:
// lib/services/inventory.ts
export class InventoryService {
private supabase: SupabaseClient
constructor(supabase: SupabaseClient) {
this.supabase = supabase
}
async getInventoryStats(): Promise<InventoryStats> {
// Complex business logic here
}
async updateStockLevel(productId: string, newLevel: number): Promise<void> {
// Stock update logic with validation
}
async checkReorderPoints(): Promise<Product[]> {
// Reorder point checking logic
}
}
// Usage in API route or Server Component
const inventoryService = new InventoryService(supabase)
const stats = await inventoryService.getInventoryStats()
Repository Pattern
Abstract database operations:
// lib/repositories/product-repository.ts
export class ProductRepository {
constructor(private supabase: SupabaseClient) {}
async findById(id: string): Promise<Product | null> {
const { data } = await this.supabase
.from('products')
.select('*')
.eq('id', id)
.single()
return data
}
async findByCategory(categoryId: string): Promise<Product[]> {
const { data } = await this.supabase
.from('products')
.select('*')
.eq('category_id', categoryId)
.eq('is_active', true)
return data || []
}
async create(product: CreateProductInput): Promise<Product> {
const { data } = await this.supabase
.from('products')
.insert(product)
.select()
.single()
return data
}
}
Error Handling Pattern
Consistent error handling across the application:
// lib/utils/error-handler.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code?: string
) {
super(message)
this.name = 'AppError'
}
}
export function handleApiError(error: unknown): Response {
if (error instanceof AppError) {
return Response.json(
{ error: error.message, code: error.code },
{ status: error.statusCode }
)
}
console.error('Unexpected error:', error)
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
Validation Pattern
Centralized validation with Zod:
// lib/validations/product.ts
import { z } from 'zod'
export const CreateProductSchema = z.object({
name: z.string().min(1, 'Product name is required'),
sku: z.string().min(1, 'SKU is required'),
category_id: z.string().uuid('Invalid category ID'),
cost_price: z.number().positive('Cost price must be positive'),
selling_price: z.number().positive('Selling price must be positive'),
description: z.string().optional(),
barcode: z.string().optional(),
})
export const UpdateProductSchema = CreateProductSchema.partial()
export type CreateProductInput = z.infer<typeof CreateProductSchema>
export type UpdateProductInput = z.infer<typeof UpdateProductSchema>
This architecture provides a scalable, maintainable foundation for the application with clear separation of concerns and consistent patterns.