Code Standards

Coding guidelines, style conventions, and best practices for Smart Shelf development.

Code Standards

This document outlines the coding standards and best practices for Smart Shelf development. Following these guidelines ensures code consistency, maintainability, and quality across the project.

General Principles

1. Consistency

  • Follow established patterns throughout the codebase
  • Use consistent naming conventions
  • Maintain uniform code structure

2. Readability

  • Write self-documenting code
  • Use descriptive variable and function names
  • Add comments for complex logic

3. Maintainability

  • Keep functions and components small and focused
  • Avoid deep nesting
  • Use TypeScript for type safety

4. Performance

  • Optimize for bundle size
  • Use React best practices (memoization, lazy loading)
  • Minimize re-renders

TypeScript Guidelines

Type Definitions

Always use explicit types for function parameters and return values:

// ✅ Good - Explicit types
export async function createProduct(
  productData: CreateProductRequest
): Promise<Product> {
  // Implementation
}

// ❌ Bad - Implicit any types
export async function createProduct(productData) {
  // Implementation
}

Interface vs Type

Use interfaces for object shapes and types for unions or computed types:

// ✅ Good - Interface for object shapes
interface ProductFilters {
  category?: string
  search?: string
  page: number
  limit: number
  sortBy?: 'name' | 'created_at' | 'price'
  sortOrder?: 'asc' | 'desc'
}

// ✅ Good - Type for unions
type OrderStatus = 'draft' | 'sent' | 'confirmed' | 'received' | 'closed'

// ✅ Good - Type for computed types
type ProductWithInventory = Product & {
  inventory: Inventory[]
}

Enums vs Union Types

Prefer union types over enums for string constants:

// ✅ Good - Union type
type UserRole = 'admin' | 'manager' | 'staff' | 'readonly'

// ❌ Avoid - Enum (unless you need reverse mapping)
enum UserRole {
  ADMIN = 'admin',
  MANAGER = 'manager',
  STAFF = 'staff',
  READONLY = 'readonly'
}

Error Handling

Use Result pattern for operations that can fail:

// Result type definition
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E }

// ✅ Good - Result pattern
export async function fetchProduct(id: string): Promise<Result<Product>> {
  try {
    const product = await productService.getById(id)
    return { success: true, data: product }
  } catch (error) {
    return { success: false, error: error as Error }
  }
}

// Usage
const result = await fetchProduct('123')
if (result.success) {
  // TypeScript knows result.data is Product
  console.log(result.data.name)
} else {
  // TypeScript knows result.error is Error
  console.error(result.error.message)
}

Utility Types

Leverage TypeScript utility types for better type safety:

// ✅ Good - Using utility types
type CreateProductRequest = Omit<Product, 'id' | 'created_at' | 'updated_at'>
type UpdateProductRequest = Partial<Pick<Product, 'name' | 'price' | 'description'>>

// Product form props
interface ProductFormProps {
  product?: Product
  onSubmit: (data: CreateProductRequest) => Promise<void>
  onCancel: () => void
}

React Component Guidelines

Component Structure

Follow a consistent component structure:

// components/products/product-card.tsx
import { memo, useCallback } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import type { Product } from '@/lib/types/database'

// Props interface
interface ProductCardProps {
  product: Product
  onEdit?: (product: Product) => void
  onDelete?: (productId: string) => void
  className?: string
}

// Component with memo for performance
export const ProductCard = memo(function ProductCard({
  product,
  onEdit,
  onDelete,
  className
}: ProductCardProps) {
  // Event handlers
  const handleEdit = useCallback(() => {
    onEdit?.(product)
  }, [onEdit, product])

  const handleDelete = useCallback(() => {
    onDelete?.(product.id)
  }, [onDelete, product.id])

  // Render
  return (
    <Card className={className}>
      <CardHeader>
        <CardTitle>{product.name}</CardTitle>
        <Badge variant="outline">{product.sku}</Badge>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground mb-4">
          {product.description}
        </p>
        <div className="flex justify-between items-center">
          <span className="font-semibold">${product.price}</span>
          <div className="space-x-2">
            {onEdit && (
              <Button size="sm" variant="outline" onClick={handleEdit}>
                Edit
              </Button>
            )}
            {onDelete && (
              <Button size="sm" variant="destructive" onClick={handleDelete}>
                Delete
              </Button>
            )}
          </div>
        </div>
      </CardContent>
    </Card>
  )
})

Naming Conventions

// ✅ Good - PascalCase for components
export const ProductCard = () => {}
export const InventoryTable = () => {}

// ✅ Good - camelCase for functions and variables
const handleSubmit = () => {}
const isLoading = true
const productData = {}

