Feature Modules
SALLY’s frontend organizes code into feature modules under apps/web/src/features/. Each feature is a self-contained unit with its own components, hooks, API calls, types, and tests. This page explains the structure, the six domains, and how features connect to pages.
Feature Module Structure
Every feature module follows this pattern:
features/<domain>/<feature>/
├── components/ # React components specific to this feature
│ ├── DriverList.tsx
│ └── DriverActivationDialog.tsx
├── hooks/ # React Query hooks and custom hooks
│ └── use-drivers.ts
├── api.ts # API client functions (fetch calls)
├── types.ts # TypeScript types for this feature
├── store.ts # Zustand store (if needed)
├── index.ts # Barrel export
└── __tests__/ # Feature-specific testsNot every feature has all these files. Smaller features may only have api.ts, hooks/, and components/.
The Six Domains
Features are grouped into domains that mirror the backend:
auth
Authentication, login, registration, and session management.
features/auth/
├── components/
│ ├── LoginScreen.tsx
│ ├── login-form.tsx
│ ├── registration-form.tsx
│ └── accept-invitation-form.tsx
├── hooks/
│ └── use-auth.ts
├── api.ts
├── store.ts # Zustand auth store (persisted)
├── types.ts
└── index.tsfleet
Driver, vehicle, and load management.
features/fleet/
├── drivers/
│ ├── components/
│ │ ├── driver-list.tsx
│ │ └── driver-activation-dialog.tsx
│ ├── hooks/
│ │ └── use-drivers.ts
│ ├── api.ts
│ ├── types.ts
│ └── index.ts
├── vehicles/
│ ├── components/
│ ├── hooks/
│ │ └── use-vehicles.ts
│ ├── api.ts
│ ├── types.ts
│ └── index.ts
└── loads/
├── hooks/
│ └── use-loads.ts
├── api.ts
├── types.ts
└── index.tsoperations
Alerts, command center, monitoring, and notifications.
features/operations/
├── alerts/
│ ├── components/
│ ├── hooks/
│ │ ├── use-alerts.ts
│ │ └── use-alert-analytics.ts
│ ├── api.ts
│ ├── api-analytics.ts
│ ├── types.ts
│ └── index.ts
├── command-center/
│ ├── hooks/
│ │ ├── use-command-center.ts
│ │ └── use-shift-notes.ts
│ ├── api.ts
│ ├── types.ts
│ └── index.ts
├── monitoring/
│ ├── components/
│ └── hooks/
└── notifications/
├── hooks/
│ └── use-notifications.ts
├── api.ts
├── types.ts
└── index.tsrouting
Route planning, HOS compliance, and optimization.
features/routing/
├── hos-compliance/
│ ├── hooks/
│ │ └── use-hos-compliance.ts
│ ├── api.ts
│ ├── types.ts
│ └── index.ts
├── optimization/
│ ├── hooks/
│ │ ├── use-optimization.ts
│ │ └── useEngineRun.ts
│ ├── api.ts
│ ├── store.ts
│ ├── types.ts
│ └── index.ts
└── route-planning/platform
Admin panel, user management, settings, feature flags, onboarding, AI chat.
features/platform/
├── admin/ # Tenant management (super admin)
├── users/ # User management (invite, list)
├── settings/ # App settings
├── onboarding/ # Onboarding wizard
├── feature-flags/ # Feature flag management
├── chat/ # SALLY chat panel
├── sally-ai/ # SALLY AI assistant (orb, chat, voice, cards)
└── preferences/ # User preferencesintegrations
External system integration configuration UI.
features/integrations/
├── components/
│ ├── ConfigureIntegrationForm.tsx
│ ├── ConnectionsTab.tsx
│ ├── IntegrationCard.tsx
│ ├── IntegrationOnboarding.tsx
│ └── IntegrationSyncHistory.tsx
├── hooks/
├── api.ts
└── index.tsHow Features Connect to Pages
Pages are thin wrappers that import and render feature components:
import { DriverList } from '@/features/fleet/drivers/components/driver-list';
export default function FleetPage() {
return <DriverList />;
}The page file handles routing. The feature component handles everything else — data fetching, state, UI, and interactions.
API Layer
Each feature has an api.ts file that defines functions for calling the backend:
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
export const driversApi = {
list: async (): Promise<Driver[]> => {
const res = await fetch(`${API_BASE}/drivers`, { headers: getAuthHeaders() });
return res.json();
},
getById: async (driverId: string): Promise<Driver> => {
const res = await fetch(`${API_BASE}/drivers/${driverId}`, { headers: getAuthHeaders() });
return res.json();
},
create: async (data: CreateDriverRequest): Promise<Driver> => {
const res = await fetch(`${API_BASE}/drivers`, {
method: 'POST',
headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return res.json();
},
};Hooks consume the API layer. Components consume hooks. This creates a clean dependency chain:
Page → Feature Component → Hook → API function → BackendHook Patterns
Hooks wrap the API layer with React Query for caching, loading states, and mutations:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { driversApi } from '../api';
const DRIVERS_QUERY_KEY = ['drivers'] as const;
// Read hook -- fetches and caches data
export function useDrivers() {
return useQuery({
queryKey: DRIVERS_QUERY_KEY,
queryFn: () => driversApi.list(),
});
}
// Single item hook -- fetches one record by ID
export function useDriverById(driverId: string) {
return useQuery({
queryKey: [...DRIVERS_QUERY_KEY, driverId],
queryFn: () => driversApi.getById(driverId),
enabled: !!driverId,
});
}
// Mutation hook -- creates and invalidates cache
export function useCreateDriver() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateDriverRequest) => driversApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: DRIVERS_QUERY_KEY });
},
});
}Components use hooks for both data and actions:
function DriverList() {
const { data: drivers, isLoading } = useDrivers();
const createDriver = useCreateDriver();
if (isLoading) return <Skeleton />;
return (
<div>
{drivers?.map((driver) => (
<DriverCard key={driver.id} driver={driver} />
))}
</div>
);
}Shared vs Feature Components
Use this decision tree:
| Question | Put It In |
|---|---|
| Is it used by only one feature? | features/<domain>/<feature>/components/ |
| Is it used by multiple features? | shared/components/common/ |
| Is it a generic UI primitive (button, card, dialog)? | shared/components/ui/ (Shadcn) |
| Is it a layout component (sidebar, header)? | shared/components/layout/ |
The shared/ directory contains:
shared/
├── components/
│ ├── ui/ # 28 Shadcn/ui components
│ ├── layout/ # Sidebar, header, navigation
│ └── common/ # Reusable components used across features
├── hooks/ # Shared hooks (useToast, useSSE, usePushNotifications)
├── lib/ # API client, Firebase, utilities
├── config/ # App configuration
├── types/ # Shared TypeScript types
└── utils/ # Utility functionsAdding a New Feature Module
- Create the directory under the appropriate domain:
mkdir -p src/features/operations/shift-reports/{components,hooks,__tests__}- Create the API layer:
export const shiftReportsApi = {
list: async () => { /* ... */ },
getById: async (id: string) => { /* ... */ },
};- Create types:
export interface ShiftReport {
id: string;
shiftDate: string;
driverId: string;
summary: string;
}- Create hooks:
export function useShiftReports() {
return useQuery({
queryKey: ['shift-reports'],
queryFn: () => shiftReportsApi.list(),
});
}- Create the barrel export:
export * from './types';
export { useShiftReports } from './hooks/use-shift-reports';- Build components and wire them to a page.