Files
nick-doc/04 - Flows/Telegram Mini App.md
Siavash Sameni e52ffce48a docs: sync vault with codebase state (2026-06-12)
- Update backend, frontend, scanner, deployment, amanat-assist service docs
- Update System Overview, Scanner Architecture, Telegram Mini App flow
- Update 10 - Services/README.md
- Add Tenant data model, Tenant API reference, Tenant Storefront Flow
- Add Multi-Shop Branch Project Scan (2026-06-10)
- Add tenant.md service doc
- Append activity log entry
- Reflects archived/search/stats route fix and new E2E test suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:42:18 +04:00

46 KiB
Raw Permalink Blame History

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
shop
cart
payment
User
POST /api/auth/telegram
Auth API
5.4

Last updated: 2026-06-12 Status: LARGELY COMPLETE — Task 5.4 core implementation done; open items are startapp deep-link auto-routing, backend Socket.IO room scoping, archived-chat surfacing, review-prompt integration, and cross-platform QA. Frontend branch: integrate-main-into-development · v2.8.94+ 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.

Two separate Mini Apps exist on this platform. This document covers the main marketplace Mini App (amn.gg/telegram) built inside the primary Next.js frontend. For the AI-assisted request-creation Mini App, see amanat-assist.


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 (disabled)
            ├─ useTelegramBackButton    ← native chrome sync
            ├─ useTelegramHaptic        ← haptic wrapper
            ├─ useTelegramCart          ← shared localStorage cart
            ├─ useTelegramNotifications ← unread badge count
            │
            ├─ [state: loading]         → TelegramLoadingState
            ├─ [state: unsupported]     → TelegramUnsupportedState
            ├─ [state: unlinked]        → TelegramUnlinkedState
            └─ [state: linked]
                 ├─ TelegramHeader
                 ├─ TelegramTabBar (Home / Shop / Requests / Chat / Account)
                 │
                 ├─ [drilldown] TelegramPaymentView        ← highest priority
                 ├─ [drilldown] TelegramChatThreadView
                 ├─ [drilldown] TelegramRequestDetailView
                 ├─ [drilldown] TelegramTemplateDetailView
                 ├─ [drilldown] TelegramSellerShopView
                 │
                 ├─ [overlay] TelegramPointsView
                 ├─ [overlay] TelegramSettingsView
                 ├─ [overlay] TelegramAddressesView
                 ├─ [overlay] TelegramCartView
                 ├─ [overlay] TelegramCheckoutView
                 ├─ [overlay] TelegramNotificationsView
                 ├─ [overlay] TelegramNewRequestView
                 │
                 ├─ TelegramHomeView
                 ├─ TelegramShopView → TelegramSellerShopView
                 ├─ TelegramRequestsView → TelegramRequestDetailView
                 ├─ TelegramChatView → TelegramChatThreadView
                 └─ TelegramAccountView

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 used only as a final escape hatch to external URLs. openTelegramExternalLink is used for deep links into the web dashboard, which opens inside Telegram's WebView or an external browser depending on the Telegram client.


2. Launch Points

Entry Mechanism startapp context Result
Bot profile User opens bot → taps "Open App" none Shell loads at Home tab
Menu button Pinned button in any chat with the bot none Shell loads at Home tab
Inline button Bot sends a message card with an embedded button req_<requestId> Shell loads; request deep-link (see below)
Direct deep link https://t.me/AmanehBot/app?startapp=req_<id> req_<requestId> Shell loads; request deep-link (see below)
Web fallback Browser at /telegram (no Telegram SDK) none TelegramUnsupportedState — "Open in Telegram" prompt + web dashboard link

2.1 startapp Context Parsing

startapp / tgWebAppStartParam is read from two sources in priority order:

  1. window.Telegram.WebApp.initDataUnsafe.start_param — primary source when the SDK is injected.
  2. URL query/hash params (tgWebAppStartParam) — fallback for older Telegram clients that append params directly to the URL.

Both are normalised into context.startParam by getTelegramContext() in src/utils/telegram-webapp.ts.

When context.startParam matches req_<requestId>, the intent is to auto-open TelegramRequestDetailView for that request on first render. This routing is not yet wiredstartParam is parsed and available in context but the shell does not yet act on it. This is open item #1 in section 16.


3. amanat-assist vs Main Mini App

Two distinct Telegram Mini Apps exist for this platform:

