Frameingo - Technical Documentation
Version: 2.0.0
Last Updated: April 2026
Status: Production-ready
Table of Contentsβ
- Executive Summary
- Technology Stack
- Architecture Overview
- Project Structure
- Application Flow
- Core Components
- State Management
- Services & Utilities
- PWA & Offline Support
- Configuration & Settings
- Testing
- Build & Deployment
- Future Considerations (ADRs)
Executive Summaryβ
This is a TanStack Start-based photobooth web application designed for event photography. It enables users to:
- Select photo frame templates
- Capture photos via camera or upload from gallery
- Reorder and edit images
- Generate printable photo collages with custom frames
- Print directly via Bluetooth to Niimbot thermal printers (web) or Android native bridge
The application is built as an offline-capable PWA that can run on kiosks or tablets at events without reliable internet connectivity.
The repo is a monorepo containing:
- apps/web - Customer-facing PWA (TanStack Start + Cloudflare Workers)
- apps/internal - Admin panel for managing customers, subscriptions, payments
- apps/mobile - Native Android app with WebView + Bluetooth printer bridge
- services/api - Backend API (Cloudflare Workers + D1)
- packages/shared - Shared TypeScript types for JSβAndroid bridge
Technology Stackβ
Core Frameworkβ
- TanStack Start 1.x - Full-stack React framework with SSR
- React 18.x - UI library
- TypeScript 5.x - Type safety
Styling & UIβ
- Tailwind CSS 3.x - Utility-first CSS framework
- Radix UI - Accessible component primitives
@radix-ui/react-dialog@radix-ui/react-select@radix-ui/react-slider@radix-ui/react-switch@radix-ui/react-tabs
- Framer Motion - Animation library
- Lucide React - Icon library
- clsx & tailwind-merge - Class utilities
State Managementβ
- React Context API - Built-in state management
- Session Storage - Ephemeral image data
- Local Storage + IndexedDB - Persistent settings
PWA & Service Workersβ
- @serwist/next - Service worker integration for Next.js
- @serwist/precaching - Precaching for offline support
Bluetooth Printingβ
- @mmote/niimbluelib - Niimbot thermal printer Bluetooth library
Drag & Dropβ
- @dnd-kit/core - Drag and drop primitives
- @dnd-kit/sortable - Sortable list components
QR Codeβ
- qrcode - QR code generation
- html5-qrcode - QR code scanning
Fontsβ
@fontsource/inter@fontsource/lato@fontsource/montserrat@fontsource/open-sans@fontsource/roboto
Development & Buildβ
- ESLint - Code linting
- Prettier - Code formatting
- Playwright - E2E testing
- Wrangler - Cloudflare Pages deployment
Architecture Overviewβ
Billing and Access Architectureβ
The backend now separates commercial access into four domains:
paymentsfor transaction records and provider statussubscriptionsfor recurring lifecycle stateentitlementsas the source of truth for access windowslicensingfor stable public API responses such as/api/v1/license/verify/:key
This allows the app to support both:
- one-time activation with perpetual entitlement
- monthly subscriptions with recurring entitlement windows
High-Level Architectureβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Pages Router (Legacy) β App Router (New) β
β - /pages/*.tsx β - /app/* β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β React Context Providers β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Settings β β Printer β β Voucher β β
β β Provider β β Provider β β Provider β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β βββββββββββββββ β
β β Collage β β
β β Provider β β
β βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Services Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Storage β β Image β β Bluetooth β β
β β (IndexedDB) β β Processing β β Printing β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Service Worker (Serwist) β
β - Precaching for offline β
β - Runtime caching strategies β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Architectural Decisionsβ
- Pages Router: Uses Next.js Pages Router (not App Router) for simpler integration with Serwist service worker
- React Context: Lightweight state management without external libraries like Redux
- Client-Side Rendering: Most pages use client-side features (Bluetooth, camera, storage)
- Hybrid Storage: IndexedDB with localStorage fallback for settings persistence
Project Structureβ
frameingo-project/
βββ apps/
β βββ web/ # Customer-facing PWA (TanStack Start)
β β βββ src/
β β β βββ routes/ # TanStack Route files
β β β βββ components/ # Shared components
β β β βββ features/ # Feature modules
β β β βββ hooks/ # Custom React hooks
β β β βββ lib/ # Utilities
β β β βββ providers/ # Context providers
β β β βββ store/ # Zustand stores
β β βββ package.json
β β
β βββ internal/ # Admin panel (Vite + React)
β β βββ src/
β β β βββ pages/ # Admin pages (customers, subscriptions, payments)
β β β βββ lib/ # API client
β β β βββ components/ # UI components
β β βββ package.json
β β
β βββ mobile/ # Native Android app
β βββ app/
β β βββ src/main/
β β βββ java/ # Kotlin code (MainActivity, Bluetooth service)
β β βββ res/ # Android resources
β βββ build.gradle.kts
β
βββ services/
β βββ api/ # Backend API (Cloudflare Workers)
β βββ core/ # Core utilities, middleware, repositories
β βββ customers/ # Customer management
β βββ payments/ # Payment processing
β βββ subscriptions/ # Subscription management
β βββ admin/ # Admin API controllers
β
βββ packages/
β βββ shared/ # Shared TypeScript types
β βββ src/
β βββ bridge.ts # JSβAndroid bridge contract
β
βββ docs/ # Documentation
βββ scripts/ # Build/deploy scripts
βββ package.json # Workspace root (pnpm/npm workspaces)
Key Changes from v1.0β
- Monorepo structure - Multiple apps in one repo
- TanStack Start - Replaced Next.js with TanStack Start for full-stack React
- TanStack Router - File-based routing with type safety
- Cloudflare Workers - Edge deployment for API
- Android app - Native WebView wrapper with ESCPOS Bluetooth printing βββ docs/ # Architecture decision records βββ next.config.mjs # Next.js configuration βββ tailwind.config.ts # Tailwind configuration βββ tsconfig.json # TypeScript configuration βββ Dockerfile # Docker build βββ package.json # Dependencies
---
## Application Flow
### Main User Flow
ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββ β HOME ββββββΆβ FRAME SELECTION ββββββΆβ IMAGE SELECTION β β (index) β β (frame-id) β β (camera/gallery) β ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββ β β β βΌ β βββββββββββββββββββββ β β IMAGE UPLOAD β β β (capture/select) β β βββββββββββββββββββββ β β βΌ βΌ ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββ β HOME βββββββ IMAGE FINAL βββββββ IMAGE EDITING β β (restart) β β (print/preview) β β (reorder/filter) β ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββ
### Detailed Page Flow
1. **Home Page (`/`)**
- Entry point with customizable start button
- Shows "Connect Printer" button if printer enabled
- Clears previous session verification
2. **Frame Selection (`/frame-selection`)**
- Displays available frame templates (1-4 photos)
- Shows voucher scan if voucher system enabled
- Passes frame ID to next page
3. **Image Selection (`/image-selection`)**
- Choose between camera or gallery upload
- Detects camera availability
4. **Image Upload (`/image-upload`)**
- Camera mode: Live preview with countdown capture
- Gallery mode: File picker for local images
- Supports multiple images based on frame selection
- Stores images in sessionStorage
5. **Image Editing (`/image-editing`)**
- Drag-and-drop reordering via @dnd-kit
- Preview mode with brightness adjustment
- Atkinson dithering filter for thermal printing
- Auto-enters preview mode for single images
6. **Image Final (`/image-final`)**
- Displays final collage with frame applied
- Auto-print if printer connected and enabled
- Manual print/reprint button
- Countdown timer to auto-reset (120s)
- "Done" button returns to home
7. **Customization (`/customization`)**
- PIN-protected settings page (default: 1234)
- Tabs: Page, Frame, Button, Printer, Voucher, Security
- Settings persisted to IndexedDB/localStorage
---
## Core Components
### Layout Components
| Component | Purpose |
| ---------------- | ---------------------------------------------------------- |
| `SharedLayout` | Common page wrapper with header, back button, content area |
| `PageTransition` | Framer Motion page transitions |
### UI Components (Radix-based)
| Component | Description |
| --------- | -------------------------------------- |
| `Button` | Customizable button with theme support |
| `Dialog` | Modal dialogs |
| `Select` | Dropdown selection |
| `Slider` | Range input (brightness control) |
| `Switch` | Toggle (on/off settings) |
| `Tabs` | Tabbed interface for customization |
### Feature Components
| Component | Purpose |
| ------------------ | ------------------------------------------ |
| `Frame` | Frame preview with settings applied |
| `VideoPreview` | Camera live preview + capture |
| `ImageThumbnails` | Multi-image slot display |
| `VoucherScan` | QR code scanner for voucher validation |
| `LockWidget` | PIN entry overlay for customization access |
| `OfflineIndicator` | Network status indicator |
| `InstallPrompt` | PWA installation prompt |
---
## State Management
### Provider Architecture
```text
<App>
β
ββ SettingsProvider
β ββ CustomizationSettings (persisted)
β ββ PageSettings
β ββ FrameSettings
β ββ ButtonSettings
β ββ SecuritySettings (PIN)
β ββ PrinterSettings
β ββ VoucherSettings
β
ββ PrinterProvider
β ββ Bluetooth client state
β ββ Connection status
β ββ Print functions
β
ββ VoucherProvider
β ββ Verification state
β ββ Verify function
β
ββ CollageProvider
ββ Final image (base64)
ββ Brightness value
ββ Generate/clear functions
Settings Providerβ
File: src/providers/SettingsProvider.tsx
Manages all application settings with persistence:
- Uses
useLocalStoragehook with IndexedDB + localStorage fallback - Supports settings migration for schema updates
- Default values defined in provider
Stored Settings:
pageSettings: Background, title, subtitleframeSettings: Header, footer, background, photo frame stylingbutton: Start button text, colors, border stylingsecurity: PIN code for admin accessprinter: Enable/disable, default copies, max copies, paper sizevoucher: Enable/disable, passcode for voucher access
Printer Providerβ
File: src/providers/PrinterProvider.tsx
Handles Bluetooth communication with Niimbot printers:
- Connect/disconnect via Web Bluetooth API
- Print images with configurable copies
- Supports E2E testing mode (
NEXT_PUBLIC_E2E=true) - Enforces max copies limit from settings
Voucher Providerβ
File: src/providers/VoucherProvider.tsx
Manages voucher/PIN-based access control:
- Validates passcode from QR scan
- Stores verification state in sessionStorage
- Clears on new session start
Collage Providerβ
File: src/providers/CollageProvider.tsx
Generates final printable image:
- Combines multiple images into collage based on frame settings
- Applies Atkinson dithering for thermal printer output
- Supports brightness adjustment
Services & Utilitiesβ
Storage Serviceβ
File: src/lib/storage.ts
Dual-layer persistent storage:
- IndexedDB - Primary storage (PWA-reliable)
- localStorage - Fallback, synced with IndexedDB
Key Functions:
getItem(key)- Read from storagesetItem(key, value)- Write to storageremoveItem(key)- Remove a persisted valueclear()- Clear all persisted values
Image Processingβ
File: src/lib/image-processing.ts
Canvas-based image manipulation:
Functions:
generateCollage(images, settings, width, height)- Creates combined imageatkinson(imageData, threshold)- Applies Atkinson ditheringadjustBrightnessContrastGamma()- Print compensationcalculateCropCoordinates()- Aspect ratio preservation
Collage Layouts:
- 1 image: Full area
- 2 images: Vertical split (top/bottom)
- 3 images: 2 top, 1 bottom centered
- 4 images: 2x2 grid
Custom Hooksβ
| Hook | Purpose |
|---|---|
useCamera | Camera stream management |
useImageCapture | Photo capture to canvas |
useImageUpload | Gallery file handling |
useImageDimensions | Target dimensions calculation |
useFrameConfig | URL param parsing for frame |
useOffline | Offline detection |
useInstallPrompt | PWA install handling |
PWA & Offline Supportβ
Service Worker Configurationβ
File: src/sw.ts (compiled to public/sw.js)
Uses Serwist for Next.js service worker integration:
Caching Strategies:
| Request Type | Strategy | Cache Name |
|---|---|---|
| Static assets (images, fonts) | Cache-first | static-assets-v1 |
| API/JSON | Cache-first | api-cache-v1 |
| Navigation (pages) | Cache-first | pages-cache-v1 |
Offline Behavior:
- All app pages pre-cached
- Falls back to
/offline.htmlfor navigation failures - Assets cached on first load
PWA Manifestβ
File: public/manifest.json
- App name: "Photobooth"
- Standalone display mode
- Theme color: Black
- App icons: 48x48 to 512x512
Offline Pagesβ
/offline- React component for offline state/offline.html- Static fallback HTML
Configuration & Settingsβ
Default Settingsβ
All default values are defined in src/providers/SettingsProvider.tsx:
// Default button
{
text: "Mulai Sesi",
backText: "Kembali",
bgColor: "#000000",
textColor: "#ffffff",
borderRadius: 8,
font: "Default",
fontSize: "1rem"
}
// Default printer
{
active: true,
name: "NIIMBOT",
defaultCopies: 1,
maxCopies: 10,
paperWidthMM: 30,
paperHeightMM: 50,
dpi: 300
}
// Default security
{
pin: "1234",
iconSize: 24,
iconOpacity: 0
}
Frame Templatesβ
File: src/data/frames.json
Static frame definitions with IDs mapping to photo counts:
1β 1 photo2β 2 photos3β 3 photos4β 4 photos
Testingβ
Test Structureβ
tests/
βββ e2e/
β βββ flows/
β β βββ photobooth.spec.ts # Main flow tests
β β βββ customization.spec.ts # Settings tests
β β βββ voucher.spec.ts # Voucher flow tests
β β βββ pin.spec.ts # PIN access tests
β β βββ offline.spec.ts # Offline tests
β βββ visual/
β β βββ snapshots.spec.ts # Visual regression tests
β βββ fixtures/
β β βββ test-image.png # Test image fixture
β βββ README.md
Running Testsβ
# All E2E tests
npm run test:e2e
# Visual snapshot tests (iPad)
npm run test:e2e:visual
# UI mode (interactive)
npm run test:e2e:ui
# Debug mode
npm run test:e2e:debug
Test Configurationβ
- Uses Playwright with Chromium/iPad emulation
- Base URL:
http://localhost:3000 - E2E mock mode available via
NEXT_PUBLIC_E2E=true
Build & Deploymentβ
Local Developmentβ
# Install dependencies
npm install
# Start dev server (Turbo mode)
npm run dev
# Lint
npm run lint
# Format
npm run format
Production Buildβ
# Build for production
npm run build
# Start production server
npm run start
Deployment Optionsβ
1. Cloudflare Pages (Primary)β
npm run deploy
Deploys to Cloudflare Pages using Wrangler.
Configuration: wrangler.toml (implicit)
2. Dockerβ
# Build image
docker build -t photobooth-web-app .
# Run container
docker run -p 3000:3000 photobooth-web-app
Environment Variablesβ
| Variable | Purpose | Default |
|---|---|---|
NODE_ENV | Environment | development |
NEXT_PUBLIC_E2E | E2E test mode | false |
Future Considerations (ADRs)β
ADR-001: QR Code Download Featureβ
Status: Proposed
Decision: Use Supabase for cloud image storage with QR code download.
Trade-offs:
- β Excellent DX, rapid implementation, free tier
- β οΈ Egress cost risk (2GB/month limit on free tier)
- β Scalable architecture path available
Alternative: Cloudflare R2 (no egress fees) - Re-evaluate if traffic exceeds free tier
ADR-002: Vendor Access Controlβ
Status: Proposed
Decision: Remote validation server with Cloudflare Workers + D1.
Implementation:
- License key generation and validation
- JWT token for offline verification
- D1 database for license storage
Trade-offs:
- β Robust security, centralized management, scalable
- β οΈ Internet required for initial activation
- β οΈ Requires separate admin UI development
Known Limitations & Notesβ
- Browser Support: Requires Web Bluetooth API support (Chrome, Edge, Opera)
- Camera: Requires HTTPS or localhost
- Print Size: Currently hardcoded to 30x50mm (Niimbot default)
- Image Count: Maximum 4 images per session
- PIN Default: Change default PIN (1234) in production
- Voucher: Disable in production if not used
API Reference (Internal)β
Settings Schemaβ
interface CustomizationSettings {
pageSettings: {
background: {
type: "Gradien" | "Warna" | "Gambar";
color?: string;
gradientStart?: string;
gradientEnd?: string;
};
title: { text: string; font: string; size: string; color: string };
subtitle: { text: string; font: string; size: string; color: string };
};
frameSettings: {
header: {
active: boolean;
text: string;
textColor: string;
fontSize: string;
fontFamily: string;
textAlign: string;
bold: boolean;
italic: boolean;
underline: boolean;
height: number;
};
footer: {
active: boolean;
text: string;
textColor: string;
fontSize: string;
fontFamily: string;
textAlign: string;
bold: boolean;
italic: boolean;
underline: boolean;
height: number;
};
background: { type: string; colorStart: string; colorEnd: string };
photoFrame: {
active: boolean;
thickness: number;
style: string;
color: string;
horizontalMargin: number;
};
};
button: ButtonSettings;
security: SecuritySettings;
printer: PrinterSettings;
voucher: VoucherSettings;
}
Quick Reference for New Engineersβ
Starting Developmentβ
- Clone repo β
npm installβnpm run dev - Open
http://localhost:3000 - Default PIN:
1234β Access/customization
Key Files to Modifyβ
| Task | File(s) |
|---|---|
| Add new frame | src/data/frames.json |
| Change defaults | src/providers/SettingsProvider.tsx |
| Modify print logic | src/providers/PrinterProvider.tsx |
| Image processing | src/lib/image-processing.ts |
| Add page | Create in src/pages/ |
| Styling | src/app/globals.css, tailwind.config.ts |
Debugging Tipsβ
- Bluetooth issues: Check browser compatibility (Chrome required)
- Camera issues: Ensure HTTPS or localhost
- Storage issues: Check browser storage quota
- E2E tests: Use
NEXT_PUBLIC_E2E=truefor mock printing
End of Technical Documentation