// ✅ Good - SCREAMING_SNAKE_CASE for constants
const MAX_PRODUCTS_PER_PAGE = 50
const API_BASE_URL = 'https://api.example.com'

// ✅ Good - kebab-case for file names
// product-card.tsx
// inventory-table.tsx
// user-profile-form.tsx

Props and State

// ✅ Good - Destructure props
function ProductCard({ product, onEdit, className }: ProductCardProps) {
  // Component logic
}

// ❌ Bad - Using props object
function ProductCard(props: ProductCardProps) {
  return <div>{props.product.name}</div>
}

// ✅ Good - Initialize state with proper types
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)

Custom Hooks

Structure custom hooks for reusability:

// hooks/use-products.ts
export function useProducts(filters: ProductFilters) {
  const [products, setProducts] = useState<Product[]>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  const fetchProducts = useCallback(async () => {
    setLoading(true)
    setError(null)
    
    try {
      const result = await productService.getAll(filters)
      if (result.success) {
        setProducts(result.data)
      } else {
        setError(result.error)
      }
    } catch (err) {
      setError(err as Error)
    } finally {
      setLoading(false)
    }
  }, [filters])

  useEffect(() => {
    fetchProducts()
  }, [fetchProducts])

  const refetch = useCallback(() => {
    fetchProducts()
  }, [fetchProducts])

  return { 
    products, 
    loading, 
    error, 
    refetch 
  }
}

CSS and Styling Guidelines

Tailwind CSS Best Practices

// ✅ Good - Consistent spacing scale
<div className="p-4 m-2 space-y-4">

// ✅ Good - Semantic color usage
<Button variant="destructive">Delete</Button>
<Badge variant="secondary">Draft</Badge>

// ✅ Good - Responsive design
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">

// ✅ Good - Logical grouping
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow">

// ❌ Bad - Arbitrary values without reason
<div className="p-[13px] m-[7px]">

// ❌ Bad - Inline styles when Tailwind exists
<div style={{ padding: '16px', margin: '8px' }}>

Component Variants

Use consistent variant patterns:

// ✅ Good - Variant system
interface ButtonProps {
  variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost'
  size?: 'default' | 'sm' | 'lg' | 'icon'
}

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input hover:bg-accent hover:text-accent-foreground",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

Custom CSS Organization

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Component-specific styles */
@layer components {
  .btn-primary {
    @apply bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors;
  }
  
  .card-shadow {
    @apply shadow-sm hover:shadow-md transition-shadow;
  }
}

