- 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>
46 KiB
title, tags, related_models, related_apis, task
| title | tags | related_models | related_apis | task | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Telegram Mini App Flow |
|
|
|
5.4 |
Last updated: 2026-06-12 Status: LARGELY COMPLETE — Task 5.4 core implementation done; open items are
startappdeep-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:
window.Telegram.WebApp.initDataUnsafe.start_param— primary source when the SDK is injected.- 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.
2.2 startapp Deep-Link Routing (Partial)
When context.startParam matches req_<requestId>, the intent is to auto-open TelegramRequestDetailView for that request on first render. This routing is not yet wired — startParam 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 initData → POST /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:
window.Telegram.WebApp— primary SDK surface (available when the app is opened inside Telegram).- URL query/hash fallback —
tgWebAppStartParam,tgWebAppData,tgWebAppVersion,tgWebAppPlatform— used by older clients or during dev testing.
Fields extracted:
| Field | Source | Notes |
|---|---|---|
isMiniApp |
Any Telegram signal present | Drives unsupported vs unlinked state |
initData |
webApp.initData or tgWebAppData URL param |
HMAC-signed payload sent to /api/auth/telegram |
initDataUnsafe |
webApp.initDataUnsafe |
Client-side user identity (not trusted) |
safeArea |
contentSafeAreaInset or safe_area_insets |
Parsed to {top, right, bottom, left} in px |
theme |
webApp.themeParams |
Both camelCase and snake_case normalised |
platform |
webApp.platform or URL param |
e.g. ios, android, tdesktop |
startParam |
startapp / tgWebAppStartParam / start_param |
Deep-link context |
isUnsupported |
!webApp && Boolean(startParam) |
Partial signal — no SDK but has URL param |
Polling on mount (useTelegramLiveContext): Telegram sometimes finishes injecting the WebApp object after the first React render. The hook re-probes at 0 ms, 100 ms, 500 ms, and 1000 ms after mount, and also re-probes on hashchange events (triggered by the native back-button on some platforms).
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:
- Exchange
initDatafor a JWT by callingsignInWithTelegram({ initData })→POST /api/auth/telegram. - On success, call
checkUserSession()to refresh the auth context. - If the backend returns
isNewUser: true, showTelegramOnboardingSheet. - A
useRefdeduplication guard (attemptedInitDataRef) prevents re-runs under React Strict Mode's double-effect behaviour.
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 fromuseTelegramAutoSignIn. - Sign in with email —
window.location.assign(paths.auth.jwt.signIn). - Create an account —
window.location.assign(paths.auth.jwt.register).
When initData is absent (accessed via a path that skips Telegram context), only the email/register buttons appear.
6.3 Backend Endpoint
POST /api/auth/telegram — expects { initData: string }.
Verification steps (backend):
- Parse the
initDataquery string into key-value pairs. - Extract
hashfrom the pairs; remove it from the set. - Build the data-check string: sort remaining pairs alphabetically, join as
key=value\n. - Compute
HMAC-SHA256(data_check_string, HMAC-SHA256("WebAppData", TELEGRAM_BOT_TOKEN)). - Compare computed hash with the extracted
hash— reject with 401 on mismatch. - Parse
userJSON frominitDataUnsafe; upsertUserrecord withtelegramId,telegramVerified: true. - 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
Useris created withtelegramIdset;isNewUser: trueis 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:
telegramIdandtelegramVerified: trueare 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):
openPaymentRequestId→TelegramPaymentView← new, highest priorityopenConversationId→TelegramChatThreadViewopenRequestId→TelegramRequestDetailViewopenTemplate→TelegramTemplateDetailView← newopenSellerId→TelegramSellerShopViewoverlayScreen === 'points'→TelegramPointsView← newoverlayScreen === 'settings'→TelegramSettingsView← newoverlayScreen === 'addresses'→TelegramAddressesView← newoverlayScreen === 'cart'→TelegramCartViewoverlayScreen === 'checkout'→TelegramCheckoutView← new (replaces web handoff)overlayScreen === 'notifications'→TelegramNotificationsViewoverlayScreen === 'new-request'→TelegramNewRequestViewactiveTab→ 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 (
checkoutsteps back tocart) → clearsoverlayScreen. - 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 toassist.amn.ggin the same WebView (see section 3).
9.2 Shop Tab — Sellers List
TelegramShopView (telegram-shop-view.tsx):
- Fetches all sellers via
useTelegramShops()→ SWR wrappinggetTemplateSellers()→GET /api/request-templates/sellers. - Renders
TelegramShopRowper seller: avatar, name, rating, template count, sales count. - Shows a floating cart badge button (
TelegramCartFab) in the header whentotalItems > 0; tap opensoverlayScreen = 'cart'. - Tap a seller row → sets
openSellerId→ navigates toTelegramSellerShopView.
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 toTelegramTemplateDetailView.
- Add to cart / Remove from cart — toggles item in
- Floating "Cart · N templates" sticky button at bottom when
totalItems > 0; tap callsonOpenCart().
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 (
openTemplatecleared,openSellerIdretained).
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-IRfor Persian,en-USfor English); amounts alwaysdir="ltr". - "Continue to payment" → calls
onCheckout()which setsoverlayScreen = 'checkout'(in-shell checkout, replacing the previous web handoff).
Cart storage (useTelegramCart):
- Reads/writes
localStoragekeyapp-request-template-checkout— the same key the webRequestTemplateCheckoutProviderreads. This enables the web dashboard checkout to hydrate the same cart. - Dispatches a custom
tg-cart-changedDOM event on every write; listens on both that event and the nativestorageevent 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 tooverlayScreen = '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
reqIdis returned, setspaymentCheckoutFlow = trueandopenPaymentRequestId = reqId→ immediately opens the payment view. - If no
reqId, switchesactiveTabto'requests'.
- If a
- Stock validation clamps or removes items exceeding
remainingCapacitybefore payment. - Integrates with
onManageAddresses()to open theaddressesoverlay 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): afterTelegramCheckoutViewcreates 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.
- Shop checkout flow (
- Fetches request details via
useTelegramRequest. - Fetches offers via
useTelegramOffers. - Calls
getPaymentOptions()→GET /api/payment/optionsandcreateDirectBalanceIntent()→POST /api/payment/direct-balance. - Polls
checkDirectBalancePayment()for confirmation. - On successful payment: calls
onPaid()→ clearsopenPaymentRequestId, switches toactiveTab = 'requests'. - Back button: if
paymentCheckoutFlow, steps back tooverlayScreen = 'cart'; otherwise clears the payment state.
9.8 Browse Requests (Requests Tab)
TelegramRequestsViewfetches the user's purchase requests viauseTelegramMyRequests(GET/api/requests).- Displays a skeleton loader, then a scrollable list of
TelegramRequestRowitems. - Each row shows: title, status chip, budget, creation date.
- Tap → sets
openRequestId→ rendersTelegramRequestDetailView.
9.9 Request Detail with Stepper and Offers
TelegramRequestDetailViewfetches a single request viauseTelegramRequest.- Renders
TelegramRequestStepper— a visual timeline of the escrow status flow frompending_payment→completed. determineCurrentStepFromStatusmaps the currentstatusto a step index.- Also renders: budget, description, creation date, category, urgency.
- 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)→ setsopenPaymentRequestId. - Web fallback: "View full details" →
openTelegramExternalLink(context.webApp, path). - Chat seller: taps the seller chat icon → calls
onChatSeller(sellerId)→createConversation+ setsopenConversationId. - Role-aware:
roleprop is'seller'or'buyer'based onuser.role. - Dates formatted via
toLocaleDateStringwithfa-IRlocale for Persian.
9.10 Create New Request
TelegramNewRequestViewis a full-screen overlay (not a routed page).- Form fields: title, description, category (fetched from
/api/categories), budget min/max, urgency. - 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
activeTabto'requests'.
9.11 Chat Tab
TelegramChatViewshows the user's active conversations viauseTelegramConversations.- Includes a Support row that calls
createSupportChat()→POST /api/chat/support, then opensTelegramChatThreadViewwith the returned conversation ID. - Tap a conversation row → sets
openConversationId→ rendersTelegramChatThreadView. TelegramChatThreadViewloads messages viauseTelegramChatThread, rendersTelegramChatBubbleitems, and includesTelegramChatComposerfor sending.- Optimistic send: message appears immediately, confirmed/rolled back on API response.
- Real-time updates via Socket.IO events; SWR is mutated on
new-notificationandunread-count-updateevents.
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" (ifuser.isEmailVerified).
Preferences section:
- Language toggle (FA / EN, in-shell via
TelegramLanguageToggle). - Settings → opens
overlayScreen = 'settings'(in-shellTelegramSettingsView). - Points → opens
overlayScreen = 'points'(in-shellTelegramPointsView). - Wallet → truncated address (
0x1234…abcd) or "not connected" →/dashboard/account/wallet(web viaopenTelegramExternalLink). - Notifications → opens
TelegramNotificationsViewoverlay in-shell. - Addresses → opens
overlayScreen = 'addresses'(in-shellTelegramAddressesView). - Passkey →
/dashboard/account/passkey(web).
Help section:
- Support →
createSupportChat()→ opensTelegramChatThreadViewin-shell. - Terms & Conditions → placeholder, "coming soon".
Session section:
- Sign Out →
TelegramBottomSheetconfirmation 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 triggerscheckUserSession()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
TelegramNotificationsViewis rendered asoverlayScreen = '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-updatetrigger SWR mutate. - "Mark all read" calls
markAllNotificationsAsRead(userId)→PATCH /api/notifications/mark-all-read. - Unread count is also surfaced in the
TelegramHeaderbell icon badge.
10. 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 |
| Offers for request | useTelegramOffers → getOffers(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):
?lang=URL query param — dev preview override.localStoragekeyamn_tg_lang— user's persisted manual selection.initDataUnsafe.user.language_code— Telegram-reported language ("fa"or"fa-IR"→ Persian).- Fallback → English.
Language toggle: TelegramLanguageToggle in the header — two buttons [ EN | فا ]. On tap: haptic light + language switch + persist to localStorage.
RTL layout:
| Element | EN (LTR) | FA (RTL) |
|---|---|---|
Root dir attribute |
ltr |
rtl |
| Font family | IBM Plex Sans | Vazirmatn |
| Arrow icons | → |
← |
| Text alignment | left | right (inherits from dir) |
| Chip list wrap | left-to-right | right-to-left |
| 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.
13.5 External Links
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
startappdeep link routing: ifcontext.startParammatchesreq_<id>, auto-openTelegramRequestDetailViewon first render.startParamis already parsed and available in context; the shell needs a one-time effect on mount to act on it.- 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).
- In-shell dispute filing: add
TelegramDisputeViewmatching the web dashboard dispute surface; currently only accessible viaopenTelegramExternalLinkescape hatch. - Review prompt: wire
TelegramReviewPromptto trigger after payment confirmation or delivery acknowledgement. - Archived chats: surface
TelegramArchivedChatsViewfromTelegramChatView(e.g. an "Archived" row at the bottom of the conversation list). - 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.
17. Related Documents
- amanat-assist — the separate AI-driven Mini App for LLM-assisted request creation
- 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
- Request Template Checkout — web checkout flow; the Mini App now has its own in-shell checkout, but the localStorage cart key is shared