Property Main Mini App (this doc) amanat-assist
URL amn.gg/telegram assist.amn.gg
Bot AmanehBot AmanehBot (same)
Codebase frontend/ (Next.js, src/sections/telegram/) /amanat-assist (React + Vite, separate repo)
Purpose Full marketplace shell: browse, buy, sell, chat, manage account Conversational LLM wizard to create one purchase request
LLM None Mistral → DeepSeek fallback (via amanat-llm-proxy on port 3001)
Backend access Direct calls to api.amn.gg Proxied through amanat-llm-proxy which holds the LLM API keys
Auth Telegram initDataPOST /api/auth/telegram Same endpoint; also supports web redirect via ?access_token=
Deep links between apps Main Mini App has "New Request" overlay with an "Open Assist" CTA that navigates window.location.href to assist.amn.gg?access_token=... Assist submits the finished request then the user returns to the main app
Status In production Live at assist.amn.gg v1.1.0

Hand-off from main app to assist: handleOpenAssist() in TelegramMiniAppView constructs a URL to https://assist.amn.gg with access_token, user_json, theme, and source=miniapp query params. window.location.href is used (not openLink) to keep the navigation inside Telegram's WebView rather than opening Safari on iOS.


4. 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).


5. 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)

6. Authentication Flow

6.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.

6.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.

6.3 Backend Endpoint

POST /api/auth/telegram — expects { initData: string }.

Verification steps (backend):

  1. Parse the initData query string into key-value pairs.
  2. Extract hash from the pairs; remove it from the set.
  3. Build the data-check string: sort remaining pairs alphabetically, join as key=value\n.
  4. Compute HMAC-SHA256(data_check_string, HMAC-SHA256("WebAppData", TELEGRAM_BOT_TOKEN)).
  5. Compare computed hash with the extracted hash — reject with 401 on mismatch.
  6. Parse user JSON from initDataUnsafe; upsert User record with telegramId, telegramVerified: true.
  7. Issue JWT + refresh token. Return { token, refreshToken, isNewUser }.

Registered at authRoutes.ts line 24: router.post("/telegram", ctrl.telegramAuth.bind(ctrl)) — public route, no auth middleware required (HMAC is the authentication proof).

6.4 Session Linking (Telegram ↔ Amaneh Account)

The POST /api/auth/telegram endpoint both creates and links accounts:

  • New Telegram user, no existing Amanat account: a new User is created with telegramId set; isNewUser: true is returned and the onboarding sheet is shown.
  • Existing Amanat account with the same telegramId: the existing user is returned; session continues.
  • Existing Amanat account that has never used Telegram: telegramId and telegramVerified: true are written onto the existing record (matched by Telegram user id).

After the JWT is issued the standard checkUserSession() re-hydrates the React auth context. The Mini App shell reads user.telegramVerified and user.isEmailVerified from this context to render verification chips in the Account tab.


7. 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' | 'checkout'
                        | 'points' | 'settings' | 'addresses' | null
openConversationId    : string | null
openRequestId         : string | null
openPaymentRequestId  : string | null   ← payment drilldown (highest priority)
paymentCheckoutFlow   : boolean         ← true when reached from shop checkout
openSellerId          : string | null
openTemplate          : { template, seller } | null

Priority rendering (first match wins):

  1. openPaymentRequestIdTelegramPaymentView ← new, highest priority
  2. openConversationIdTelegramChatThreadView
  3. openRequestIdTelegramRequestDetailView
  4. openTemplateTelegramTemplateDetailView ← new
  5. openSellerIdTelegramSellerShopView
  6. overlayScreen === 'points'TelegramPointsView ← new
  7. overlayScreen === 'settings'TelegramSettingsView ← new
  8. overlayScreen === 'addresses'TelegramAddressesView ← new
  9. overlayScreen === 'cart'TelegramCartView
  10. overlayScreen === 'checkout'TelegramCheckoutView ← new (replaces web handoff)
  11. overlayScreen === 'notifications'TelegramNotificationsView
  12. overlayScreen === 'new-request'TelegramNewRequestView
  13. activeTab → appropriate tab view

Back button (Telegram native BackButton) dismisses in reverse priority order:

  • Payment drilldown → if paymentCheckoutFlow, steps back to cart; otherwise clears payment state.
  • Chat thread → clears openConversationId.
  • Request detail → clears openRequestId.
  • Template detail → clears openTemplate.
  • Seller shop → clears openSellerId.
  • Overlay (checkout steps back to cart) → clears overlayScreen.
  • Non-home tab → returns to home.

