Area Manager Frontend Architecture
Overview
The Area Manager feature is built using Next.js 15 App Router with Effect-TS ecosystem for type-safe functional programming. This document provides a high-level architectural overview of the frontend implementation.
The architecture follows Clean Architecture principles with clear separation between domain logic, application services, and presentation components. The feature is split into two main sub-pages (Managers and Rolling), each self-contained with its own domain models, services, and UI components.
Technology Stack
Core Framework
- Next.js 15 - App Router with React Server Components
- React 18 - UI library with server/client component split
- TypeScript - Static typing throughout
Effect-TS Ecosystem
- effect - Functional programming primitives and Schema validation
- @effect-atom/atom-react - Reactive state management
- @effect/platform - HTTP client abstractions
- @effect/experimental - Reactivity layer for cache invalidation
- AtomHttpApi - Type-safe API client generation
Supporting Libraries
- TanStack Query v5 - Server state management (bridges to React)
- React Hook Form - Form state management
- Tailwind CSS + shadcn/ui - Styling and components
- Lingui - Internationalization (i18n)
Project Structure
The feature follows a sub-page pattern where each major workflow has its own isolated domain and services:
admin-lite/src/app/admin/area-manager/
├── layout.tsx # Tab navigation wrapper
├── page.tsx # Root redirect
├── _domain/ # Shared domain entities
├── _lib/ # Shared services
├── _locales/ # Shared translations
│
├── managers/ # Managers Tab (CRUD operations)
│ ├── page.tsx # Main page
│ ├── _domain/
│ │ └── manager-entities.ts # Schema.Class entities
│ ├── _lib/
│ │ ├── api-client.ts # AtomHttpApi client
│ │ ├── manager-atoms.ts # UI state atoms
│ │ ├── manager-repository.ts # Data access
│ │ └── manager-queries.ts # TanStack Query options
│ ├── _components/ # Manager-specific UI
│ └── _locales/ # Manager translations
│
└── rolling/ # Rolling Tab (Transfer operations)
├── page.tsx # 3-step workflow page
├── _domain/
│ ├── rolling-entities.ts # Schema.Class entities
│ ├── rolling-api.ts # HttpApi contracts
│ └── rolling-form-schema.ts # Form validation schemas
├── _lib/
│ ├── api-client.ts # RollingClient (AtomHttpApi)
│ ├── runtime.ts # Effect runtime with layers
│ ├── rolling-atoms.ts # Reactive state atoms
│ ├── rolling-repository.ts # Data access layer
│ ├── rolling-service.ts # Business logic
│ └── rolling-queries.ts # TanStack Query bridge
├── _components/
│ ├── site-selection/ # Step 1 components
│ ├── transfer-config/ # Step 2 components
│ ├── execution/ # Step 3 components
│ └── progress-dialog/ # Real-time monitoring
└── _locales/ # Rolling translations
Key Principles:
- Feature-first organization - Each sub-page is self-contained
- Lazy extraction - Shared code moves to root
_domain//_lib/only when used by 2+ features - Co-location - Components live near the pages that use them
Architecture Layers
1. Domain Layer (_domain/)
Contains pure business entities and contracts with no external dependencies.
Entities are defined using effect/Schema.Class:
- Runtime-validated data structures
- Type inference from schema
- Computed properties via getters
- Immutable by design
Example entity with computed property:
export class RollingSite extends Schema.Class<RollingSite>('RollingSite')({
siteID: Schema.String,
name: Schema.String,
team: Schema.optional(Schema.Array(Schema.String)),
}) {
get assignedUserCount(): number {
return this.team?.length || 0;
}
}API Contracts use @effect/platform HttpApi for type-safe endpoint definitions that generate both client and validation automatically.
2. Application Layer (_lib/)
Orchestrates business logic and data access between domain and presentation layers.
Components:
-
API Client (
api-client.ts)- Uses
AtomHttpApito generate type-safe API client from HttpApi contract - Integrates with effect-atom for reactive state
- Handles authentication via layered HTTP client
- Uses
-
Runtime (
runtime.ts)- Composes Effect layers (API client, reactivity, HTTP transport)
- Provides dependency injection for the feature
- Used by atoms for executing effects
-
Atoms (
*-atoms.ts)- UI state only (filters, selections, form state)
- Derived atoms for computed values (counts, validations)
- Write atoms for actions (add, remove, update)
- URL state atoms for persistence (
Atom.searchParam)
-
Repository (
*-repository.ts)- Data access layer extending
BaseRepository - Wraps API calls with monitoring
- Bridges to TanStack Query
- Data access layer extending
-
Service (
*-service.ts)- Business logic extending
BaseService - Validation and orchestration
- Error handling with monitoring
- Business logic extending
-
Queries (
*-queries.ts)- TanStack Query
queryOptionsandmutationOptions - Bridges Effect services to React components
- Manages server state caching and invalidation
- TanStack Query
3. Presentation Layer (_components/)
React components following Server Component by default pattern.
Component Types:
- Server Components - Static pages, layouts, initial data fetching
- Client Components - Interactive UI with
'use client'directive
State Access:
useAtomValue()- Read atom stateuseAtomSet()- Write to atom (action)useAtom()- Read + writeuseQuery()/useMutation()- Server state
State Management Strategy
The architecture separates UI state from server state with clear boundaries:
UI State (effect-atom)
What it manages:
- Filters and search inputs
- Selected items (Map data structure for O(1) lookups)
- Form state (before submission)
- URL parameters (operation IDs, dialog state)
- Derived/computed values (counts, validations)
Why effect-atom:
- Reactive updates without re-renders
- Derived atoms for memoized computations
- URL state synchronization via
Atom.searchParam - Type-safe with TypeScript inference
- Integrates with Effect runtime
Atom patterns:
// State atom
export const selectedSitesAtom = Atom.make<Map<string, TransferSiteData>>(new Map());
// Derived atom (memoized)
export const selectedSitesCountAtom = Atom.make((get) => {
return get(selectedSitesAtom).size;
});
// Action atom
export const addSiteAtom = Atom.writable(
() => {},
(ctx, site) => {
const current = ctx.get(selectedSitesAtom);
ctx.set(selectedSitesAtom, new Map([...current, [site.siteID, site]]));
}
);
// URL state atom
export const operationIdAtom = Atom.searchParam('opsId', { schema: S.String });Server State (TanStack Query)
What it manages:
- API data fetching (sites, departments, operations)
- Caching with stale-time configuration
- Background refetching and polling
- Optimistic updates
- Request deduplication
Why TanStack Query:
- Proven React ecosystem solution
- Automatic cache invalidation
- Built-in loading/error states
- Polling support for real-time updates
- DevTools for debugging
Query patterns:
export const rollingQueryOptions = {
sites: (deptId: string, filters: Filters) => queryOptions({
queryKey: ['rolling', 'sites', deptId, filters],
queryFn: () => rollingService.getDepartmentSites(deptId, filters),
staleTime: 30000, // 30s cache
}),
operation: (opsId: string | null) => queryOptions({
queryKey: ['rolling', 'operation', opsId],
queryFn: () => rollingService.getOperationStatus(opsId!),
enabled: opsId !== null,
refetchInterval: (query) => {
// Poll every 3s if in progress, stop otherwise
return query.state.data?.status === 'in_progress' ? 3000 : false;
},
}),
};Data Flow
Flow 1: Filter → Query → Display
User Input (Search/Filter)
↓
UI State Atom Updated (setSearchAtom)
↓
Derived Atom Recomputes (apiFiltersAtom)
↓
TanStack Query Refetches (new filters in queryKey)
↓
Repository Calls API
↓
Data Returns & Cached
↓
Component Re-renders with New Data
Key: Atoms control the filters, TanStack Query handles the fetching.
Flow 2: User Action → State Update → Validation
User Selects Site
↓
Action Atom Executed (addSiteToTransferAtom)
↓
State Atom Updated (selectedSitesAtom)
↓
Derived Atoms Recompute (count, validation)
↓
UI Reactively Updates (buttons enable/disable)
Key: Immutable updates with new Map instances trigger reactive updates.
Flow 3: Execute → Optimistic UI → Real-time Updates
User Clicks Execute
↓
Mutation Starts (executeRolling)
↓
Optimistic Update (set operation ID in URL)
↓
Progress Dialog Opens (derived from URL param)
↓
API Call Executes
↓
Operation ID Returned
↓
Query Starts Polling (every 3s)
↓
Progress Updates Display
↓
Operation Completes
↓
Polling Stops (refetchInterval returns false)
Key: URL state persistence enables page reload without losing progress tracking.
Key Architectural Decisions
1. Schema.Class for Entities
Why: Runtime validation + type safety + computed properties in one construct.
Benefit: API responses are validated at runtime, preventing bad data from propagating. Getters provide clean derived data without manual computation.
2. AtomHttpApi for API Clients
Why: Type-safe API client generated from HttpApi contract, integrated with atoms.
Benefit: Single source of truth for API contracts. Client code generation ensures request/response types always match backend contract.
3. Effect Runtime for Dependency Injection
Why: Layer-based composition of services (HTTP client, auth, reactivity).
Benefit: Services are testable (layers can be mocked), and dependencies are explicit and type-safe.
4. Map for Selected Sites
Why: O(1) lookup/insert/delete instead of O(n) with arrays.
Benefit: Performance remains constant even with hundreds of selected sites.
5. URL State for Operation Tracking
Why: Atom.searchParam syncs operation ID to URL.
Benefit: Users can refresh the page during a rolling operation and progress monitoring continues. Shareable URLs for support.
6. Polling with Conditional Interval
Why: refetchInterval function stops polling when operation completes.
Benefit: No unnecessary API calls after operation finishes. Automatic cleanup.
Component Organization
Server Components (Default)
Used for:
- Page shells (
page.tsx) - Layouts (
layout.tsx) - Static content containers
Benefits:
- Zero JavaScript bundle
- SEO-friendly
- Fast initial load
Client Components ('use client')
Used for:
- Forms with user input
- Data tables with filters
- Interactive dialogs
- Real-time progress displays
Requirements:
- Must use effect-atom hooks
- Must use TanStack Query hooks
- Have event handlers
Client component example:
'use client';
export function SitesTable() {
const filters = useAtomValue(apiFiltersAtom);
const { data } = useQuery(rollingQueryOptions.sites('dept-1', filters));
return <Table data={data?.sites} />;
}Integration Points
1. Authentication
Handled by AuthenticatedHttpClient layer provided to all API clients. Firebase auth token automatically attached to requests.
2. Monitoring
All repository and service methods wrapped with withMonitoring() for:
- Operation timing
- Error tracking (Sentry)
- Business impact tagging
- Context metadata
3. Internationalization
@lingui/react macros for translations:
<Trans>for component textmsgfor dynamic strings- Compile-time extraction to
_locales/
4. Error Handling
Pattern:
- Repository: Catches HTTP errors, logs to Sentry
- Service: Validates business rules, throws domain errors
- Component: Displays user-friendly error messages via TanStack Query error state
Performance Characteristics
Optimization Strategies
- Derived Atoms - Memoized computations only run when dependencies change
- Map Data Structure - O(1) operations for large selections
- Query Stale Time - Prevents unnecessary refetches (30s default)
- Server Components - Reduces client JavaScript bundle
- Lazy Loading - Progress dialog code-split via
lazy() - Conditional Polling - Stops automatically when operation completes
Expected Performance
- Initial Page Load: <1s (server component advantage)
- Filter Change: <100ms (derived atom recompute + query cache hit)
- Site Selection: <10ms (Map.set operation)
- Execute Rolling: <200ms API round-trip, polling starts
- Progress Update: 3s intervals, negligible overhead
Deployment
Built as Next.js standalone for Cloudflare Workers deployment:
- Edge runtime for global low-latency
- Environment variables via
env.ts - Auto-deployment on push to
master
Related Documentation
- Feature Overview - Business context and capabilities
- BE Architecture - Backend rolling operations implementation
- UI flow - User workflows and interface guide
- Effect-TS Documentation - Official effect-ts docs
- TanStack Query - Query documentation