Skip to main content

Directory Overview

The Zarna frontend follows a structured, modular organization:
zarna-frontend/
├── src/                      # Source code
│   ├── components/           # React components
│   │   ├── ui/              # shadcn/ui components (auto-generated)
│   │   ├── layout/          # Layout components
│   │   ├── features/        # Feature-specific components
│   │   └── generated/       # Generated components
│   │
│   ├── context/             # React Context providers
│   │   ├── AuthContext.tsx
│   │   └── CompanyContext.tsx
│   │
│   ├── hooks/               # Custom React hooks
│   │   ├── use-mobile.ts
│   │   ├── use-toast.ts
│   │   └── use-auth.ts
│   │
│   ├── lib/                 # Utility functions
│   │   └── utils.ts
│   │
│   ├── services/            # API integration
│   │   └── api.ts
│   │
│   ├── utils/               # Helper functions
│   │
│   ├── App.tsx              # Main application component
│   ├── main.tsx             # Application entry point
│   └── index.css            # Global styles

├── public/                   # Static assets
│   ├── images/
│   ├── fonts/
│   └── icons/

├── components.json           # shadcn/ui configuration
├── tsconfig.json            # TypeScript configuration
├── vite.config.ts           # Vite configuration
├── tailwind.config.js       # Tailwind CSS configuration
├── postcss.config.mjs       # PostCSS configuration
├── package.json             # Dependencies and scripts
├── .eslintrc.json          # ESLint configuration
├── .prettierrc             # Prettier configuration
└── README.md               # Project documentation

Directory Guidelines

src/components/

Component organization by purpose:

ui/ - shadcn/ui Components

🚨 DO NOT MANUALLY EDIT - Generated by shadcn CLI
components/ui/
├── button.tsx               # Button component
├── card.tsx                 # Card component
├── dialog.tsx               # Dialog/modal component
├── dropdown-menu.tsx        # Dropdown menu component
├── form.tsx                 # Form component
├── input.tsx                # Input component
├── select.tsx               # Select component
├── table.tsx                # Table component
├── toast.tsx                # Toast notification component
└── ...                      # 40+ more components
Adding components:
npx shadcn@latest add button
npx shadcn@latest add dialog

layout/ - Layout Components

Reusable layout components:
components/layout/
├── Layout.tsx               # Main layout wrapper
├── Header.tsx               # Application header
├── Sidebar.tsx              # Navigation sidebar
├── Footer.tsx               # Application footer
└── Container.tsx            # Content container

features/ - Feature Components

Domain-specific components organized by feature:
components/features/
├── auth/
│   ├── LoginForm.tsx
│   ├── RegisterForm.tsx
│   └── ResetPasswordForm.tsx

├── companies/
│   ├── CompanyCard.tsx
│   ├── CompanyList.tsx
│   ├── CompanyForm.tsx
│   └── CompanyDetails.tsx

├── deals/
│   ├── DealPipeline.tsx
│   ├── DealCard.tsx
│   └── DealForm.tsx

└── files/
    ├── FileUploader.tsx
    ├── FileViewer.tsx
    └── PDFViewer.tsx

src/context/

React Context for global state:
// AuthContext.tsx
import { createContext, useContext, useState } from 'react'

interface AuthContextType {
  user: User | null
  login: (credentials: Credentials) => Promise<void>
  logout: () => void
  isAuthenticated: boolean
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider({ children }) {
  // Context implementation
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) throw new Error('useAuth must be used within AuthProvider')
  return context
}
Current Contexts:
  • AuthContext - Authentication state
  • CompanyContext - Current company selection

src/hooks/

Custom React hooks for reusable logic:
hooks/
├── use-auth.ts              # Authentication hook
├── use-mobile.ts            # Mobile detection hook
├── use-toast.ts             # Toast notifications hook
├── use-debounce.ts          # Debounce hook
├── use-local-storage.ts     # LocalStorage hook
└── use-api.ts               # API call hook
Example hook:
// hooks/use-mobile.ts
import { useEffect, useState } from "react"

const MOBILE_BREAKPOINT = 768

export function useMobile() {
  const [isMobile, setIsMobile] = useState<boolean>(false)

  useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
    const onChange = () => setIsMobile(mql.matches)

    mql.addEventListener("change", onChange)
    setIsMobile(mql.matches)

    return () => mql.removeEventListener("change", onChange)
  }, [])

  return isMobile
}

src/lib/

Pure utility functions (no React dependencies):
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount)
}

export function truncate(str: string, length: number): string {
  return str.length > length ? str.substring(0, length) + '...' : str
}

src/services/

API integration and external service calls:
// services/api.ts
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'

export async function fetchCompanies() {
  const response = await fetch(`${API_BASE_URL}/api/companies`)
  return response.json()
}

export async function createCompany(data: CompanyCreate) {
  const response = await fetch(`${API_BASE_URL}/api/companies`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  })
  return response.json()
}

public/

Static assets served as-is:
public/
├── images/
│   ├── logo.svg
│   ├── hero.png
│   └── avatars/

├── fonts/
│   └── custom-font.woff2

└── icons/
    ├── favicon.ico
    └── manifest.json
Usage:
<img src="/images/logo.svg" alt="Logo" />

File Naming Conventions

Components

  • PascalCase: ComponentName.tsx
  • Examples: Button.tsx, UserProfile.tsx, DealPipeline.tsx

Hooks

  • kebab-case: use-hook-name.ts
  • Examples: use-auth.ts, use-mobile.ts, use-debounce.ts

Utilities

  • kebab-case: util-name.ts
  • Examples: format-date.ts, api-client.ts, validation.ts

Context

  • PascalCase: ContextName.tsx
  • Examples: AuthContext.tsx, ThemeContext.tsx

Types

  • PascalCase: TypeName.ts or inline in component files
  • Examples: types.ts, api-types.ts

Import Organization

Order imports for consistency:
// 1. External dependencies
import * as React from "react"
import { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"

// 2. Internal components
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"

// 3. Hooks
import { useAuth } from "@/hooks/use-auth"
import { useMobile } from "@/hooks/use-mobile"

// 4. Utils
import { cn } from "@/lib/utils"
import { formatDate } from "@/utils/format"

// 5. Types
import type { User, Company } from "@/types"

// 6. Styles (if any)
import "./styles.css"

Path Aliases

Configured in tsconfig.json:
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
Usage:
// ✅ Good - using alias
import { Button } from "@/components/ui/button"
import { useAuth } from "@/hooks/use-auth"

// ❌ Bad - relative imports
import { Button } from "../../../components/ui/button"

Configuration Files

tsconfig.json

TypeScript configuration with strict mode:
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

vite.config.ts

Vite build configuration:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true
      }
    }
  }
})

tailwind.config.js

Tailwind CSS configuration:
/** @type {import('tailwindcss').Config} */
export default {
  darkMode: ["class"],
  content: [
    "./index.html",
    "./src/**/*.{ts,tsx}"
  ],
  theme: {
    extend: {
      colors: {
        border: "hsl(var(--border))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        // ... theme colors
      }
    }
  },
  plugins: [require("tailwindcss-animate")]
}

components.json

shadcn/ui configuration:
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}

Best Practices

Component Organization

Each component should have a single responsibility. If a component does too much, split it.
Group related components by feature rather than type.
Move common logic to custom hooks or utility functions.

Naming Conventions

  • Be descriptive: UserProfileCard > Card
  • Be consistent: Always use PascalCase for components
  • Prefix hooks: Always start with use (e.g., useAuth)
  • Suffix contexts: End with Context (e.g., AuthContext)

Next Steps