BackButton visibility: shown whenever state === 'linked' and either an overlay/drilldown is active, or activeTab !== 'home'.

MainButton visibility: intentionally disabled (isReady: false) — the native Telegram MainButton cannot use the project font and duplicates in-shell CTAs, so it is kept hidden. All primary actions live inside the shell UI itself.

Both chrome buttons retain the amaneh saffron palette (color: #C2410C, text_color: #FFFFFF) via setParams (WebApp SDK >= 6.1) as a fallback should the MainButton ever be re-enabled.


8. 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.


9. Supported Flows

9.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.
  • "New Request" CTA → opens overlayScreen = 'new-request'.
  • "Open Assist" CTA → calls handleOpenAssist() to navigate to assist.amn.gg in the same WebView (see section 3).

9.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 (TelegramCartFab) in the header when totalItems > 0; tap opens overlayScreen = 'cart'.
  • Tap a seller row → sets openSellerId → navigates to TelegramSellerShopView.

9.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).
    • View template details — sets openTemplate → navigates to TelegramTemplateDetailView.
  • Floating "Cart · N templates" sticky button at bottom when totalItems > 0; tap calls onOpenCart().

9.4 Shop Tab — Template Detail

TelegramTemplateDetailView (telegram-template-detail-view.tsx):

  • Full-screen view of a single template.
  • Shows full description, seller info, price, delivery info, usage/capacity counters.
  • Add/remove cart action; direct "Order this template" link to /dashboard/request/from-template?shareableLink=... (exits to web dashboard).
  • Back button returns to the seller store (openTemplate cleared, openSellerId retained).

9.5 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" → calls onCheckout() which sets overlayScreen = 'checkout' (in-shell checkout, replacing the previous web handoff).

Cart storage (useTelegramCart):

  • Reads/writes localStorage key app-request-template-checkout — the same key the web RequestTemplateCheckoutProvider reads. This enables the web dashboard checkout to hydrate the same cart.
  • 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.

9.6 In-Shell Checkout Overlay

TelegramCheckoutView (telegram-checkout-view.tsx):

  • Rendered as overlayScreen = 'checkout'; BackButton steps back to overlayScreen = 'cart'.
  • A 3-step stepper running entirely inside the Mini App shell:
    • Step 0 (Cart review): item list, quantities, totals, discount.
    • Step 1 (Address): physical address or online delivery email.
    • Step 2 (Payment): wallet-based payment execution.
  • On successful order (onPlaced(reqId) callback):
    • If a reqId is returned, sets paymentCheckoutFlow = true and openPaymentRequestId = reqId → immediately opens the payment view.
    • If no reqId, switches activeTab to 'requests'.
  • Stock validation clamps or removes items exceeding remainingCapacity before payment.
  • Integrates with onManageAddresses() to open the addresses overlay mid-flow.

9.7 Payment View (In-Shell)

TelegramPaymentView (telegram-payment-view.tsx):

  • Highest-priority drilldown (rendered before all other overlays).
  • Loaded for a specific requestId. Used from two entry points:
    • Shop checkout flow (paymentCheckoutFlow = true): after TelegramCheckoutView creates the requests. Shows a 3-step progress header (cart → address → payment).
    • Requests tab (paymentCheckoutFlow = false): buyer taps "Pay" on an existing request. No progress header.
  • Fetches request details via useTelegramRequest.
  • Fetches offers via useTelegramOffers.
  • Calls getPaymentOptions()GET /api/payment/options and createDirectBalanceIntent()POST /api/payment/direct-balance.
  • Polls checkDirectBalancePayment() for confirmation.
  • On successful payment: calls onPaid() → clears openPaymentRequestId, switches to activeTab = 'requests'.
  • Back button: if paymentCheckoutFlow, steps back to overlayScreen = 'cart'; otherwise clears the payment state.

9.8 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.

9.9 Request Detail with Stepper and Offers

  • 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.
  • Offer review: fetches offers via useTelegramOffers; renders offer cards with seller info, price, and accept/reject actions.
  • Pay action: renders a "Pay" button when request is in a payable state → calls onPay(id) → sets openPaymentRequestId.
  • Web fallback: "View full details" → openTelegramExternalLink(context.webApp, path).
  • Chat seller: taps the seller chat icon → calls onChatSeller(sellerId)createConversation + sets openConversationId.
  • Role-aware: role prop is 'seller' or 'buyer' based on user.role.
  • Dates formatted via toLocaleDateString with fa-IR locale for Persian.

