Files
nick-doc/04 - Flows/Telegram Mini App.md
Siavash Sameni d072238fe8 docs: update PG migration status, data models, architecture + add Telegram Mini App flow (v2.8.59)
- 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>
2026-06-03 10:30:51 +04:00

21 KiB

title, tags, related_models, related_apis, task
title tags related_models related_apis task
Telegram Mini App Flow
flow
telegram
mini-app
auth
bilingual
RTL
User
POST /api/auth/telegram
Auth API
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:

  1. window.Telegram.WebApp — primary SDK surface (available when the app is opened inside Telegram).
  2. 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:

  1. Exchange initData for a JWT by calling signInWithTelegram({ initData })POST /api/auth/telegram.
  2. On success, call checkUserSession() to refresh the auth context.
  3. If the backend returns isNewUser: true, show TelegramOnboardingSheet.
  4. A useRef deduplication 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 from useTelegramAutoSignIn.
  • Sign in with emailwindow.location.assign(paths.auth.jwt.signIn).
  • Create an accountwindow.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):

  1. openConversationIdTelegramChatThreadView
  2. openRequestIdTelegramRequestDetailView
  3. overlayScreen === 'new-request'TelegramNewRequestView
  4. activeTab → 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)

  • TelegramRequestsView fetches the user's purchase requests via useTelegramMyRequests (GET /api/purchase-requests/my).
  • Displays a skeleton loader, then a scrollable list of TelegramRequestRow items.
  • Each row shows: title, status chip, budget, creation date.
  • Tap → sets openRequestId → renders TelegramRequestDetailView.

7.2 Request Detail with Stepper

  • TelegramRequestDetailView fetches a single request via useTelegramRequest.
  • Renders TelegramRequestStepper — a visual timeline of the escrow status flow from pending_paymentcompleted.
  • determineCurrentStepFromStatus maps the current status to a step index.
  • Also renders: budget, description, creation date, category, urgency.
  • Dates formatted via toLocaleDateString with fa-IR locale for Persian.

7.3 Create New Request

  • TelegramNewRequestView is 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 activeTab to 'requests'.
  • MainButton is hidden while the overlay is open (submit lives in the form itself).

7.4 Chat

  • TelegramChatView shows the user's active conversations via useTelegramConversations.
  • Tap a row → sets openConversationId → renders TelegramChatThreadView.
  • TelegramChatThreadView loads messages via useTelegramChatThread, renders TelegramChatBubble items, and includes TelegramChatComposer for sending.
  • Optimistic send: message appears immediately, confirmed/rolled back on API response.

7.5 Account

  • TelegramAccountView shows profile info (name, email, Telegram username, telegramVerified status), linked wallet (if any), and notification preferences.
  • Contains sign-out action and language toggle.

8. Bilingual Support (EN / FA)

Language detection priority (useTelegramLanguage):

  1. localStorage key amn_tg_lang — user's persisted manual selection.
  2. initDataUnsafe.user.language_code — Telegram-reported language ("fa" or "fa-IR" → Persian).
  3. 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=fa URL override for browser dev preview (one-line addition to useTelegramLanguage).
  • startapp deep link routing: if context.startParam matches req_<id>, auto-open TelegramRequestDetailView on 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).