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>
This commit is contained in:
Siavash Sameni
2026-06-03 10:29:48 +04:00
parent 6f13903644
commit d072238fe8
10 changed files with 1998 additions and 171 deletions

View File

@@ -0,0 +1,441 @@
---
title: Telegram Mini App Flow
tags: [flow, telegram, mini-app, auth, bilingual, RTL]
related_models: ["[[User]]"]
related_apis: ["POST /api/auth/telegram", "[[Auth API]]"]
task: "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 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):
1. `openConversationId``TelegramChatThreadView`
2. `openRequestId``TelegramRequestDetailView`
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_payment``completed`.
- `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:**
```ts
// 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
```ts
// 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
```ts
// 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
```ts
useTelegramBackButton({ webApp, isVisible, onClick })
// Calls webApp.BackButton.show() / hide() and registers onClick handler
// Cleanup: offClick() on unmount / visibility change
```
### 10.4 Main Button
```ts
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).
---
## 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