9.10 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.
  • Includes an "Open Assist" button that delegates to handleOpenAssist() for users who prefer the conversational LLM flow.
  • On submit: calls createPurchaseRequest() → POST /api/purchase-requests.
  • On success: closes overlay, switches activeTab to 'requests'.

9.11 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.

9.12 Account Tab

TelegramAccountView (telegram-account-view.tsx):

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).
  • Settings → opens overlayScreen = 'settings' (in-shell TelegramSettingsView).
  • Points → opens overlayScreen = 'points' (in-shell TelegramPointsView).
  • Wallet → truncated address (0x1234…abcd) or "not connected" → /dashboard/account/wallet (web via openTelegramExternalLink).
  • Notifications → opens TelegramNotificationsView overlay in-shell.
  • Addresses → opens overlayScreen = 'addresses' (in-shell TelegramAddressesView).
  • 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).

9.13 Settings Overlay

TelegramSettingsView (telegram-settings-view.tsx):

  • Rendered as overlayScreen = 'settings'.
  • Allows editing profile fields (name, bio) in-shell.
  • On save: calls onSaved() which triggers checkUserSession() to refresh the auth context.

9.14 Addresses Overlay

TelegramAddressesView (telegram-addresses-view.tsx):

  • Rendered as overlayScreen = 'addresses'.
  • Fetches addresses via use-telegram-addresses.ts.
  • Used both from the Account tab and as a mid-flow step from TelegramCheckoutView.

9.15 Points Overlay

TelegramPointsView (telegram-points-view.tsx):

  • Rendered as overlayScreen = 'points'.
  • Fetches user points via use-telegram-points.ts.
  • Shows points balance and transaction history.

9.16 Dispute Surface

The Mini App does not yet have a dedicated dispute-filing view. Dispute access is handled via two escape hatches:

  • Request detail "View full details" link (openTelegramExternalLink) — opens the web dashboard request detail page where dispute filing is available.
  • Support chat — buyer or seller can reach a support agent from the Account tab or the Home tab quick-action cards; the support agent can escalate to a formal dispute.

A native in-shell dispute flow (matching the web dashboard DisputeView) is planned but not yet implemented. This is a known gap for the Task 5.4 feature surface.

9.17 Notifications Overlay

  • TelegramNotificationsView is rendered as overlayScreen = 'notifications'.
  • Fetches via useTelegramNotificationsgetNotifications(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.
  • Unread count is also surfaced in the TelegramHeader bell icon badge.

10. API Calls

Action Hook / call Backend endpoint
Auto sign-in useTelegramAutoSignInsignInWithTelegram({initData}) POST /api/auth/telegram
Sellers list useTelegramShopsgetTemplateSellers() GET /api/request-templates/sellers
Seller + templates useTelegramSellerShopgetSellerWithTemplates(id) GET /api/request-templates/sellers/:id
Marketplace sellers useTelegramSellersgetSellers() 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
Offers for request useTelegramOffersgetOffers(requestId) GET /api/marketplace/offers?requestId=...
Payment options getPaymentOptions() GET /api/payment/options
Create payment intent createDirectBalanceIntent() POST /api/payment/direct-balance
Poll payment status checkDirectBalancePayment() GET /api/payment/:id
Update request status updateRequestStatus() PATCH /api/marketplace/requests/:id/status
Conversations useTelegramConversations GET /api/chat/conversations
Chat thread useTelegramChatThread GET /api/chat/:id + Socket.IO real-time
Support chat createSupportChat() POST /api/chat/support
Direct conversation createConversation({ type: 'direct', participantIds }) POST /api/chat/conversations
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
Addresses use-telegram-addresses.ts GET /api/user/addresses
Points use-telegram-points.ts GET /api/user/points

Cart operations (add/remove/quantity) are pure localStorage — no API calls until checkout.

Dispute endpoints (POST /api/disputes, GET /api/disputes/:id) are not yet called from the Mini App shell — dispute access is delegated to the web dashboard via openTelegramExternalLink.


11. 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:

// 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.<section>.<key> — no inline strings in components.


12. 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
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. Dark mode: .tg-shell--dark class toggled from themeScheme.

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.


13. Telegram SDK Usage Patterns

13.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.

13.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), payment actions (medium). All calls are wrapped in try/catch — the API may be absent on older clients.

