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.2.0 | 🚀 Week One Sprint