Skip to main content

Frontend Overview

The Intellicon CRM frontend is a React single-page application (SPA) built with TypeScript, Vite, and Tailwind CSS.

Technology Stack

TechnologyPurpose
React 18UI component library
TypeScriptType safety
ViteBuild tool and dev server
Tailwind CSSUtility-first styling
ZustandLightweight state management
AxiosHTTP client with interceptors
React RouterClient-side routing
Lucide ReactIcon library
RechartsDashboard charts and visualizations

Build and Development

Development Server

cd apps/web
npm run dev
# Starts on http://localhost:5173

Production Build

cd apps/web
npm run build
# Output in apps/web/dist/

Type Checking

cd apps/web
npm run type-check
# OR
npx tsc --noEmit

Project Structure

apps/web/src/
├── App.tsx ← Route definitions
├── main.tsx ← Entry point
├── index.css ← Tailwind imports
├── api/ ← API layer (one file per module)
├── components/ ← Shared + layout components
├── features/ ← Feature pages (organized by module)
├── hooks/ ← Custom React hooks
├── stores/ ← Zustand state stores
├── config/ ← Constants and configuration
└── utils/ ← Utility functions

Design System

Color Palette

PurposeLight ModeDark Mode
Primarypurple-600purple-500
Primary Hoverpurple-700purple-600
Successgreen-600green-500
Dangerred-600red-500
Warningamber-500amber-400
Infoblue-600blue-500
Backgroundwhite / gray-50slate-900 / slate-800
Cardwhiteslate-800
Bordergray-200slate-700
Text Primarygray-900white
Text Secondarygray-500gray-400

Component Conventions

Buttons

// Primary button
<button className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-xl
transition-colors duration-200">
Save
</button>

// Secondary button
<button className="bg-gray-100 hover:bg-gray-200 dark:bg-slate-700 dark:hover:bg-slate-600
text-gray-700 dark:text-gray-300 px-4 py-2 rounded-xl">
Cancel
</button>

// Danger button
<button className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-xl">
Delete
</button>

Cards

<div className="bg-white dark:bg-slate-800 rounded-xl border border-gray-200
dark:border-slate-700 p-6 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Card Title
</h3>
<p className="text-gray-500 dark:text-gray-400 mt-2">
Card content
</p>
</div>

Form Inputs

<input
type="text"
className="w-full px-3 py-2 border border-gray-300 dark:border-slate-600
rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white
focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder="Enter value..."
/>

Loading States

import { Loader2 } from 'lucide-react';

// Loading spinner
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-purple-600" />
</div>

// Button loading state
<button disabled className="bg-purple-600 text-white px-4 py-2 rounded-xl opacity-75">
<Loader2 className="h-4 w-4 animate-spin inline mr-2" />
Saving...
</button>

Error States

// Error with retry
<div className="text-center py-12">
<p className="text-red-600 dark:text-red-400 mb-4">
Failed to load data. Please try again.
</p>
<button
onClick={refetch}
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-xl"
>
Retry
</button>
</div>
Always Include Both Modes

Every Tailwind class that sets a color must include its dark: variant. The app supports both light and dark modes.

// CORRECT
<div className="bg-white dark:bg-slate-800 text-gray-900 dark:text-white">

// WRONG — no dark mode support
<div className="bg-white text-gray-900">

Typography

// Page title
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Page Title</h1>

// Section title
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Section</h2>

// Body text
<p className="text-sm text-gray-600 dark:text-gray-400">Body text</p>

// Muted text
<span className="text-xs text-gray-400 dark:text-gray-500">Timestamp</span>

Spacing and Layout

// Page layout
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<h1>Title</h1>
<button>Action</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Cards */}
</div>
</div>

Responsive Design

The application uses Tailwind's responsive prefixes:

PrefixBreakpointUse Case
(none)< 640pxMobile
sm:>= 640pxLarge mobile
md:>= 768pxTablet
lg:>= 1024pxDesktop
xl:>= 1280pxLarge desktop
// Responsive grid
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">

// Hide on mobile
<div className="hidden md:block">Desktop only</div>

// Stack on mobile, row on desktop
<div className="flex flex-col md:flex-row gap-4">

State Management

Zustand Stores

// stores/auth.store.ts
import { create } from 'zustand';

interface AuthState {
user: User | null;
accessToken: string | null;
refreshToken: string | null;
setAuth: (user: User, accessToken: string, refreshToken: string) => void;
logout: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
user: null,
accessToken: null,
refreshToken: null,
setAuth: (user, accessToken, refreshToken) =>
set({ user, accessToken, refreshToken }),
logout: () =>
set({ user: null, accessToken: null, refreshToken: null }),
}));
// stores/sidebar.store.ts
interface SidebarState {
collapsed: boolean;
mobileOpen: boolean;
toggle: () => void;
setMobileOpen: (open: boolean) => void;
}

Using Stores in Components

import { useAuthStore } from '../stores/auth.store';

function UserProfile() {
const user = useAuthStore((state) => state.user);
const logout = useAuthStore((state) => state.logout);

return (
<div>
<span>{user?.firstName} {user?.lastName}</span>
<button onClick={logout}>Logout</button>
</div>
);
}
Zustand vs Context

Zustand was chosen over React Context for its simpler API, built-in selector optimization (re-renders only when selected state changes), and no need for Provider wrappers.