Skip to content

Frontend Architecture

The Vulcan web frontend is built with React 19, TypeScript, and Vite, following a feature-based architecture.

Technology Stack

TechnologyPurpose
React 19UI framework
TypeScriptType safety
ViteBuild tooling
TanStack QueryServer state management
Redux ToolkitClient state management
MUI (Material UI)Component library
React RouterNavigation
VitestUnit testing
PlaywrightE2E testing

Directory Structure

vulcan-web/
├── src/
│   ├── api/             # Generated API clients
│   ├── components/      # Shared UI components
│   │   ├── common/      # Buttons, Cards, Dialogs
│   │   ├── layout/      # AppShell, Navigation
│   │   └── feedback/    # Toast, Loading, Error
│   ├── features/        # Feature modules
│   │   ├── dashboard/
│   │   ├── leads/
│   │   ├── quotation/
│   │   └── ...
│   ├── hooks/           # Shared custom hooks
│   ├── lib/             # Pure utility functions
│   ├── pages/           # Route entry points
│   ├── routes/          # Route configuration
│   ├── store/           # Redux store
│   ├── styles/          # Global styles + theme
│   └── types/           # Shared TypeScript types
├── tests/               # E2E tests
├── vite.config.ts
└── package.json

Feature Module Structure

Each feature is a self-contained module with colocated components, hooks, and API calls.

features/leads/
├── components/
│   ├── LeadList.tsx
│   ├── LeadDetail.tsx
│   ├── LeadForm.tsx
│   └── index.ts
├── hooks/
│   ├── useLeads.ts
│   └── useCreateLead.ts
├── api/
│   └── leadsApi.ts
├── types.ts
└── index.ts

State Management

Server State (TanStack Query)

For data fetched from the API.

typescript
// features/leads/hooks/useLeads.ts
export function useLeads() {
  return useQuery({
    queryKey: ['leads'],
    queryFn: () => leadsApi.getAll(),
  });
}

export function useCreateLead() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: CreateLeadRequest) => leadsApi.create(data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['leads'] });
    },
  });
}

Client State (Redux Toolkit)

For UI state that doesn't come from the server.

typescript
// store/slices/uiSlice.ts
const uiSlice = createSlice({
  name: 'ui',
  initialState: {
    sidebarOpen: true,
    theme: 'light',
  },
  reducers: {
    toggleSidebar: (state) => {
      state.sidebarOpen = !state.sidebarOpen;
    },
  },
});

Component Patterns

Props Interface Above Component

typescript
interface LeadCardProps {
  lead: Lead;
  onEdit: (id: string) => void;
  onDelete: (id: string) => void;
}

export function LeadCard({ lead, onEdit, onDelete }: LeadCardProps) {
  // Component implementation
}

Hooks at Top

typescript
export function LeadDetail({ id }: { id: string }) {
  // Hooks first
  const { data: lead, isLoading } = useLead(id);
  const { mutate: deleteLead } = useDeleteLead();

  // Early returns for loading/error
  if (isLoading) return <LoadingSpinner />;
  if (!lead) return <NotFound />;

  // Main render
  return (
    <Paper>
      {/* ... */}
    </Paper>
  );
}

API Integration

Generated Client

API clients are generated from OpenAPI spec using openapi-generator.

bash
npm run generate:client

TanStack Query Hooks

Wrap generated client calls in TanStack Query hooks.

typescript
// features/leads/api/leadsApi.ts
import { LeadsApi } from '@/api/generated';

const api = new LeadsApi();

export const leadsApi = {
  getAll: () => api.getLeads(),
  getById: (id: string) => api.getLead({ id }),
  create: (data: CreateLeadRequest) => api.createLead({ createLeadRequest: data }),
  update: (id: string, data: UpdateLeadRequest) => api.updateLead({ id, updateLeadRequest: data }),
  delete: (id: string) => api.deleteLead({ id }),
};

Naming Conventions

TypeConventionExample
ComponentsPascalCaseLeadCard.tsx
HookscamelCase with useuseLeads.ts
UtilitiescamelCaseformatDate.ts
TypesPascalCaseLead, CreateLeadRequest
ConstantsSCREAMING_SNAKEAPI_BASE_URL

Testing

Unit Tests (Vitest)

typescript
// features/leads/hooks/useLeads.test.ts
describe('useLeads', () => {
  it('should fetch leads', async () => {
    // Test implementation
  });
});

E2E Tests (Playwright)

typescript
// tests/leads.spec.ts
test('should create a new lead', async ({ page }) => {
  await page.goto('/leads');
  await page.click('button:has-text("New Lead")');
  // ...
});

Commands

bash
npm run dev          # Start dev server
npm run build        # Production build
npm run test         # Run unit tests
npm run test:e2e     # Run E2E tests
npm run lint         # Lint code
npm run type-check   # TypeScript check
npm run generate:client  # Generate API client

Built with VitePress | v1.1.0