/* Utility classes */
@layer utilities {
  .text-shadow {
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
}

API Development Guidelines

Route Handler Structure

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { productService } from '@/lib/services/product'
import { withAuth } from '@/lib/middleware/auth'
import { withErrorHandler } from '@/lib/middleware/error-handler'

// Validation schema
const createProductSchema = z.object({
  name: z.string().min(1, 'Name is required').max(255, 'Name is too long'),
  sku: z.string().min(1, 'SKU is required').max(100, 'SKU is too long'),
  price: z.number().positive('Price must be positive'),
  category_id: z.string().uuid('Invalid category ID').optional(),
  description: z.string().max(1000, 'Description is too long').optional(),
})

// GET endpoint
export const GET = withAuth(
  withErrorHandler(async (request: NextRequest) => {
    const { searchParams } = new URL(request.url)
    
    // Parse query parameters
    const page = parseInt(searchParams.get('page') || '1')
    const limit = parseInt(searchParams.get('limit') || '20')
    const search = searchParams.get('search') || ''
    
    // Validate parameters
    if (page < 1 || limit < 1 || limit > 100) {
      return NextResponse.json(
        { success: false, error: { code: 'INVALID_PARAMS', message: 'Invalid pagination parameters' } },
        { status: 400 }
      )
    }
    
    // Fetch data
    const result = await productService.getAll({ page, limit, search })
    
    return NextResponse.json({
      success: true,
      data: result.products,
      meta: {
        page,
        limit,
        total: result.total,
        hasNext: page * limit < result.total,
        hasPrev: page > 1
      }
    })
  })
)

// POST endpoint
export const POST = withAuth(
  withErrorHandler(async (request: NextRequest) => {
    const body = await request.json()
    const validatedData = createProductSchema.parse(body)
    
    const product = await productService.create(validatedData)
    
    return NextResponse.json(
      { success: true, data: product },
      { status: 201 }
    )
  })
)

Error Response Format

// lib/types/api.ts
export interface ApiResponse<T = any> {
  success: boolean
  data?: T
  error?: {
    code: string
    message: string
    details?: Record<string, string[]> // For validation errors
  }
  meta?: {
    page?: number
    limit?: number
    total?: number
    hasNext?: boolean
    hasPrev?: boolean
  }
}

// Success response
return NextResponse.json({
  success: true,
  data: products,
  meta: { page: 1, limit: 20, total: 150 }
})

// Error response
return NextResponse.json({
  success: false,
  error: {
    code: 'VALIDATION_ERROR',
    message: 'Invalid input data',
    details: {
      name: ['Name is required'],
      price: ['Price must be a positive number']
    }
  }
}, { status: 400 })

File Organization

Directory Structure

src/
├── app/                    # Next.js app directory
│   ├── (app)/             # Route groups
│   ├── api/               # API routes
│   └── globals.css        # Global styles
├── components/            # React components
│   ├── ui/               # Base UI components
│   ├── forms/            # Form components
│   ├── layout/           # Layout components
│   └── features/         # Feature-specific components
├── lib/                  # Utilities and services
│   ├── actions/          # Server actions
│   ├── hooks/            # Custom hooks
│   ├── services/         # Business logic
│   ├── types/            # Type definitions
│   ├── utils/            # Helper functions
│   └── validations/      # Zod schemas
├── public/               # Static assets
└── tests/               # Test files

Import Organization

// ✅ Good - Import order
// 1. React and Next.js imports
import { useState, useCallback } from 'react'
import { NextRequest, NextResponse } from 'next/server'

// 2. Third-party libraries
import { z } from 'zod'
import { toast } from 'sonner'

// 3. Internal imports (absolute paths)
import { Button } from '@/components/ui/button'
import { productService } from '@/lib/services/product'
import type { Product } from '@/lib/types/database'

// 4. Relative imports
import './styles.css'

Code Documentation

Function Documentation

/**
 * Creates a new product with the provided data
 * 
 * @param productData - The product information to create
 * @returns Promise resolving to the created product
 * @throws {ValidationError} When product data is invalid
 * @throws {DatabaseError} When database operation fails
 * 
 * @example
 * ```typescript
 * const product = await createProduct({
 *   name: 'New Product',
 *   sku: 'NP-001',
 *   price: 29.99,
 *   category_id: 'cat-123'
 * })
 * ```
 */
export async function createProduct(
  productData: CreateProductRequest
): Promise<Product> {
  // Validate input data
  const validated = createProductSchema.parse(productData)
  
  // Create product in database
  const product = await db.product.create({
    data: validated
  })
  
  return product
}

Complex Logic Comments

// ✅ Good - Explain the why, not the what
export function calculateInventoryMetrics(inventory: Inventory[]) {
  // Group inventory by warehouse to calculate per-location metrics
  // This enables warehouse-specific reporting and alerts
  const warehouseGroups = inventory.reduce((groups, item) => {
    const key = item.warehouse_id
    groups[key] = groups[key] || []
    groups[key].push(item)
    return groups
  }, {} as Record<string, Inventory[]>)
  
  // Calculate total value using cost price instead of selling price
  // This gives us the actual investment value for financial reporting
  const totalValue = inventory.reduce((sum, item) => {
    return sum + (item.quantity_on_hand * item.product.cost_price)
  }, 0)
  
  return { warehouseGroups, totalValue }
}

Performance Guidelines

React Performance

// ✅ Good - Memoize expensive calculations
const expensiveValue = useMemo(() => {
  return products.reduce((total, product) => {
    return total + (product.quantity * product.price)
  }, 0)
}, [products])

// ✅ Good - Memoize callbacks to prevent re-renders
const handleProductSelect = useCallback((productId: string) => {
  onProductSelect(productId)
}, [onProductSelect])

// ✅ Good - Lazy load heavy components
const ReportGenerator = lazy(() => import('./ReportGenerator'))

// Usage with Suspense
<Suspense fallback={<ReportSkeleton />}>
  <ReportGenerator />
</Suspense>

Bundle Optimization

// ✅ Good - Use dynamic imports for code splitting
const DynamicChart = dynamic(() => import('./Chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false // Disable SSR for client-only components
})

// ✅ Good - Tree-shake imports
import { format } from 'date-fns'
// ❌ Bad - Imports entire library
import * as dateFns from 'date-fns'

Linting and Formatting

ESLint Configuration

Key rules to follow:

{
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-explicit-any": "warn",
    "react-hooks/exhaustive-deps": "error",
    "react/jsx-key": "error",
    "prefer-const": "error",
    "no-console": "warn"
  }
}

Prettier Configuration

{
  "semi": false,
  "trailingComma": "es5",
  "singleQuote": true,
  "tabWidth": 2,
  "useTabs": false,
  "printWidth": 80
}

Following these code standards ensures a maintainable, performant, and consistent codebase that all contributors can work with effectively.