13.3 Back Button

useTelegramBackButton({ webApp, isVisible, onClick })
// Calls webApp.BackButton.show() / hide() and registers onClick handler
// Cleanup: offClick() on unmount / visibility change

13.4 Main Button

useTelegramMainButton({ webApp, isReady: false, text: '', onClick: mainButtonAction })
// isReady is always false — MainButton is intentionally kept hidden.
// The hook is retained so it can be re-enabled without structural changes.
openTelegramExternalLink(context.webApp, path)
// Uses webApp.openLink() for fully external URLs (opens browser).
// Uses window.location.href for same-origin navigation that must stay in WebView.

13.6 Theme Integration

Telegram's themeParams is normalised (both camelCase and snake_case accepted) and injected as CSS custom properties on the shell root. The amaneh palette overrides these for the Mini App's own UI.


14. 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
Cart cross-tab sync Multiple tabs / Mini App + web tg-cart-changed DOM event + storage event both trigger re-render
Template at capacity remainingCapacity === 0 at checkout Stock validation clamps/removes over-capacity items before payment
Payment from shop checkout paymentCheckoutFlow === true BackButton steps back to cart; progress header shows 3-step flow
Display name resolution User may have no name set in DB Falls back to Telegram profile name (first_name / last_name), then generic label
Seller chat from request detail onChatSeller(sellerId) createConversation({ type: 'direct', participantIds: [sellerId] }) → opens chat thread in-shell
Assist hand-off on iOS webApp.openLink() opens Safari window.location.href used instead to keep navigation in the Telegram WebView

15. 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
    avatar-url.ts                              # avatar URL helper
    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 + ?lang= + localStorage persist
      use-telegram-auto-sign-in.ts             # initData → JWT exchange
      use-telegram-main-button.ts              # MainButton lifecycle (kept, isReady=false)
      use-telegram-back-button.ts              # BackButton lifecycle
      use-telegram-haptic.ts                   # HapticFeedback wrapper
      use-telegram-cart.ts                     # localStorage cart (shared with web checkout)
      use-telegram-theme.ts                    # dark/light theme detection
      use-telegram-realtime.ts                 # shared Socket.IO real-time helper
      use-telegram-shops.ts                    # GET /api/request-templates/sellers
      use-telegram-seller-shop.ts              # GET /api/request-templates/sellers/:id
      use-telegram-sellers.ts                  # GET /api/marketplace/sellers
      use-telegram-my-requests.ts              # GET /api/requests
      use-telegram-request.ts                  # GET /api/purchase-requests/:id
      use-telegram-offers.ts                   # GET /api/marketplace/offers?requestId=...
      use-telegram-conversations.ts            # Chat conversation list
      use-telegram-chat-thread.ts              # Chat thread + optimistic send
      use-telegram-notifications.ts            # GET /api/notifications
      use-telegram-addresses.ts               # GET /api/user/addresses
      use-telegram-points.ts                   # GET /api/user/points
      index.ts
    view/
      telegram-mini-app-view.tsx               # Shell orchestrator (all state lives here)
      telegram-home-view.tsx                   # Home tab
      telegram-shop-view.tsx                   # Shop tab — sellers list
      telegram-seller-shop-view.tsx            # Seller store drill-down + cart actions
      telegram-template-detail-view.tsx        # Template full detail + cart/order actions
      telegram-cart-view.tsx                   # Cart overlay
      telegram-checkout-view.tsx               # In-shell 3-step checkout overlay
      telegram-payment-view.tsx                # In-shell payment drilldown
      telegram-requests-view.tsx               # Requests list tab
      telegram-request-detail-view.tsx         # Request drilldown + stepper + offers
      telegram-new-request-view.tsx            # New request overlay form + Assist CTA
      telegram-chat-view.tsx                   # Chat conversation list tab
      telegram-chat-thread-view.tsx            # Chat thread drilldown
      telegram-archived-chats-view.tsx         # Archived conversations
      telegram-account-view.tsx                # Account + preferences + sign-out tab
      telegram-notifications-view.tsx          # Notifications overlay
      telegram-settings-view.tsx               # In-shell profile/settings overlay
      telegram-addresses-view.tsx              # In-shell address management overlay
      telegram-points-view.tsx                 # In-shell points/loyalty overlay
      index.ts
    components/
      telegram-header.tsx                      # AMN logo + subtitle + language toggle + bell
      telegram-tab-bar.tsx                     # Bottom tab bar (5 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-shop-row.tsx                    # Shop: seller list row
      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-list-controls.tsx               # List sort/filter controls
      telegram-chat-row.tsx                    # Chat: conversation list row
      telegram-chat-bubble.tsx                 # Chat: message bubble
      telegram-chat-composer.tsx               # Chat: message input
      telegram-review-prompt.tsx               # Post-transaction review prompt
      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-theme-toggle.tsx                # Dark / light theme toggle
      telegram-bottom-sheet.tsx                # Generic bottom sheet primitive
      telegram-form-field.tsx                  # Form field + input style helper
      telegram-cart-fab.tsx                    # Floating cart badge button
      telegram-support-fab.tsx                 # Floating support chat button
      telegram-seal-mark.tsx                   # SealMark logo component
      telegram-icons.tsx                       # Telegram-scoped icon set
      index.ts

