1

Vị trí trong hệ thống

Siêu Đại Lý là tầng cao nhất trong chuỗi phân phối. Mọi đại lý đều được tạo ra và quản lý bởi siêu đại lý.

Mô hình quản lý vùng: Siêu Đại Lý giống như một Regional Manager — quản lý nhiều Đại Lý cấp dưới, mỗi đại lý lại quản lý nhiều Shop, mỗi Shop quản lý nhiều Player. Quyền hạn và tầm nhìn của Siêu Đại Lý bao trùm toàn bộ cây phân cấp.
Cây phân cấp — SuperAgent → Agent → Shop → Player
👑 Siêu Đại Lý (Super Agent) SA Tầng 0 — đỉnh phân cấp
🧑‍💼 Đại Lý A (Agent) AGT Tạo bởi SA · cost rate riêng
🏪 Shop A1 SHOP Thuộc Agent A
👤 Player 1, Player 2, ... PLR End users
🏪 Shop A2 SHOP Có thể move sang Agent khác
🧑‍💼 Đại Lý B (Agent) AGT Cost rate độc lập
🏪 Shop B1 SHOP
🎯 SA có thể làm gì?
  • Tạo / Suspend / Terminate agent
  • Move shop giữa các agent
  • Move player giữa các shop
  • Cấp credits cho agent
  • Cấu hình game provider per-agent
  • Xem toàn bộ sub-hierarchy
📊 Agent thấy gì?
  • Chỉ shops của mình
  • Players trong shops của mình
  • Không thể tạo agent khác
  • Không move shop sang SA khác
  • Không biết credit balance của SA
🔐 Auth endpoint

Login qua

POST /super-agent-auth/login { email, password, captchaToken? }

Endpoint riêng — tách biệt với /agent-auth

2

So sánh với Agent Portal

Superagent Portal kế thừa toàn bộ tính năng của Agent Portal và bổ sung thêm khả năng quản lý đa cấp.

Tính năng Agent Portal Superagent Portal Ghi chú
Dashboard & Analytics Cùng DashboardPage, nhưng SA thấy summary toàn hierarchy
Quản lý Shops (shops của mình) (tất cả shops) SA thấy shops của MỌI agent con
Quản lý Players (players của mình) (tất cả players) SA thấy players trong mọi shop
Tạo Agent mới +SA createManagedAgent()
Suspend / Terminate Agent +SA 3 trạng thái: active → suspended → terminated
Move Shop sang Agent khác +SA moveManagedShop(shopId, targetAgentId)
Move Player sang Shop khác +SA moveManagedPlayer(playerId, targetShopId)
Cấp Credits cho Agent +SA SuperAgentAgentCreditTrigger
Cấp Credits cho Shop SellCreditsCashToAgentTrigger (dùng chung)
Cấu hình Game Provider per-Agent +SA putManagedAgentGameMappings()
Sửa Cost Rate của Agent +SA updateManagedAgentRate()
Xem Hierarchy Tree ~ (1 cấp: shops) (2 cấp: agents+shops) SA: HierarchyMiniTree · Agent: AgentHierarchyMiniTree
Transactions History AgentTransactionsPage dùng chung
Credit Purchase PurchaseCheckoutPage đã bỏ — mua qua CreditPurchaseModal dùng chung
Broadcast / Popup SendBroadcastPage, SendShopPopupPage dùng chung
PWA Service Worker +SA Workbox, StaleWhileRevalidate, precache
Tóm tắt: Superagent Portal = Agent Portal + khả năng tạo và quản trị Agent + 2-level hierarchy + credit flow xuống agent + game mapping per-agent + PWA. Hai trang hoàn toàn mới so với Agent Portal là SuperAgentManagementPageManagedAgentDetailPage.
3

Quản lý Đại Lý

SuperAgentManagementPage.tsx — trang trung tâm với 3 tab: agents | shops | players. Route /agents, /shops, /players dùng cùng component với prop section.

CREATE Tạo Agent mới — createManagedAgent()

Form modal thu thập đầy đủ thông tin và gửi lên POST /super-agent/agents.

