Web Application Architecture¶
The Web Application is the user-facing frontend of the Sartiq platform, providing an intuitive interface for fashion brands to manage their products, configure shootings, and review AI-generated imagery.
Overview¶
| Aspect | Details |
|---|---|
| Framework | Next.js 14 with App Router |
| UI Library | React 18 |
| Language | TypeScript |
| Styling | Tailwind CSS |
| State Management | Zustand + React Query |
| Port | 3000 |
Architecture Diagram¶
flowchart TB
subgraph Browser["Browser"]
UI[React Components]
Stores[Zustand Stores]
RQ[React Query Cache]
WS[WebSocket Client]
end
subgraph NextJS["Next.js Server"]
Pages[App Router Pages]
API[API Routes]
SSR[Server Components]
end
subgraph External["External"]
Backend[Backend API]
CDN[CDN]
end
UI --> Stores
UI --> RQ
UI --> WS
Pages --> SSR
Pages --> API
RQ -->|fetch| Backend
WS -->|events| Backend
API -->|proxy| Backend
CDN -->|content| UI
Key Features¶
E-Commerce Generator¶
The main generation interface where users configure and execute image generation.
Capabilities:
- Product selection from catalog
- Subject assignment
- Style and guideline configuration
- Shot type selection
- Real-time generation progress
- Side-by-side comparison views
Gallery¶
Review and manage generated images.
Capabilities:
- Grid and list views
- Filtering and sorting
- Image approval workflow
- Revision requests
- Bulk operations
- Export functionality
Asset Management¶
Manage products, subjects, styles, guidelines, and other assets.
Modules:
- Products: Upload, categorize, and manage product catalog
- Subjects: Browse and select AI models (base images)
- Styles: View and configure visual styles
- Guidelines: Configure technical specs (background, resolution, format, shot types)
- Shootings: Organize generation sessions
Admin Dashboard¶
Platform administration for internal users.
Features:
- User management
- Organization management
- System monitoring
- Configuration management
Page Structure¶
app/
├── (auth)/
│ ├── login/
│ └── signup/
├── (dashboard)/
│ ├── products/
│ │ ├── page.tsx # Product list
│ │ └── [id]/page.tsx # Product detail
│ ├── subjects/
│ ├── shootings/
│ │ ├── page.tsx # Shooting list
│ │ └── [id]/
│ │ ├── page.tsx # Shooting detail
│ │ └── looks/[lookId]/
│ ├── gallery/
│ │ ├── page.tsx # Gallery grid
│ │ └── [id]/page.tsx # Image detail
│ └── generator/
│ └── page.tsx # E-Commerce Generator
├── admin/
│ ├── users/
│ ├── organizations/
│ └── settings/
└── api/
└── [...proxy]/ # Backend proxy routes
State Management¶
Zustand Stores¶
Global application state managed with Zustand.
flowchart TB
subgraph Stores [Zustand Stores]
Auth[authStore]
UI[uiStore]
Gen[generatorStore]
Gallery[galleryStore]
end
subgraph Components [Components]
C1[Header]
C2[Generator]
C3[Gallery]
C4[Sidebar]
end
Auth --> C1
Auth --> C4
UI --> C1
UI --> C4
Gen --> C2
Gallery --> C3
Key Stores:
| Store | Purpose |
|---|---|
authStore |
User authentication state, session management |
uiStore |
UI state (sidebar, modals, theme) |
generatorStore |
Generator configuration and state |
galleryStore |
Gallery filters and selection |
React Query¶
Server state management for API data.
Patterns:
- Automatic caching and invalidation
- Optimistic updates for better UX
- Background refetching
- Pagination support
// Example: Products query
const { data: products, isLoading } = useQuery({
queryKey: ['products', filters],
queryFn: () => api.products.list(filters),
});
// Example: Mutation with optimistic update
const mutation = useMutation({
mutationFn: api.products.update,
onMutate: async (newProduct) => {
await queryClient.cancelQueries(['products']);
const previous = queryClient.getQueryData(['products']);
queryClient.setQueryData(['products'], (old) => /* optimistic update */);
return { previous };
},
onError: (err, variables, context) => {
queryClient.setQueryData(['products'], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries(['products']);
},
});
API Integration¶
Auto-Generated Client¶
The API client is auto-generated from the Backend's OpenAPI schema.
flowchart LR
Backend[Backend API] -->|OpenAPI spec| Generator[openapi-typescript]
Generator -->|types| Types[API Types]
Generator -->|client| Client[API Client]
Client -->|used by| App[Application]
Benefits:
- Type-safe API calls
- Automatic updates when API changes
- Reduced boilerplate
- IntelliSense support
API Client Structure¶
lib/
├── api/
│ ├── client.ts # HTTP client setup
│ ├── types.ts # Generated types
│ ├── products.ts # Product endpoints
│ ├── shootings.ts # Shooting endpoints
│ ├── generations.ts # Generation endpoints
│ └── index.ts # Exports
File Upload System¶
The webapp uploads files directly to Cloudflare R2 via presigned URLs, bypassing the Backend for file data transfer.
Upload Architecture¶
sequenceDiagram
participant U as User
participant W as Webapp
participant B as Backend
participant R2 as Cloudflare R2
U->>W: Select files
W->>W: Validate (type, size)
W->>B: POST /api/v1/uploads/presigned-url
B-->>W: {upload_url, file_url, expires_in_seconds}
W->>R2: PUT file directly to presigned URL
R2-->>W: 200 OK
W->>W: Store file_url in upload state
Upload Module Structure¶
| Path | Purpose |
|---|---|
src/lib/uploads/services/ |
Core upload orchestration, API client, error handling |
src/lib/uploads/hooks/ |
React hooks (useFileUpload, useUploadProgress, useUploadUrls) |
src/lib/uploads/store/ |
Zustand store for upload state management |
src/lib/uploads/types/ |
TypeScript type definitions |
src/lib/uploads/utils/ |
Retry logic, file validation, helper utilities |
Upload Groups¶
| Group | Constant | Usage |
|---|---|---|
| Product images | product-images |
Product cover, back, and reference images |
| Style images | style-images |
Style reference images |
| Subject images | subject-images |
Subject base images |
| Background images | background-images |
Background reference images |
State Management¶
Upload state is managed via a dedicated Zustand store with the following lifecycle:
stateDiagram-v2
[*] --> pending: addUpload
pending --> uploading: Start upload
uploading --> completed: Upload success
uploading --> error: Upload failed
uploading --> retrying: Retry triggered
retrying --> uploading: Retry attempt
error --> retrying: Manual retry
completed --> [*]: markUrlsConsumed
Retry & Error Handling¶
| Setting | Value |
|---|---|
| Max attempts | 3 |
| Initial delay | 1 second |
| Backoff multiplier | 2x |
| Max delay | 10 seconds |
| Jitter | Enabled |
| Error Type | Description |
|---|---|
network_error |
Network connectivity issue |
file_too_large |
Exceeds 50MB limit |
invalid_file_type |
Unsupported MIME type |
presigned_url_failed |
Backend failed to generate presigned URL |
upload_failed |
R2 upload request failed |
timeout |
Upload timed out |
Validation¶
| Check | Requirement |
|---|---|
| Max file size | 50MB |
| Allowed types | image/jpeg, image/png, image/webp, image/gif, image/avif |
| Max files per batch | 10 |
Content Delivery¶
CDN URL Resolution¶
The webapp resolves relative file paths to full CDN URLs using the NEXT_PUBLIC_CDN_URL environment variable:
| Environment | NEXT_PUBLIC_CDN_URL |
|---|---|
| Production | https://media.sartiq.com |
| Staging | https://staging-media.sartiq.com |
| Development | http://localhost:9002/shootify-media-dev |
CORS Proxy¶
The webapp includes a CORS proxy route at /api/proxy/image for cross-origin image downloads (e.g., downloading generated images to the user's device):
| Setting | Value |
|---|---|
| Max file size | 200MB |
| Supported formats | WebP, PNG, JPEG (with ?type= conversion) |
| Cache | 1 year (public, max-age=31536000) |
| Security | SSRF protection, private IP blocking, protocol validation |
Real-Time Updates¶
WebSocket Connection¶
Real-time updates for generation progress and notifications.
sequenceDiagram
participant Browser
participant WebSocket
participant Backend
participant Redis
Browser->>WebSocket: Connect
WebSocket->>Backend: Authenticate
Backend-->>WebSocket: Connected
Note over Backend,Redis: Generation completes
Backend->>Redis: Publish event
Redis->>Backend: Event
Backend->>WebSocket: Push notification
WebSocket->>Browser: Update UI
Event Types:
| Event | Description |
|---|---|
generation.started |
Generation task began |
generation.progress |
Progress update |
generation.completed |
Generation finished |
generation.failed |
Generation error |
prediction.created |
New image available |
WebSocket Hook¶
// Simplified WebSocket hook usage
const { isConnected, lastEvent } = useWebSocket({
onMessage: (event) => {
if (event.type === 'generation.completed') {
queryClient.invalidateQueries(['generations', event.generationId]);
}
},
});
Authentication Flow¶
sequenceDiagram
participant User
participant App
participant Backend
User->>App: Enter credentials
App->>Backend: POST /auth/login
Backend-->>App: JWT token + user data
App->>App: Store token in authStore
App->>App: Redirect to dashboard
Note over App,Backend: Subsequent requests
App->>Backend: GET /api/... (with JWT)
Backend-->>App: Protected data
Token Management:
- JWT stored in memory (Zustand)
- Refresh token in httpOnly cookie
- Automatic token refresh
- Logout clears all state
Component Architecture¶
Component Hierarchy¶
components/
├── ui/ # Primitive UI components
│ ├── Button/
│ ├── Input/
│ ├── Modal/
│ └── ...
├── features/ # Feature-specific components
│ ├── products/
│ │ ├── ProductCard.tsx
│ │ ├── ProductGrid.tsx
│ │ └── ProductForm.tsx
│ ├── generator/
│ │ ├── GeneratorPanel.tsx
│ │ ├── ProductSelector.tsx
│ │ └── ProgressTracker.tsx
│ └── gallery/
│ ├── ImageGrid.tsx
│ ├── ImageDetail.tsx
│ └── ApprovalControls.tsx
├── layout/ # Layout components
│ ├── Header.tsx
│ ├── Sidebar.tsx
│ └── PageContainer.tsx
└── shared/ # Shared components
├── LoadingSpinner.tsx
├── ErrorBoundary.tsx
└── EmptyState.tsx
Design Patterns¶
Compound Components:
// Product selection with compound pattern
<ProductSelector>
<ProductSelector.Grid>
<ProductSelector.Item product={product} />
</ProductSelector.Grid>
<ProductSelector.Pagination />
</ProductSelector>
Render Props:
// Data fetching with render props
<DataLoader query={productsQuery}>
{(products) => <ProductGrid products={products} />}
</DataLoader>
Styling¶
Tailwind CSS¶
Utility-first CSS with custom design tokens.
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
primary: { /* brand colors */ },
secondary: { /* ... */ },
},
fontFamily: {
sans: ['Plus Jakarta Sans', 'sans-serif'],
},
},
},
};
Component Styling Pattern¶
// cn utility for conditional classes
import { cn } from '@/lib/utils';
function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(
'rounded-lg font-medium transition-colors',
variant === 'primary' && 'bg-primary-500 text-white',
variant === 'secondary' && 'bg-gray-100 text-gray-900',
size === 'sm' && 'px-3 py-1.5 text-sm',
size === 'md' && 'px-4 py-2 text-base',
className
)}
{...props}
/>
);
}
Performance Optimization¶
Code Splitting¶
- Route-based splitting via Next.js
- Dynamic imports for heavy components
- Lazy loading for images
Image Optimization¶
- Next.js Image component for automatic optimization
- CDN for generated content
- Responsive image sizes
- WebP/AVIF format support
Caching Strategy¶
- React Query caching for API data
- Service Worker for static assets
- CDN caching for images
Key Directories¶
| Directory | Purpose |
|---|---|
app/ |
Next.js App Router pages and layouts |
components/ |
React components |
lib/ |
Utilities, API client, helpers |
src/lib/uploads/ |
File upload system (presigned URLs, state, retry logic) |
hooks/ |
Custom React hooks |
stores/ |
Zustand store definitions |
types/ |
TypeScript type definitions |
styles/ |
Global styles and Tailwind config |
public/ |
Static assets |
Related Documentation¶
- Backend Architecture - API that the webapp consumes
- Frontend Development - Coding standards
- State Management - Detailed patterns
- API Integration - API client usage