- Postgres Runtime Cutover Status: 17 migrations (0000–0017), dual-write repo matrix - Backend Architecture: dual-DB architecture, repo factory, MONGO_CONNECT_MODE modes - Data Model Overview: 23-model index with PG table names and migration status - User, PurchaseRequest, SellerOffer, Chat, Dispute: Drizzle schema + cutover status added - 04 - Flows/Telegram Mini App.md: new doc covering Mini App architecture and flows - mongo-to-pg-migration-prd.md: status block prepended with 2026-06-03 milestone tracking Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
21 KiB
title, tags, related_models, related_apis, task
| title | tags | related_models | related_apis | task | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Telegram Mini App Flow |
|
|
|
5.4 |
Last updated: 2026-06-03 Status: IN PROGRESS — Task 5.4 (dependencies: 5.1 auth infra, 5.2 Telegram sign-in endpoint) Frontend branch:
integrate-main-into-development· v2.8.44 Entry point:src/sections/telegram/· route/telegram
Telegram Mini App Flow
End-to-end specification for the Amaneh Telegram Mini App — a fully self-contained marketplace shell surfaced inside Telegram's in-app browser via the WebApp SDK. Buyers and sellers can browse requests, create new escrow requests, review offer state, follow payments, and message each other without leaving Telegram.
1. Architecture Overview
Telegram Client
└─ Mini App iframe (https://amn.gg/telegram)
└─ TelegramMiniAppView ← shell orchestrator
├─ useTelegramLiveContext ← SDK probe + polling
├─ useTelegramLanguage ← EN / FA detection
├─ useTelegramAutoSignIn ← silent JWT exchange
├─ useTelegramMainButton ← native chrome sync
├─ useTelegramBackButton ← native chrome sync
├─ useTelegramHaptic ← haptic wrapper
│
├─ [state: loading] → TelegramLoadingState
├─ [state: unsupported] → TelegramUnsupportedState
├─ [state: unlinked] → TelegramUnlinkedState
└─ [state: linked]
├─ TelegramHeader
├─ TelegramTabBar (Home / Requests / Chat / Account)
│
├─ TelegramHomeView
├─ TelegramRequestsView → TelegramRequestDetailView
├─ TelegramChatView → TelegramChatThreadView
├─ TelegramAccountView
└─ [overlay] TelegramNewRequestView
The shell is a single-page, no-router design: all navigation (tabs, overlays, detail drilldowns) is pure React state in TelegramMiniAppView. window.location.assign is only used as a final escape hatch to the full web dashboard.
2. Launch Points
| Entry | Mechanism | startapp context |
|---|---|---|
| Bot profile | User opens bot → taps "Open App" | none |
| Menu button | Pinned button in any chat with the bot | none |
| Inline button | Bot sends a card with an embedded button | req_<requestId> |
| Direct deep link | https://t.me/AmanehBot/app?startapp=req_<id> |
req_<requestId> |
| Web fallback | Browser at /telegram |
none (unsupported state) |
startapp / tgWebAppStartParam is read from either the WebApp SDK (window.Telegram.WebApp) or from URL query/hash params (for older Telegram clients that append them directly).
3. SDK Initialisation & Context Probe
File: src/utils/telegram-webapp.ts · getTelegramContext()
The function assembles a TelegramContext object from:
window.Telegram.WebApp— primary SDK surface (available when the app is opened inside Telegram).- URL query/hash fallback —
tgWebAppStartParam,tgWebAppData,tgWebAppVersion,tgWebAppPlatform— used by older clients or during dev testing.
Fields extracted:
| Field | Source | Notes |
|---|---|---|
isMiniApp |
Any Telegram signal present | Drives unsupported vs unlinked state |
initData |
webApp.initData or tgWebAppData URL param |
HMAC-signed payload sent to /api/auth/telegram |
initDataUnsafe |
webApp.initDataUnsafe |
Client-side user identity (not trusted) |
safeArea |
contentSafeAreaInset or safe_area_insets |
Parsed to {top, right, bottom, left} in px |
theme |
webApp.themeParams |
Both camelCase and snake_case normalised |
platform |
webApp.platform or URL param |
e.g. ios, android, tdesktop |
startParam |
startapp / tgWebAppStartParam / start_param |
Deep-link context |
isUnsupported |
!webApp && Boolean(startParam) |
Partial signal — no SDK but has URL param |
Polling on mount (useTelegramLiveContext): Telegram sometimes finishes injecting the WebApp object after the first React render. The hook re-probes at 0 ms, 100 ms, 500 ms, and 1000 ms after mount, and also re-probes on hashchange events (triggered by the native back-button on some platforms).
4. Shell State Machine
getTelegramStatus(context, hasWebAccount) returns one of three states:
unsupported ─── !context.isMiniApp
(opened in browser, not Telegram)
unlinked ─────── isMiniApp && (!user || !telegramUser.id)
(inside Telegram but no JWT session linked)
linked ──────── isMiniApp && user && telegramUser.id
(authenticated, full shell rendered)
State transitions occur on:
- Auth session check completing (
loading → false) - Telegram auto sign-in completing (
tgAuthLoading → false) - Manual sign-in button tap (unlinked → linked)
5. Authentication Flow
5.1 Silent Auto Sign-In
Hook: useTelegramAutoSignIn · File: hooks/use-telegram-auto-sign-in.ts
On mount, if context.isMiniApp && context.initData && !user:
- Exchange
initDatafor a JWT by callingsignInWithTelegram({ initData })→POST /api/auth/telegram. - On success, call
checkUserSession()to refresh the auth context. - If the backend returns
isNewUser: true, showTelegramOnboardingSheet. - A
useRefdeduplication guard (attemptedInitDataRef) prevents re-runs under React Strict Mode's double-effect behaviour.
5.2 Manual Sign-In (Unlinked State)
When initData is present but auto sign-in failed (or hasn't run yet), TelegramUnlinkedState renders:
- Continue with Telegram — calls the same
signIn()function fromuseTelegramAutoSignIn. - Sign in with email —
window.location.assign(paths.auth.jwt.signIn). - Create an account —
window.location.assign(paths.auth.jwt.register).
When initData is absent (accessed via a path that skips Telegram context), only the email/register buttons appear.
5.3 Backend Endpoint
POST /api/auth/telegram — expects { initData: string }. Backend verifies the HMAC using the Telegram bot token, extracts user from the payload, upserts a User record (telegramId, telegramVerified: true), and issues a JWT + refresh token. Returns { token, refreshToken, isNewUser }.
6. Navigation Model
All navigation is in-shell React state — no Next.js router is involved.
activeTab : 'home' | 'requests' | 'chat' | 'account'
overlayScreen : 'new-request' | null
openConversationId : string | null
openRequestId : string | null
Priority rendering (first match wins):
openConversationId→TelegramChatThreadViewopenRequestId→TelegramRequestDetailViewoverlayScreen === 'new-request'→TelegramNewRequestViewactiveTab→ appropriate tab view
Back button (Telegram native BackButton) dismisses in reverse priority order: chat thread → request detail → overlay → returns to home tab.
BackButton visibility: shown whenever state === 'linked' and either an overlay/drilldown is active, or activeTab !== 'home'.
MainButton visibility: hidden while any overlay is open. When visible:
- Linked → "New Request" (opens
overlayScreen = 'new-request') - Unlinked → "Sign In" (navigates to the JWT sign-in page)
Both chrome buttons are styled with the amaneh saffron palette (color: #C2410C, text_color: #FFFFFF) via setParams (WebApp SDK >= 6.1).
7. Supported Flows
7.1 Browse Requests (Requests Tab)
TelegramRequestsViewfetches the user's purchase requests viauseTelegramMyRequests(GET/api/purchase-requests/my).- Displays a skeleton loader, then a scrollable list of
TelegramRequestRowitems. - Each row shows: title, status chip, budget, creation date.
- Tap → sets
openRequestId→ rendersTelegramRequestDetailView.
7.2 Request Detail with Stepper
TelegramRequestDetailViewfetches a single request viauseTelegramRequest.- Renders
TelegramRequestStepper— a visual timeline of the escrow status flow frompending_payment→completed. determineCurrentStepFromStatusmaps the currentstatusto a step index.- Also renders: budget, description, creation date, category, urgency.
- Dates formatted via
toLocaleDateStringwithfa-IRlocale for Persian.
7.3 Create New Request
TelegramNewRequestViewis a full-screen overlay (not a routed page).- Form fields: title, description, category (fetched from
/api/categories), budget min/max, urgency. - On submit: calls
createPurchaseRequest()→ POST/api/purchase-requests. - On success: closes overlay, switches
activeTabto'requests'. MainButtonis hidden while the overlay is open (submit lives in the form itself).
7.4 Chat
TelegramChatViewshows the user's active conversations viauseTelegramConversations.- Tap a row → sets
openConversationId→ rendersTelegramChatThreadView. TelegramChatThreadViewloads messages viauseTelegramChatThread, rendersTelegramChatBubbleitems, and includesTelegramChatComposerfor sending.- Optimistic send: message appears immediately, confirmed/rolled back on API response.
7.5 Account
TelegramAccountViewshows profile info (name, email, Telegram username,telegramVerifiedstatus), linked wallet (if any), and notification preferences.- Contains sign-out action and language toggle.
8. Bilingual Support (EN / FA)
Language detection priority (useTelegramLanguage):
localStoragekeyamn_tg_lang— user's persisted manual selection.initDataUnsafe.user.language_code— Telegram-reported language ("fa"or"fa-IR"→ Persian).- Fallback → English.
Language toggle: TelegramLanguageToggle in the header — two buttons [ EN | فا ]. On tap: haptic light + language switch + persist to localStorage.
RTL layout:
| Element | EN (LTR) | FA (RTL) |
|---|---|---|
Root dir attribute |
ltr |
rtl |
| Font family | IBM Plex Sans | Vazirmatn |
| Arrow icons | → |
← |
| Text alignment | left | right (inherits from dir) |
| Chip list wrap | left-to-right | right-to-left |
Font size bumps for Persian: body 13 px → 14 px, labels 10 px → 11 px (Vazirmatn renders optically smaller).
Translation structure:
// src/sections/telegram/locales/en.ts + fa.ts
const TR = {
en: { loading, unsupported, unlinked, header, home, requests,
chat, account, newRequest, tabs, main, onboarding, errors, displayName, dir },
fa: { /* same keys, Farsi strings, dir: 'rtl' */ },
};
All JSX uses t.<section>.<key> — no inline strings in components.
9. Design System
File: src/sections/telegram/constants.ts · src/sections/telegram/telegram-shell-css.ts
The Mini App has a distinct visual identity (cream/saffron Persian palette) that does not inherit from the main dashboard theme. All tokens are feature-scoped.
Palette: TG_PALETTE
| Token | Hex | Usage |
|---|---|---|
cream50 |
#FBF6EB |
Page background |
ink900 |
#1C1410 |
Primary text |
ink600 |
#6B5D4E |
Secondary text / labels |
saffron600 |
#C2410C |
Primary action, MainButton |
saffron500 |
#D97757 |
Hover states |
pistachio700 |
#3D6B4F |
Success / released states |
pomegranate700 |
#8E2424 |
Error / disputed states |
bgPage |
#E7DFCB |
Shell outer background |
Fonts: TG_FONTS — Source Serif 4 (headings), IBM Plex Sans (body LTR), Vazirmatn (body RTL), IBM Plex Mono (amounts/addresses).
CSS: buildTelegramShellCss() injects a <style> tag at shell root with all class utilities (.tg-chip, .tg-shell, .tg-tab-bar, .tg-header, etc.). Theme CSS variables (--cream-50, --ink-900, etc.) are set on .tg-shell root.
Safe area: getTelegramSafeAreaStyle(safeArea) maps the Telegram-reported safe area insets to CSS padding using max(${px}px, env(safe-area-inset-*)) to handle both Telegram-native and iOS/Android safe areas.
10. Telegram SDK Usage Patterns
10.1 Safe-Area Inset
// TelegramContext.safeArea = { top, right, bottom, left } (px)
// Source: webApp.contentSafeAreaInset || webApp.safe_area_insets
// Normalised to number via parseNumber() — rejects non-finite strings
const topInset = (context.safeArea?.top ?? 0) as number;
All views receive topInset / bottomInset props and add them as explicit paddingTop / paddingBottom to avoid content being obscured by the Telegram chrome.
10.2 Haptic Feedback
// useTelegramHaptic(webApp) → haptic('light' | 'medium')
webApp?.HapticFeedback?.impactOccurred?.(type)
Used on: tab switches (light), new-request CTA (medium), language toggle (light), back button (light). All calls are wrapped in try/catch — the API may be absent on older clients.
10.3 Back Button
useTelegramBackButton({ webApp, isVisible, onClick })
// Calls webApp.BackButton.show() / hide() and registers onClick handler
// Cleanup: offClick() on unmount / visibility change
10.4 Main Button
useTelegramMainButton({ webApp, isReady, text, onClick })
// Calls webApp.MainButton.show() / hide(), setText(), setParams()
// Saffron palette: color: '#C2410C', text_color: '#FFFFFF'
// setParams requires WebApp >= 6.1; silent fallback for older clients
10.5 Theme Integration
Telegram's themeParams is normalised (both camelCase and snake_case accepted) and injected as CSS custom properties on the shell root (--telegram-shell-bg, --telegram-shell-text, etc.). The amaneh palette overrides these for the Mini App's own UI, but components can reference them for adaptive behaviours.
11. Edge Cases
| Scenario | Detection | Handling |
|---|---|---|
| Opened in browser (not Telegram) | context.isMiniApp === false |
TelegramUnsupportedState — shows "Open in Telegram" badge, web dashboard link |
| Partial Telegram signal (URL params but no SDK) | !webApp && Boolean(startParam) → isUnsupported: true |
Same unsupported state |
| Telegram SDK injected late | useTelegramLiveContext polls at 0/100/500/1000 ms |
Re-probes until SDK is ready; seed context bypasses polling |
initData absent (no auth data) |
!context.initData in unlinked state |
Sign-in button triggers error string t.errors.no_init_data; email/create buttons remain available |
| Auto sign-in replay (React Strict Mode) | attemptedInitDataRef.current === context.initData |
Deduplication ref — second effect is a no-op |
| Backend sign-in failure | Catch block in useTelegramAutoSignIn |
Error string displayed in TelegramUnlinkedState; retry via "Continue with Telegram" |
| New user first login | result.isNewUser === true |
TelegramOnboardingSheet shown over the shell; dismissed to account settings or "Later" |
| Expired session inside Mini App | Auth context user === null after session check |
Shell falls back to unlinked state |
| Old Telegram client (< 6.1) | setParams throws |
Try/catch silences it; button shows without saffron colour |
| RTL + keyboard overlap | Viewport shrinks on soft keyboard open | flex: 1 + overflowY: auto on content area; bottom safe-area inset on tab bar |
| Persian locale date formatting | lang === 'fa' |
toLocaleDateString('fa-IR', ...) in formatDate helper |
12. File Map
src/
app/telegram/page.tsx # Next.js route (thin shell, no auth guard)
utils/telegram-webapp.ts # SDK probe, context types, shell style helpers
sections/telegram/
constants.ts # TG_PALETTE, TG_FONTS, TG_EASE, status maps
telegram-shell-css.ts # buildTelegramShellCss() — inlined CSS blob
index.ts # barrel
locales/
types.ts # TelegramDict, TelegramLang, TelegramTabId
en.ts # English strings
fa.ts # Persian strings
index.ts # getTelegramDict(lang)
hooks/
use-telegram-live-context.ts # SDK polling
use-telegram-language.ts # EN/FA detection + localStorage persist
use-telegram-auto-sign-in.ts # initData → JWT exchange
use-telegram-main-button.ts # MainButton lifecycle
use-telegram-back-button.ts # BackButton lifecycle
use-telegram-haptic.ts # HapticFeedback wrapper
use-telegram-my-requests.ts # GET /api/purchase-requests/my
use-telegram-request.ts # GET /api/purchase-requests/:id
use-telegram-conversations.ts # Chat conversation list
use-telegram-chat-thread.ts # Chat thread + optimistic send
index.ts
view/
telegram-mini-app-view.tsx # Shell orchestrator (all state lives here)
telegram-home-view.tsx # Home tab
telegram-requests-view.tsx # Requests list tab
telegram-request-detail-view.tsx # Request drilldown + stepper
telegram-new-request-view.tsx # New request overlay form
telegram-chat-view.tsx # Chat conversation list tab
telegram-chat-thread-view.tsx # Chat thread drilldown
telegram-account-view.tsx # Account + sign-out tab
index.ts
components/
telegram-header.tsx # AMN logo + subtitle + language toggle
telegram-tab-bar.tsx # Bottom tab bar (4 tabs)
telegram-welcome-banner.tsx # Home: escrow account banner + CTA
telegram-quick-actions.tsx # Home: action cards (Requests / Payments / Chat)
telegram-escrow-state-chips.tsx # Home: status chip legend
telegram-request-row.tsx # Requests: list row
telegram-request-stepper.tsx # Detail: visual escrow timeline
telegram-list-row.tsx # Generic list row primitive
telegram-list-skeleton.tsx # Skeleton loader for lists
telegram-chat-row.tsx # Chat: conversation list row
telegram-chat-bubble.tsx # Chat: message bubble
telegram-chat-composer.tsx # Chat: message input
telegram-loading-state.tsx # Loading spinner state
telegram-unlinked-state.tsx # Unlinked / sign-in prompt state
telegram-unsupported-state.tsx # Not-in-Telegram fallback state
telegram-onboarding-sheet.tsx # New-user onboarding bottom sheet
telegram-empty-state.tsx # Generic empty list state
telegram-language-toggle.tsx # EN | FA header toggle
telegram-bottom-sheet.tsx # Generic bottom sheet primitive
telegram-form-field.tsx # Form field + input style helper
telegram-seal-mark.tsx # SealMark logo component
telegram-icons.tsx # Telegram-scoped icon set
index.ts
13. Current Implementation Status
| Area | Status | Notes |
|---|---|---|
| Shell + state machine | Done | TelegramMiniAppView — all states wired |
| SDK probe + live context | Done | Polling + hashchange listener |
| Auto sign-in | Done | Deduped initData exchange |
| Manual sign-in (unlinked) | Done | Email + create account fallbacks |
| Bilingual EN/FA | Done | Full string inventory, RTL layout, Vazirmatn font |
| Language toggle | Done | Header toggle + localStorage persist |
| Home tab | Done | Banner + quick actions + state chips |
| Requests list | Done | API-backed with skeleton + empty states |
| Request detail + stepper | Done | Status timeline, budget, dates with fa-IR locale |
| New request form | Done | In-shell overlay, category fetch, validation |
| Chat list | Done | API-backed conversation list |
| Chat thread | Done | Messages + optimistic send |
| Account view | Done | Profile, wallet stub, sign-out |
| Telegram chrome (MainButton / BackButton) | Done | Saffron palette, lifecycle hooks |
| Haptic feedback | Done | All tap interactions |
| Safe area insets | Done | Normalised from SDK + CSS env() fallback |
Deep link startapp context |
Partial | Parsed but not yet used to auto-navigate to a request |
| Bilingual onboarding sheet | Done | Shown on isNewUser flag |
| Unsupported / browser fallback | Done | Web dashboard link |
| Bilingual PRD (Task 5.4 scope) | IN PROGRESS | String extraction done; ?lang=fa dev preview param pending |
Open Items (Task 5.4)
?lang=faURL override for browser dev preview (one-line addition touseTelegramLanguage).startappdeep link routing: ifcontext.startParammatchesreq_<id>, auto-openTelegramRequestDetailViewon first render.- Backend room-scoped Socket.IO for real-time chat updates (global socket event broadcast was fixed client-side in v2.8.4; server-side scoping is a follow-up).
14. Related Documents
- PRD - Telegram Mini App Bilingual (EN + FA) — bilingual string inventory and RTL layout spec
- PRD - Telegram Phone Number Authentication — phone-number auth as a future sign-in path
- Authentication Flow — JWT lifecycle shared with the Mini App auth
- Purchase Request Flow — escrow state machine surfaced in the stepper
- Chat Flow — real-time messaging that the Mini App embeds