createManagedAgent({ name: string, // Tên đại lý email: string, // Email đăng nhập phone?: string, // Phone (react-phone-number-input) password: string, // Mật khẩu (PASSWORD_REGEX) cost_rate: number, // 0–1 (toRate(costPercent)) can_create_shop_popup?: boolean, can_manage_shop_popup_permission?: boolean, default_shop_can_create_player_popup?: boolean })
Validation — Password
  • Tối thiểu 8 ký tự (PASSWORD_MIN = 8)
  • Phải có ít nhất 1 chữ HOA
  • Phải có ít nhất 1 chữ thường
  • Phải có ít nhất 1 chữ số
const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/
Validation — Cost Rate
  • Giá trị nhập vào: 0 – 100 (phần trăm)
  • Chuyển đổi: toRate(percent) → 0–1
  • Khi edit: phải lớn hơn SA's own cost rate
// Input "5" → stored as 0.0500 const rate = Math.min(1, (n / 100).toFixed(4))
Popup permissions (3 checkboxes):
  • can_create_shop_popup — Agent có thể gửi popup/broadcast cho shops/players
  • can_manage_shop_popup_permission — Agent có thể bật/tắt quyền popup cho từng shop
  • default_shop_can_create_player_popup — Mặc định shop mới được tạo bởi agent này sẽ có quyền gửi popup cho players
Vòng đời Agent — Suspend / Unsuspend / Terminate
🟢
Active
Hoạt động bình thường
🟡
Suspended
Tạm đình chỉ
🟢
Active
Unsuspend
🔴
Terminated
Không thể đảo ngược
// Suspend / Unsuspend (toggle) item.status === 'suspended' ? unsuspendManagedAgent(item.id) : suspendManagedAgent(item.id) // Terminate — yêu cầu xác nhận gõ "terminate" const confirmWord = window.prompt(`Type "terminate" to terminate ${item.email}`) if ((confirmWord ?? '').trim().toLowerCase() !== 'terminate') return
MOVE Move Shop sang Agent khác

Trong tab shops, mỗi row có nút "Move". SA chọn agent đích (target agent ≠ current agent), confirm → moveManagedShop(shopId, targetAgentId).

PUT /super-agent/shops/{shopId}/move { target_agent_id: string }
UI Guard: agent hiện tại của shop bị disabled trong dropdown — không thể "move" vào chính agent đó.
MOVE Move Player sang Shop khác

Trong tab players, mỗi row có nút "Move". SA chọn shop đích (target shop ≠ current shop), confirm → moveManagedPlayer(playerId, targetShopId).

PUT /super-agent/players/{playerId}/move { target_shop_id: string }
Context: Dropdown hiển thị tất cả shops (kèm tên agent chủ shop) để SA có đủ ngữ cảnh khi chọn.
Search & Filter trong từng tab
Tab Agents
  • Search theo name / email / phone
  • useMemo: filteredAgents
Tab Shops
  • Search theo name / email / phone
  • Filter theo agent (dropdown)
  • useMemo: filteredShops
Tab Players
  • Search theo username / email / phone
  • Filter theo shop (dropdown)
  • useMemo: filteredPlayers
4

Chi tiết Đại Lý — ManagedAgentDetailPage

Route /agents/:agentId. Trang có 4 tab: Info · Games · Shops · Players. Tab được quản lý qua URL search param ?tab=....

Tab Info
  • Tên, email, status badge (active/suspended/terminated)
  • Credit balance của agent (agent.credit_balance)
  • Nút Suspend / Unsuspend / Terminate
  • SuperAgentAgentCreditTrigger — phân phối credits
  • Sửa Cost Rate inline (edit → nhập → save)
  • Guard: cost rate mới phải lớn hơn SA's own rate
Tab Games
  • Fetch fetchManagedAgentGameMappings(agentId)
  • Fetch fetchPublicGameProviders()
  • parentGamePool: tập game SA đang có (filter)
  • 2 mode: All (kế thừa pool SA) hoặc Selected
  • Checkbox list — chọn game providers cụ thể
  • Save → putManagedAgentGameMappings(agentId, ids[])
  • Empty array = "all" (kế thừa SA pool)
Tab Shops
  • Stat: Total shops / Active shops
  • Table: shop name, email, status
  • Filter: chỉ hiện shops của agent này (s.agent?.id === agentId)
Tab Players
  • Stat: Total players / Active players
  • Table: username/email/phone, shop, status
  • Filter: p.agent_id === agentId
URL-driven tab navigation
// Tab state trong URL — không mất khi F5 const [searchParams, setSearchParams] = useSearchParams() const tabParam = searchParams.get('tab') // Tabs: 'info' | 'games' | 'shops' | 'players' // /agents/123 → info tab (default) // /agents/123?tab=games → games tab // /agents/123?tab=shops → shops tab // /agents/123?tab=players → players tab
5

Cấu hình Game Provider per-Agent

SA có thể giới hạn hoặc mở rộng game providers mà mỗi agent có quyền sử dụng. Logic kế thừa và override từ SA pool xuống agent pool.

Luồng kế thừa Game Provider
👑
SA Game Pool
Tập nguồn gốc
parentGamePool[]
🔧
SA Cấu hình
Chọn: All hoặc subset
cho từng Agent
🧑‍💼
Agent Pool
games_ids[] hoặc
kế thừa toàn bộ SA
🏪
Shop Players
Chơi được các game
trong agent pool
// Lấy game mappings hiện tại của agent const [mapRes, gpRes] = await Promise.all([ fetchManagedAgentGameMappings(agentId), fetchPublicGameProviders() ]) // mapRes.data.game_ids → [] = "all" | [...ids] = subset // mapRes.data.super_agent_game_ids → SA's own pool (parent) // Lưu lại (PUT) const payload = gameMode === 'all' ? [] : selectedGameIds putManagedAgentGameMappings(agentId, payload)
Quy tắc filter: Dropdown checkbox chỉ hiện games có trong parentGamePool của SA. Nếu SA không có game nào trong pool (parentGamePool.length === 0), hiển thị tất cả game providers không ở trạng thái maintenance.
GET /super-agent/agents/{id}/game-mappings
Lấy game mappings hiện tại của agent, kèm super_agent_game_ids
PUT /super-agent/agents/{id}/game-mappings
Cập nhật game ids cho agent — [] = kế thừa SA pool
GET /public/game-providers
Danh sách game providers public (filter status !== 'maintenance')
6

Phân phối Credits

SA phân phối credits xuống agent — một cơ chế hoàn toàn mới so với Agent Portal (chỉ cấp credits xuống shop).

SuperAgentAgentCreditTrigger — Component chuyên biệt

Component SuperAgentAgentCreditTrigger.tsx trong components/credits/. Xuất hiện trong ManagedAgentDetailPage tab Info, bên cạnh các nút govern agent.

<SuperAgentAgentCreditTrigger agentId={agent.id} agentName={agent.name} agentCostRate={agent.costRate} // decimal 0–1 onSuccess={() => load()} size="sm" className="shadow-sm" />
Modal flow
  • Click "Credit" button → open modal
  • Load SA's own balance (fetchDashboard())
  • Hiển thị tên agent + cost rate hiện tại
  • Nhập amount (credits)
  • Preview: credits → cash equivalent
  • Submit → distributeCreditsToManagedAgent()
  • Close → callback onSuccess()
Cash preview logic
// Khi nhập 100 credits // agent cost rate = 0.95 (95%) credits = 100 cash = credits × effectiveCostRate = 100 × 0.95 = $95.00 // Hiển thị: "Credits to agent: 100.00" "Cash recorded: $95.00 USD"
Cơ chế thanh toán tương tự Agent → Shop: Khi SA cấp credits cho Agent, hệ thống ghi nhận một payment record — số cash mà Agent phải trả cho SA. Công thức: cash = credits × agent_cost_rate. Cùng quy tắc như Agent cấp credits cho Shop.
Luồng Credits toàn hệ thống
🏦
Platform
Nguồn gốc
👑
Super Agent
Mua credits
🧑‍💼
Agent
SuperAgentAgentCreditTrigger
🏪
Shop
SellCreditsCashToAgentTrigger
👤
Player
Nạp / rút
POST /super-agent/agents/{id}/credits
Cấp credits từ SA wallet sang Agent wallet
7

Visibility đa cấp — HierarchyMiniTree

Component HierarchyMiniTree.tsx trong components/dashboard/. Hiển thị 2 cấp: SA → Agents → Shops. Khác với Agent Portal chỉ thấy 1 cấp (Agent → Shops).

Tính năng HierarchyMiniTree
  • Expandable tree — root SA node expand/collapse
  • Mỗi Agent có thể expand/collapse riêng để xem shops
  • StatusDot per-node (green=active, amber=suspended, gray=terminated)
  • Shops hiển thị dạng card grid (1-3 cols tùy viewport)
  • Mỗi shop card có link đến /agents/{id}
  • Date range filter (from/to) — thống kê theo kỳ
  • Summary row: total deposits, withdrawals, new users, total users
  • Stats per-shop: deposits, withdrawals, new users, total users
  • API: fetchSuperAgentShopHierarchyStats({from, to})
  • Default range: 30 ngày gần nhất
// Props type Props = { superAgentName: string; // Tên SA root node agents: SuperManagedAgent[]; // Từ fetchManagedAgents() shops: SuperManagedShop[]; // Từ fetchManagedShops() } // Internal grouping const shopsByAgent = useMemo(() => { // Map<agentId, SuperManagedShop[]> // sorted alphabetically per agent }, [shops])
2 cấp vs 1 cấp: Agent Portal dùng AgentHierarchyMiniTree — chỉ thấy Agent → Shops (1 level). Superagent Portal dùng HierarchyMiniTree — thấy SA → Agents → Shops (2 levels). SA cũng thấy stats tổng hợp toàn hierarchy trong summary row.
Data Loading — Promise.all
// SuperAgentManagementPage và DashboardPage đều dùng const [agentsRes, shopsRes, playersRes] = await Promise.all([ fetchManagedAgents(), // GET /super-agent/agents fetchManagedShops(), // GET /super-agent/shops fetchManagedPlayers() // GET /super-agent/players ]) // Parallel fetch — tất cả fire cùng lúc
8

PWA Capability — Service Worker

Superagent Portal là repo duy nhất trong ecosystem có Service Worker — hướng tới khả năng dùng trên mobile và hoạt động khi mạng không ổn định.

File: src/service-worker.js + src/serviceWorkerRegistration.ts — sử dụng Workbox (Google). Được inject tự động bởi CRA + Workbox webpack plugin tại build time.
📦
Precache & Route

precacheAndRoute(self.__WB_MANIFEST) — cache toàn bộ assets tĩnh (JS, CSS, fonts) tại install time. Tự động cập nhật khi build mới.

📄
SPA Navigation

createHandlerBoundToURL('/index.html') — intercept navigate requests, trả về index.html cho React Router handle client-side.

🖼️
Image Cache

StaleWhileRevalidate strategy cho .png files. Cache tên "images", max 100 entries. Hiển thị ảnh cached ngay, revalidate ngầm.

🔄
Skip Waiting

clientsClaim() — SA mới được activate ngay sau install, không cần chờ tab đóng/mở. Đảm bảo cập nhật nhanh.

📱
Mobile Intent

SA portal có Service Worker gợi ý dùng trên mobile (tablet/phone) — phù hợp với vai trò "regional manager" di động, cần truy cập mọi lúc mọi nơi.

⚙️
Workbox Strategies

Sử dụng workbox-core, workbox-routing, workbox-strategies, workbox-expiration, workbox-precaching.

9

Routes của Superagent Portal

Cấu trúc React Router v6 — tất cả routes đều được bọc trong ProtectedRoute (trừ /login).

Path Component Độc quyền SA? Mô tả
/dashboard DashboardPage Dashboard với HierarchyMiniTree (2 cấp)
/agents SuperAgentManagementPage (section="agents") SA Only Danh sách agents + tạo mới + govern
/agents/:agentId ManagedAgentDetailPage SA Only Chi tiết agent: Info/Games/Shops/Players
/shops SuperAgentManagementPage (section="shops") Tất cả shops + filter theo agent + move
/players SuperAgentManagementPage (section="players") Tất cả players + filter theo shop + move
/transactions AgentTransactionsPage Lịch sử giao dịch (dùng chung)
/purchase/checkout PurchaseCheckoutPage đã bỏ — mua qua CreditPurchaseModal Mua credits (dùng chung)
/payment/return AgentPaymentReturnPage Kết quả thanh toán (dùng chung)
/login LoginPage Gọi POST /super-agent-auth/login
Lưu ý: Routes /shops/players dùng cùng SuperAgentManagementPage với prop section khác nhau. Route mặc định / redirect tới /dashboard. Route * (wildcard) cũng redirect tới /dashboard.
10

Technical Stack & Architecture

Stack chính
React 18 · CRA + Craco TypeScript 4.9 React Router v6 react-hook-form v7 Axios Tailwind CSS 3 Lucide React react-hot-toast react-phone-number-input v3 Workbox (PWA)
Internal packages
@kioskgaming/ui @kioskgaming/page-loading
  • @kioskgaming/page-loadingPageLoadingIndicator variant="page" và variant="inline"
  • @kioskgaming/ui — shared UI components
Cấu trúc thư mục
kioskgaming_superagent/src/ ├── pages/ │ ├── SuperAgentManagementPage.tsx ← MAJOR: 3-tab management │ ├── ManagedAgentDetailPage.tsx ← MAJOR: agent detail 4-tab │ ├── DashboardPage.tsx │ ├── ShopsPage.tsx │ ├── ShopDetailPage.tsx │ ├── AgentTransactionsPage.tsx │ ├── AgentPaymentReturnPage.tsx │ ├── PurchaseCheckoutPage đã bỏ — mua qua CreditPurchaseModal.tsx │ ├── SendBroadcastPage.tsx │ ├── SendShopPopupPage.tsx │ └── SettingsPage.tsx ├── components/ │ ├── credits/ │ │ ├── SuperAgentAgentCreditTrigger.tsx ← SA exclusive │ │ ├── SellCreditsCashToAgentTrigger.tsx │ │ ├── AvailableCreditsCallout.tsx │ │ └── CreditPurchaseTrigger.tsx │ ├── dashboard/ │ │ ├── HierarchyMiniTree.tsx ← SA 2-level tree │ │ └── AgentHomeRecentTransactions.tsx │ ├── layout/, auth/, shops/, ui/ ├── services/ │ └── api.ts ← /super-agent/* endpoints ├── types/ │ └── index.ts (SuperManagedAgent, etc.) ├── service-worker.js ← Workbox PWA └── serviceWorkerRegistration.ts
API Client — Axios với JWT interceptor
// services/api.ts — createClient() client.interceptors.request.use((config) => { const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN) if (token) config.headers.Authorization = `Bearer ${token}` return config }) client.interceptors.response.use( (res) => res, (error) => { if (error.response?.status === 401) window.dispatchEvent(new Event('auth:unauthorized')) return Promise.reject(error) } )
Timeout 15s: Axios config timeout: 15000. Unauthorized 401 phát event 'auth:unauthorized' — AuthProvider listen và tự động logout.
Types riêng cho SA
// Các type Super-Agent-specific trong types/index.ts type SuperManagedAgent = { id: string; name: string; email: string; phone?: string; status: 'active' | 'suspended' | 'terminated'; costRate?: number; // 0–1 credit_balance?: number; } type SuperManagedShop = { id: string; name: string; agent?: { id: string; name: string }; costRate?: number; status: string; } type SuperManagedPlayer = { id: string; username?: string; shop_id: string; shop_name?: string; agent_id: string; agent_name?: string; }
11

API Endpoints tổng hợp

Tất cả endpoints dưới prefix /super-agent//super-agent-auth/.

Method Endpoint Function Mục đích
POST /super-agent-auth/login loginWithEmailPassword() Đăng nhập SA
GET /super-agent/agents fetchManagedAgents() Danh sách agents con
POST /super-agent/agents createManagedAgent() Tạo agent mới
PUT /super-agent/agents/{id}/suspend suspendManagedAgent() Suspend agent
PUT /super-agent/agents/{id}/unsuspend unsuspendManagedAgent() Unsuspend agent
POST /super-agent/agents/{id}/terminate terminateManagedAgent() Terminate agent (không đảo ngược)
PUT /super-agent/agents/{id}/rate updateManagedAgentRate() Sửa cost rate của agent
POST /super-agent/agents/{id}/credits distributeCreditsToManagedAgent() Cấp credits cho agent
GET /super-agent/agents/{id}/game-mappings fetchManagedAgentGameMappings() Game mappings của agent
PUT /super-agent/agents/{id}/game-mappings putManagedAgentGameMappings() Cập nhật game access
GET /super-agent/shops fetchManagedShops() Tất cả shops
PUT /super-agent/shops/{id}/move moveManagedShop() Move shop sang agent khác
GET /super-agent/players fetchManagedPlayers() Tất cả players
PUT /super-agent/players/{id}/move moveManagedPlayer() Move player sang shop khác
GET /super-agent/shop-hierarchy-stats fetchSuperAgentShopHierarchyStats() Stats hierarchy (deposits, withdrawals, users)