@withpotter/lib 0.13.0-next.62 → 0.13.0-next.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__fixtures__/demo-content-server.json +484 -0
- package/dist/admin-api.d.ts +7 -0
- package/dist/admin-api.js.map +1 -1
- package/dist/api/demo-content.d.ts +104 -0
- package/dist/api/demo-content.js +36 -0
- package/dist/api/demo-content.js.map +1 -0
- package/dist/demo-content-mock.d.ts +22 -0
- package/dist/demo-content-mock.js +70 -0
- package/dist/demo-content-mock.js.map +1 -0
- package/dist/demo-content-poller.d.ts +33 -0
- package/dist/demo-content-poller.js +80 -0
- package/dist/demo-content-poller.js.map +1 -0
- package/dist/demo-content.d.ts +43 -0
- package/dist/demo-content.js +78 -0
- package/dist/demo-content.js.map +1 -0
- package/dist/mobile-routes.js +1 -0
- package/dist/mobile-routes.js.map +1 -1
- package/package.json +2 -2
package/dist/admin-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin-api.ts"],"sourcesContent":["import {\n apiFetch,\n type DashboardStats,\n type FinanceAnalytics,\n type Transaction,\n} from './api';\n\nexport type {\n AccountRow,\n AccountsPage,\n AccountTenantRow,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type {\n AccountRow,\n AccountsPage,\n AccountTenantRow,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type { TenantSetup } from './admin-tenant-setup-types';\nimport type { CountryFields } from './admin-country-api';\nimport { tenantsQuery, type TenantListParams } from './admin-tenants-query';\nimport type { AccountOfficer } from './admin-account-officer-api';\nimport type {\n CurrencyAmount,\n CurrencyGmvKpi,\n CurrencyOverdue,\n CurrencyPerformance,\n CurrencyRevenueGmv,\n CurrencyRevenueQuality,\n CurrencySeries,\n CurrencySettlementSummary,\n CurrencyTopTenants,\n CurrencyTotals,\n FinanceByCurrency,\n TenantCurrencyMetrics,\n} from './admin-currency-types';\nexport type * from './admin-currency-types';\n\nexport type { DashboardStats, FinanceAnalytics, Transaction } from './api';\n\n/** A merchant's finance dashboard as the admin API sends it: per currency too. */\nexport type AdminFinanceAnalytics = FinanceAnalytics & FinanceByCurrency;\n\nexport interface TenantTransactionsPage {\n transactions: Transaction[];\n pagination: {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n };\n}\n\n// ============================================\n// PLATFORM ADMIN API (growth cockpit)\n// Cross-tenant aggregates — admin/superadmin only.\n// ============================================\n\nexport interface PlatformOverview {\n tenants: { total: number; active: number; new30d: number; new7d: number };\n accounts: { total: number; new30d: number };\n orders: { total: number; gmv: number; gmv30d: number };\n subscriptions: { active: number; trialing: number };\n growth: { tenantSeries: Array<{ date: string; count: number }> };\n}\n\nexport interface TenantsAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n tenants: number;\n accounts: number;\n avgTenantsPerAccount: number;\n activeRate: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvInRange: number;\n ordersInRange: number;\n newTenantsInRange: number;\n };\n statusBreakdown: Array<{ status: string; count: number }>;\n industryBreakdown: Array<{ industry: string; count: number }>;\n onboardingSeries: Array<{ date: string; count: number }>;\n performanceSeries: Array<{ date: string; gmv: number; orders: number }>;\n /** Absent until the matching api deploy lands — guard with `?? []`. */\n previousOnboardingSeries?: Array<{ date: string; count: number }>;\n previousPerformanceSeries?: Array<{\n date: string;\n gmv: number;\n orders: number;\n }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n /** @deprecated adds currencies together, not converted. */\n baseCurrency: string;\n /** @deprecated never convert with these. */\n rates: Record<string, number>;\n currencies?: string[];\n totalsByCurrency?: CurrencyTotals[];\n performanceSeriesByCurrency?: CurrencySeries[];\n topTenantsByCurrency?: CurrencyTopTenants[];\n}\n\nexport interface CockpitKpi {\n current: number;\n previous: number;\n pct: number | null;\n}\n\ninterface AttentionTenantRef {\n id: string;\n businessName: string;\n slug: string;\n}\n\nexport interface PlatformCockpit {\n range: { startDate: string; endDate: string };\n // Base currency all monetary figures are expressed in (USD). `rates` maps a\n // currency code to its rate-to-USD, so the client can render figures in a\n // chosen display currency by dividing a USD amount by the target's rate.\n baseCurrency: string;\n rates: Record<string, number>;\n /** Currencies with money in this payload, A→Z. */\n currencies?: string[];\n topTenantsByCurrency?: CurrencyTopTenants[];\n kpis: {\n newTenants: CockpitKpi;\n newAccounts: CockpitKpi;\n /** @deprecated adds currencies together; use `gmvByCurrency`. */\n gmv: CockpitKpi;\n orders: CockpitKpi;\n gmvByCurrency?: CurrencyGmvKpi[];\n };\n totals: {\n totalTenants: number;\n activeTenants: number;\n totalAccounts: number;\n activeSubscriptions: number;\n trialingSubscriptions: number;\n /** @deprecated adds currencies together; use `lifetimeGmvByCurrency`. */\n lifetimeGmv: number;\n lifetimeGmvByCurrency?: CurrencyAmount[];\n };\n sparklines: {\n tenants: number[];\n accounts: number[];\n gmv: number[];\n orders: number[];\n };\n statusBreakdown: Array<{ status: string; count: number }>;\n industryBreakdown: Array<{ industry: string; count: number }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n health?: {\n mrr: number;\n newMrr: number;\n churnedMrr: number;\n netNewMrr: number;\n grossMrrChurnPct: number;\n grossRevenueRetentionPct: number;\n newLogos: number;\n churnedLogos: number;\n netNewLogos: number;\n logoChurnPct: number;\n activePayingLogos: number;\n gmvConcentrationTop10Pct: number;\n };\n performance?: {\n takeRatePct: number | null;\n annualizedGmvRunRate: number;\n gmvPerActiveTenant: number;\n revenuePerActiveTenant: number;\n transactingTenants: number;\n transactingTenantRatePct: number;\n gmvGrowthPct: number | null;\n ordersGrowthPct: number | null;\n netNewLogos: number;\n byCurrency?: CurrencyPerformance[];\n };\n verticals?: Array<{\n industry: string;\n tenants: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvGrowthPct: number | null;\n share: number;\n }>;\n /** GMV/orders across every service Box offers (sell-products, event-tickets, …). */\n salesByService?: Array<{\n service: string;\n merchants: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvGrowthPct: number | null;\n share: number;\n }>;\n activation?: {\n funnel: Array<{ stage: string; count: number }>;\n medianDaysToFirstOrder: number | null;\n activationRatePct: number;\n orderConversionPct: number;\n };\n accounts?: {\n total: number;\n active: number;\n suspended: number;\n verified: number;\n unverifiedEmail: number;\n neverLoggedIn: number;\n dormant: number;\n new: number;\n };\n trend: {\n performanceSeries: Array<{ date: string; gmv: number; orders: number }>;\n onboardingSeries: Array<{ date: string; count: number }>;\n /** Absent until the matching api deploy lands — guard with `?? []`. */\n previousPerformanceSeries?: Array<{\n date: string;\n gmv: number;\n orders: number;\n }>;\n previousOnboardingSeries?: Array<{ date: string; count: number }>;\n performanceSeriesByCurrency?: CurrencySeries[];\n };\n attention: {\n trialsEndingSoon: Array<\n AttentionTenantRef & { trialEndsAt: string; daysLeft: number }\n >;\n needsReview: Array<\n AttentionTenantRef & { status: string; createdAt: string }\n >;\n dormantTenants: Array<AttentionTenantRef & { createdAt: string }>;\n overdueInvoices: {\n count: number;\n /** @deprecated adds currencies together; use `totalByCurrency`. */\n total: number;\n totalByCurrency?: CurrencyOverdue[];\n sample: Array<{\n id: string;\n invoiceNumber: string;\n tenantId: string;\n tenantName: string;\n customerName: string | null;\n amountDue: number;\n currency: string;\n dueDate: string | null;\n }>;\n };\n failedSettlements: {\n count: number;\n sample: Array<{\n id: string;\n tenantId: string;\n tenantName: string;\n netAmount: number;\n currency: string;\n createdAt: string;\n }>;\n };\n };\n}\n\nexport interface PlatformRevenue {\n range: { startDate: string; endDate: string };\n currency: string;\n recurring: {\n mrr: number;\n arr: number;\n arpa: number;\n activeSubscriptions: number;\n trialingSubscriptions: number;\n pastDueSubscriptions: number;\n nonRenewingSubscriptions: number;\n cancelledSubscriptions: number;\n newInRange: number;\n churnedInRange: number;\n planBreakdown: Array<{\n planId: string;\n name: string;\n interval: string;\n subscribers: number;\n mrr: number;\n }>;\n };\n gmv: {\n lifetime: number;\n inRange: number;\n ordersInRange: number;\n aov: number;\n series: Array<{ date: string; gmv: number; orders: number }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n };\n quality?: {\n quickRatio: number | null;\n nrrPct: number | null;\n expansionMrr: number;\n contractionMrr: number;\n mrrChurnRatePct: number | null;\n logoChurnRatePct: number | null;\n grossGmv: number;\n refundedGmv: number;\n netGmv: number;\n refundRatePct: number;\n refundedOrders: number;\n cohorts: Array<{\n cohort: string;\n size: number;\n retention: Array<number | null>;\n }>;\n gmvByCurrency?: CurrencyRevenueQuality[];\n };\n /** @deprecated never convert with these. */\n rates: Record<string, number>;\n currencies?: string[];\n gmvByCurrency?: CurrencyRevenueGmv[];\n}\n\nexport type TenantStatus = 'active' | 'suspended' | 'pending' | 'deleted';\n\nexport interface TenantRow extends CountryFields {\n id: string;\n slug: string;\n businessName: string;\n status: TenantStatus;\n industryKey: string | null;\n createdAt: string;\n /** @deprecated adds currencies together; use `gmvByCurrency`. */\n gmv: number;\n gmvByCurrency?: CurrencyAmount[];\n /** The merchant's account officer (absent on older APIs). */\n accountOfficer?: Pick<AccountOfficer, 'id' | 'name'> | null;\n}\n\nexport interface TenantsPage {\n rows: TenantRow[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface TenantActivityOrder {\n id: string;\n orderNumber: string;\n total: number;\n currency: string;\n status: string;\n paymentStatus: string;\n customerEmail: string;\n customerName: string | null;\n createdAt: string;\n}\n\nexport interface TenantDetail extends TenantSetup {\n /** Who runs the workspace and how to reach it (absent on older APIs). */\n contact?: { email: string | null; phone: string | null };\n owner?: TenantTeamMember | null;\n team?: TenantTeamMember[];\n /** The admin in charge of this merchant (absent on older APIs). */\n accountOfficer?: AccountOfficer | null;\n id: string;\n slug: string;\n businessName: string;\n status: TenantStatus;\n industryKey: string | null;\n customDomain: string | null;\n createdAt: string;\n chargeBearer: 'merchant' | 'customer';\n currency: string;\n metrics: {\n totalOrders: number;\n paidOrders: number;\n pendingOrders: number;\n gmv: number;\n gmv30d: number;\n gmv7d: number;\n aov: number;\n totalCustomers: number;\n subscriptionStatus: string | null;\n window: {\n startDate: string;\n endDate: string;\n gmv: number;\n orders: number;\n paidOrders: number;\n aov: number;\n customers: number;\n };\n byCurrency?: TenantCurrencyMetrics[];\n };\n /** Currencies with paid orders, A→Z. */\n currencies?: string[];\n revenueSeries: Array<{ date: string; amount: number }>;\n revenueSeriesByCurrency?: Array<{\n currency: string;\n series: Array<{ date: string; amount: number }>;\n }>;\n recentOrders: TenantActivityOrder[];\n}\n\n// ---- Iteration 3: tenant cockpit domains ----\n\nexport interface TenantCustomer {\n id: string;\n email: string;\n phone: string | null;\n name: string | null;\n type: string;\n source: string | null;\n status: string;\n emailVerified: boolean;\n totalOrders: number;\n totalSpent: number;\n lastOrderAt: string | null;\n createdAt: string;\n}\n\nexport interface TenantCustomersPage {\n members: TenantCustomer[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface TenantProduct {\n id: string;\n type: string;\n slug: string;\n title: string;\n category: string | null;\n tags: string[];\n featured: boolean;\n isActive: boolean;\n image?: { url: string } | null;\n price: number | null;\n compareAtPrice: number | null;\n currency: string | null;\n trackInventory: boolean;\n quantity: number | null;\n reserved: number | null;\n lowStockThreshold: number | null;\n createdAt: string;\n}\n\nexport interface TenantProductStats {\n total: number;\n active: number;\n featured: number;\n lowStock: number;\n outOfStock: number;\n}\n\nexport interface TenantProductsData {\n items: TenantProduct[];\n total: number;\n stats: TenantProductStats;\n}\n\nexport interface ProductRef {\n id: string;\n title: string;\n}\n\nexport interface SellerRow {\n id: string;\n title: string;\n units: number;\n revenue: number;\n}\n\nexport interface CategorySlice {\n category: string;\n count: number;\n value: number;\n}\n\nexport interface ProductInsights {\n currency: string;\n totalProducts: number;\n activeProducts: number;\n featuredProducts: number;\n lowStock: number;\n outOfStock: number;\n trackedProducts: number;\n catalogValue: number;\n inventoryValue: number;\n inventoryAtRiskValue: number;\n avgPrice: number;\n medianPrice: number;\n totalUnitsSold: number;\n totalRevenue: number;\n pctEverSold: number;\n deadStock: number;\n newestProduct: (ProductRef & { createdAt: string }) | null;\n lastUpdatedProduct: (ProductRef & { updatedAt: string }) | null;\n categories: CategorySlice[];\n bestSellers: SellerRow[];\n worstSellers: SellerRow[];\n featuredSellThrough: {\n featuredTotal: number;\n featuredSold: number;\n featuredUnits: number;\n nonFeaturedTotal: number;\n nonFeaturedSold: number;\n nonFeaturedUnits: number;\n };\n unitsSeries: Array<{ date: string; count: number }>;\n}\n\nexport interface SpenderRow {\n id: string;\n name: string | null;\n email: string;\n totalSpent: number;\n totalOrders: number;\n}\n\nexport interface BreakdownSlice {\n label: string;\n count: number;\n}\n\nexport interface CustomerInsights {\n currency: string;\n totalCustomers: number;\n newCustomers30d: number;\n payingCustomers: number;\n repeatCustomers: number;\n repeatRate: number;\n dormantCustomers: number;\n marketingOptInRate: number;\n lifetimeValue: number;\n avgSpendPerCustomer: number;\n avgOrdersPerCustomer: number;\n avgOrderValue: number;\n topSpenders: SpenderRow[];\n bySource: BreakdownSlice[];\n byType: BreakdownSlice[];\n signupsSeries: Array<{ date: string; count: number }>;\n}\n\nexport interface TenantInvoice {\n id: string;\n invoiceNumber: string;\n status: string;\n customerEmail: string | null;\n customerName: string | null;\n total: number;\n amountPaid: number;\n amountDue: number;\n currency: string;\n dueDate: string | null;\n createdAt: string;\n}\n\nexport interface TenantInvoiceStats {\n draft: number;\n sent: number;\n paid: number;\n overdue: number;\n cancelled: number;\n total: number;\n totalRevenue: number;\n totalOutstanding: number;\n}\n\nexport interface TenantInvoicesData {\n invoices: TenantInvoice[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n stats: TenantInvoiceStats;\n}\n\nexport interface TenantLoyaltyProgram {\n id: string;\n name: string;\n pointsName: string;\n pointsPerCurrency: number;\n currencyPerPoint: number;\n isActive: boolean;\n tiers?: Array<{ id: string; name: string; threshold: number }>;\n earnRules?: Array<{ id: string; name: string; points: number }>;\n}\n\nexport interface TenantLoyaltyStats {\n totalMembers: number;\n totalPointsIssued: number;\n totalPointsRedeemed: number;\n activeMembers: number;\n programActive: boolean;\n}\n\nexport interface TenantLoyaltyData {\n program: TenantLoyaltyProgram | null;\n stats: TenantLoyaltyStats;\n}\n\nexport interface TenantDiscount {\n id: string;\n name: string;\n code: string;\n discountType: string;\n value: number;\n maxDiscountAmount: number | null;\n startsAt: string | null;\n endsAt: string | null;\n usageLimit: number | null;\n usageCount: number;\n status: string;\n autoApply: boolean;\n isValid: boolean;\n isExpired: boolean;\n createdAt: string;\n}\n\nexport interface TenantDiscountsPage {\n discounts: TenantDiscount[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface TenantSettlement {\n id: string;\n grossAmount: number;\n paystackFee: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n status: string;\n settlementType: string;\n bankAccount?: { bankName: string; accountNumber: string } | null;\n settledAt: string | null;\n createdAt: string;\n}\n\nexport interface TenantSettlementSummary {\n totalSettled: number;\n totalPending: number;\n totalFailed: number;\n count: number;\n platformFeesCollected: number;\n currency: string;\n currencies?: string[];\n byCurrency?: CurrencySettlementSummary[];\n}\n\nexport interface TenantBankAccount {\n id: string;\n bankCode: string;\n bankName: string;\n accountNumber: string;\n accountName: string;\n splitPercentage: number | null;\n status: string;\n isDefault: boolean;\n /** Paystack subaccount code (ACCT_…). */\n subaccountCode?: string | null;\n failureReason?: string | null;\n createdAt: string;\n}\n\nexport interface TenantSettlementsData {\n settlements: TenantSettlement[];\n total: number;\n page: number;\n limit: number;\n summary: TenantSettlementSummary;\n bankAccounts: TenantBankAccount[];\n}\n\nexport interface TenantSubscriptionPlan {\n id: string;\n name: string;\n interval: string;\n amount: number;\n currency: string;\n}\n\nexport interface TenantSubscription {\n id: string;\n planId: string;\n customerId: string;\n customerEmail: string;\n status: string;\n amount: number;\n currency: string;\n currentPeriodStart: string | null;\n currentPeriodEnd: string | null;\n nextPaymentDate: string | null;\n cancelledAt: string | null;\n paymentsCount: number;\n createdAt: string;\n plan?: TenantSubscriptionPlan | null;\n customer?: { id: string; name: string | null; email: string } | null;\n}\n\nexport interface TenantSubscriptionStats {\n totalActive: number;\n totalNonRenewing: number;\n totalCancelled: number;\n monthlyRecurringRevenue: number;\n}\n\nexport interface TenantSubscriptionsData {\n subscriptions: TenantSubscription[];\n total: number;\n page: number;\n limit: number;\n stats: TenantSubscriptionStats;\n}\n\nexport interface AdminsAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n admins: number;\n active: number;\n suspended: number;\n superadmins: number;\n standardAdmins: number;\n neverLoggedIn: number;\n activeInRange: number;\n actionsInRange: number;\n };\n roleBreakdown: Array<{ role: string; count: number }>;\n statusBreakdown: Array<{ status: string; count: number }>;\n activitySeries: Array<{ date: string; count: number }>;\n topActions: Array<{ action: string; count: number }>;\n recentActivity: Array<{\n id: string;\n adminEmail: string | null;\n action: string;\n targetType: string | null;\n createdAt: string;\n }>;\n}\n\nexport interface AdminSession {\n id: string;\n email: string;\n name: string | null;\n role: 'admin' | 'superadmin';\n permissions: string[];\n mustChangePassword: boolean;\n}\n\nexport interface AdminLoginCodeRequired {\n codeRequired: true;\n challengeId: string;\n /** The address the code went to, masked: `a***@gmail.com`. */\n sentTo: string;\n}\n\nexport type AdminLoginAnswer =\n | AdminLoginCodeRequired\n | {\n codeRequired?: undefined;\n mustChangePassword: boolean;\n admin: Omit<AdminSession, 'permissions'>;\n };\n\nexport const platformAdminApi = {\n /**\n * Password step. A box that emails sign-in codes answers `codeRequired` and\n * sets no session yet; finish with `verifyLoginCode`.\n */\n login: (email: string, password: string) =>\n apiFetch<AdminLoginAnswer>('/platform-admin/v1/auth/login', {\n method: 'POST',\n body: JSON.stringify({ email, password }),\n }),\n verifyLoginCode: (challengeId: string, code: string) =>\n apiFetch<{\n mustChangePassword: boolean;\n admin: Omit<AdminSession, 'permissions'>;\n }>('/platform-admin/v1/auth/login/verify', {\n method: 'POST',\n body: JSON.stringify({ challengeId, code }),\n }),\n resendLoginCode: (challengeId: string) =>\n apiFetch<AdminLoginCodeRequired>('/platform-admin/v1/auth/login/resend', {\n method: 'POST',\n body: JSON.stringify({ challengeId }),\n }),\n logout: () =>\n apiFetch<{ success: boolean }>('/platform-admin/v1/auth/logout', {\n method: 'POST',\n }),\n me: () => apiFetch<AdminSession>('/platform-admin/v1/auth/me'),\n changePassword: (currentPassword: string, newPassword: string) =>\n apiFetch<{ success: boolean }>('/platform-admin/v1/auth/change-password', {\n method: 'POST',\n body: JSON.stringify({ currentPassword, newPassword }),\n }),\n overview: () => apiFetch<PlatformOverview>('/platform-admin/v1/overview'),\n cockpit: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<PlatformCockpit>(\n `/platform-admin/v1/overview/cockpit${q ? `?${q}` : ''}`,\n );\n },\n revenue: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<PlatformRevenue>(\n `/platform-admin/v1/overview/revenue${q ? `?${q}` : ''}`,\n );\n },\n tenantsAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<TenantsAnalytics>(\n `/platform-admin/v1/overview/tenants-analytics${q ? `?${q}` : ''}`,\n );\n },\n tenants: (params: TenantListParams = {}) =>\n apiFetch<TenantsPage>(`/platform-admin/v1/tenants${tenantsQuery(params)}`),\n tenant: (\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n const query = qs.toString();\n return apiFetch<TenantDetail>(\n `/platform-admin/v1/tenants/${id}${query ? `?${query}` : ''}`,\n );\n },\n tenantAnalytics: (\n id: string,\n range: { startDate?: string; endDate?: string; limit?: number } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n if (range.limit) qs.set('limit', String(range.limit));\n const query = qs.toString();\n return apiFetch<DashboardStats>(\n `/platform-admin/v1/tenants/${id}/analytics${query ? `?${query}` : ''}`,\n );\n },\n tenantFinance: (\n id: string,\n range: { startDate?: string; endDate?: string; period?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n if (range.period) qs.set('period', range.period);\n const query = qs.toString();\n return apiFetch<AdminFinanceAnalytics>(\n `/platform-admin/v1/tenants/${id}/finance${query ? `?${query}` : ''}`,\n );\n },\n tenantTransactions: (\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.type) qs.set('type', params.type);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantTransactionsPage>(\n `/platform-admin/v1/tenants/${id}/transactions${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantCustomers: (\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.search) qs.set('search', params.search);\n if (params.type) qs.set('type', params.type);\n if (params.status) qs.set('status', params.status);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantCustomersPage>(\n `/platform-admin/v1/tenants/${id}/customers${query ? `?${query}` : ''}`,\n );\n },\n tenantProducts: (\n id: string,\n params: { type?: string; search?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.type) qs.set('type', params.type);\n if (params.search) qs.set('search', params.search);\n const query = qs.toString();\n return apiFetch<TenantProductsData>(\n `/platform-admin/v1/tenants/${id}/products${query ? `?${query}` : ''}`,\n );\n },\n tenantProductInsights: (id: string, range: { startDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n const query = qs.toString();\n return apiFetch<ProductInsights>(\n `/platform-admin/v1/tenants/${id}/products/insights${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantCustomerInsights: (id: string, range: { startDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n const query = qs.toString();\n return apiFetch<CustomerInsights>(\n `/platform-admin/v1/tenants/${id}/customers/insights${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantInvoices: (\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantInvoicesData>(\n `/platform-admin/v1/tenants/${id}/invoices${query ? `?${query}` : ''}`,\n );\n },\n tenantLoyalty: (id: string) =>\n apiFetch<TenantLoyaltyData>(`/platform-admin/v1/tenants/${id}/loyalty`),\n tenantDiscounts: (\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.discountType) qs.set('discountType', params.discountType);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantDiscountsPage>(\n `/platform-admin/v1/tenants/${id}/discounts${query ? `?${query}` : ''}`,\n );\n },\n tenantSettlements: (\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const query = qs.toString();\n return apiFetch<TenantSettlementsData>(\n `/platform-admin/v1/tenants/${id}/settlements${query ? `?${query}` : ''}`,\n );\n },\n tenantSubscriptions: (\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.planId) qs.set('planId', params.planId);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantSubscriptionsData>(\n `/platform-admin/v1/tenants/${id}/subscriptions${\n query ? `?${query}` : ''\n }`,\n );\n },\n setTenantStatus: (id: string, status: TenantStatus) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/tenants/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n accounts: (\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.pageSize) qs.set('pageSize', String(params.pageSize));\n if (params.search) qs.set('search', params.search);\n if (params.joinedFrom) qs.set('joinedFrom', params.joinedFrom);\n if (params.joinedTo) qs.set('joinedTo', params.joinedTo);\n const query = qs.toString();\n return apiFetch<AccountsPage>(\n `/platform-admin/v1/accounts${query ? `?${query}` : ''}`,\n );\n },\n accountsAnalytics: (\n params: { startDate?: string; endDate?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AccountsAnalytics>(\n `/platform-admin/v1/accounts/analytics${q ? `?${q}` : ''}`,\n );\n },\n setAccountStatus: (id: string, status: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/accounts/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n verifyAccount: (id: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/accounts/${id}/verify`, {\n method: 'POST',\n }),\n accountTenants: (id: string) =>\n apiFetch<AccountTenantRow[]>(`/platform-admin/v1/accounts/${id}/tenants`),\n impersonateAccount: (id: string) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/accounts/${id}/impersonate`,\n { method: 'POST' },\n ),\n admins: () => apiFetch<AdminRow[]>('/platform-admin/v1/admins'),\n adminsAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AdminsAnalytics>(\n `/platform-admin/v1/admins/analytics${q ? `?${q}` : ''}`,\n );\n },\n adminPermissions: () =>\n apiFetch<string[]>('/platform-admin/v1/admins/permissions'),\n inviteAdmin: (\n email: string,\n role: 'admin' | 'superadmin',\n permissions: string[] = [],\n roleIds: string[] = [],\n ) =>\n apiFetch<{ email: string; tempPassword: string }>(\n '/platform-admin/v1/admins',\n {\n method: 'POST',\n body: JSON.stringify({ email, role, permissions, roleIds }),\n },\n ),\n setAdminStatus: (id: string, status: 'active' | 'suspended') =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/admins/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n setAdminPermissions: (id: string, permissions: string[]) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/admins/${id}/permissions`,\n { method: 'PATCH', body: JSON.stringify({ permissions }) },\n ),\n setAdminRoles: (id: string, roleIds: string[]) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/admins/${id}/roles`, {\n method: 'PATCH',\n body: JSON.stringify({ roleIds }),\n }),\n resetAdminPassword: (id: string) =>\n apiFetch<{ tempPassword: string }>(\n `/platform-admin/v1/admins/${id}/reset-password`,\n { method: 'POST' },\n ),\n roles: () => apiFetch<RoleRow[]>('/platform-admin/v1/roles'),\n createRole: (name: string, permissions: string[], description?: string) =>\n apiFetch<RoleRow>('/platform-admin/v1/roles', {\n method: 'POST',\n body: JSON.stringify({ name, permissions, description }),\n }),\n updateRole: (\n id: string,\n data: { name?: string; permissions?: string[]; description?: string },\n ) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/roles/${id}`, {\n method: 'PATCH',\n body: JSON.stringify(data),\n }),\n deleteRole: (id: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/roles/${id}`, {\n method: 'DELETE',\n }),\n audit: (\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.pageSize) qs.set('pageSize', String(params.pageSize));\n if (params.search) qs.set('search', params.search);\n if (params.action) qs.set('action', params.action);\n if (params.adminEmail) qs.set('adminEmail', params.adminEmail);\n if (params.from) qs.set('from', params.from);\n if (params.to) qs.set('to', params.to);\n const q = qs.toString();\n return apiFetch<AuditPage>(`/platform-admin/v1/audit${q ? `?${q}` : ''}`);\n },\n auditAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AuditAnalytics>(\n `/platform-admin/v1/audit/analytics${q ? `?${q}` : ''}`,\n );\n },\n};\n\nexport interface AdminRow {\n id: string;\n email: string;\n name: string | null;\n role: 'admin' | 'superadmin';\n status: 'active' | 'suspended';\n permissions: string[];\n roleIds: string[];\n mustChangePassword: boolean;\n lastLoginAt: string | null;\n createdAt: string;\n}\n\nexport interface RoleRow {\n id: string;\n key: string;\n name: string;\n description: string | null;\n permissions: string[];\n isSystem: boolean;\n assignedCount: number;\n}\n\nexport interface AuditRow {\n id: string;\n /** Who did it, by name. Absent from an engine that does not send it, or when the admin has none. */\n adminName?: string | null;\n adminEmail: string | null;\n action: string;\n targetType: string | null;\n targetId: string | null;\n metadata: Record<string, unknown>;\n ipAddress: string | null;\n /** The browser or tool the action came from. Absent from an engine that does not send it. */\n userAgent?: string | null;\n createdAt: string;\n}\n\nexport interface AuditPage {\n rows: AuditRow[];\n total: number;\n page: number;\n pageSize: number;\n}\nexport interface AuditAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n actions: number;\n activeAdmins: number;\n actionTypes: number;\n topAction: string | null;\n };\n actionSeries: Array<{ date: string; count: number }>;\n actionBreakdown: Array<{ action: string; count: number }>;\n adminBreakdown: Array<{ adminEmail: string; count: number }>;\n targetBreakdown: Array<{ targetType: string; count: number }>;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/admin-api.ts"],"sourcesContent":["import {\n apiFetch,\n type DashboardStats,\n type FinanceAnalytics,\n type Transaction,\n} from './api';\n\nexport type {\n AccountRow,\n AccountsPage,\n AccountTenantRow,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type {\n AccountRow,\n AccountsPage,\n AccountTenantRow,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type { TenantSetup } from './admin-tenant-setup-types';\nimport type { CountryFields } from './admin-country-api';\nimport { tenantsQuery, type TenantListParams } from './admin-tenants-query';\nimport type { AccountOfficer } from './admin-account-officer-api';\nimport type {\n CurrencyAmount,\n CurrencyGmvKpi,\n CurrencyOverdue,\n CurrencyPerformance,\n CurrencyRevenueGmv,\n CurrencyRevenueQuality,\n CurrencySeries,\n CurrencySettlementSummary,\n CurrencyTopTenants,\n CurrencyTotals,\n FinanceByCurrency,\n TenantCurrencyMetrics,\n} from './admin-currency-types';\nexport type * from './admin-currency-types';\n\nexport type { DashboardStats, FinanceAnalytics, Transaction } from './api';\n\n/** A merchant's finance dashboard as the admin API sends it: per currency too. */\nexport type AdminFinanceAnalytics = FinanceAnalytics & FinanceByCurrency;\n\nexport interface TenantTransactionsPage {\n transactions: Transaction[];\n pagination: {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n };\n}\n\n// ============================================\n// PLATFORM ADMIN API (growth cockpit)\n// Cross-tenant aggregates — admin/superadmin only.\n// ============================================\n\nexport interface PlatformOverview {\n tenants: { total: number; active: number; new30d: number; new7d: number };\n accounts: { total: number; new30d: number };\n orders: { total: number; gmv: number; gmv30d: number };\n subscriptions: { active: number; trialing: number };\n growth: { tenantSeries: Array<{ date: string; count: number }> };\n}\n\nexport interface TenantsAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n tenants: number;\n accounts: number;\n avgTenantsPerAccount: number;\n activeRate: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvInRange: number;\n ordersInRange: number;\n newTenantsInRange: number;\n };\n statusBreakdown: Array<{ status: string; count: number }>;\n industryBreakdown: Array<{ industry: string; count: number }>;\n onboardingSeries: Array<{ date: string; count: number }>;\n performanceSeries: Array<{ date: string; gmv: number; orders: number }>;\n /** Absent until the matching api deploy lands — guard with `?? []`. */\n previousOnboardingSeries?: Array<{ date: string; count: number }>;\n previousPerformanceSeries?: Array<{\n date: string;\n gmv: number;\n orders: number;\n }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n /** @deprecated adds currencies together, not converted. */\n baseCurrency: string;\n /** @deprecated never convert with these. */\n rates: Record<string, number>;\n currencies?: string[];\n totalsByCurrency?: CurrencyTotals[];\n performanceSeriesByCurrency?: CurrencySeries[];\n topTenantsByCurrency?: CurrencyTopTenants[];\n}\n\nexport interface CockpitKpi {\n current: number;\n previous: number;\n pct: number | null;\n}\n\ninterface AttentionTenantRef {\n id: string;\n businessName: string;\n slug: string;\n}\n\nexport interface PlatformCockpit {\n range: { startDate: string; endDate: string };\n // Base currency all monetary figures are expressed in (USD). `rates` maps a\n // currency code to its rate-to-USD, so the client can render figures in a\n // chosen display currency by dividing a USD amount by the target's rate.\n baseCurrency: string;\n rates: Record<string, number>;\n /** Currencies with money in this payload, A→Z. */\n currencies?: string[];\n topTenantsByCurrency?: CurrencyTopTenants[];\n kpis: {\n newTenants: CockpitKpi;\n newAccounts: CockpitKpi;\n /** @deprecated adds currencies together; use `gmvByCurrency`. */\n gmv: CockpitKpi;\n orders: CockpitKpi;\n gmvByCurrency?: CurrencyGmvKpi[];\n };\n totals: {\n totalTenants: number;\n activeTenants: number;\n totalAccounts: number;\n activeSubscriptions: number;\n trialingSubscriptions: number;\n /** @deprecated adds currencies together; use `lifetimeGmvByCurrency`. */\n lifetimeGmv: number;\n lifetimeGmvByCurrency?: CurrencyAmount[];\n };\n sparklines: {\n tenants: number[];\n accounts: number[];\n gmv: number[];\n orders: number[];\n };\n statusBreakdown: Array<{ status: string; count: number }>;\n industryBreakdown: Array<{ industry: string; count: number }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n health?: {\n mrr: number;\n newMrr: number;\n churnedMrr: number;\n netNewMrr: number;\n grossMrrChurnPct: number;\n grossRevenueRetentionPct: number;\n newLogos: number;\n churnedLogos: number;\n netNewLogos: number;\n logoChurnPct: number;\n activePayingLogos: number;\n gmvConcentrationTop10Pct: number;\n };\n performance?: {\n takeRatePct: number | null;\n annualizedGmvRunRate: number;\n gmvPerActiveTenant: number;\n revenuePerActiveTenant: number;\n transactingTenants: number;\n transactingTenantRatePct: number;\n gmvGrowthPct: number | null;\n ordersGrowthPct: number | null;\n netNewLogos: number;\n byCurrency?: CurrencyPerformance[];\n };\n verticals?: Array<{\n industry: string;\n tenants: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvGrowthPct: number | null;\n share: number;\n }>;\n /** GMV/orders across every service Box offers (sell-products, event-tickets, …). */\n salesByService?: Array<{\n service: string;\n merchants: number;\n gmv: number;\n orders: number;\n aov: number;\n gmvGrowthPct: number | null;\n share: number;\n }>;\n activation?: {\n funnel: Array<{ stage: string; count: number }>;\n medianDaysToFirstOrder: number | null;\n activationRatePct: number;\n orderConversionPct: number;\n };\n accounts?: {\n total: number;\n active: number;\n suspended: number;\n verified: number;\n unverifiedEmail: number;\n neverLoggedIn: number;\n dormant: number;\n new: number;\n };\n trend: {\n performanceSeries: Array<{ date: string; gmv: number; orders: number }>;\n onboardingSeries: Array<{ date: string; count: number }>;\n /** Absent until the matching api deploy lands — guard with `?? []`. */\n previousPerformanceSeries?: Array<{\n date: string;\n gmv: number;\n orders: number;\n }>;\n previousOnboardingSeries?: Array<{ date: string; count: number }>;\n performanceSeriesByCurrency?: CurrencySeries[];\n };\n attention: {\n /**\n * Feeds whose query failed; their counts are placeholders, not zeros.\n * Absent when talking to an API that predates this field.\n */\n unavailable?: Array<\n 'needsReview' | 'dormantTenants' | 'overdueInvoices' | 'failedSettlements'\n >;\n trialsEndingSoon: Array<\n AttentionTenantRef & { trialEndsAt: string; daysLeft: number }\n >;\n needsReview: Array<\n AttentionTenantRef & { status: string; createdAt: string }\n >;\n dormantTenants: Array<AttentionTenantRef & { createdAt: string }>;\n overdueInvoices: {\n count: number;\n /** @deprecated adds currencies together; use `totalByCurrency`. */\n total: number;\n totalByCurrency?: CurrencyOverdue[];\n sample: Array<{\n id: string;\n invoiceNumber: string;\n tenantId: string;\n tenantName: string;\n customerName: string | null;\n amountDue: number;\n currency: string;\n dueDate: string | null;\n }>;\n };\n failedSettlements: {\n count: number;\n sample: Array<{\n id: string;\n tenantId: string;\n tenantName: string;\n netAmount: number;\n currency: string;\n createdAt: string;\n }>;\n };\n };\n}\n\nexport interface PlatformRevenue {\n range: { startDate: string; endDate: string };\n currency: string;\n recurring: {\n /** False when no billing source feeds the figures below (all are 0). */\n tracked?: boolean;\n mrr: number;\n arr: number;\n arpa: number;\n activeSubscriptions: number;\n trialingSubscriptions: number;\n pastDueSubscriptions: number;\n nonRenewingSubscriptions: number;\n cancelledSubscriptions: number;\n newInRange: number;\n churnedInRange: number;\n planBreakdown: Array<{\n planId: string;\n name: string;\n interval: string;\n subscribers: number;\n mrr: number;\n }>;\n };\n gmv: {\n lifetime: number;\n inRange: number;\n ordersInRange: number;\n aov: number;\n series: Array<{ date: string; gmv: number; orders: number }>;\n topTenants: Array<{\n id: string;\n businessName: string;\n slug: string;\n gmv: number;\n orders: number;\n }>;\n };\n quality?: {\n quickRatio: number | null;\n nrrPct: number | null;\n expansionMrr: number;\n contractionMrr: number;\n mrrChurnRatePct: number | null;\n logoChurnRatePct: number | null;\n grossGmv: number;\n refundedGmv: number;\n netGmv: number;\n refundRatePct: number;\n refundedOrders: number;\n cohorts: Array<{\n cohort: string;\n size: number;\n retention: Array<number | null>;\n }>;\n gmvByCurrency?: CurrencyRevenueQuality[];\n };\n /** @deprecated never convert with these. */\n rates: Record<string, number>;\n currencies?: string[];\n gmvByCurrency?: CurrencyRevenueGmv[];\n}\n\nexport type TenantStatus = 'active' | 'suspended' | 'pending' | 'deleted';\n\nexport interface TenantRow extends CountryFields {\n id: string;\n slug: string;\n businessName: string;\n status: TenantStatus;\n industryKey: string | null;\n createdAt: string;\n /** @deprecated adds currencies together; use `gmvByCurrency`. */\n gmv: number;\n gmvByCurrency?: CurrencyAmount[];\n /** The merchant's account officer (absent on older APIs). */\n accountOfficer?: Pick<AccountOfficer, 'id' | 'name'> | null;\n}\n\nexport interface TenantsPage {\n rows: TenantRow[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface TenantActivityOrder {\n id: string;\n orderNumber: string;\n total: number;\n currency: string;\n status: string;\n paymentStatus: string;\n customerEmail: string;\n customerName: string | null;\n createdAt: string;\n}\n\nexport interface TenantDetail extends TenantSetup {\n /** Who runs the workspace and how to reach it (absent on older APIs). */\n contact?: { email: string | null; phone: string | null };\n owner?: TenantTeamMember | null;\n team?: TenantTeamMember[];\n /** The admin in charge of this merchant (absent on older APIs). */\n accountOfficer?: AccountOfficer | null;\n id: string;\n slug: string;\n businessName: string;\n status: TenantStatus;\n industryKey: string | null;\n customDomain: string | null;\n createdAt: string;\n chargeBearer: 'merchant' | 'customer';\n currency: string;\n metrics: {\n totalOrders: number;\n paidOrders: number;\n pendingOrders: number;\n gmv: number;\n gmv30d: number;\n gmv7d: number;\n aov: number;\n totalCustomers: number;\n subscriptionStatus: string | null;\n window: {\n startDate: string;\n endDate: string;\n gmv: number;\n orders: number;\n paidOrders: number;\n aov: number;\n customers: number;\n };\n byCurrency?: TenantCurrencyMetrics[];\n };\n /** Currencies with paid orders, A→Z. */\n currencies?: string[];\n revenueSeries: Array<{ date: string; amount: number }>;\n revenueSeriesByCurrency?: Array<{\n currency: string;\n series: Array<{ date: string; amount: number }>;\n }>;\n recentOrders: TenantActivityOrder[];\n}\n\n// ---- Iteration 3: tenant cockpit domains ----\n\nexport interface TenantCustomer {\n id: string;\n email: string;\n phone: string | null;\n name: string | null;\n type: string;\n source: string | null;\n status: string;\n emailVerified: boolean;\n totalOrders: number;\n totalSpent: number;\n lastOrderAt: string | null;\n createdAt: string;\n}\n\nexport interface TenantCustomersPage {\n members: TenantCustomer[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface TenantProduct {\n id: string;\n type: string;\n slug: string;\n title: string;\n category: string | null;\n tags: string[];\n featured: boolean;\n isActive: boolean;\n image?: { url: string } | null;\n price: number | null;\n compareAtPrice: number | null;\n currency: string | null;\n trackInventory: boolean;\n quantity: number | null;\n reserved: number | null;\n lowStockThreshold: number | null;\n createdAt: string;\n}\n\nexport interface TenantProductStats {\n total: number;\n active: number;\n featured: number;\n lowStock: number;\n outOfStock: number;\n}\n\nexport interface TenantProductsData {\n items: TenantProduct[];\n total: number;\n stats: TenantProductStats;\n}\n\nexport interface ProductRef {\n id: string;\n title: string;\n}\n\nexport interface SellerRow {\n id: string;\n title: string;\n units: number;\n revenue: number;\n}\n\nexport interface CategorySlice {\n category: string;\n count: number;\n value: number;\n}\n\nexport interface ProductInsights {\n currency: string;\n totalProducts: number;\n activeProducts: number;\n featuredProducts: number;\n lowStock: number;\n outOfStock: number;\n trackedProducts: number;\n catalogValue: number;\n inventoryValue: number;\n inventoryAtRiskValue: number;\n avgPrice: number;\n medianPrice: number;\n totalUnitsSold: number;\n totalRevenue: number;\n pctEverSold: number;\n deadStock: number;\n newestProduct: (ProductRef & { createdAt: string }) | null;\n lastUpdatedProduct: (ProductRef & { updatedAt: string }) | null;\n categories: CategorySlice[];\n bestSellers: SellerRow[];\n worstSellers: SellerRow[];\n featuredSellThrough: {\n featuredTotal: number;\n featuredSold: number;\n featuredUnits: number;\n nonFeaturedTotal: number;\n nonFeaturedSold: number;\n nonFeaturedUnits: number;\n };\n unitsSeries: Array<{ date: string; count: number }>;\n}\n\nexport interface SpenderRow {\n id: string;\n name: string | null;\n email: string;\n totalSpent: number;\n totalOrders: number;\n}\n\nexport interface BreakdownSlice {\n label: string;\n count: number;\n}\n\nexport interface CustomerInsights {\n currency: string;\n totalCustomers: number;\n newCustomers30d: number;\n payingCustomers: number;\n repeatCustomers: number;\n repeatRate: number;\n dormantCustomers: number;\n marketingOptInRate: number;\n lifetimeValue: number;\n avgSpendPerCustomer: number;\n avgOrdersPerCustomer: number;\n avgOrderValue: number;\n topSpenders: SpenderRow[];\n bySource: BreakdownSlice[];\n byType: BreakdownSlice[];\n signupsSeries: Array<{ date: string; count: number }>;\n}\n\nexport interface TenantInvoice {\n id: string;\n invoiceNumber: string;\n status: string;\n customerEmail: string | null;\n customerName: string | null;\n total: number;\n amountPaid: number;\n amountDue: number;\n currency: string;\n dueDate: string | null;\n createdAt: string;\n}\n\nexport interface TenantInvoiceStats {\n draft: number;\n sent: number;\n paid: number;\n overdue: number;\n cancelled: number;\n total: number;\n totalRevenue: number;\n totalOutstanding: number;\n}\n\nexport interface TenantInvoicesData {\n invoices: TenantInvoice[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n stats: TenantInvoiceStats;\n}\n\nexport interface TenantLoyaltyProgram {\n id: string;\n name: string;\n pointsName: string;\n pointsPerCurrency: number;\n currencyPerPoint: number;\n isActive: boolean;\n tiers?: Array<{ id: string; name: string; threshold: number }>;\n earnRules?: Array<{ id: string; name: string; points: number }>;\n}\n\nexport interface TenantLoyaltyStats {\n totalMembers: number;\n totalPointsIssued: number;\n totalPointsRedeemed: number;\n activeMembers: number;\n programActive: boolean;\n}\n\nexport interface TenantLoyaltyData {\n program: TenantLoyaltyProgram | null;\n stats: TenantLoyaltyStats;\n}\n\nexport interface TenantDiscount {\n id: string;\n name: string;\n code: string;\n discountType: string;\n value: number;\n maxDiscountAmount: number | null;\n startsAt: string | null;\n endsAt: string | null;\n usageLimit: number | null;\n usageCount: number;\n status: string;\n autoApply: boolean;\n isValid: boolean;\n isExpired: boolean;\n createdAt: string;\n}\n\nexport interface TenantDiscountsPage {\n discounts: TenantDiscount[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface TenantSettlement {\n id: string;\n grossAmount: number;\n paystackFee: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n status: string;\n settlementType: string;\n bankAccount?: { bankName: string; accountNumber: string } | null;\n settledAt: string | null;\n createdAt: string;\n}\n\nexport interface TenantSettlementSummary {\n totalSettled: number;\n totalPending: number;\n totalFailed: number;\n count: number;\n platformFeesCollected: number;\n currency: string;\n currencies?: string[];\n byCurrency?: CurrencySettlementSummary[];\n}\n\nexport interface TenantBankAccount {\n id: string;\n bankCode: string;\n bankName: string;\n accountNumber: string;\n accountName: string;\n splitPercentage: number | null;\n status: string;\n isDefault: boolean;\n /** Paystack subaccount code (ACCT_…). */\n subaccountCode?: string | null;\n failureReason?: string | null;\n createdAt: string;\n}\n\nexport interface TenantSettlementsData {\n settlements: TenantSettlement[];\n total: number;\n page: number;\n limit: number;\n summary: TenantSettlementSummary;\n bankAccounts: TenantBankAccount[];\n}\n\nexport interface TenantSubscriptionPlan {\n id: string;\n name: string;\n interval: string;\n amount: number;\n currency: string;\n}\n\nexport interface TenantSubscription {\n id: string;\n planId: string;\n customerId: string;\n customerEmail: string;\n status: string;\n amount: number;\n currency: string;\n currentPeriodStart: string | null;\n currentPeriodEnd: string | null;\n nextPaymentDate: string | null;\n cancelledAt: string | null;\n paymentsCount: number;\n createdAt: string;\n plan?: TenantSubscriptionPlan | null;\n customer?: { id: string; name: string | null; email: string } | null;\n}\n\nexport interface TenantSubscriptionStats {\n totalActive: number;\n totalNonRenewing: number;\n totalCancelled: number;\n monthlyRecurringRevenue: number;\n}\n\nexport interface TenantSubscriptionsData {\n subscriptions: TenantSubscription[];\n total: number;\n page: number;\n limit: number;\n stats: TenantSubscriptionStats;\n}\n\nexport interface AdminsAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n admins: number;\n active: number;\n suspended: number;\n superadmins: number;\n standardAdmins: number;\n neverLoggedIn: number;\n activeInRange: number;\n actionsInRange: number;\n };\n roleBreakdown: Array<{ role: string; count: number }>;\n statusBreakdown: Array<{ status: string; count: number }>;\n activitySeries: Array<{ date: string; count: number }>;\n topActions: Array<{ action: string; count: number }>;\n recentActivity: Array<{\n id: string;\n adminEmail: string | null;\n action: string;\n targetType: string | null;\n createdAt: string;\n }>;\n}\n\nexport interface AdminSession {\n id: string;\n email: string;\n name: string | null;\n role: 'admin' | 'superadmin';\n permissions: string[];\n mustChangePassword: boolean;\n}\n\nexport interface AdminLoginCodeRequired {\n codeRequired: true;\n challengeId: string;\n /** The address the code went to, masked: `a***@gmail.com`. */\n sentTo: string;\n}\n\nexport type AdminLoginAnswer =\n | AdminLoginCodeRequired\n | {\n codeRequired?: undefined;\n mustChangePassword: boolean;\n admin: Omit<AdminSession, 'permissions'>;\n };\n\nexport const platformAdminApi = {\n /**\n * Password step. A box that emails sign-in codes answers `codeRequired` and\n * sets no session yet; finish with `verifyLoginCode`.\n */\n login: (email: string, password: string) =>\n apiFetch<AdminLoginAnswer>('/platform-admin/v1/auth/login', {\n method: 'POST',\n body: JSON.stringify({ email, password }),\n }),\n verifyLoginCode: (challengeId: string, code: string) =>\n apiFetch<{\n mustChangePassword: boolean;\n admin: Omit<AdminSession, 'permissions'>;\n }>('/platform-admin/v1/auth/login/verify', {\n method: 'POST',\n body: JSON.stringify({ challengeId, code }),\n }),\n resendLoginCode: (challengeId: string) =>\n apiFetch<AdminLoginCodeRequired>('/platform-admin/v1/auth/login/resend', {\n method: 'POST',\n body: JSON.stringify({ challengeId }),\n }),\n logout: () =>\n apiFetch<{ success: boolean }>('/platform-admin/v1/auth/logout', {\n method: 'POST',\n }),\n me: () => apiFetch<AdminSession>('/platform-admin/v1/auth/me'),\n changePassword: (currentPassword: string, newPassword: string) =>\n apiFetch<{ success: boolean }>('/platform-admin/v1/auth/change-password', {\n method: 'POST',\n body: JSON.stringify({ currentPassword, newPassword }),\n }),\n overview: () => apiFetch<PlatformOverview>('/platform-admin/v1/overview'),\n cockpit: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<PlatformCockpit>(\n `/platform-admin/v1/overview/cockpit${q ? `?${q}` : ''}`,\n );\n },\n revenue: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<PlatformRevenue>(\n `/platform-admin/v1/overview/revenue${q ? `?${q}` : ''}`,\n );\n },\n tenantsAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<TenantsAnalytics>(\n `/platform-admin/v1/overview/tenants-analytics${q ? `?${q}` : ''}`,\n );\n },\n tenants: (params: TenantListParams = {}) =>\n apiFetch<TenantsPage>(`/platform-admin/v1/tenants${tenantsQuery(params)}`),\n tenant: (\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n const query = qs.toString();\n return apiFetch<TenantDetail>(\n `/platform-admin/v1/tenants/${id}${query ? `?${query}` : ''}`,\n );\n },\n tenantAnalytics: (\n id: string,\n range: { startDate?: string; endDate?: string; limit?: number } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n if (range.limit) qs.set('limit', String(range.limit));\n const query = qs.toString();\n return apiFetch<DashboardStats>(\n `/platform-admin/v1/tenants/${id}/analytics${query ? `?${query}` : ''}`,\n );\n },\n tenantFinance: (\n id: string,\n range: { startDate?: string; endDate?: string; period?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n if (range.endDate) qs.set('endDate', range.endDate);\n if (range.period) qs.set('period', range.period);\n const query = qs.toString();\n return apiFetch<AdminFinanceAnalytics>(\n `/platform-admin/v1/tenants/${id}/finance${query ? `?${query}` : ''}`,\n );\n },\n tenantTransactions: (\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.type) qs.set('type', params.type);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantTransactionsPage>(\n `/platform-admin/v1/tenants/${id}/transactions${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantCustomers: (\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.search) qs.set('search', params.search);\n if (params.type) qs.set('type', params.type);\n if (params.status) qs.set('status', params.status);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantCustomersPage>(\n `/platform-admin/v1/tenants/${id}/customers${query ? `?${query}` : ''}`,\n );\n },\n tenantProducts: (\n id: string,\n params: { type?: string; search?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.type) qs.set('type', params.type);\n if (params.search) qs.set('search', params.search);\n const query = qs.toString();\n return apiFetch<TenantProductsData>(\n `/platform-admin/v1/tenants/${id}/products${query ? `?${query}` : ''}`,\n );\n },\n tenantProductInsights: (id: string, range: { startDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n const query = qs.toString();\n return apiFetch<ProductInsights>(\n `/platform-admin/v1/tenants/${id}/products/insights${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantCustomerInsights: (id: string, range: { startDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (range.startDate) qs.set('startDate', range.startDate);\n const query = qs.toString();\n return apiFetch<CustomerInsights>(\n `/platform-admin/v1/tenants/${id}/customers/insights${\n query ? `?${query}` : ''\n }`,\n );\n },\n tenantInvoices: (\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantInvoicesData>(\n `/platform-admin/v1/tenants/${id}/invoices${query ? `?${query}` : ''}`,\n );\n },\n tenantLoyalty: (id: string) =>\n apiFetch<TenantLoyaltyData>(`/platform-admin/v1/tenants/${id}/loyalty`),\n tenantDiscounts: (\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.discountType) qs.set('discountType', params.discountType);\n if (params.search) qs.set('search', params.search);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantDiscountsPage>(\n `/platform-admin/v1/tenants/${id}/discounts${query ? `?${query}` : ''}`,\n );\n },\n tenantSettlements: (\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const query = qs.toString();\n return apiFetch<TenantSettlementsData>(\n `/platform-admin/v1/tenants/${id}/settlements${query ? `?${query}` : ''}`,\n );\n },\n tenantSubscriptions: (\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.status) qs.set('status', params.status);\n if (params.planId) qs.set('planId', params.planId);\n if (params.page) qs.set('page', String(params.page));\n if (params.limit) qs.set('limit', String(params.limit));\n const query = qs.toString();\n return apiFetch<TenantSubscriptionsData>(\n `/platform-admin/v1/tenants/${id}/subscriptions${\n query ? `?${query}` : ''\n }`,\n );\n },\n setTenantStatus: (id: string, status: TenantStatus) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/tenants/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n accounts: (\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.pageSize) qs.set('pageSize', String(params.pageSize));\n if (params.search) qs.set('search', params.search);\n if (params.joinedFrom) qs.set('joinedFrom', params.joinedFrom);\n if (params.joinedTo) qs.set('joinedTo', params.joinedTo);\n const query = qs.toString();\n return apiFetch<AccountsPage>(\n `/platform-admin/v1/accounts${query ? `?${query}` : ''}`,\n );\n },\n accountsAnalytics: (\n params: { startDate?: string; endDate?: string } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AccountsAnalytics>(\n `/platform-admin/v1/accounts/analytics${q ? `?${q}` : ''}`,\n );\n },\n setAccountStatus: (id: string, status: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/accounts/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n verifyAccount: (id: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/accounts/${id}/verify`, {\n method: 'POST',\n }),\n accountTenants: (id: string) =>\n apiFetch<AccountTenantRow[]>(`/platform-admin/v1/accounts/${id}/tenants`),\n impersonateAccount: (id: string) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/accounts/${id}/impersonate`,\n { method: 'POST' },\n ),\n admins: () => apiFetch<AdminRow[]>('/platform-admin/v1/admins'),\n adminsAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AdminsAnalytics>(\n `/platform-admin/v1/admins/analytics${q ? `?${q}` : ''}`,\n );\n },\n adminPermissions: () =>\n apiFetch<string[]>('/platform-admin/v1/admins/permissions'),\n inviteAdmin: (\n email: string,\n role: 'admin' | 'superadmin',\n permissions: string[] = [],\n roleIds: string[] = [],\n ) =>\n apiFetch<{ email: string; tempPassword: string }>(\n '/platform-admin/v1/admins',\n {\n method: 'POST',\n body: JSON.stringify({ email, role, permissions, roleIds }),\n },\n ),\n setAdminStatus: (id: string, status: 'active' | 'suspended') =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/admins/${id}/status`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n }),\n setAdminPermissions: (id: string, permissions: string[]) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/admins/${id}/permissions`,\n { method: 'PATCH', body: JSON.stringify({ permissions }) },\n ),\n setAdminRoles: (id: string, roleIds: string[]) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/admins/${id}/roles`, {\n method: 'PATCH',\n body: JSON.stringify({ roleIds }),\n }),\n resetAdminPassword: (id: string) =>\n apiFetch<{ tempPassword: string }>(\n `/platform-admin/v1/admins/${id}/reset-password`,\n { method: 'POST' },\n ),\n roles: () => apiFetch<RoleRow[]>('/platform-admin/v1/roles'),\n createRole: (name: string, permissions: string[], description?: string) =>\n apiFetch<RoleRow>('/platform-admin/v1/roles', {\n method: 'POST',\n body: JSON.stringify({ name, permissions, description }),\n }),\n updateRole: (\n id: string,\n data: { name?: string; permissions?: string[]; description?: string },\n ) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/roles/${id}`, {\n method: 'PATCH',\n body: JSON.stringify(data),\n }),\n deleteRole: (id: string) =>\n apiFetch<{ success: boolean }>(`/platform-admin/v1/roles/${id}`, {\n method: 'DELETE',\n }),\n audit: (\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n ) => {\n const qs = new URLSearchParams();\n if (params.page) qs.set('page', String(params.page));\n if (params.pageSize) qs.set('pageSize', String(params.pageSize));\n if (params.search) qs.set('search', params.search);\n if (params.action) qs.set('action', params.action);\n if (params.adminEmail) qs.set('adminEmail', params.adminEmail);\n if (params.from) qs.set('from', params.from);\n if (params.to) qs.set('to', params.to);\n const q = qs.toString();\n return apiFetch<AuditPage>(`/platform-admin/v1/audit${q ? `?${q}` : ''}`);\n },\n auditAnalytics: (params: { startDate?: string; endDate?: string } = {}) => {\n const qs = new URLSearchParams();\n if (params.startDate) qs.set('startDate', params.startDate);\n if (params.endDate) qs.set('endDate', params.endDate);\n const q = qs.toString();\n return apiFetch<AuditAnalytics>(\n `/platform-admin/v1/audit/analytics${q ? `?${q}` : ''}`,\n );\n },\n};\n\nexport interface AdminRow {\n id: string;\n email: string;\n name: string | null;\n role: 'admin' | 'superadmin';\n status: 'active' | 'suspended';\n permissions: string[];\n roleIds: string[];\n mustChangePassword: boolean;\n lastLoginAt: string | null;\n createdAt: string;\n}\n\nexport interface RoleRow {\n id: string;\n key: string;\n name: string;\n description: string | null;\n permissions: string[];\n isSystem: boolean;\n assignedCount: number;\n}\n\nexport interface AuditRow {\n id: string;\n /** Who did it, by name. Absent from an engine that does not send it, or when the admin has none. */\n adminName?: string | null;\n adminEmail: string | null;\n action: string;\n targetType: string | null;\n targetId: string | null;\n metadata: Record<string, unknown>;\n ipAddress: string | null;\n /** The browser or tool the action came from. Absent from an engine that does not send it. */\n userAgent?: string | null;\n createdAt: string;\n}\n\nexport interface AuditPage {\n rows: AuditRow[];\n total: number;\n page: number;\n pageSize: number;\n}\nexport interface AuditAnalytics {\n range: { startDate: string; endDate: string };\n totals: {\n actions: number;\n activeAdmins: number;\n actionTypes: number;\n topAction: string | null;\n };\n actionSeries: Array<{ date: string; count: number }>;\n actionBreakdown: Array<{ action: string; count: number }>;\n adminBreakdown: Array<{ adminEmail: string; count: number }>;\n targetBreakdown: Array<{ targetType: string; count: number }>;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo content: sample products, collections, pages and pictures a merchant
|
|
3
|
+
* can start a store with, clear in one go, or apply again later.
|
|
4
|
+
*
|
|
5
|
+
* Contract: api-next `DEMO_CONTENT_API.md`. Every call is the current tenant's
|
|
6
|
+
* and needs an owner or admin; anyone else is refused by the server, and the
|
|
7
|
+
* screens hide the actions before that.
|
|
8
|
+
*/
|
|
9
|
+
export type DemoContentState = 'none' | 'applying' | 'applied' | 'clearing' | 'failed';
|
|
10
|
+
export interface DemoContentPack {
|
|
11
|
+
id: string;
|
|
12
|
+
template: string;
|
|
13
|
+
/** Absent until the pack has been read, as is `hash` (empty while the job starts). */
|
|
14
|
+
version?: string;
|
|
15
|
+
audience: string;
|
|
16
|
+
hash: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DemoContentProgress {
|
|
19
|
+
phase: string;
|
|
20
|
+
done: number;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
export interface DemoContentCounts {
|
|
24
|
+
products: number;
|
|
25
|
+
collections: number;
|
|
26
|
+
pages: number;
|
|
27
|
+
media: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DemoContentStatus {
|
|
30
|
+
state: DemoContentState;
|
|
31
|
+
pack?: DemoContentPack;
|
|
32
|
+
progress?: DemoContentProgress;
|
|
33
|
+
counts?: DemoContentCounts;
|
|
34
|
+
startedAt?: string;
|
|
35
|
+
finishedAt?: string;
|
|
36
|
+
/** Pages the server left alone because the merchant had already edited them. */
|
|
37
|
+
skippedPages?: string[];
|
|
38
|
+
/** Which job is running, or which one failed. */
|
|
39
|
+
job?: 'apply' | 'clear';
|
|
40
|
+
error?: {
|
|
41
|
+
code: string;
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
/** A clear that left uploaded pictures in the media library. */
|
|
45
|
+
orphanedMedia?: {
|
|
46
|
+
count: number;
|
|
47
|
+
clearedAt?: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Whether the tenant's template has a pack this caller may apply. */
|
|
51
|
+
export interface DemoContentAvailability {
|
|
52
|
+
available: boolean;
|
|
53
|
+
template?: string;
|
|
54
|
+
/** The current status state, so a screen can hide the offer once content is there. */
|
|
55
|
+
state?: DemoContentState;
|
|
56
|
+
/** Why there is nothing to offer: no_pack, audience_not_permitted, catalogue_unreachable, template_unknown. */
|
|
57
|
+
reason?: string;
|
|
58
|
+
pack?: {
|
|
59
|
+
id: string;
|
|
60
|
+
name?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
version?: string;
|
|
63
|
+
audience?: string;
|
|
64
|
+
locale?: string;
|
|
65
|
+
hash?: string;
|
|
66
|
+
};
|
|
67
|
+
/** What the pack holds. Only what the server gave is ever shown. */
|
|
68
|
+
summary?: {
|
|
69
|
+
products?: number;
|
|
70
|
+
collections?: number;
|
|
71
|
+
contentItems?: number;
|
|
72
|
+
pages?: number;
|
|
73
|
+
images?: number;
|
|
74
|
+
shipping?: boolean;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export interface DemoContentApplyInput {
|
|
78
|
+
template?: string;
|
|
79
|
+
dryRun?: boolean;
|
|
80
|
+
}
|
|
81
|
+
/** The calls the screens make, so a test or a preview can stand in for the server. */
|
|
82
|
+
export interface DemoContentClient {
|
|
83
|
+
status(): Promise<DemoContentStatus>;
|
|
84
|
+
available(): Promise<DemoContentAvailability>;
|
|
85
|
+
apply(input?: DemoContentApplyInput): Promise<{
|
|
86
|
+
jobId: string;
|
|
87
|
+
}>;
|
|
88
|
+
clear(options: {
|
|
89
|
+
keepMedia: boolean;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
adopt(input: {
|
|
92
|
+
productIds?: string[];
|
|
93
|
+
collectionIds?: string[];
|
|
94
|
+
contentItemIds?: string[];
|
|
95
|
+
all?: boolean;
|
|
96
|
+
}): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
export declare function createDemoContentApi(tenantSlug: string): DemoContentClient;
|
|
99
|
+
/**
|
|
100
|
+
* The contract's error `code` (`job_in_progress`, `not_owner_or_admin`,
|
|
101
|
+
* `no_demo_content`, ...) when the server sent one. The body is
|
|
102
|
+
* `{ statusCode, code, message, details? }` and `ApiRequestError.details` holds it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function apiErrorCode(error: unknown): string | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { apiFetch, ApiRequestError } from "./core";
|
|
2
|
+
const BASE = "/tenants/current/demo-content";
|
|
3
|
+
function createDemoContentApi(tenantSlug) {
|
|
4
|
+
return {
|
|
5
|
+
status: () => apiFetch(BASE, { tenantSlug }),
|
|
6
|
+
available: () => apiFetch(`${BASE}/available`, { tenantSlug }),
|
|
7
|
+
apply: (input) => apiFetch(BASE, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
tenantSlug,
|
|
10
|
+
body: JSON.stringify(input ?? {})
|
|
11
|
+
}),
|
|
12
|
+
clear: async ({ keepMedia }) => {
|
|
13
|
+
await apiFetch(`${BASE}?keepMedia=${keepMedia ? "true" : "false"}`, {
|
|
14
|
+
method: "DELETE",
|
|
15
|
+
tenantSlug
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
adopt: async (input) => {
|
|
19
|
+
await apiFetch(`${BASE}/adopt`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
tenantSlug,
|
|
22
|
+
body: JSON.stringify(input)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function apiErrorCode(error) {
|
|
28
|
+
if (!(error instanceof ApiRequestError)) return void 0;
|
|
29
|
+
const code = error.details?.code;
|
|
30
|
+
return typeof code === "string" ? code : void 0;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
apiErrorCode,
|
|
34
|
+
createDemoContentApi
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=demo-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/api/demo-content.ts"],"sourcesContent":["import { apiFetch, ApiRequestError } from './core';\n\n/**\n * Demo content: sample products, collections, pages and pictures a merchant\n * can start a store with, clear in one go, or apply again later.\n *\n * Contract: api-next `DEMO_CONTENT_API.md`. Every call is the current tenant's\n * and needs an owner or admin; anyone else is refused by the server, and the\n * screens hide the actions before that.\n */\nexport type DemoContentState =\n | 'none'\n | 'applying'\n | 'applied'\n | 'clearing'\n | 'failed';\n\nexport interface DemoContentPack {\n id: string;\n template: string;\n /** Absent until the pack has been read, as is `hash` (empty while the job starts). */\n version?: string;\n audience: string;\n hash: string;\n}\n\nexport interface DemoContentProgress {\n phase: string;\n done: number;\n total: number;\n}\n\nexport interface DemoContentCounts {\n products: number;\n collections: number;\n pages: number;\n media: number;\n}\n\nexport interface DemoContentStatus {\n state: DemoContentState;\n pack?: DemoContentPack;\n progress?: DemoContentProgress;\n counts?: DemoContentCounts;\n startedAt?: string;\n finishedAt?: string;\n /** Pages the server left alone because the merchant had already edited them. */\n skippedPages?: string[];\n /** Which job is running, or which one failed. */\n job?: 'apply' | 'clear';\n error?: { code: string; message: string };\n /** A clear that left uploaded pictures in the media library. */\n orphanedMedia?: { count: number; clearedAt?: string };\n}\n\n/** Whether the tenant's template has a pack this caller may apply. */\nexport interface DemoContentAvailability {\n available: boolean;\n template?: string;\n /** The current status state, so a screen can hide the offer once content is there. */\n state?: DemoContentState;\n /** Why there is nothing to offer: no_pack, audience_not_permitted, catalogue_unreachable, template_unknown. */\n reason?: string;\n pack?: {\n id: string;\n name?: string;\n description?: string;\n version?: string;\n audience?: string;\n locale?: string;\n hash?: string;\n };\n /** What the pack holds. Only what the server gave is ever shown. */\n summary?: {\n products?: number;\n collections?: number;\n contentItems?: number;\n pages?: number;\n images?: number;\n shipping?: boolean;\n };\n}\n\nexport interface DemoContentApplyInput {\n template?: string;\n dryRun?: boolean;\n}\n\n/** The calls the screens make, so a test or a preview can stand in for the server. */\nexport interface DemoContentClient {\n status(): Promise<DemoContentStatus>;\n available(): Promise<DemoContentAvailability>;\n apply(input?: DemoContentApplyInput): Promise<{ jobId: string }>;\n clear(options: { keepMedia: boolean }): Promise<void>;\n adopt(input: {\n productIds?: string[];\n collectionIds?: string[];\n contentItemIds?: string[];\n all?: boolean;\n }): Promise<void>;\n}\n\nconst BASE = '/tenants/current/demo-content';\n\nexport function createDemoContentApi(tenantSlug: string): DemoContentClient {\n return {\n status: () => apiFetch<DemoContentStatus>(BASE, { tenantSlug }),\n available: () =>\n apiFetch<DemoContentAvailability>(`${BASE}/available`, { tenantSlug }),\n apply: (input) =>\n apiFetch<{ jobId: string }>(BASE, {\n method: 'POST',\n tenantSlug,\n body: JSON.stringify(input ?? {}),\n }),\n clear: async ({ keepMedia }) => {\n await apiFetch<unknown>(`${BASE}?keepMedia=${keepMedia ? 'true' : 'false'}`, {\n method: 'DELETE',\n tenantSlug,\n });\n },\n adopt: async (input) => {\n await apiFetch<unknown>(`${BASE}/adopt`, {\n method: 'POST',\n tenantSlug,\n body: JSON.stringify(input),\n });\n },\n };\n}\n\n/**\n * The contract's error `code` (`job_in_progress`, `not_owner_or_admin`,\n * `no_demo_content`, ...) when the server sent one. The body is\n * `{ statusCode, code, message, details? }` and `ApiRequestError.details` holds it.\n */\nexport function apiErrorCode(error: unknown): string | undefined {\n if (!(error instanceof ApiRequestError)) return undefined;\n const code = (error.details as { code?: unknown } | undefined)?.code;\n return typeof code === 'string' ? code : undefined;\n}\n"],"mappings":"AAAA,SAAS,UAAU,uBAAuB;AAsG1C,MAAM,OAAO;AAEN,SAAS,qBAAqB,YAAuC;AAC1E,SAAO;AAAA,IACL,QAAQ,MAAM,SAA4B,MAAM,EAAE,WAAW,CAAC;AAAA,IAC9D,WAAW,MACT,SAAkC,GAAG,IAAI,cAAc,EAAE,WAAW,CAAC;AAAA,IACvE,OAAO,CAAC,UACN,SAA4B,MAAM;AAAA,MAChC,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,SAAS,CAAC,CAAC;AAAA,IAClC,CAAC;AAAA,IACH,OAAO,OAAO,EAAE,UAAU,MAAM;AAC9B,YAAM,SAAkB,GAAG,IAAI,cAAc,YAAY,SAAS,OAAO,IAAI;AAAA,QAC3E,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,OAAO,OAAO,UAAU;AACtB,YAAM,SAAkB,GAAG,IAAI,UAAU;AAAA,QACvC,QAAQ;AAAA,QACR;AAAA,QACA,MAAM,KAAK,UAAU,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAOO,SAAS,aAAa,OAAoC;AAC/D,MAAI,EAAE,iBAAiB,iBAAkB,QAAO;AAChD,QAAM,OAAQ,MAAM,SAA4C;AAChE,SAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DemoContentAvailability, DemoContentClient, DemoContentStatus } from './api/demo-content';
|
|
2
|
+
/**
|
|
3
|
+
* A stand-in server for the demo-content screens: tests, and previewing the
|
|
4
|
+
* screens with no backend. It holds the state the real one would, and moves a
|
|
5
|
+
* job along each time the status is read.
|
|
6
|
+
*/
|
|
7
|
+
export interface MockDemoOptions {
|
|
8
|
+
status?: DemoContentStatus;
|
|
9
|
+
availability?: DemoContentAvailability;
|
|
10
|
+
/** Reads before a started job finishes. */
|
|
11
|
+
stepsPerJob?: number;
|
|
12
|
+
/** Make the next calls to these fail with the server's message. */
|
|
13
|
+
failNext?: Partial<Record<keyof DemoContentClient, string>>;
|
|
14
|
+
}
|
|
15
|
+
export declare function createMockDemoClient(options?: MockDemoOptions): {
|
|
16
|
+
client: DemoContentClient;
|
|
17
|
+
calls: {
|
|
18
|
+
name: string;
|
|
19
|
+
args: unknown;
|
|
20
|
+
}[];
|
|
21
|
+
setStatus: (s: DemoContentStatus) => DemoContentStatus;
|
|
22
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function createMockDemoClient(options = {}) {
|
|
2
|
+
let status = options.status ?? { state: "none" };
|
|
3
|
+
const stepsPerJob = options.stepsPerJob ?? 3;
|
|
4
|
+
const failNext = { ...options.failNext ?? {} };
|
|
5
|
+
let step = 0;
|
|
6
|
+
let nextJob = 1;
|
|
7
|
+
const calls = [];
|
|
8
|
+
const maybeFail = (name) => {
|
|
9
|
+
const message = failNext[name];
|
|
10
|
+
if (message) {
|
|
11
|
+
delete failNext[name];
|
|
12
|
+
throw new Error(message);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const client = {
|
|
16
|
+
async status() {
|
|
17
|
+
calls.push({ name: "status", args: void 0 });
|
|
18
|
+
maybeFail("status");
|
|
19
|
+
if (status.state === "applying" || status.state === "clearing") {
|
|
20
|
+
step += 1;
|
|
21
|
+
if (step >= stepsPerJob) {
|
|
22
|
+
status = status.state === "applying" ? {
|
|
23
|
+
state: "applied",
|
|
24
|
+
pack: status.pack,
|
|
25
|
+
counts: { products: 12, collections: 3, pages: 4, media: 20 },
|
|
26
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27
|
+
} : { state: "none", finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
28
|
+
} else {
|
|
29
|
+
status = {
|
|
30
|
+
...status,
|
|
31
|
+
progress: { phase: "products", done: step, total: stepsPerJob }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return status;
|
|
36
|
+
},
|
|
37
|
+
async available() {
|
|
38
|
+
calls.push({ name: "available", args: void 0 });
|
|
39
|
+
maybeFail("available");
|
|
40
|
+
return options.availability ?? { available: true };
|
|
41
|
+
},
|
|
42
|
+
async apply(input) {
|
|
43
|
+
calls.push({ name: "apply", args: input });
|
|
44
|
+
maybeFail("apply");
|
|
45
|
+
step = 0;
|
|
46
|
+
status = {
|
|
47
|
+
state: "applying",
|
|
48
|
+
pack: { id: "pack", template: "demo", version: "1", audience: "all", hash: "h" },
|
|
49
|
+
progress: { phase: "media", done: 0, total: stepsPerJob },
|
|
50
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
};
|
|
52
|
+
return { jobId: `job-${nextJob++}` };
|
|
53
|
+
},
|
|
54
|
+
async clear(args) {
|
|
55
|
+
calls.push({ name: "clear", args });
|
|
56
|
+
maybeFail("clear");
|
|
57
|
+
step = 0;
|
|
58
|
+
status = { ...status, state: "clearing", progress: { phase: "products", done: 0, total: stepsPerJob } };
|
|
59
|
+
},
|
|
60
|
+
async adopt(input) {
|
|
61
|
+
calls.push({ name: "adopt", args: input });
|
|
62
|
+
maybeFail("adopt");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return { client, calls, setStatus: (s) => status = s };
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
createMockDemoClient
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=demo-content-mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/demo-content-mock.ts"],"sourcesContent":["import type {\n DemoContentAvailability,\n DemoContentClient,\n DemoContentStatus,\n} from './api/demo-content';\n\n/**\n * A stand-in server for the demo-content screens: tests, and previewing the\n * screens with no backend. It holds the state the real one would, and moves a\n * job along each time the status is read.\n */\nexport interface MockDemoOptions {\n status?: DemoContentStatus;\n availability?: DemoContentAvailability;\n /** Reads before a started job finishes. */\n stepsPerJob?: number;\n /** Make the next calls to these fail with the server's message. */\n failNext?: Partial<Record<keyof DemoContentClient, string>>;\n}\n\nexport function createMockDemoClient(options: MockDemoOptions = {}) {\n let status: DemoContentStatus = options.status ?? { state: 'none' };\n const stepsPerJob = options.stepsPerJob ?? 3;\n const failNext = { ...(options.failNext ?? {}) };\n let step = 0;\n let nextJob = 1;\n const calls: { name: string; args: unknown }[] = [];\n\n const maybeFail = (name: keyof DemoContentClient) => {\n const message = failNext[name];\n if (message) {\n delete failNext[name];\n throw new Error(message);\n }\n };\n\n const client: DemoContentClient = {\n async status() {\n calls.push({ name: 'status', args: undefined });\n maybeFail('status');\n if (status.state === 'applying' || status.state === 'clearing') {\n step += 1;\n if (step >= stepsPerJob) {\n status =\n status.state === 'applying'\n ? {\n state: 'applied',\n pack: status.pack,\n counts: { products: 12, collections: 3, pages: 4, media: 20 },\n finishedAt: new Date().toISOString(),\n }\n : { state: 'none', finishedAt: new Date().toISOString() };\n } else {\n status = {\n ...status,\n progress: { phase: 'products', done: step, total: stepsPerJob },\n };\n }\n }\n return status;\n },\n async available() {\n calls.push({ name: 'available', args: undefined });\n maybeFail('available');\n return options.availability ?? { available: true };\n },\n async apply(input) {\n calls.push({ name: 'apply', args: input });\n maybeFail('apply');\n step = 0;\n status = {\n state: 'applying',\n pack: { id: 'pack', template: 'demo', version: '1', audience: 'all', hash: 'h' },\n progress: { phase: 'media', done: 0, total: stepsPerJob },\n startedAt: new Date().toISOString(),\n };\n return { jobId: `job-${nextJob++}` };\n },\n async clear(args) {\n calls.push({ name: 'clear', args });\n maybeFail('clear');\n step = 0;\n status = { ...status, state: 'clearing', progress: { phase: 'products', done: 0, total: stepsPerJob } };\n },\n async adopt(input) {\n calls.push({ name: 'adopt', args: input });\n maybeFail('adopt');\n },\n };\n return { client, calls, setStatus: (s: DemoContentStatus) => (status = s) };\n}\n"],"mappings":"AAoBO,SAAS,qBAAqB,UAA2B,CAAC,GAAG;AAClE,MAAI,SAA4B,QAAQ,UAAU,EAAE,OAAO,OAAO;AAClE,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,WAAW,EAAE,GAAI,QAAQ,YAAY,CAAC,EAAG;AAC/C,MAAI,OAAO;AACX,MAAI,UAAU;AACd,QAAM,QAA2C,CAAC;AAElD,QAAM,YAAY,CAAC,SAAkC;AACnD,UAAM,UAAU,SAAS,IAAI;AAC7B,QAAI,SAAS;AACX,aAAO,SAAS,IAAI;AACpB,YAAM,IAAI,MAAM,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,SAA4B;AAAA,IAChC,MAAM,SAAS;AACb,YAAM,KAAK,EAAE,MAAM,UAAU,MAAM,OAAU,CAAC;AAC9C,gBAAU,QAAQ;AAClB,UAAI,OAAO,UAAU,cAAc,OAAO,UAAU,YAAY;AAC9D,gBAAQ;AACR,YAAI,QAAQ,aAAa;AACvB,mBACE,OAAO,UAAU,aACb;AAAA,YACE,OAAO;AAAA,YACP,MAAM,OAAO;AAAA,YACb,QAAQ,EAAE,UAAU,IAAI,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG;AAAA,YAC5D,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACrC,IACA,EAAE,OAAO,QAAQ,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,QAC9D,OAAO;AACL,mBAAS;AAAA,YACP,GAAG;AAAA,YACH,UAAU,EAAE,OAAO,YAAY,MAAM,MAAM,OAAO,YAAY;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,KAAK,EAAE,MAAM,aAAa,MAAM,OAAU,CAAC;AACjD,gBAAU,WAAW;AACrB,aAAO,QAAQ,gBAAgB,EAAE,WAAW,KAAK;AAAA,IACnD;AAAA,IACA,MAAM,MAAM,OAAO;AACjB,YAAM,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC;AACzC,gBAAU,OAAO;AACjB,aAAO;AACP,eAAS;AAAA,QACP,OAAO;AAAA,QACP,MAAM,EAAE,IAAI,QAAQ,UAAU,QAAQ,SAAS,KAAK,UAAU,OAAO,MAAM,IAAI;AAAA,QAC/E,UAAU,EAAE,OAAO,SAAS,MAAM,GAAG,OAAO,YAAY;AAAA,QACxD,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AACA,aAAO,EAAE,OAAO,OAAO,SAAS,GAAG;AAAA,IACrC;AAAA,IACA,MAAM,MAAM,MAAM;AAChB,YAAM,KAAK,EAAE,MAAM,SAAS,KAAK,CAAC;AAClC,gBAAU,OAAO;AACjB,aAAO;AACP,eAAS,EAAE,GAAG,QAAQ,OAAO,YAAY,UAAU,EAAE,OAAO,YAAY,MAAM,GAAG,OAAO,YAAY,EAAE;AAAA,IACxG;AAAA,IACA,MAAM,MAAM,OAAO;AACjB,YAAM,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC;AACzC,gBAAU,OAAO;AAAA,IACnB;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO,WAAW,CAAC,MAA0B,SAAS,EAAG;AAC5E;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DemoContentStatus } from './api/demo-content';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps asking the server how a demo-content job is going until it stops.
|
|
4
|
+
*
|
|
5
|
+
* No React in it, and the clock and the tab are handed in, so a test moves time
|
|
6
|
+
* by hand. The rules:
|
|
7
|
+
*
|
|
8
|
+
* - Asks once straight away, which is also what picks up a job that was
|
|
9
|
+
* running when the page was closed and opened again.
|
|
10
|
+
* - Keeps asking only while the job is running (`applying` or `clearing`).
|
|
11
|
+
* - A hidden tab stops asking and asks once when it is shown again.
|
|
12
|
+
* - Requests never overlap; the next is timed from the end of the last.
|
|
13
|
+
* - A failed ask is not the end of the job: it backs off and tries again, and
|
|
14
|
+
* reports the error without losing the last good answer.
|
|
15
|
+
*/
|
|
16
|
+
export interface DemoPollEnv {
|
|
17
|
+
isVisible: () => boolean;
|
|
18
|
+
onVisibilityChange: (listener: () => void) => () => void;
|
|
19
|
+
setTimer: (run: () => void, ms: number) => unknown;
|
|
20
|
+
clearTimer: (timer: unknown) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function browserDemoPollEnv(): DemoPollEnv;
|
|
23
|
+
export interface DemoPollHandlers {
|
|
24
|
+
read: () => Promise<DemoContentStatus>;
|
|
25
|
+
onStatus: (status: DemoContentStatus) => void;
|
|
26
|
+
onError: (error: unknown) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface DemoPoller {
|
|
29
|
+
/** Ask now (after the merchant starts or clears), then keep going while busy. */
|
|
30
|
+
refresh: () => void;
|
|
31
|
+
stop: () => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function startDemoPoller(handlers: DemoPollHandlers, env?: DemoPollEnv): DemoPoller;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isDemoContentBusy, nextPollDelay } from "./demo-content";
|
|
2
|
+
function browserDemoPollEnv() {
|
|
3
|
+
return {
|
|
4
|
+
isVisible: () => document.visibilityState !== "hidden",
|
|
5
|
+
onVisibilityChange: (listener) => {
|
|
6
|
+
document.addEventListener("visibilitychange", listener);
|
|
7
|
+
return () => document.removeEventListener("visibilitychange", listener);
|
|
8
|
+
},
|
|
9
|
+
setTimer: (run, ms) => window.setTimeout(run, ms),
|
|
10
|
+
clearTimer: (timer) => window.clearTimeout(timer)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function startDemoPoller(handlers, env = browserDemoPollEnv()) {
|
|
14
|
+
let timer = null;
|
|
15
|
+
let inFlight = false;
|
|
16
|
+
let stopped = false;
|
|
17
|
+
let attempt = 0;
|
|
18
|
+
let failures = 0;
|
|
19
|
+
let lastKey = "";
|
|
20
|
+
const clear = () => {
|
|
21
|
+
if (timer !== null) env.clearTimer(timer);
|
|
22
|
+
timer = null;
|
|
23
|
+
};
|
|
24
|
+
const schedule = (ms) => {
|
|
25
|
+
clear();
|
|
26
|
+
if (stopped) return;
|
|
27
|
+
timer = env.setTimer(() => {
|
|
28
|
+
timer = null;
|
|
29
|
+
void ask();
|
|
30
|
+
}, ms);
|
|
31
|
+
};
|
|
32
|
+
async function ask() {
|
|
33
|
+
if (stopped || inFlight) return;
|
|
34
|
+
inFlight = true;
|
|
35
|
+
try {
|
|
36
|
+
const status = await handlers.read();
|
|
37
|
+
if (stopped) return;
|
|
38
|
+
failures = 0;
|
|
39
|
+
const key = `${status.state}:${status.progress?.phase}:${status.progress?.done}`;
|
|
40
|
+
attempt = key === lastKey ? attempt + 1 : 0;
|
|
41
|
+
lastKey = key;
|
|
42
|
+
handlers.onStatus(status);
|
|
43
|
+
if (isDemoContentBusy(status)) schedule(nextPollDelay(attempt, 0));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (stopped) return;
|
|
46
|
+
failures += 1;
|
|
47
|
+
handlers.onError(error);
|
|
48
|
+
schedule(nextPollDelay(attempt, failures));
|
|
49
|
+
} finally {
|
|
50
|
+
inFlight = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const stopWatching = env.onVisibilityChange(() => {
|
|
54
|
+
if (stopped) return;
|
|
55
|
+
if (!env.isVisible()) {
|
|
56
|
+
clear();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
clear();
|
|
60
|
+
void ask();
|
|
61
|
+
});
|
|
62
|
+
void ask();
|
|
63
|
+
return {
|
|
64
|
+
refresh: () => {
|
|
65
|
+
clear();
|
|
66
|
+
attempt = 0;
|
|
67
|
+
void ask();
|
|
68
|
+
},
|
|
69
|
+
stop: () => {
|
|
70
|
+
stopped = true;
|
|
71
|
+
clear();
|
|
72
|
+
stopWatching();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
browserDemoPollEnv,
|
|
78
|
+
startDemoPoller
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=demo-content-poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/demo-content-poller.ts"],"sourcesContent":["import type { DemoContentStatus } from './api/demo-content';\nimport { isDemoContentBusy, nextPollDelay } from './demo-content';\n\n/**\n * Keeps asking the server how a demo-content job is going until it stops.\n *\n * No React in it, and the clock and the tab are handed in, so a test moves time\n * by hand. The rules:\n *\n * - Asks once straight away, which is also what picks up a job that was\n * running when the page was closed and opened again.\n * - Keeps asking only while the job is running (`applying` or `clearing`).\n * - A hidden tab stops asking and asks once when it is shown again.\n * - Requests never overlap; the next is timed from the end of the last.\n * - A failed ask is not the end of the job: it backs off and tries again, and\n * reports the error without losing the last good answer.\n */\nexport interface DemoPollEnv {\n isVisible: () => boolean;\n onVisibilityChange: (listener: () => void) => () => void;\n setTimer: (run: () => void, ms: number) => unknown;\n clearTimer: (timer: unknown) => void;\n}\n\nexport function browserDemoPollEnv(): DemoPollEnv {\n return {\n isVisible: () => document.visibilityState !== 'hidden',\n onVisibilityChange: (listener) => {\n document.addEventListener('visibilitychange', listener);\n return () => document.removeEventListener('visibilitychange', listener);\n },\n setTimer: (run, ms) => window.setTimeout(run, ms),\n clearTimer: (timer) => window.clearTimeout(timer as number),\n };\n}\n\nexport interface DemoPollHandlers {\n read: () => Promise<DemoContentStatus>;\n onStatus: (status: DemoContentStatus) => void;\n onError: (error: unknown) => void;\n}\n\nexport interface DemoPoller {\n /** Ask now (after the merchant starts or clears), then keep going while busy. */\n refresh: () => void;\n stop: () => void;\n}\n\nexport function startDemoPoller(\n handlers: DemoPollHandlers,\n env: DemoPollEnv = browserDemoPollEnv(),\n): DemoPoller {\n let timer: unknown = null;\n let inFlight = false;\n let stopped = false;\n let attempt = 0;\n let failures = 0;\n let lastKey = '';\n\n const clear = () => {\n if (timer !== null) env.clearTimer(timer);\n timer = null;\n };\n\n const schedule = (ms: number) => {\n clear();\n if (stopped) return;\n timer = env.setTimer(() => {\n timer = null;\n void ask();\n }, ms);\n };\n\n async function ask() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const status = await handlers.read();\n if (stopped) return;\n failures = 0;\n const key = `${status.state}:${status.progress?.phase}:${status.progress?.done}`;\n attempt = key === lastKey ? attempt + 1 : 0;\n lastKey = key;\n handlers.onStatus(status);\n if (isDemoContentBusy(status)) schedule(nextPollDelay(attempt, 0));\n } catch (error) {\n if (stopped) return;\n failures += 1;\n handlers.onError(error);\n schedule(nextPollDelay(attempt, failures));\n } finally {\n inFlight = false;\n }\n }\n\n const stopWatching = env.onVisibilityChange(() => {\n if (stopped) return;\n if (!env.isVisible()) {\n clear();\n return;\n }\n // Shown again: ask once now. The answer decides whether to keep going.\n clear();\n void ask();\n });\n\n void ask();\n\n return {\n refresh: () => {\n clear();\n attempt = 0;\n void ask();\n },\n stop: () => {\n stopped = true;\n clear();\n stopWatching();\n },\n };\n}\n"],"mappings":"AACA,SAAS,mBAAmB,qBAAqB;AAuB1C,SAAS,qBAAkC;AAChD,SAAO;AAAA,IACL,WAAW,MAAM,SAAS,oBAAoB;AAAA,IAC9C,oBAAoB,CAAC,aAAa;AAChC,eAAS,iBAAiB,oBAAoB,QAAQ;AACtD,aAAO,MAAM,SAAS,oBAAoB,oBAAoB,QAAQ;AAAA,IACxE;AAAA,IACA,UAAU,CAAC,KAAK,OAAO,OAAO,WAAW,KAAK,EAAE;AAAA,IAChD,YAAY,CAAC,UAAU,OAAO,aAAa,KAAe;AAAA,EAC5D;AACF;AAcO,SAAS,gBACd,UACA,MAAmB,mBAAmB,GAC1B;AACZ,MAAI,QAAiB;AACrB,MAAI,WAAW;AACf,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,WAAW;AACf,MAAI,UAAU;AAEd,QAAM,QAAQ,MAAM;AAClB,QAAI,UAAU,KAAM,KAAI,WAAW,KAAK;AACxC,YAAQ;AAAA,EACV;AAEA,QAAM,WAAW,CAAC,OAAe;AAC/B,UAAM;AACN,QAAI,QAAS;AACb,YAAQ,IAAI,SAAS,MAAM;AACzB,cAAQ;AACR,WAAK,IAAI;AAAA,IACX,GAAG,EAAE;AAAA,EACP;AAEA,iBAAe,MAAM;AACnB,QAAI,WAAW,SAAU;AACzB,eAAW;AACX,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,UAAI,QAAS;AACb,iBAAW;AACX,YAAM,MAAM,GAAG,OAAO,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI,OAAO,UAAU,IAAI;AAC9E,gBAAU,QAAQ,UAAU,UAAU,IAAI;AAC1C,gBAAU;AACV,eAAS,SAAS,MAAM;AACxB,UAAI,kBAAkB,MAAM,EAAG,UAAS,cAAc,SAAS,CAAC,CAAC;AAAA,IACnE,SAAS,OAAO;AACd,UAAI,QAAS;AACb,kBAAY;AACZ,eAAS,QAAQ,KAAK;AACtB,eAAS,cAAc,SAAS,QAAQ,CAAC;AAAA,IAC3C,UAAE;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AAEA,QAAM,eAAe,IAAI,mBAAmB,MAAM;AAChD,QAAI,QAAS;AACb,QAAI,CAAC,IAAI,UAAU,GAAG;AACpB,YAAM;AACN;AAAA,IACF;AAEA,UAAM;AACN,SAAK,IAAI;AAAA,EACX,CAAC;AAED,OAAK,IAAI;AAET,SAAO;AAAA,IACL,SAAS,MAAM;AACb,YAAM;AACN,gBAAU;AACV,WAAK,IAAI;AAAA,IACX;AAAA,IACA,MAAM,MAAM;AACV,gBAAU;AACV,YAAM;AACN,mBAAa;AAAA,IACf;AAAA,EACF;AACF;","names":[]}
|