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.