@withpotter/lib 0.13.0-next.120 → 0.13.0-next.123
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/admin-api.d.ts +6 -27
- package/dist/admin-api.js +0 -9
- package/dist/admin-api.js.map +1 -1
- package/dist/admin-earnings-api.d.ts +90 -0
- package/dist/admin-earnings-api.js +15 -0
- package/dist/admin-earnings-api.js.map +1 -0
- package/dist/admin-earnings-model.d.ts +30 -0
- package/dist/admin-earnings-model.js +47 -0
- package/dist/admin-earnings-model.js.map +1 -0
- package/package.json +1 -1
package/dist/admin-api.d.ts
CHANGED
|
@@ -1167,8 +1167,6 @@ export declare const platformAdminApi: {
|
|
|
1167
1167
|
inviteAdmin: (email: string, role: "admin" | "superadmin", permissions?: string[], roleIds?: string[]) => Promise<{
|
|
1168
1168
|
email: string;
|
|
1169
1169
|
tempPassword: string;
|
|
1170
|
-
/** Set when roles were asked for: they apply once a second super admin approves. */
|
|
1171
|
-
pendingRoles?: PendingRoleChange;
|
|
1172
1170
|
}>;
|
|
1173
1171
|
setAdminStatus: (id: string, status: "active" | "suspended") => Promise<{
|
|
1174
1172
|
success: boolean;
|
|
@@ -1176,23 +1174,22 @@ export declare const platformAdminApi: {
|
|
|
1176
1174
|
setAdminPermissions: (id: string, permissions: string[]) => Promise<{
|
|
1177
1175
|
success: boolean;
|
|
1178
1176
|
}>;
|
|
1179
|
-
setAdminRoles: (id: string, roleIds: string[]) => Promise<
|
|
1177
|
+
setAdminRoles: (id: string, roleIds: string[]) => Promise<{
|
|
1178
|
+
success: boolean;
|
|
1179
|
+
}>;
|
|
1180
1180
|
resetAdminPassword: (id: string) => Promise<{
|
|
1181
1181
|
tempPassword: string;
|
|
1182
1182
|
}>;
|
|
1183
1183
|
roles: () => Promise<RoleRow[]>;
|
|
1184
|
-
createRole: (name: string, permissions: string[], description?: string) => Promise<
|
|
1184
|
+
createRole: (name: string, permissions: string[], description?: string) => Promise<RoleRow>;
|
|
1185
1185
|
updateRole: (id: string, data: {
|
|
1186
1186
|
name?: string;
|
|
1187
1187
|
permissions?: string[];
|
|
1188
1188
|
description?: string;
|
|
1189
|
-
}) => Promise<
|
|
1190
|
-
deleteRole: (id: string) => Promise<PendingRoleChange>;
|
|
1191
|
-
roleRequests: () => Promise<RoleChangeRow[]>;
|
|
1192
|
-
approveRoleChange: (id: string, note?: string) => Promise<{
|
|
1189
|
+
}) => Promise<{
|
|
1193
1190
|
success: boolean;
|
|
1194
1191
|
}>;
|
|
1195
|
-
|
|
1192
|
+
deleteRole: (id: string) => Promise<{
|
|
1196
1193
|
success: boolean;
|
|
1197
1194
|
}>;
|
|
1198
1195
|
audit: (params?: {
|
|
@@ -1221,24 +1218,6 @@ export interface AdminRow {
|
|
|
1221
1218
|
lastLoginAt: string | null;
|
|
1222
1219
|
createdAt: string;
|
|
1223
1220
|
}
|
|
1224
|
-
/** A role change that now waits for a second super admin. */
|
|
1225
|
-
export interface PendingRoleChange {
|
|
1226
|
-
pending: true;
|
|
1227
|
-
requestId: string;
|
|
1228
|
-
summary: string;
|
|
1229
|
-
}
|
|
1230
|
-
export interface RoleChangeRow {
|
|
1231
|
-
id: string;
|
|
1232
|
-
action: 'role.create' | 'role.update' | 'role.delete' | 'admin.roles.assign';
|
|
1233
|
-
targetId: string | null;
|
|
1234
|
-
summary: string;
|
|
1235
|
-
status: 'pending' | 'approved' | 'rejected';
|
|
1236
|
-
requestedByEmail: string;
|
|
1237
|
-
decidedByEmail: string | null;
|
|
1238
|
-
decisionNote: string | null;
|
|
1239
|
-
decidedAt: string | null;
|
|
1240
|
-
createdAt: string;
|
|
1241
|
-
}
|
|
1242
1221
|
export interface RoleRow {
|
|
1243
1222
|
id: string;
|
|
1244
1223
|
key: string;
|
package/dist/admin-api.js
CHANGED
|
@@ -304,15 +304,6 @@ const platformAdminApi = {
|
|
|
304
304
|
deleteRole: (id) => apiFetch(`/platform-admin/v1/roles/${id}`, {
|
|
305
305
|
method: "DELETE"
|
|
306
306
|
}),
|
|
307
|
-
roleRequests: () => apiFetch("/platform-admin/v1/roles/requests"),
|
|
308
|
-
approveRoleChange: (id, note) => apiFetch(
|
|
309
|
-
`/platform-admin/v1/roles/requests/${id}/approve`,
|
|
310
|
-
{ method: "POST", body: JSON.stringify({ note }) }
|
|
311
|
-
),
|
|
312
|
-
rejectRoleChange: (id, note) => apiFetch(
|
|
313
|
-
`/platform-admin/v1/roles/requests/${id}/reject`,
|
|
314
|
-
{ method: "POST", body: JSON.stringify({ note }) }
|
|
315
|
-
),
|
|
316
307
|
audit: (params = {}) => {
|
|
317
308
|
const qs = new URLSearchParams();
|
|
318
309
|
if (params.page) qs.set("page", String(params.page));
|
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 AccountSession,\n AccountStage,\n AccountKycStatus,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type {\n AccountRow,\n AccountSession,\n AccountStage,\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 * Platform fees collected on card payments, one row per currency. Absent\n * until the api serves it; the Overview shows no card without it.\n */\n gatewayFeesByCurrency?: CurrencyAmount[];\n /**\n * Wallets whose business passed KYC / KYB. Absent until the api serves\n * it; the Overview shows no card without it.\n */\n verifiedWallets?: number;\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\n/**\n * What one payment channel (Card, USSD, Bank transfer, Opay...) brought in, in one\n * currency. `amountMinor` is in minor units. Sent only by a box that records how\n * customers paid; others answer 404, and the panels that use it stay hidden.\n */\nexport interface GatewayChannelTotal {\n channel: { key: string; label: string };\n currency: string;\n count: number;\n amountMinor: string;\n}\n\n/**\n * Revenue's Merchant payments and Subscriptions views. Amounts are whole minor\n * units in strings (kobo), per currency, never added across currencies.\n * Boxes older than this answer 404 and the views stay hidden.\n */\nexport interface LedgerFilters {\n startDate?: string;\n endDate?: string;\n /** Shown as a word in the table; the API takes a card, ussd, transfer, opay… key. */\n method?: string;\n type?: string;\n status?: string;\n /** Matches a reference, a payer's name, email or phone, or a merchant's name. */\n q?: string;\n /** One merchant's id. */\n merchantId?: string;\n page?: number;\n limit?: number;\n}\n\nexport interface MerchantPaymentRow {\n id: string;\n reference: string;\n paidAt: string;\n payer: { name: string | null; email: string | null; phone: string | null };\n merchant: { id: string; name: string | null };\n channel: { key: string; label: string };\n /** The card (brand and last four) or the bank, when known. */\n methodDetail: string | null;\n type: string;\n status: string;\n currency: string;\n amountMinor: string;\n feeMinor: string;\n netMinor: string;\n orderId: string | null;\n}\n\nexport interface LedgerCard {\n currency: string;\n grossMinor: string;\n feeMinor: string;\n netMinor: string;\n refundsMinor: string;\n payments: number;\n /** Percent of attempts that went through; null when nothing was attempted. */\n successRate: number | null;\n}\n\nexport interface MerchantPaymentsPage {\n rows: MerchantPaymentRow[];\n page: number;\n limit: number;\n total: number;\n /** Totals of exactly the rows the filters match. */\n strip: Array<{\n currency: string;\n payments: number;\n amountMinor: string;\n feeMinor: string;\n netMinor: string;\n }>;\n cards: { current: LedgerCard[]; previous: LedgerCard[] | null };\n byChannel: {\n current: GatewayChannelTotal[];\n previous: GatewayChannelTotal[] | null;\n };\n}\n\n/** One merchant on the Merchants view: what it received, and how customers paid it. */\nexport interface MerchantRevenueRow {\n merchant: { id: string; name: string | null };\n currency: string;\n payments: number;\n grossMinor: string;\n byMethod: Array<{\n key: string;\n label: string;\n payments: number;\n amountMinor: string;\n }>;\n}\n\nexport interface MerchantRevenuePage {\n rows: MerchantRevenueRow[];\n page: number;\n limit: number;\n}\n\nexport interface SubscriptionPaymentRow {\n id: string;\n reference: string;\n merchantName: string | null;\n merchantEmail: string | null;\n tenantId: string;\n plan: string;\n /** select_plan, upgrade, renewal, reactivation or mandate_setup. */\n purpose: string;\n amountKobo: number;\n /** gateway, wallet or manual. */\n method: string;\n /** pending, succeeded or failed. */\n status: string;\n paidAt: string | null;\n createdAt: string;\n failureReason: string | null;\n}\n\nexport interface SubscriptionPaymentsPage {\n items: SubscriptionPaymentRow[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface PlatformRevenue {\n range: { startDate: string; endDate: string };\n currency: string;\n /**\n * What Olive keeps from payouts, settlements and subscription charges: net\n * credits on the ledger's platform fees account, in major units, one row per\n * currency, A to Z. Empty when nothing was earned. Not card fees only, no\n * refund netting, and test merchants are included. Absent until the api\n * serves it, so the UI hides the figure rather than showing a zero.\n */\n platformFeesEarnedByCurrency?: CurrencyAmount[];\n /**\n * What merchants hold in their wallets right now: pending, available,\n * restricted and payout-pending together, major units, one row per currency.\n * A balance today, not a window. Absent until the api serves it.\n */\n merchantWalletsByCurrency?: CurrencyAmount[];\n /**\n * What merchants withdrew to their banks in the window, net of payouts the\n * bank returned, major units, one row per currency. Absent until the api\n * serves it.\n */\n merchantPayoutsByCurrency?: CurrencyAmount[];\n recurring: {\n /** False when no billing source feeds the figures below (all are 0). */\n tracked?: boolean;\n /** The currency of the figures below, when the box names it. */\n currency?: string;\n /** Merchants on a paid plan right now (absent on older APIs). */\n payingMerchants?: number;\n /**\n * Plan payments that succeeded in the window, in the currency above.\n * What came in, not the expected monthly figure. Absent until the api\n * serves it.\n */\n collected?: number;\n /**\n * The same payments by how they were paid: `wallet` (Box wallet), `gateway` (Olive payment gateway) or `manual`,\n * in the currency above. Only ways that were used appear. Absent until the api serves it.\n */\n collectedByMethod?: Array<{ method: string; payments: number; amount: number }>;\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 /** The business's contact email (absent on older APIs). */\n email?: string | null;\n phone?: string | null;\n /** The owner's sign-in email. */\n ownerEmail?: string | null;\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 customerPhone?: string | null;\n /** How the customer paid (Card, USSD, Bank transfer, Opay); absent on older APIs. */\n paymentMethod?: 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\n/** The query string for a Revenue view; empty values are left out. */\nfunction ledgerQuery(f: LedgerFilters, pageSizeKey: 'limit' | 'pageSize' = 'limit'): string {\n const qs = new URLSearchParams();\n const { limit, ...rest } = f;\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== '') qs.set(key, String(value));\n }\n if (limit) qs.set(pageSizeKey, String(limit));\n const q = qs.toString();\n\n return q ? `?${q}` : '';\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 gatewayChannels: (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<{ byChannel: GatewayChannelTotal[] }>(\n `/platform-admin/v1/gateway-payments/channels${q ? `?${q}` : ''}`,\n );\n },\n merchantPayments: (f: LedgerFilters = {}) =>\n apiFetch<MerchantPaymentsPage>(\n `/platform-admin/v1/revenue/merchant-payments${ledgerQuery(f)}`,\n ),\n /** The same payments as CSV text. */\n merchantPaymentsCsv: (f: LedgerFilters = {}) =>\n apiFetch<string>(\n `/platform-admin/v1/revenue/merchant-payments/export${ledgerQuery(f)}`,\n ),\n merchantRevenue: (f: LedgerFilters = {}) =>\n apiFetch<MerchantRevenuePage>(\n `/platform-admin/v1/revenue/merchants${ledgerQuery(f)}`,\n ),\n /** Merchants that took payments in the window, for a picker. */\n merchantOptions: (f: LedgerFilters = {}) =>\n apiFetch<{ merchants: Array<{ id: string; name: string }> }>(\n `/platform-admin/v1/revenue/merchants/options${ledgerQuery({ startDate: f.startDate, endDate: f.endDate })}`,\n ),\n subscriptionPayments: (f: LedgerFilters = {}) =>\n apiFetch<SubscriptionPaymentsPage>(\n `/platform-admin/v1/revenue/subscription-payments${ledgerQuery(f, 'pageSize')}`,\n ),\n subscriptionPaymentsCsv: (f: LedgerFilters = {}) =>\n apiFetch<string>(\n `/platform-admin/v1/revenue/subscription-payments/export${ledgerQuery(f, 'pageSize')}`,\n ),\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 stage?: AccountStage;\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 if (params.stage) qs.set('stage', params.stage);\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 accountSessions: (id: string) =>\n apiFetch<AccountSession[]>(`/platform-admin/v1/accounts/${id}/sessions`),\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<{\n email: string;\n tempPassword: string;\n /** Set when roles were asked for: they apply once a second super admin approves. */\n pendingRoles?: PendingRoleChange;\n }>(\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<PendingRoleChange>(`/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<PendingRoleChange>('/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<PendingRoleChange>(`/platform-admin/v1/roles/${id}`, {\n method: 'PATCH',\n body: JSON.stringify(data),\n }),\n deleteRole: (id: string) =>\n apiFetch<PendingRoleChange>(`/platform-admin/v1/roles/${id}`, {\n method: 'DELETE',\n }),\n roleRequests: () =>\n apiFetch<RoleChangeRow[]>('/platform-admin/v1/roles/requests'),\n approveRoleChange: (id: string, note?: string) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/roles/requests/${id}/approve`,\n { method: 'POST', body: JSON.stringify({ note }) },\n ),\n rejectRoleChange: (id: string, note?: string) =>\n apiFetch<{ success: boolean }>(\n `/platform-admin/v1/roles/requests/${id}/reject`,\n { method: 'POST', body: JSON.stringify({ note }) },\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\n/** A role change that now waits for a second super admin. */\nexport interface PendingRoleChange {\n pending: true;\n requestId: string;\n summary: string;\n}\n\nexport interface RoleChangeRow {\n id: string;\n action: 'role.create' | 'role.update' | 'role.delete' | 'admin.roles.assign';\n targetId: string | null;\n summary: string;\n status: 'pending' | 'approved' | 'rejected';\n requestedByEmail: string;\n decidedByEmail: string | null;\n decisionNote: string | null;\n decidedAt: 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":"AAAA;AAAA,EACE;AAAA,OAIK;AAuBP,SAAS,oBAA2C;AAw7BpD,SAAS,YAAY,GAAkB,cAAoC,SAAiB;AAC1F,QAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAM,EAAE,OAAO,GAAG,KAAK,IAAI;AAC3B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,QAAI,UAAU,UAAa,UAAU,GAAI,IAAG,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,EACpE;AACA,MAAI,MAAO,IAAG,IAAI,aAAa,OAAO,KAAK,CAAC;AAC5C,QAAM,IAAI,GAAG,SAAS;AAEtB,SAAO,IAAI,IAAI,CAAC,KAAK;AACvB;AAEO,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,OAAO,CAAC,OAAe,aACrB,SAA2B,iCAAiC;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,EAC1C,CAAC;AAAA,EACH,iBAAiB,CAAC,aAAqB,SACrC,SAGG,wCAAwC;AAAA,IACzC,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,aAAa,KAAK,CAAC;AAAA,EAC5C,CAAC;AAAA,EACH,iBAAiB,CAAC,gBAChB,SAAiC,wCAAwC;AAAA,IACvE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,YAAY,CAAC;AAAA,EACtC,CAAC;AAAA,EACH,QAAQ,MACN,SAA+B,kCAAkC;AAAA,IAC/D,QAAQ;AAAA,EACV,CAAC;AAAA,EACH,IAAI,MAAM,SAAuB,4BAA4B;AAAA,EAC7D,gBAAgB,CAAC,iBAAyB,gBACxC,SAA+B,2CAA2C;AAAA,IACxE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,iBAAiB,YAAY,CAAC;AAAA,EACvD,CAAC;AAAA,EACH,UAAU,MAAM,SAA2B,6BAA6B;AAAA,EACxE,iBAAiB,CAAC,SAAmD,CAAC,MAAM;AAC1E,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,+CAA+C,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IACjE;AAAA,EACF;AAAA,EACA,kBAAkB,CAAC,IAAmB,CAAC,MACrC;AAAA,IACE,+CAA+C,YAAY,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA,EAEF,qBAAqB,CAAC,IAAmB,CAAC,MACxC;AAAA,IACE,sDAAsD,YAAY,CAAC,CAAC;AAAA,EACtE;AAAA,EACF,iBAAiB,CAAC,IAAmB,CAAC,MACpC;AAAA,IACE,uCAAuC,YAAY,CAAC,CAAC;AAAA,EACvD;AAAA;AAAA,EAEF,iBAAiB,CAAC,IAAmB,CAAC,MACpC;AAAA,IACE,+CAA+C,YAAY,EAAE,WAAW,EAAE,WAAW,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC5G;AAAA,EACF,sBAAsB,CAAC,IAAmB,CAAC,MACzC;AAAA,IACE,mDAAmD,YAAY,GAAG,UAAU,CAAC;AAAA,EAC/E;AAAA,EACF,yBAAyB,CAAC,IAAmB,CAAC,MAC5C;AAAA,IACE,0DAA0D,YAAY,GAAG,UAAU,CAAC;AAAA,EACtF;AAAA,EACF,SAAS,CAAC,SAAmD,CAAC,MAAM;AAClE,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,sCAAsC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EACA,SAAS,CAAC,SAAmD,CAAC,MAAM;AAClE,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,sCAAsC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EACA,kBAAkB,CAAC,SAAmD,CAAC,MAAM;AAC3E,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,gDAAgD,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IAClE;AAAA,EACF;AAAA,EACA,SAAS,CAAC,SAA2B,CAAC,MACpC,SAAsB,6BAA6B,aAAa,MAAM,CAAC,EAAE;AAAA,EAC3E,QAAQ,CACN,IACA,QAAkD,CAAC,MAChD;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,UAAW,IAAG,IAAI,aAAa,MAAM,SAAS;AACxD,QAAI,MAAM,QAAS,IAAG,IAAI,WAAW,MAAM,OAAO;AAClD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IAC7D;AAAA,EACF;AAAA,EACA,iBAAiB,CACf,IACA,QAAkE,CAAC,MAChE;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,UAAW,IAAG,IAAI,aAAa,MAAM,SAAS;AACxD,QAAI,MAAM,QAAS,IAAG,IAAI,WAAW,MAAM,OAAO;AAClD,QAAI,MAAM,MAAO,IAAG,IAAI,SAAS,OAAO,MAAM,KAAK,CAAC;AACpD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,aAAa,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACvE;AAAA,EACF;AAAA,EACA,eAAe,CACb,IACA,QAAmE,CAAC,MACjE;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,UAAW,IAAG,IAAI,aAAa,MAAM,SAAS;AACxD,QAAI,MAAM,QAAS,IAAG,IAAI,WAAW,MAAM,OAAO;AAClD,QAAI,MAAM,OAAQ,IAAG,IAAI,UAAU,MAAM,MAAM;AAC/C,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,WAAW,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACrE;AAAA,EACF;AAAA,EACA,oBAAoB,CAClB,IACA,SAMI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,IAAI;AAC3C,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,gBAC9B,QAAQ,IAAI,KAAK,KAAK,EACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,iBAAiB,CACf,IACA,SAMI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,IAAI;AAC3C,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,aAAa,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACvE;AAAA,EACF;AAAA,EACA,gBAAgB,CACd,IACA,SAA6C,CAAC,MAC3C;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,IAAI;AAC3C,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,YAAY,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACtE;AAAA,EACF;AAAA,EACA,uBAAuB,CAAC,IAAY,QAAgC,CAAC,MAAM;AACzE,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,UAAW,IAAG,IAAI,aAAa,MAAM,SAAS;AACxD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,qBAC9B,QAAQ,IAAI,KAAK,KAAK,EACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,wBAAwB,CAAC,IAAY,QAAgC,CAAC,MAAM;AAC1E,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,UAAW,IAAG,IAAI,aAAa,MAAM,SAAS;AACxD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,sBAC9B,QAAQ,IAAI,KAAK,KAAK,EACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,gBAAgB,CACd,IACA,SAKI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,YAAY,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACtE;AAAA,EACF;AAAA,EACA,eAAe,CAAC,OACd,SAA4B,8BAA8B,EAAE,UAAU;AAAA,EACxE,iBAAiB,CACf,IACA,SAMI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,aAAc,IAAG,IAAI,gBAAgB,OAAO,YAAY;AACnE,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,aAAa,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACvE;AAAA,EACF;AAAA,EACA,mBAAmB,CACjB,IACA,SAKI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,eAAe,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACzE;AAAA,EACF;AAAA,EACA,qBAAqB,CACnB,IACA,SAKI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACtD,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,EAAE,iBAC9B,QAAQ,IAAI,KAAK,KAAK,EACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,iBAAiB,CAAC,IAAY,WAC5B,SAA+B,8BAA8B,EAAE,WAAW;AAAA,IACxE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,EACjC,CAAC;AAAA,EACH,UAAU,CACR,SAOI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,SAAU,IAAG,IAAI,YAAY,OAAO,OAAO,QAAQ,CAAC;AAC/D,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,WAAY,IAAG,IAAI,cAAc,OAAO,UAAU;AAC7D,QAAI,OAAO,SAAU,IAAG,IAAI,YAAY,OAAO,QAAQ;AACvD,QAAI,OAAO,MAAO,IAAG,IAAI,SAAS,OAAO,KAAK;AAC9C,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO;AAAA,MACL,8BAA8B,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EACA,mBAAmB,CACjB,SAAmD,CAAC,MACjD;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,wCAAwC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IAC1D;AAAA,EACF;AAAA,EACA,kBAAkB,CAAC,IAAY,WAC7B,SAA+B,+BAA+B,EAAE,WAAW;AAAA,IACzE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,EACjC,CAAC;AAAA,EACH,eAAe,CAAC,OACd,SAA+B,+BAA+B,EAAE,WAAW;AAAA,IACzE,QAAQ;AAAA,EACV,CAAC;AAAA,EACH,iBAAiB,CAAC,OAChB,SAA2B,+BAA+B,EAAE,WAAW;AAAA,EACzE,gBAAgB,CAAC,OACf,SAA6B,+BAA+B,EAAE,UAAU;AAAA,EAC1E,oBAAoB,CAAC,OACnB;AAAA,IACE,+BAA+B,EAAE;AAAA,IACjC,EAAE,QAAQ,OAAO;AAAA,EACnB;AAAA,EACF,QAAQ,MAAM,SAAqB,2BAA2B;AAAA,EAC9D,iBAAiB,CAAC,SAAmD,CAAC,MAAM;AAC1E,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,sCAAsC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EACA,kBAAkB,MAChB,SAAmB,uCAAuC;AAAA,EAC5D,aAAa,CACX,OACA,MACA,cAAwB,CAAC,GACzB,UAAoB,CAAC,MAErB;AAAA,IAME;AAAA,IACA;AAAA,MACE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,MAAM,aAAa,QAAQ,CAAC;AAAA,IAC5D;AAAA,EACF;AAAA,EACF,gBAAgB,CAAC,IAAY,WAC3B,SAA+B,6BAA6B,EAAE,WAAW;AAAA,IACvE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,EACjC,CAAC;AAAA,EACH,qBAAqB,CAAC,IAAY,gBAChC;AAAA,IACE,6BAA6B,EAAE;AAAA,IAC/B,EAAE,QAAQ,SAAS,MAAM,KAAK,UAAU,EAAE,YAAY,CAAC,EAAE;AAAA,EAC3D;AAAA,EACF,eAAe,CAAC,IAAY,YAC1B,SAA4B,6BAA6B,EAAE,UAAU;AAAA,IACnE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,EAClC,CAAC;AAAA,EACH,oBAAoB,CAAC,OACnB;AAAA,IACE,6BAA6B,EAAE;AAAA,IAC/B,EAAE,QAAQ,OAAO;AAAA,EACnB;AAAA,EACF,OAAO,MAAM,SAAoB,0BAA0B;AAAA,EAC3D,YAAY,CAAC,MAAc,aAAuB,gBAChD,SAA4B,4BAA4B;AAAA,IACtD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,MAAM,aAAa,YAAY,CAAC;AAAA,EACzD,CAAC;AAAA,EACH,YAAY,CACV,IACA,SAEA,SAA4B,4BAA4B,EAAE,IAAI;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAAA,EACH,YAAY,CAAC,OACX,SAA4B,4BAA4B,EAAE,IAAI;AAAA,IAC5D,QAAQ;AAAA,EACV,CAAC;AAAA,EACH,cAAc,MACZ,SAA0B,mCAAmC;AAAA,EAC/D,mBAAmB,CAAC,IAAY,SAC9B;AAAA,IACE,qCAAqC,EAAE;AAAA,IACvC,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE;AAAA,EACnD;AAAA,EACF,kBAAkB,CAAC,IAAY,SAC7B;AAAA,IACE,qCAAqC,EAAE;AAAA,IACvC,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE;AAAA,EACnD;AAAA,EACF,OAAO,CACL,SAQI,CAAC,MACF;AACH,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,OAAO,IAAI,CAAC;AACnD,QAAI,OAAO,SAAU,IAAG,IAAI,YAAY,OAAO,OAAO,QAAQ,CAAC;AAC/D,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,OAAQ,IAAG,IAAI,UAAU,OAAO,MAAM;AACjD,QAAI,OAAO,WAAY,IAAG,IAAI,cAAc,OAAO,UAAU;AAC7D,QAAI,OAAO,KAAM,IAAG,IAAI,QAAQ,OAAO,IAAI;AAC3C,QAAI,OAAO,GAAI,IAAG,IAAI,MAAM,OAAO,EAAE;AACrC,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO,SAAoB,2BAA2B,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE;AAAA,EAC1E;AAAA,EACA,gBAAgB,CAAC,SAAmD,CAAC,MAAM;AACzE,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,OAAO,UAAW,IAAG,IAAI,aAAa,OAAO,SAAS;AAC1D,QAAI,OAAO,QAAS,IAAG,IAAI,WAAW,OAAO,OAAO;AACpD,UAAM,IAAI,GAAG,SAAS;AACtB,WAAO;AAAA,MACL,qCAAqC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAA,IACvD;AAAA,EACF;AACF;","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 AccountSession,\n AccountStage,\n AccountKycStatus,\n TenantTeamMember,\n AccountsAnalytics,\n} from './admin-account-types';\nimport type {\n AccountRow,\n AccountSession,\n AccountStage,\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 * Platform fees collected on card payments, one row per currency. Absent\n * until the api serves it; the Overview shows no card without it.\n */\n gatewayFeesByCurrency?: CurrencyAmount[];\n /**\n * Wallets whose business passed KYC / KYB. Absent until the api serves\n * it; the Overview shows no card without it.\n */\n verifiedWallets?: number;\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\n/**\n * What one payment channel (Card, USSD, Bank transfer, Opay...) brought in, in one\n * currency. `amountMinor` is in minor units. Sent only by a box that records how\n * customers paid; others answer 404, and the panels that use it stay hidden.\n */\nexport interface GatewayChannelTotal {\n channel: { key: string; label: string };\n currency: string;\n count: number;\n amountMinor: string;\n}\n\n/**\n * Revenue's Merchant payments and Subscriptions views. Amounts are whole minor\n * units in strings (kobo), per currency, never added across currencies.\n * Boxes older than this answer 404 and the views stay hidden.\n */\nexport interface LedgerFilters {\n startDate?: string;\n endDate?: string;\n /** Shown as a word in the table; the API takes a card, ussd, transfer, opay… key. */\n method?: string;\n type?: string;\n status?: string;\n /** Matches a reference, a payer's name, email or phone, or a merchant's name. */\n q?: string;\n /** One merchant's id. */\n merchantId?: string;\n page?: number;\n limit?: number;\n}\n\nexport interface MerchantPaymentRow {\n id: string;\n reference: string;\n paidAt: string;\n payer: { name: string | null; email: string | null; phone: string | null };\n merchant: { id: string; name: string | null };\n channel: { key: string; label: string };\n /** The card (brand and last four) or the bank, when known. */\n methodDetail: string | null;\n type: string;\n status: string;\n currency: string;\n amountMinor: string;\n feeMinor: string;\n netMinor: string;\n orderId: string | null;\n}\n\nexport interface LedgerCard {\n currency: string;\n grossMinor: string;\n feeMinor: string;\n netMinor: string;\n refundsMinor: string;\n payments: number;\n /** Percent of attempts that went through; null when nothing was attempted. */\n successRate: number | null;\n}\n\nexport interface MerchantPaymentsPage {\n rows: MerchantPaymentRow[];\n page: number;\n limit: number;\n total: number;\n /** Totals of exactly the rows the filters match. */\n strip: Array<{\n currency: string;\n payments: number;\n amountMinor: string;\n feeMinor: string;\n netMinor: string;\n }>;\n cards: { current: LedgerCard[]; previous: LedgerCard[] | null };\n byChannel: {\n current: GatewayChannelTotal[];\n previous: GatewayChannelTotal[] | null;\n };\n}\n\n/** One merchant on the Merchants view: what it received, and how customers paid it. */\nexport interface MerchantRevenueRow {\n merchant: { id: string; name: string | null };\n currency: string;\n payments: number;\n grossMinor: string;\n byMethod: Array<{\n key: string;\n label: string;\n payments: number;\n amountMinor: string;\n }>;\n}\n\nexport interface MerchantRevenuePage {\n rows: MerchantRevenueRow[];\n page: number;\n limit: number;\n}\n\nexport interface SubscriptionPaymentRow {\n id: string;\n reference: string;\n merchantName: string | null;\n merchantEmail: string | null;\n tenantId: string;\n plan: string;\n /** select_plan, upgrade, renewal, reactivation or mandate_setup. */\n purpose: string;\n amountKobo: number;\n /** gateway, wallet or manual. */\n method: string;\n /** pending, succeeded or failed. */\n status: string;\n paidAt: string | null;\n createdAt: string;\n failureReason: string | null;\n}\n\nexport interface SubscriptionPaymentsPage {\n items: SubscriptionPaymentRow[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface PlatformRevenue {\n range: { startDate: string; endDate: string };\n currency: string;\n /**\n * What Olive keeps from payouts, settlements and subscription charges: net\n * credits on the ledger's platform fees account, in major units, one row per\n * currency, A to Z. Empty when nothing was earned. Not card fees only, no\n * refund netting, and test merchants are included. Absent until the api\n * serves it, so the UI hides the figure rather than showing a zero.\n */\n platformFeesEarnedByCurrency?: CurrencyAmount[];\n /**\n * What merchants hold in their wallets right now: pending, available,\n * restricted and payout-pending together, major units, one row per currency.\n * A balance today, not a window. Absent until the api serves it.\n */\n merchantWalletsByCurrency?: CurrencyAmount[];\n /**\n * What merchants withdrew to their banks in the window, net of payouts the\n * bank returned, major units, one row per currency. Absent until the api\n * serves it.\n */\n merchantPayoutsByCurrency?: CurrencyAmount[];\n recurring: {\n /** False when no billing source feeds the figures below (all are 0). */\n tracked?: boolean;\n /** The currency of the figures below, when the box names it. */\n currency?: string;\n /** Merchants on a paid plan right now (absent on older APIs). */\n payingMerchants?: number;\n /**\n * Plan payments that succeeded in the window, in the currency above.\n * What came in, not the expected monthly figure. Absent until the api\n * serves it.\n */\n collected?: number;\n /**\n * The same payments by how they were paid: `wallet` (Box wallet), `gateway` (Olive payment gateway) or `manual`,\n * in the currency above. Only ways that were used appear. Absent until the api serves it.\n */\n collectedByMethod?: Array<{ method: string; payments: number; amount: number }>;\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 /** The business's contact email (absent on older APIs). */\n email?: string | null;\n phone?: string | null;\n /** The owner's sign-in email. */\n ownerEmail?: string | null;\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 customerPhone?: string | null;\n /** How the customer paid (Card, USSD, Bank transfer, Opay); absent on older APIs. */\n paymentMethod?: 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\n/** The query string for a Revenue view; empty values are left out. */\nfunction ledgerQuery(f: LedgerFilters, pageSizeKey: 'limit' | 'pageSize' = 'limit'): string {\n const qs = new URLSearchParams();\n const { limit, ...rest } = f;\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== '') qs.set(key, String(value));\n }\n if (limit) qs.set(pageSizeKey, String(limit));\n const q = qs.toString();\n\n return q ? `?${q}` : '';\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 gatewayChannels: (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<{ byChannel: GatewayChannelTotal[] }>(\n `/platform-admin/v1/gateway-payments/channels${q ? `?${q}` : ''}`,\n );\n },\n merchantPayments: (f: LedgerFilters = {}) =>\n apiFetch<MerchantPaymentsPage>(\n `/platform-admin/v1/revenue/merchant-payments${ledgerQuery(f)}`,\n ),\n /** The same payments as CSV text. */\n merchantPaymentsCsv: (f: LedgerFilters = {}) =>\n apiFetch<string>(\n `/platform-admin/v1/revenue/merchant-payments/export${ledgerQuery(f)}`,\n ),\n merchantRevenue: (f: LedgerFilters = {}) =>\n apiFetch<MerchantRevenuePage>(\n `/platform-admin/v1/revenue/merchants${ledgerQuery(f)}`,\n ),\n /** Merchants that took payments in the window, for a picker. */\n merchantOptions: (f: LedgerFilters = {}) =>\n apiFetch<{ merchants: Array<{ id: string; name: string }> }>(\n `/platform-admin/v1/revenue/merchants/options${ledgerQuery({ startDate: f.startDate, endDate: f.endDate })}`,\n ),\n subscriptionPayments: (f: LedgerFilters = {}) =>\n apiFetch<SubscriptionPaymentsPage>(\n `/platform-admin/v1/revenue/subscription-payments${ledgerQuery(f, 'pageSize')}`,\n ),\n subscriptionPaymentsCsv: (f: LedgerFilters = {}) =>\n apiFetch<string>(\n `/platform-admin/v1/revenue/subscription-payments/export${ledgerQuery(f, 'pageSize')}`,\n ),\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 stage?: AccountStage;\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 if (params.stage) qs.set('stage', params.stage);\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 accountSessions: (id: string) =>\n apiFetch<AccountSession[]>(`/platform-admin/v1/accounts/${id}/sessions`),\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,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Olive earns, from the fee ledger (api-next `admin-core`, `overview/earnings`). Every amount is a string of
|
|
3
|
+
* minor units at the currency's own exponent; currencies are never added together.
|
|
4
|
+
*/
|
|
5
|
+
export type EarningsSource = 'online' | 'pos' | 'payments' | 'cash' | 'settlements' | 'payouts' | 'plans' | 'transfers' | 'fx' | 'lending' | 'other';
|
|
6
|
+
export interface EarningsSlice {
|
|
7
|
+
source: EarningsSource;
|
|
8
|
+
label: string;
|
|
9
|
+
feeMinor: string;
|
|
10
|
+
providerMinor: string;
|
|
11
|
+
netMinor: string;
|
|
12
|
+
count: number;
|
|
13
|
+
previousFeeMinor: string;
|
|
14
|
+
}
|
|
15
|
+
export interface EarningsDay {
|
|
16
|
+
date: string;
|
|
17
|
+
feeMinor: string;
|
|
18
|
+
providerMinor: string;
|
|
19
|
+
}
|
|
20
|
+
export interface EarningsMerchant {
|
|
21
|
+
tenantId: string | null;
|
|
22
|
+
name: string | null;
|
|
23
|
+
feeMinor: string;
|
|
24
|
+
count: number;
|
|
25
|
+
}
|
|
26
|
+
export interface EarningsCurrency {
|
|
27
|
+
currency: string;
|
|
28
|
+
exponent: number;
|
|
29
|
+
feeMinor: string;
|
|
30
|
+
providerMinor: string;
|
|
31
|
+
netMinor: string;
|
|
32
|
+
previousFeeMinor: string;
|
|
33
|
+
previousProviderMinor: string;
|
|
34
|
+
count: number;
|
|
35
|
+
/** False when the ledger carries no processor cost for these fees: net equals gross by absence, not by fact. */
|
|
36
|
+
providerCostsRecorded: boolean;
|
|
37
|
+
bySource: EarningsSlice[];
|
|
38
|
+
series: EarningsDay[];
|
|
39
|
+
/** The period before, day by day, for the dotted compare line. */
|
|
40
|
+
previousSeries: EarningsDay[];
|
|
41
|
+
topMerchants: EarningsMerchant[];
|
|
42
|
+
}
|
|
43
|
+
export interface EarningsSummary {
|
|
44
|
+
from: string;
|
|
45
|
+
to: string;
|
|
46
|
+
previousFrom: string;
|
|
47
|
+
previousTo: string;
|
|
48
|
+
currencies: EarningsCurrency[];
|
|
49
|
+
}
|
|
50
|
+
export interface EarningsRow {
|
|
51
|
+
id: string;
|
|
52
|
+
at: string;
|
|
53
|
+
source: EarningsSource;
|
|
54
|
+
sourceLabel: string;
|
|
55
|
+
description: string;
|
|
56
|
+
merchant: {
|
|
57
|
+
id: string | null;
|
|
58
|
+
name: string | null;
|
|
59
|
+
};
|
|
60
|
+
currency: string;
|
|
61
|
+
exponent: number;
|
|
62
|
+
feeMinor: string;
|
|
63
|
+
providerMinor: string;
|
|
64
|
+
netMinor: string;
|
|
65
|
+
}
|
|
66
|
+
export interface EarningsPage {
|
|
67
|
+
rows: EarningsRow[];
|
|
68
|
+
total: number;
|
|
69
|
+
page: number;
|
|
70
|
+
limit: number;
|
|
71
|
+
totalPages: number;
|
|
72
|
+
}
|
|
73
|
+
export interface EarningsFilters {
|
|
74
|
+
startDate?: string;
|
|
75
|
+
endDate?: string;
|
|
76
|
+
source?: string;
|
|
77
|
+
merchantId?: string;
|
|
78
|
+
q?: string;
|
|
79
|
+
page?: number;
|
|
80
|
+
limit?: number;
|
|
81
|
+
}
|
|
82
|
+
export declare const adminEarningsApi: {
|
|
83
|
+
summary: (range?: {
|
|
84
|
+
startDate?: string;
|
|
85
|
+
endDate?: string;
|
|
86
|
+
}) => Promise<EarningsSummary>;
|
|
87
|
+
transactions: (f?: EarningsFilters) => Promise<EarningsPage>;
|
|
88
|
+
/** The newest 5,000 that match, as CSV text. */
|
|
89
|
+
transactionsCsv: (f?: EarningsFilters) => Promise<string>;
|
|
90
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { apiFetch } from "./api";
|
|
2
|
+
import { toQuery } from "./admin-pos-query";
|
|
3
|
+
const BASE = "/platform-admin/v1/overview/earnings";
|
|
4
|
+
const adminEarningsApi = {
|
|
5
|
+
summary: (range = {}) => apiFetch(`${BASE}${toQuery({ ...range })}`),
|
|
6
|
+
transactions: (f = {}) => apiFetch(`${BASE}/transactions${toQuery({ ...f })}`),
|
|
7
|
+
/** The newest 5,000 that match, as CSV text. */
|
|
8
|
+
transactionsCsv: (f = {}) => apiFetch(
|
|
9
|
+
`${BASE}/transactions.csv${toQuery({ ...f, page: void 0, limit: void 0 })}`
|
|
10
|
+
)
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
adminEarningsApi
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=admin-earnings-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/admin-earnings-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport { toQuery } from './admin-pos-query';\n\n/**\n * What Olive earns, from the fee ledger (api-next `admin-core`, `overview/earnings`). Every amount is a string of\n * minor units at the currency's own exponent; currencies are never added together.\n */\n\nexport type EarningsSource =\n | 'online'\n | 'pos'\n | 'payments'\n | 'cash'\n | 'settlements'\n | 'payouts'\n | 'plans'\n | 'transfers'\n | 'fx'\n | 'lending'\n | 'other';\n\nexport interface EarningsSlice {\n source: EarningsSource;\n label: string;\n feeMinor: string;\n providerMinor: string;\n netMinor: string;\n count: number;\n previousFeeMinor: string;\n}\n\nexport interface EarningsDay {\n date: string;\n feeMinor: string;\n providerMinor: string;\n}\n\nexport interface EarningsMerchant {\n tenantId: string | null;\n name: string | null;\n feeMinor: string;\n count: number;\n}\n\nexport interface EarningsCurrency {\n currency: string;\n exponent: number;\n feeMinor: string;\n providerMinor: string;\n netMinor: string;\n previousFeeMinor: string;\n previousProviderMinor: string;\n count: number;\n /** False when the ledger carries no processor cost for these fees: net equals gross by absence, not by fact. */\n providerCostsRecorded: boolean;\n bySource: EarningsSlice[];\n series: EarningsDay[];\n /** The period before, day by day, for the dotted compare line. */\n previousSeries: EarningsDay[];\n topMerchants: EarningsMerchant[];\n}\n\nexport interface EarningsSummary {\n from: string;\n to: string;\n previousFrom: string;\n previousTo: string;\n currencies: EarningsCurrency[];\n}\n\nexport interface EarningsRow {\n id: string;\n at: string;\n source: EarningsSource;\n sourceLabel: string;\n description: string;\n merchant: { id: string | null; name: string | null };\n currency: string;\n exponent: number;\n feeMinor: string;\n providerMinor: string;\n netMinor: string;\n}\n\nexport interface EarningsPage {\n rows: EarningsRow[];\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\nexport interface EarningsFilters {\n startDate?: string;\n endDate?: string;\n source?: string;\n merchantId?: string;\n q?: string;\n page?: number;\n limit?: number;\n}\n\nconst BASE = '/platform-admin/v1/overview/earnings';\n\nexport const adminEarningsApi = {\n summary: (range: { startDate?: string; endDate?: string } = {}) =>\n apiFetch<EarningsSummary>(`${BASE}${toQuery({ ...range })}`),\n\n transactions: (f: EarningsFilters = {}) =>\n apiFetch<EarningsPage>(`${BASE}/transactions${toQuery({ ...f })}`),\n\n /** The newest 5,000 that match, as CSV text. */\n transactionsCsv: (f: EarningsFilters = {}) =>\n apiFetch<string>(\n `${BASE}/transactions.csv${toQuery({ ...f, page: undefined, limit: undefined })}`,\n ),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAqGxB,MAAM,OAAO;AAEN,MAAM,mBAAmB;AAAA,EAC9B,SAAS,CAAC,QAAkD,CAAC,MAC3D,SAA0B,GAAG,IAAI,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE;AAAA,EAE7D,cAAc,CAAC,IAAqB,CAAC,MACnC,SAAuB,GAAG,IAAI,gBAAgB,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE;AAAA;AAAA,EAGnE,iBAAiB,CAAC,IAAqB,CAAC,MACtC;AAAA,IACE,GAAG,IAAI,oBAAoB,QAAQ,EAAE,GAAG,GAAG,MAAM,QAAW,OAAO,OAAU,CAAC,CAAC;AAAA,EACjF;AACJ;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EarningsCurrency, EarningsSource } from './admin-earnings-api';
|
|
2
|
+
/** A minor-unit string as a number in major units. For charts and ratios only; exact text goes through `formatMinor`. */
|
|
3
|
+
export declare const toMajor: (minor: string, exponent: number) => number;
|
|
4
|
+
/** The change from `before` to `now` as a percentage; null when there was nothing before to compare with. */
|
|
5
|
+
export declare function pctChange(now: number, before: number): number | null;
|
|
6
|
+
export interface EarningsPart {
|
|
7
|
+
source: EarningsSource;
|
|
8
|
+
label: string;
|
|
9
|
+
/** Major units. */
|
|
10
|
+
value: number;
|
|
11
|
+
previous: number;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
export interface EarningsFigures {
|
|
15
|
+
/** Fees from the ledger without wallet-paid plans, plus plan income from billing. */
|
|
16
|
+
gross: number;
|
|
17
|
+
provider: number;
|
|
18
|
+
net: number;
|
|
19
|
+
/** The same ledger fees, without plans, in the period before: for the change. */
|
|
20
|
+
feesNow: number;
|
|
21
|
+
feesBefore: number;
|
|
22
|
+
plan: number;
|
|
23
|
+
parts: EarningsPart[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One currency's earnings as the Overview reads them. Plan income is taken once, from billing (`planIncome`, which
|
|
27
|
+
* covers every way a plan was paid). The ledger's wallet-paid plans are the same money, so they are left out of the
|
|
28
|
+
* ledger side. Without a billing figure for this currency, the ledger's own plans stand in, so plan income is never lost.
|
|
29
|
+
*/
|
|
30
|
+
export declare function earningsFigures(cur: EarningsCurrency, planIncome: number | null): EarningsFigures;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const toMajor = (minor, exponent) => Number(minor) / 10 ** exponent;
|
|
2
|
+
function pctChange(now, before) {
|
|
3
|
+
if (!Number.isFinite(now) || !Number.isFinite(before) || before === 0)
|
|
4
|
+
return null;
|
|
5
|
+
return Math.round((now - before) / Math.abs(before) * 1e3) / 10;
|
|
6
|
+
}
|
|
7
|
+
function earningsFigures(cur, planIncome) {
|
|
8
|
+
const major = (minor) => toMajor(minor, cur.exponent);
|
|
9
|
+
const wallet = cur.bySource.find((s) => s.source === "plans");
|
|
10
|
+
const plan = planIncome ?? (wallet ? major(wallet.feeMinor) : 0);
|
|
11
|
+
const others = cur.bySource.filter((s) => s.source !== "plans");
|
|
12
|
+
const feesNow = others.reduce((a, s) => a + major(s.feeMinor), 0);
|
|
13
|
+
const feesBefore = others.reduce((a, s) => a + major(s.previousFeeMinor), 0);
|
|
14
|
+
const provider = major(cur.providerMinor);
|
|
15
|
+
const parts = others.map((s) => ({
|
|
16
|
+
source: s.source,
|
|
17
|
+
label: s.label,
|
|
18
|
+
value: major(s.feeMinor),
|
|
19
|
+
previous: major(s.previousFeeMinor),
|
|
20
|
+
count: s.count
|
|
21
|
+
})).concat(
|
|
22
|
+
plan > 0 || wallet ? [
|
|
23
|
+
{
|
|
24
|
+
source: "plans",
|
|
25
|
+
label: "Plan subscriptions",
|
|
26
|
+
value: plan,
|
|
27
|
+
previous: 0,
|
|
28
|
+
count: wallet?.count ?? 0
|
|
29
|
+
}
|
|
30
|
+
] : []
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
gross: feesNow + plan,
|
|
34
|
+
provider,
|
|
35
|
+
net: feesNow + plan - provider,
|
|
36
|
+
feesNow,
|
|
37
|
+
feesBefore,
|
|
38
|
+
plan,
|
|
39
|
+
parts
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
earningsFigures,
|
|
44
|
+
pctChange,
|
|
45
|
+
toMajor
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=admin-earnings-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/admin-earnings-model.ts"],"sourcesContent":["import type { EarningsCurrency, EarningsSource } from './admin-earnings-api';\n\n/** A minor-unit string as a number in major units. For charts and ratios only; exact text goes through `formatMinor`. */\nexport const toMajor = (minor: string, exponent: number): number =>\n Number(minor) / 10 ** exponent;\n\n/** The change from `before` to `now` as a percentage; null when there was nothing before to compare with. */\nexport function pctChange(now: number, before: number): number | null {\n if (!Number.isFinite(now) || !Number.isFinite(before) || before === 0)\n return null;\n\n return Math.round(((now - before) / Math.abs(before)) * 1000) / 10;\n}\n\nexport interface EarningsPart {\n source: EarningsSource;\n label: string;\n /** Major units. */\n value: number;\n previous: number;\n count: number;\n}\n\nexport interface EarningsFigures {\n /** Fees from the ledger without wallet-paid plans, plus plan income from billing. */\n gross: number;\n provider: number;\n net: number;\n /** The same ledger fees, without plans, in the period before: for the change. */\n feesNow: number;\n feesBefore: number;\n plan: number;\n parts: EarningsPart[];\n}\n\n/**\n * One currency's earnings as the Overview reads them. Plan income is taken once, from billing (`planIncome`, which\n * covers every way a plan was paid). The ledger's wallet-paid plans are the same money, so they are left out of the\n * ledger side. Without a billing figure for this currency, the ledger's own plans stand in, so plan income is never lost.\n */\nexport function earningsFigures(\n cur: EarningsCurrency,\n planIncome: number | null,\n): EarningsFigures {\n const major = (minor: string) => toMajor(minor, cur.exponent);\n const wallet = cur.bySource.find((s) => s.source === 'plans');\n const plan = planIncome ?? (wallet ? major(wallet.feeMinor) : 0);\n const others = cur.bySource.filter((s) => s.source !== 'plans');\n const feesNow = others.reduce((a, s) => a + major(s.feeMinor), 0);\n const feesBefore = others.reduce((a, s) => a + major(s.previousFeeMinor), 0);\n const provider = major(cur.providerMinor);\n const parts: EarningsPart[] = others\n .map((s) => ({\n source: s.source,\n label: s.label,\n value: major(s.feeMinor),\n previous: major(s.previousFeeMinor),\n count: s.count,\n }))\n .concat(\n plan > 0 || wallet\n ? [\n {\n source: 'plans' as const,\n label: 'Plan subscriptions',\n value: plan,\n previous: 0,\n count: wallet?.count ?? 0,\n },\n ]\n : [],\n );\n\n return {\n gross: feesNow + plan,\n provider,\n net: feesNow + plan - provider,\n feesNow,\n feesBefore,\n plan,\n parts,\n };\n}\n"],"mappings":"AAGO,MAAM,UAAU,CAAC,OAAe,aACrC,OAAO,KAAK,IAAI,MAAM;AAGjB,SAAS,UAAU,KAAa,QAA+B;AACpE,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,OAAO,SAAS,MAAM,KAAK,WAAW;AAClE,WAAO;AAET,SAAO,KAAK,OAAQ,MAAM,UAAU,KAAK,IAAI,MAAM,IAAK,GAAI,IAAI;AAClE;AA4BO,SAAS,gBACd,KACA,YACiB;AACjB,QAAM,QAAQ,CAAC,UAAkB,QAAQ,OAAO,IAAI,QAAQ;AAC5D,QAAM,SAAS,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO;AAC5D,QAAM,OAAO,eAAe,SAAS,MAAM,OAAO,QAAQ,IAAI;AAC9D,QAAM,SAAS,IAAI,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO;AAC9D,QAAM,UAAU,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,QAAQ,GAAG,CAAC;AAChE,QAAM,aAAa,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,gBAAgB,GAAG,CAAC;AAC3E,QAAM,WAAW,MAAM,IAAI,aAAa;AACxC,QAAM,QAAwB,OAC3B,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE;AAAA,IACT,OAAO,MAAM,EAAE,QAAQ;AAAA,IACvB,UAAU,MAAM,EAAE,gBAAgB;AAAA,IAClC,OAAO,EAAE;AAAA,EACX,EAAE,EACD;AAAA,IACC,OAAO,KAAK,SACR;AAAA,MACE;AAAA,QACE,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,OAAO;AAAA,QACP,UAAU;AAAA,QACV,OAAO,QAAQ,SAAS;AAAA,MAC1B;AAAA,IACF,IACA,CAAC;AAAA,EACP;AAEF,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB;AAAA,IACA,KAAK,UAAU,OAAO;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED