--- title: Telegram Mini App Flow tags: [flow, telegram, mini-app, auth, bilingual, RTL, shop, cart] 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.59 > **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, shop seller templates, manage a cart, 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 ├─ useTelegramCart ← shared localStorage cart │ ├─ [state: loading] → TelegramLoadingState ├─ [state: unsupported] → TelegramUnsupportedState ├─ [state: unlinked] → TelegramUnlinkedState └─ [state: linked] ├─ TelegramHeader ├─ TelegramTabBar (Home / Shop / Requests / Chat / Account) │ ├─ TelegramHomeView ├─ TelegramShopView → TelegramSellerShopView ├─ TelegramRequestsView → TelegramRequestDetailView ├─ TelegramChatView → TelegramChatThreadView ├─ TelegramAccountView └─ [overlay] TelegramNewRequestView └─ [overlay] TelegramNotificationsView └─ [overlay] TelegramCartView ``` 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_` | | Direct deep link | `https://t.me/AmanehBot/app?startapp=req_` | `req_` | | 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' | 'shop' | 'requests' | 'chat' | 'account' overlayScreen : 'new-request' | 'notifications' | 'cart' | null openConversationId : string | null openRequestId : string | null openSellerId : string | null ``` **Priority rendering** (first match wins): 1. `openConversationId` → `TelegramChatThreadView` 2. `openRequestId` → `TelegramRequestDetailView` 3. `openSellerId` → `TelegramSellerShopView` 4. `overlayScreen === 'cart'` → `TelegramCartView` 5. `overlayScreen === 'notifications'` → `TelegramNotificationsView` 6. `overlayScreen === 'new-request'` → `TelegramNewRequestView` 7. `activeTab` → appropriate tab view **Back button** (Telegram native `BackButton`) dismisses in reverse priority order: chat thread → request detail → seller shop → 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. Tab Structure The shell has **five bottom tabs** rendered by `TelegramTabBar`: | Tab | Icon | View | Purpose | |---|---|---|---| | Home | house | `TelegramHomeView` | Welcome banner, quick-action cards, escrow-state chips | | Shop | storefront | `TelegramShopView` | Sellers list; drill into seller store; add templates to cart | | Requests | list | `TelegramRequestsView` | User's escrow requests with status stepper | | Chat | speech bubble | `TelegramChatView` | Conversation list + support entry | | Account | person | `TelegramAccountView` | Profile, preferences, links to web dashboard sections | `handleTabSelect` clears all overlays and drill-down IDs before switching tab. --- ## 8. Supported Flows ### 8.1 Home Tab `TelegramHomeView` is the landing screen shown on first open. It contains: - **Welcome banner** (`TelegramWelcomeBanner`): escrow account summary, primary CTA. - **Quick-action cards** (`TelegramQuickActions`): shortcuts to Requests, Payments, Chat. - **Escrow state chips** (`TelegramEscrowStateChips`): legend of status values visible in the platform. ### 8.2 Shop Tab — Sellers List **`TelegramShopView`** (`telegram-shop-view.tsx`): - Fetches all sellers via `useTelegramShops()` → SWR wrapping `getTemplateSellers()` → `GET /api/request-templates/sellers`. - Renders `TelegramShopRow` per seller: avatar, name, rating, template count, sales count. - Shows a floating cart badge button in the header when `totalItems > 0`; tap opens `overlayScreen = 'cart'`. - Tap a seller row → sets `openSellerId` → navigates to `TelegramSellerShopView`. ### 8.3 Shop Tab — Seller Store **`TelegramSellerShopView`** (`telegram-seller-shop-view.tsx`): - Fetches seller + active templates via `useTelegramSellerShop(sellerId)` → `GET /api/request-templates/sellers/:id`. - Dark header: seller avatar, name, rating, template count, description. - Each template card shows: image, title, 2-line description, budget range, usage count. - **Two actions per template:** - **Add to cart / Remove from cart** — toggles item in `useTelegramCart` (localStorage, no API). Button is filled blue when not in cart, outline when added. - **Order this template** — `` to `/dashboard/request/from-template?shareableLink=...`. Exits the Mini App to the web dashboard (single-template direct order, bypasses cart). - Floating "Cart · N templates" sticky button at bottom when `totalItems > 0`; tap calls `onOpenCart()`. ### 8.4 Shopping Cart Overlay **`TelegramCartView`** (`telegram-cart-view.tsx`): - Rendered as `overlayScreen = 'cart'`; dismissed by Telegram BackButton. - Lists each cart item: image, name, seller name, USDT price × quantity, +/− quantity controls, remove button. - Subtotal/total in USDT, locale-formatted (`fa-IR` for Persian, `en-US` for English); amounts always `dir="ltr"`. - **"Continue to payment"** — plain `` link; exits Mini App to web checkout. **Cart storage (`useTelegramCart`):** - Reads/writes `localStorage` key **`app-request-template-checkout`** — the same key the web `RequestTemplateCheckoutProvider` reads. This is the cart handoff mechanism: the cart built in Telegram IS the cart the web checkout page hydrates. - Dispatches a custom `tg-cart-changed` DOM event on every write; listens on both that event and the native `storage` event so all open tabs stay in sync. - Operations: `addTemplate(template, seller)`, `removeItem(itemId)`, `changeQuantity(itemId, qty)`, `isInCart(templateId)`. - No API calls — cart is purely client-side until checkout. - Cart item model: `id`, `templateId`, `name`, `description`, `price` (from `template.budget.min`), `quantity`, `image`, `sellerId`, `sellerName`, `category`, `shareableLink`, `deliveryInfo`, `maxUsage`, `usageCount`, `remainingCapacity`. ### 8.5 Web Checkout Handoff Destination: `/dashboard/shops/checkout` — `RequestTemplateCheckoutView` wrapped by `RequestTemplateCheckoutProvider`. The provider reads the shared `localStorage` key and hydrates the TMA cart. The checkout is a 3-step stepper: | Step | Component | Description | |---|---|---| | 0 (Cart review) | `RequestTemplateCheckoutCart` | Item list, quantities, remove, totals, discount/shipping | | 1 (Address) | `RequestTemplateCheckoutBillingAddress` | Physical address or online delivery email | | 2 (Payment) | `RequestTemplateCheckoutPayment` | Wallet payment + socket confirmation | | Complete | `RequestTemplateCheckoutOrderComplete` | Confirmation dialog, cart reset | Payment execution calls `convertTemplatesToRequests()` to create escrow records, then awaits a `template-checkout-payment-confirmed` socket event. A guard checks `createdRequestIds` is non-empty before advancing (prevents stray global socket events from triggering premature completion). Stock validation clamps or removes items exceeding `remainingCapacity` before payment. ### 8.6 Browse Requests (Requests Tab) - `TelegramRequestsView` fetches the user's purchase requests via `useTelegramMyRequests` (GET `/api/requests`). - 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`. ### 8.7 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. ### 8.8 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). ### 8.9 Chat Tab - `TelegramChatView` shows the user's active conversations via `useTelegramConversations`. - Includes a Support row that calls `createSupportChat()` → `POST /api/chat/support`, then opens `TelegramChatThreadView` with the returned conversation ID. - Tap a conversation 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. - Real-time updates via Socket.IO events; SWR is mutated on `new-notification` and `unread-count-update` events. ### 8.10 Account Tab **`TelegramAccountView`** (`telegram-account-view.tsx`): The account tab has four sections. All user data is passed as props from the shell (loaded via `useAuthContext()` — no fetch on mount). **Profile header:** - Avatar (from `user.profile.avatar`, falls back to initials), full name, Telegram `@username`, role chip (buyer / seller / admin / resolver / guard). - Verification chips: "Telegram Verified" (if `user.telegramVerified`) and "Email Verified" (if `user.isEmailVerified`). **Preferences section:** - Language toggle (FA / EN, in-shell via `TelegramLanguageToggle`). - General Settings → `/dashboard/account` (web, labeled "Opens in the web dashboard"). - Wallet → truncated address (`0x1234…abcd`) or "not connected" → `/dashboard/account/wallet` (web). - Notifications → opens `TelegramNotificationsView` overlay in-shell. - Addresses → `/dashboard/account/address` (web). - Passkey → `/dashboard/account/passkey` (web). **Help section:** - Support → `createSupportChat()` → opens `TelegramChatThreadView` in-shell. - Terms & Conditions → placeholder, "coming soon". **Session section:** - Sign Out → `TelegramBottomSheet` confirmation dialog → `authSignOut()` + `window.location.assign(paths.auth.jwt.signIn)`. ### 8.11 Notifications Overlay - `TelegramNotificationsView` is rendered as `overlayScreen = 'notifications'`. - Fetches via `useTelegramNotifications` → `getNotifications(userId, 1, 50)` → `GET /api/notifications?userId=...&page=1&limit=50`. - Real-time updates: Socket.IO events `new-notification`, `unread-count-update` trigger SWR mutate. - "Mark all read" calls `markAllNotificationsAsRead(userId)` → `PATCH /api/notifications/mark-all-read`. --- ## 9. API Calls | Action | Hook / call | Backend endpoint | |---|---|---| | Auto sign-in | `useTelegramAutoSignIn` → `signInWithTelegram({initData})` | `POST /api/auth/telegram` | | Sellers list | `useTelegramShops` → `getTemplateSellers()` | `GET /api/request-templates/sellers` | | Seller + templates | `useTelegramSellerShop` → `getSellerWithTemplates(id)` | `GET /api/request-templates/sellers/:id` | | Marketplace sellers | `useTelegramSellers` → `getSellers()` | `GET /api/marketplace/sellers` | | My requests | `useTelegramMyRequests` | `GET /api/requests` | | Single request | `useTelegramRequest` | `GET /api/purchase-requests/:id` | | Create request | shell → `createPurchaseRequest()` | `POST /api/purchase-requests` | | Conversations | `useTelegramConversations` | `GET /api/chat/conversations` | | Chat thread | `useTelegramChatThread` | `GET /api/chat/:id` + Socket.IO real-time | | Support chat | `createSupportChat()` | `POST /api/chat/support` | | Notifications | `useTelegramNotifications` | `GET /api/notifications?userId=...&page=1&limit=50` | | Mark all read | `markAllNotificationsAsRead(userId)` | `PATCH /api/notifications/mark-all-read` | | Auth sign-out | `authSignOut()` | JWT sign-out endpoint | Cart operations (add/remove/quantity) are **pure localStorage** — no API calls until web checkout. --- ## 10. Bilingual Support (EN / FA) **Language detection priority** (`useTelegramLanguage`): 1. `?lang=` URL query param — dev preview override. 2. `localStorage` key `amn_tg_lang` — user's persisted manual selection. 3. `initDataUnsafe.user.language_code` — Telegram-reported language (`"fa"` or `"fa-IR"` → Persian). 4. 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 | | Amounts | always `dir="ltr"` | always `dir="ltr"` | 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, shop, requests, chat, account, newRequest, tabs, main, onboarding, errors, displayName, dir }, fa: { /* same keys, Farsi strings, dir: 'rtl' */ }, }; ``` All JSX uses `t.
.` — no inline strings in components. --- ## 11. 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 `