16. 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
?lang= dev preview param Done URL param override
Dark mode Done .tg-shell--dark class, use-telegram-theme
Home tab Done Banner + quick actions + state chips + Assist CTA
Shop tab — sellers list Done API-backed with skeleton + empty states, cart FAB
Shop tab — seller store Done Templates list, add/remove cart, template detail drilldown
Template detail drilldown Done Full detail, cart/order actions
Shopping cart (localStorage) Done Shared key with web checkout; cross-tab sync
Cart overlay Done Quantity controls, remove, total, in-shell checkout CTA
In-shell checkout Done 3-step cart→address→payment; replaces web handoff
In-shell payment view Done Direct balance intent + polling; checkout-flow back-nav
Requests list Done API-backed with skeleton + empty states
Request detail + stepper Done Status timeline, budget, dates with fa-IR locale
Offer review in request detail Done Offers fetched via useTelegramOffers; accept/reject
New request form Done In-shell overlay, category fetch, validation, Assist CTA
Chat list Done API-backed conversation list + support row
Chat thread Done Messages + optimistic send + Socket.IO real-time
Direct seller chat Done createConversation from request detail
Account tab Done Profile, preferences, help, sign-out
Settings overlay Done In-shell profile editing
Addresses overlay Done In-shell address management; reachable from checkout
Points overlay Done In-shell points/loyalty
Notifications overlay Done API-backed; Socket.IO real-time; mark-all-read
Notifications unread badge Done Bell icon in header
Telegram chrome (BackButton) Done Full back-stack with checkout flow awareness
Telegram MainButton Disabled Intentionally hidden (isReady: false); hook retained
Haptic feedback Done All tap interactions
Safe area insets Done Normalised from SDK + CSS env() fallback
amanat-assist integration Done "Open Assist" CTA in Home + New Request; window.location hand-off with access_token
Deep link startapp routing Partial startParam parsed; auto-navigation to specific request not yet wired
Backend room-scoped Socket.IO Partial Global socket broadcast fixed client-side (v2.8.4); server-side room scoping is a follow-up
Dispute filing (in-shell) Not started Escape hatch via web dashboard link + support chat; native view planned
Review prompt integration Partial TelegramReviewPrompt component exists; trigger point (post-payment/delivery) not yet wired
Archived chats Partial TelegramArchivedChatsView exists; not yet surfaced in navigation
Client matrix QA (iOS/Android/Desktop) Pending Needs cross-platform testing pass

Open Items

  1. startapp deep link routing: if context.startParam matches req_<id>, auto-open TelegramRequestDetailView on first render. startParam is already parsed and available in context; the shell needs a one-time effect on mount to act on it.
  2. Backend room-scoped Socket.IO: server-side scoping for real-time chat and notification events (follow-up from client-side provider gate in v2.8.4 that fixed global cart-wipe).
  3. In-shell dispute filing: add TelegramDisputeView matching the web dashboard dispute surface; currently only accessible via openTelegramExternalLink escape hatch.
  4. Review prompt: wire TelegramReviewPrompt to trigger after payment confirmation or delivery acknowledgement.
  5. Archived chats: surface TelegramArchivedChatsView from TelegramChatView (e.g. an "Archived" row at the bottom of the conversation list).
  6. Client matrix QA: iOS Telegram, Android Telegram, Telegram Desktop, and web clients all need a full feature pass with particular attention to safe-area insets and BackButton behaviour on each platform.