@withpotter/lib 0.13.0-next.124 → 0.13.0-next.59
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-account-types.d.ts +0 -21
- package/dist/admin-api.d.ts +2 -224
- package/dist/admin-api.js +0 -41
- package/dist/admin-api.js.map +1 -1
- package/dist/admin-case-api.js +4 -1
- package/dist/admin-case-api.js.map +1 -1
- package/dist/admin-pos-api.d.ts +1 -25
- package/dist/admin-pos-api.js +0 -24
- package/dist/admin-pos-api.js.map +1 -1
- package/dist/admin-pos-types.d.ts +0 -43
- package/dist/api/accounts.d.ts +3 -5
- package/dist/api/accounts.js +1 -1
- package/dist/api/accounts.js.map +1 -1
- package/dist/api/analytics.js +3 -1
- package/dist/api/analytics.js.map +1 -1
- package/dist/api/core.d.ts +1 -0
- package/dist/api/core.js +7 -19
- package/dist/api/core.js.map +1 -1
- package/dist/api/potter.js +3 -2
- package/dist/api/potter.js.map +1 -1
- package/dist/api/session-scope.d.ts +2 -2
- package/dist/api/session-scope.js.map +1 -1
- package/dist/api/types/auth.d.ts +1 -7
- package/dist/api/types/auth.js.map +1 -1
- package/dist/api/types/crm.d.ts +0 -2
- package/dist/api/types/templates.d.ts +0 -6
- package/dist/auth-context.d.ts +0 -2
- package/dist/auth-context.js +37 -12
- package/dist/auth-context.js.map +1 -1
- package/dist/mobile-routes.js +2 -4
- package/dist/mobile-routes.js.map +1 -1
- package/dist/plan-price.d.ts +2 -16
- package/dist/plan-price.js +0 -9
- package/dist/plan-price.js.map +1 -1
- package/dist/resolve-enabled-behaviors.d.ts +0 -2
- package/dist/resolve-enabled-behaviors.js +1 -2
- package/dist/resolve-enabled-behaviors.js.map +1 -1
- package/dist/template-types.d.ts +0 -8
- package/dist/timeline-copy.d.ts +1 -1
- package/dist/timeline-copy.js +2 -6
- package/dist/timeline-copy.js.map +1 -1
- package/dist/use-nav-groups.js +5 -5
- package/dist/use-nav-groups.js.map +1 -1
- package/dist/utils.js +4 -4
- package/dist/utils.js.map +1 -1
- package/dist/wallet-format.js +0 -1
- package/dist/wallet-format.js.map +1 -1
- package/package.json +2 -2
- package/dist/__fixtures__/demo-content-server.json +0 -484
- package/dist/admin-earnings-api.d.ts +0 -90
- package/dist/admin-earnings-api.js +0 -15
- package/dist/admin-earnings-api.js.map +0 -1
- package/dist/admin-earnings-model.d.ts +0 -30
- package/dist/admin-earnings-model.js +0 -47
- package/dist/admin-earnings-model.js.map +0 -1
- package/dist/admin-pos-olive.d.ts +0 -44
- package/dist/admin-pos-olive.js +0 -93
- package/dist/admin-pos-olive.js.map +0 -1
- package/dist/api/demo-content.d.ts +0 -104
- package/dist/api/demo-content.js +0 -36
- package/dist/api/demo-content.js.map +0 -1
- package/dist/api/plan-refusal.d.ts +0 -21
- package/dist/api/plan-refusal.js +0 -28
- package/dist/api/plan-refusal.js.map +0 -1
- package/dist/api/session-refresh.d.ts +0 -18
- package/dist/api/session-refresh.js +0 -37
- package/dist/api/session-refresh.js.map +0 -1
- package/dist/demo-content-mock.d.ts +0 -22
- package/dist/demo-content-mock.js +0 -70
- package/dist/demo-content-mock.js.map +0 -1
- package/dist/demo-content-poller.d.ts +0 -33
- package/dist/demo-content-poller.js +0 -80
- package/dist/demo-content-poller.js.map +0 -1
- package/dist/demo-content.d.ts +0 -43
- package/dist/demo-content.js +0 -78
- package/dist/demo-content.js.map +0 -1
- package/dist/stored-token.d.ts +0 -1
- package/dist/stored-token.js +0 -11
- package/dist/stored-token.js.map +0 -1
package/dist/admin-case-api.js
CHANGED
|
@@ -2,7 +2,8 @@ import { apiFetch } from "./api";
|
|
|
2
2
|
import {
|
|
3
3
|
ApiRequestError,
|
|
4
4
|
getAccountId,
|
|
5
|
-
getApiUrl
|
|
5
|
+
getApiUrl,
|
|
6
|
+
getAuthToken
|
|
6
7
|
} from "./api/core";
|
|
7
8
|
const BASE = "/platform-admin/v1/compliance/cases";
|
|
8
9
|
const caseUrl = (tenantId) => `${BASE}/${encodeURIComponent(tenantId)}`;
|
|
@@ -11,7 +12,9 @@ const photoPath = (tenantId, personId, kind) => kind === "selfie" ? `${personUrl
|
|
|
11
12
|
async function fetchImage(path) {
|
|
12
13
|
const headers = {};
|
|
13
14
|
const account = getAccountId();
|
|
15
|
+
const token = getAuthToken();
|
|
14
16
|
if (account) headers["x-account-id"] = account;
|
|
17
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
15
18
|
const res = await fetch(`${getApiUrl()}${path}`, {
|
|
16
19
|
headers,
|
|
17
20
|
credentials: "include",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin-case-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport type {\n CaseList,\n CaseStatus,\n DecisionBody,\n DocumentLink,\n VerificationCase,\n} from './admin-case-types';\nimport {\n ApiRequestError,\n getAccountId,\n getApiUrl,\n} from './api/core';\n\n/**\n * One business's wallet verification case, as a platform admin reviews it.\n * Same `apiFetch` and cookie session as the rest of the console; the routes\n * answer with the record itself, no envelope. Shapes: `admin-case-types.ts`.\n */\n\nexport type PhotoKind = 'selfie' | 'bvn' | 'nin';\n\nconst BASE = '/platform-admin/v1/compliance/cases';\n\nconst caseUrl = (tenantId: string) => `${BASE}/${encodeURIComponent(tenantId)}`;\n\nconst personUrl = (tenantId: string, personId: string) =>\n `${caseUrl(tenantId)}/people/${encodeURIComponent(personId)}`;\n\nexport const photoPath = (\n tenantId: string,\n personId: string,\n kind: PhotoKind,\n) =>\n kind === 'selfie'\n ? `${personUrl(tenantId, personId)}/photo`\n : `${personUrl(tenantId, personId)}/reference-photo?source=${kind}`;\n\n/**\n * Image bytes, not JSON, so not `apiFetch`: the same proxy, cookies and\n * headers, read as a blob. Never cached; the caller makes the object URL.\n */\nasync function fetchImage(path: string): Promise<Blob> {\n const headers: Record<string, string> = {};\n const account = getAccountId();\n\n if (account) headers['x-account-id'] = account;\n\n const res = await fetch(`${getApiUrl()}${path}`, {\n headers,\n credentials: 'include',\n cache: 'no-store',\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => null);\n\n throw new ApiRequestError(\n body?.message || `HTTP ${res.status}`,\n res.status,\n body,\n );\n }\n\n return res.blob();\n}\n\nconst post = <T>(path: string, body: unknown) =>\n apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) });\n\nexport const adminCaseApi = {\n /** 404 when the business has not started verification. */\n get: (tenantId: string) => apiFetch<VerificationCase>(caseUrl(tenantId)),\n\n /** Oldest wait first. */\n list: (q: { status?: CaseStatus; page?: number; limit?: number } = {}) => {\n const params = new URLSearchParams();\n\n if (q.status) params.set('status', q.status);\n\n if (q.page) params.set('page', String(q.page));\n\n if (q.limit) params.set('limit', String(q.limit));\n\n const qs = params.toString();\n\n return apiFetch<CaseList>(qs ? `${BASE}?${qs}` : BASE);\n },\n\n /** `total` is all the queue needs; one row keeps the answer small. */\n waitingCount: () =>\n apiFetch<{ total: number }>(`${BASE}?status=WAITING_REVIEW&limit=1`),\n\n photo: (tenantId: string, personId: string, kind: PhotoKind) =>\n fetchImage(photoPath(tenantId, personId, kind)),\n\n reveal: (tenantId: string, personId: string, field: 'bvn' | 'nin') =>\n post<{ value: string }>(`${personUrl(tenantId, personId)}/reveal`, {\n field,\n }),\n\n /** A short-lived link to an ID photo or certificate; 502 when it could not be opened. */\n viewDocument: (tenantId: string, documentId: string) =>\n post<DocumentLink>(\n `${caseUrl(tenantId)}/documents/${encodeURIComponent(documentId)}/view`,\n {},\n ),\n\n /** 409 when the case is not waiting for review. */\n decide: (tenantId: string, body: DecisionBody) =>\n post<VerificationCase>(`${caseUrl(tenantId)}/decision`, body),\n\n /** 403 for the admin who decided; 409 when there is nothing to reverse. */\n reverse: (tenantId: string, note: string) =>\n post<VerificationCase>(`${caseUrl(tenantId)}/reverse`, { note }),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AAQzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,MAAM,OAAO;AAEb,MAAM,UAAU,CAAC,aAAqB,GAAG,IAAI,IAAI,mBAAmB,QAAQ,CAAC;AAE7E,MAAM,YAAY,CAAC,UAAkB,aACnC,GAAG,QAAQ,QAAQ,CAAC,WAAW,mBAAmB,QAAQ,CAAC;AAEtD,MAAM,YAAY,CACvB,UACA,UACA,SAEA,SAAS,WACL,GAAG,UAAU,UAAU,QAAQ,CAAC,WAChC,GAAG,UAAU,UAAU,QAAQ,CAAC,2BAA2B,IAAI;AAMrE,eAAe,WAAW,MAA6B;AACrD,QAAM,UAAkC,CAAC;AACzC,QAAM,UAAU,aAAa;
|
|
1
|
+
{"version":3,"sources":["../src/admin-case-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport type {\n CaseList,\n CaseStatus,\n DecisionBody,\n DocumentLink,\n VerificationCase,\n} from './admin-case-types';\nimport {\n ApiRequestError,\n getAccountId,\n getApiUrl,\n getAuthToken,\n} from './api/core';\n\n/**\n * One business's wallet verification case, as a platform admin reviews it.\n * Same `apiFetch` and cookie session as the rest of the console; the routes\n * answer with the record itself, no envelope. Shapes: `admin-case-types.ts`.\n */\n\nexport type PhotoKind = 'selfie' | 'bvn' | 'nin';\n\nconst BASE = '/platform-admin/v1/compliance/cases';\n\nconst caseUrl = (tenantId: string) => `${BASE}/${encodeURIComponent(tenantId)}`;\n\nconst personUrl = (tenantId: string, personId: string) =>\n `${caseUrl(tenantId)}/people/${encodeURIComponent(personId)}`;\n\nexport const photoPath = (\n tenantId: string,\n personId: string,\n kind: PhotoKind,\n) =>\n kind === 'selfie'\n ? `${personUrl(tenantId, personId)}/photo`\n : `${personUrl(tenantId, personId)}/reference-photo?source=${kind}`;\n\n/**\n * Image bytes, not JSON, so not `apiFetch`: the same proxy, cookies and\n * headers, read as a blob. Never cached; the caller makes the object URL.\n */\nasync function fetchImage(path: string): Promise<Blob> {\n const headers: Record<string, string> = {};\n const account = getAccountId();\n const token = getAuthToken();\n\n if (account) headers['x-account-id'] = account;\n\n if (token) headers.Authorization = `Bearer ${token}`;\n\n const res = await fetch(`${getApiUrl()}${path}`, {\n headers,\n credentials: 'include',\n cache: 'no-store',\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => null);\n\n throw new ApiRequestError(\n body?.message || `HTTP ${res.status}`,\n res.status,\n body,\n );\n }\n\n return res.blob();\n}\n\nconst post = <T>(path: string, body: unknown) =>\n apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) });\n\nexport const adminCaseApi = {\n /** 404 when the business has not started verification. */\n get: (tenantId: string) => apiFetch<VerificationCase>(caseUrl(tenantId)),\n\n /** Oldest wait first. */\n list: (q: { status?: CaseStatus; page?: number; limit?: number } = {}) => {\n const params = new URLSearchParams();\n\n if (q.status) params.set('status', q.status);\n\n if (q.page) params.set('page', String(q.page));\n\n if (q.limit) params.set('limit', String(q.limit));\n\n const qs = params.toString();\n\n return apiFetch<CaseList>(qs ? `${BASE}?${qs}` : BASE);\n },\n\n /** `total` is all the queue needs; one row keeps the answer small. */\n waitingCount: () =>\n apiFetch<{ total: number }>(`${BASE}?status=WAITING_REVIEW&limit=1`),\n\n photo: (tenantId: string, personId: string, kind: PhotoKind) =>\n fetchImage(photoPath(tenantId, personId, kind)),\n\n reveal: (tenantId: string, personId: string, field: 'bvn' | 'nin') =>\n post<{ value: string }>(`${personUrl(tenantId, personId)}/reveal`, {\n field,\n }),\n\n /** A short-lived link to an ID photo or certificate; 502 when it could not be opened. */\n viewDocument: (tenantId: string, documentId: string) =>\n post<DocumentLink>(\n `${caseUrl(tenantId)}/documents/${encodeURIComponent(documentId)}/view`,\n {},\n ),\n\n /** 409 when the case is not waiting for review. */\n decide: (tenantId: string, body: DecisionBody) =>\n post<VerificationCase>(`${caseUrl(tenantId)}/decision`, body),\n\n /** 403 for the admin who decided; 409 when there is nothing to reverse. */\n reverse: (tenantId: string, note: string) =>\n post<VerificationCase>(`${caseUrl(tenantId)}/reverse`, { note }),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AAQzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,MAAM,OAAO;AAEb,MAAM,UAAU,CAAC,aAAqB,GAAG,IAAI,IAAI,mBAAmB,QAAQ,CAAC;AAE7E,MAAM,YAAY,CAAC,UAAkB,aACnC,GAAG,QAAQ,QAAQ,CAAC,WAAW,mBAAmB,QAAQ,CAAC;AAEtD,MAAM,YAAY,CACvB,UACA,UACA,SAEA,SAAS,WACL,GAAG,UAAU,UAAU,QAAQ,CAAC,WAChC,GAAG,UAAU,UAAU,QAAQ,CAAC,2BAA2B,IAAI;AAMrE,eAAe,WAAW,MAA6B;AACrD,QAAM,UAAkC,CAAC;AACzC,QAAM,UAAU,aAAa;AAC7B,QAAM,QAAQ,aAAa;AAE3B,MAAI,QAAS,SAAQ,cAAc,IAAI;AAEvC,MAAI,MAAO,SAAQ,gBAAgB,UAAU,KAAK;AAElD,QAAM,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,IAAI,IAAI;AAAA,IAC/C;AAAA,IACA,aAAa;AAAA,IACb,OAAO;AAAA,EACT,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAE9C,UAAM,IAAI;AAAA,MACR,MAAM,WAAW,QAAQ,IAAI,MAAM;AAAA,MACnC,IAAI;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,SAAO,IAAI,KAAK;AAClB;AAEA,MAAM,OAAO,CAAI,MAAc,SAC7B,SAAY,MAAM,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,IAAI,EAAE,CAAC;AAE3D,MAAM,eAAe;AAAA;AAAA,EAE1B,KAAK,CAAC,aAAqB,SAA2B,QAAQ,QAAQ,CAAC;AAAA;AAAA,EAGvE,MAAM,CAAC,IAA4D,CAAC,MAAM;AACxE,UAAM,SAAS,IAAI,gBAAgB;AAEnC,QAAI,EAAE,OAAQ,QAAO,IAAI,UAAU,EAAE,MAAM;AAE3C,QAAI,EAAE,KAAM,QAAO,IAAI,QAAQ,OAAO,EAAE,IAAI,CAAC;AAE7C,QAAI,EAAE,MAAO,QAAO,IAAI,SAAS,OAAO,EAAE,KAAK,CAAC;AAEhD,UAAM,KAAK,OAAO,SAAS;AAE3B,WAAO,SAAmB,KAAK,GAAG,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,EACvD;AAAA;AAAA,EAGA,cAAc,MACZ,SAA4B,GAAG,IAAI,gCAAgC;AAAA,EAErE,OAAO,CAAC,UAAkB,UAAkB,SAC1C,WAAW,UAAU,UAAU,UAAU,IAAI,CAAC;AAAA,EAEhD,QAAQ,CAAC,UAAkB,UAAkB,UAC3C,KAAwB,GAAG,UAAU,UAAU,QAAQ,CAAC,WAAW;AAAA,IACjE;AAAA,EACF,CAAC;AAAA;AAAA,EAGH,cAAc,CAAC,UAAkB,eAC/B;AAAA,IACE,GAAG,QAAQ,QAAQ,CAAC,cAAc,mBAAmB,UAAU,CAAC;AAAA,IAChE,CAAC;AAAA,EACH;AAAA;AAAA,EAGF,QAAQ,CAAC,UAAkB,SACzB,KAAuB,GAAG,QAAQ,QAAQ,CAAC,aAAa,IAAI;AAAA;AAAA,EAG9D,SAAS,CAAC,UAAkB,SAC1B,KAAuB,GAAG,QAAQ,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;AACnE;","names":[]}
|
package/dist/admin-pos-api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ShiftFilters, type TerminalFilters } from './admin-pos-query';
|
|
2
|
-
import type { PosPage,
|
|
2
|
+
import type { PosPage, PosShift, PosSummary, PosTerminal, PosTerminalDetail, PosTerminalHistory, PosWindow, TenantPos } from './admin-pos-types';
|
|
3
3
|
export type TerminalAct = 'suspend' | 'resume' | 'revoke';
|
|
4
4
|
export declare const adminPosApi: {
|
|
5
5
|
summary: (w?: PosWindow) => Promise<PosSummary>;
|
|
@@ -9,30 +9,6 @@ export declare const adminPosApi: {
|
|
|
9
9
|
history: (terminalId: string) => Promise<PosTerminalHistory>;
|
|
10
10
|
shifts: (filters?: ShiftFilters) => Promise<PosPage<PosShift>>;
|
|
11
11
|
tenant: (tenantId: string, w?: PosWindow) => Promise<TenantPos>;
|
|
12
|
-
/** A business's whole till roster, removed people marked. */
|
|
13
|
-
roster: (tenantId: string) => Promise<PosRosterEntry[]>;
|
|
14
|
-
/** A business's requests for terminals, newest first. */
|
|
15
|
-
tillRequests: (tenantId: string) => Promise<PosTillRequest[]>;
|
|
16
|
-
/** Card payments, newest first, one business when given; `cursor` is the last page's `nextCursor`. */
|
|
17
|
-
payments: (filters?: {
|
|
18
|
-
tenantId?: string;
|
|
19
|
-
deviceId?: string;
|
|
20
|
-
/** CREATED, PROCESSING, SUCCEEDED, FAILED, CANCELLED or UNKNOWN. */
|
|
21
|
-
status?: string;
|
|
22
|
-
cursor?: string;
|
|
23
|
-
limit?: number;
|
|
24
|
-
}) => Promise<PosPaymentsPage>;
|
|
25
|
-
/**
|
|
26
|
-
* What the business sees from Olive, read live under its own Olive login. Read-only; each call is
|
|
27
|
-
* written to the admin audit log. The bodies are Olive's own, so they go through `admin-pos-olive`.
|
|
28
|
-
* A 400 says the business has not connected Olive.
|
|
29
|
-
*/
|
|
30
|
-
olive: {
|
|
31
|
-
account: (tenantId: string) => Promise<unknown>;
|
|
32
|
-
accountTransactions: (tenantId: string, query?: Record<string, string>) => Promise<unknown>;
|
|
33
|
-
outlets: (tenantId: string) => Promise<unknown>;
|
|
34
|
-
outletTerminals: (tenantId: string, outletId: string) => Promise<unknown>;
|
|
35
|
-
};
|
|
36
12
|
/** 409 when the terminal's status no longer allows it. */
|
|
37
13
|
setTerminalStatus: (terminalId: string, act: TerminalAct, reason: string) => Promise<PosTerminalDetail>;
|
|
38
14
|
/** Signs the shift out at its till. 409 when it is no longer open. */
|
package/dist/admin-pos-api.js
CHANGED
|
@@ -8,7 +8,6 @@ const BASE = "/platform-admin/v1/pos";
|
|
|
8
8
|
const id = (value) => encodeURIComponent(value);
|
|
9
9
|
const post = (path, body) => apiFetch(path, { method: "POST", body: JSON.stringify(body) });
|
|
10
10
|
const windowQuery = (w = {}) => toQuery({ from: w.from, to: w.to });
|
|
11
|
-
const tenantBase = (tenantId) => `/platform-admin/v1/tenants/${id(tenantId)}/pos`;
|
|
12
11
|
const adminPosApi = {
|
|
13
12
|
summary: (w) => apiFetch(`${BASE}/summary${windowQuery(w)}`),
|
|
14
13
|
terminals: (filters) => apiFetch(
|
|
@@ -23,29 +22,6 @@ const adminPosApi = {
|
|
|
23
22
|
tenant: (tenantId, w) => apiFetch(
|
|
24
23
|
`/platform-admin/v1/tenants/${id(tenantId)}/pos${windowQuery(w)}`
|
|
25
24
|
),
|
|
26
|
-
/** A business's whole till roster, removed people marked. */
|
|
27
|
-
roster: (tenantId) => apiFetch(`${tenantBase(tenantId)}/operators`),
|
|
28
|
-
/** A business's requests for terminals, newest first. */
|
|
29
|
-
tillRequests: (tenantId) => apiFetch(
|
|
30
|
-
`/platform-admin/v1/till-requests${toQuery({ tenantId })}`
|
|
31
|
-
),
|
|
32
|
-
/** Card payments, newest first, one business when given; `cursor` is the last page's `nextCursor`. */
|
|
33
|
-
payments: (filters = {}) => apiFetch(`${BASE}/payments${toQuery(filters)}`),
|
|
34
|
-
/**
|
|
35
|
-
* What the business sees from Olive, read live under its own Olive login. Read-only; each call is
|
|
36
|
-
* written to the admin audit log. The bodies are Olive's own, so they go through `admin-pos-olive`.
|
|
37
|
-
* A 400 says the business has not connected Olive.
|
|
38
|
-
*/
|
|
39
|
-
olive: {
|
|
40
|
-
account: (tenantId) => apiFetch(`${tenantBase(tenantId)}/olive/account`),
|
|
41
|
-
accountTransactions: (tenantId, query = {}) => apiFetch(
|
|
42
|
-
`${tenantBase(tenantId)}/olive/account/transactions${toQuery(query)}`
|
|
43
|
-
),
|
|
44
|
-
outlets: (tenantId) => apiFetch(`${tenantBase(tenantId)}/olive/outlets`),
|
|
45
|
-
outletTerminals: (tenantId, outletId) => apiFetch(
|
|
46
|
-
`${tenantBase(tenantId)}/olive/outlets/${id(outletId)}/terminals`
|
|
47
|
-
)
|
|
48
|
-
},
|
|
49
25
|
/** 409 when the terminal's status no longer allows it. */
|
|
50
26
|
setTerminalStatus: (terminalId, act, reason) => post(`${BASE}/terminals/${id(terminalId)}/${act}`, {
|
|
51
27
|
reason
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin-pos-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport {\n shiftsQuery,\n terminalsQuery,\n toQuery,\n type ShiftFilters,\n type TerminalFilters,\n} from './admin-pos-query';\nimport type {\n PosPage,\n
|
|
1
|
+
{"version":3,"sources":["../src/admin-pos-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport {\n shiftsQuery,\n terminalsQuery,\n toQuery,\n type ShiftFilters,\n type TerminalFilters,\n} from './admin-pos-query';\nimport type {\n PosPage,\n PosShift,\n PosSummary,\n PosTerminal,\n PosTerminalDetail,\n PosTerminalHistory,\n PosWindow,\n TenantPos,\n} from './admin-pos-types';\n\n/**\n * Terminals and till shifts across every business, as the platform desk\n * works them. Same `apiFetch` and cookie session as the rest of the console;\n * reads need `pos.read`, every action `pos.manage` and a reason. Shapes:\n * `admin-pos-types.ts`.\n */\n\nconst BASE = '/platform-admin/v1/pos';\n\nconst id = (value: string) => encodeURIComponent(value);\n\nconst post = <T>(path: string, body: unknown) =>\n apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) });\n\nconst windowQuery = (w: PosWindow = {}) => toQuery({ from: w.from, to: w.to });\n\nexport type TerminalAct = 'suspend' | 'resume' | 'revoke';\n\nexport const adminPosApi = {\n summary: (w?: PosWindow) =>\n apiFetch<PosSummary>(`${BASE}/summary${windowQuery(w)}`),\n\n terminals: (filters?: TerminalFilters) =>\n apiFetch<PosPage<PosTerminal>>(\n `${BASE}/terminals${terminalsQuery(filters)}`,\n ),\n\n /** 404 when there is no such terminal. */\n terminal: (terminalId: string, w?: PosWindow) =>\n apiFetch<PosTerminalDetail>(\n `${BASE}/terminals/${id(terminalId)}${windowQuery(w)}`,\n ),\n\n history: (terminalId: string) =>\n apiFetch<PosTerminalHistory>(`${BASE}/terminals/${id(terminalId)}/history`),\n\n shifts: (filters?: ShiftFilters) =>\n apiFetch<PosPage<PosShift>>(`${BASE}/shifts${shiftsQuery(filters)}`),\n\n tenant: (tenantId: string, w?: PosWindow) =>\n apiFetch<TenantPos>(\n `/platform-admin/v1/tenants/${id(tenantId)}/pos${windowQuery(w)}`,\n ),\n\n /** 409 when the terminal's status no longer allows it. */\n setTerminalStatus: (terminalId: string, act: TerminalAct, reason: string) =>\n post<PosTerminalDetail>(`${BASE}/terminals/${id(terminalId)}/${act}`, {\n reason,\n }),\n\n /** Signs the shift out at its till. 409 when it is no longer open. */\n closeShift: (shiftId: string, reason: string) =>\n post<PosShift>(`${BASE}/shifts/${id(shiftId)}/close`, { reason }),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAmBP,MAAM,OAAO;AAEb,MAAM,KAAK,CAAC,UAAkB,mBAAmB,KAAK;AAEtD,MAAM,OAAO,CAAI,MAAc,SAC7B,SAAY,MAAM,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,IAAI,EAAE,CAAC;AAElE,MAAM,cAAc,CAAC,IAAe,CAAC,MAAM,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC;AAItE,MAAM,cAAc;AAAA,EACzB,SAAS,CAAC,MACR,SAAqB,GAAG,IAAI,WAAW,YAAY,CAAC,CAAC,EAAE;AAAA,EAEzD,WAAW,CAAC,YACV;AAAA,IACE,GAAG,IAAI,aAAa,eAAe,OAAO,CAAC;AAAA,EAC7C;AAAA;AAAA,EAGF,UAAU,CAAC,YAAoB,MAC7B;AAAA,IACE,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;AAAA,EACtD;AAAA,EAEF,SAAS,CAAC,eACR,SAA6B,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,UAAU;AAAA,EAE5E,QAAQ,CAAC,YACP,SAA4B,GAAG,IAAI,UAAU,YAAY,OAAO,CAAC,EAAE;AAAA,EAErE,QAAQ,CAAC,UAAkB,MACzB;AAAA,IACE,8BAA8B,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAAA,EACjE;AAAA;AAAA,EAGF,mBAAmB,CAAC,YAAoB,KAAkB,WACxD,KAAwB,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,IAAI,GAAG,IAAI;AAAA,IACpE;AAAA,EACF,CAAC;AAAA;AAAA,EAGH,YAAY,CAAC,SAAiB,WAC5B,KAAe,GAAG,IAAI,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC;AACpE;","names":[]}
|
|
@@ -120,46 +120,3 @@ export interface PosWindow {
|
|
|
120
120
|
from?: string;
|
|
121
121
|
to?: string;
|
|
122
122
|
}
|
|
123
|
-
/** One person on a business's till roster (`GET /tenants/:id/pos/operators`). */
|
|
124
|
-
export interface PosRosterEntry {
|
|
125
|
-
id: string;
|
|
126
|
-
/** The name they sign in with at the till. */
|
|
127
|
-
signIn: string;
|
|
128
|
-
name: string;
|
|
129
|
-
role: string;
|
|
130
|
-
active: boolean;
|
|
131
|
-
shiftLimitMinutes: number;
|
|
132
|
-
updatedAt: string;
|
|
133
|
-
}
|
|
134
|
-
export type TillRequestStatus = 'pending' | 'approved' | 'rejected' | 'fulfilled';
|
|
135
|
-
/** A request for terminals (`GET /till-requests?tenantId=`). */
|
|
136
|
-
export interface PosTillRequest {
|
|
137
|
-
id: string;
|
|
138
|
-
tenantId: string;
|
|
139
|
-
quantity: number;
|
|
140
|
-
note: string | null;
|
|
141
|
-
status: TillRequestStatus;
|
|
142
|
-
decidedBy: string | null;
|
|
143
|
-
decisionNote: string | null;
|
|
144
|
-
decidedAt: string | null;
|
|
145
|
-
/** The system that decides it (`olive`), or null when an admin does. */
|
|
146
|
-
source: string | null;
|
|
147
|
-
createdAt: string;
|
|
148
|
-
}
|
|
149
|
-
/** One card payment (`GET /pos/payments`). `amountMinor` is in the currency's minor units. */
|
|
150
|
-
export interface PosPayment {
|
|
151
|
-
id: string;
|
|
152
|
-
tenantId: string;
|
|
153
|
-
deviceId: string | null;
|
|
154
|
-
deviceLabel: string | null;
|
|
155
|
-
direction: 'in' | 'out';
|
|
156
|
-
amountMinor: string;
|
|
157
|
-
currency: string;
|
|
158
|
-
status: string;
|
|
159
|
-
reference: string | null;
|
|
160
|
-
at: string;
|
|
161
|
-
}
|
|
162
|
-
export interface PosPaymentsPage {
|
|
163
|
-
rows: PosPayment[];
|
|
164
|
-
nextCursor: string | null;
|
|
165
|
-
}
|
package/dist/api/accounts.d.ts
CHANGED
|
@@ -10,14 +10,13 @@ export declare const accountsApi: {
|
|
|
10
10
|
password: string;
|
|
11
11
|
}) => Promise<AuthSessionPayload>;
|
|
12
12
|
/**
|
|
13
|
-
* Get current account (
|
|
13
|
+
* Get current account (requires auth token)
|
|
14
14
|
*/
|
|
15
|
-
getMe: (token
|
|
15
|
+
getMe: (token: string) => Promise<{
|
|
16
16
|
id: string;
|
|
17
17
|
email: string;
|
|
18
18
|
name: string;
|
|
19
19
|
avatarUrl?: string;
|
|
20
|
-
phone?: string | null;
|
|
21
20
|
authProvider: string;
|
|
22
21
|
status: string;
|
|
23
22
|
mfaEnabled?: boolean;
|
|
@@ -30,7 +29,6 @@ export declare const accountsApi: {
|
|
|
30
29
|
email: string;
|
|
31
30
|
name: string;
|
|
32
31
|
avatarUrl?: string;
|
|
33
|
-
phone?: string | null;
|
|
34
32
|
authProvider: string;
|
|
35
33
|
status: string;
|
|
36
34
|
mfaEnabled?: boolean;
|
|
@@ -56,7 +54,7 @@ export declare const accountsApi: {
|
|
|
56
54
|
/**
|
|
57
55
|
* List organizations I belong to
|
|
58
56
|
*/
|
|
59
|
-
getMyOrganizations: (token
|
|
57
|
+
getMyOrganizations: (token: string) => Promise<{
|
|
60
58
|
id: string;
|
|
61
59
|
name: string;
|
|
62
60
|
slug: string;
|
package/dist/api/accounts.js
CHANGED
package/dist/api/accounts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/accounts.ts"],"sourcesContent":["import { apiFetch } from './core';\nimport type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/browser';\nimport type {\n SignupInput,\n SignupResult,\n UpdateAccountInput,\n ChangePasswordInput,\n ChangeEmailInput,\n AuthSessionPayload,\n GoogleSignInCallbackResult,\n GoogleLinkInput,\n AccountConsent,\n ConsentInput,\n MfaSetupResult,\n MfaVerifyResult,\n SendLoginCodeResult,\n VerifyLoginCodeResult,\n Passkey,\n BeginPasskeyLoginResult,\n PasskeyLoginResult,\n} from './types';\n\nexport const accountsApi = {\n /**\n * Create a new account (signup)\n */\n signup: (data: SignupInput) =>\n apiFetch<SignupResult>('/accounts', {\n method: 'POST',\n body: JSON.stringify(data),\n }),\n\n registerTeamInvite: (\n token: string,\n data: { name: string; password: string },\n ) =>\n apiFetch<AuthSessionPayload>(`/accounts/team-invites/${token}/register`, {\n method: 'POST',\n body: JSON.stringify(data),\n }),\n\n /**\n * Get current account (the session cookie, or an explicit token)\n */\n getMe: (token?: string) =>\n apiFetch<SignupResult['account']>('/accounts/me', { token }),\n\n /**\n * Update current account profile\n */\n updateMe: (data: UpdateAccountInput) =>\n apiFetch<SignupResult['account']>('/accounts/me', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Change password\n */\n changePassword: (data: ChangePasswordInput) =>\n apiFetch<{ success: boolean }>('/accounts/me/password', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Change email address\n */\n changeEmail: (data: ChangeEmailInput) =>\n apiFetch<{ success: boolean }>('/accounts/me/email', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Delete account\n */\n deleteAccount: () =>\n apiFetch<{ success: boolean }>('/accounts/me', {\n method: 'DELETE',\n }),\n\n /**\n * List organizations I belong to\n */\n getMyOrganizations: (token?: string) =>\n apiFetch<{ id: string; name: string; slug: string }[]>(\n '/accounts/me/organizations',\n { token },\n ),\n\n /**\n * Verify email address using token from verification email\n */\n verifyEmail: (token: string) =>\n apiFetch<{ message: string }>('/accounts/verify-email', {\n method: 'POST',\n body: JSON.stringify({ token }),\n }),\n\n /**\n * Resend verification email to the current account\n */\n resendVerification: () =>\n apiFetch<{ message: string }>('/accounts/resend-verification', {\n method: 'POST',\n }),\n\n forgotPassword: (email: string) =>\n apiFetch<{ message: string }>('/accounts/forgot-password', {\n method: 'POST',\n body: JSON.stringify({ email }),\n }),\n\n resetPassword: (token: string, newPassword: string) =>\n apiFetch<{ message: string }>('/accounts/reset-password', {\n method: 'POST',\n body: JSON.stringify({ token, newPassword }),\n }),\n\n /**\n * Get the Google OAuth consent URL. Caller navigates to `url`; Google\n * redirects back to the configured callback with `?code=&state=`.\n */\n googleAuthUrl: () =>\n apiFetch<{ url: string; state: string }>('/accounts/google/url'),\n\n /**\n * Exchange the OAuth code for an account + session. Returns one of:\n * - `signed-in` : existing google account, session issued\n * - `created` : brand-new account, session issued\n * - `collision` : email matches a password account; prompt for password\n * then call `googleLink` with `collision.accountId`.\n */\n googleCallback: (input: { code: string; state: string }) =>\n apiFetch<GoogleSignInCallbackResult>('/accounts/google/callback', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Confirm password ownership on collision and link Google to the account.\n * Returns a normal signed-in session payload.\n */\n googleLink: (input: GoogleLinkInput) =>\n apiFetch<AuthSessionPayload>('/accounts/google/link', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Passwordless: send a one-time login code to the email. Always resolves the\n * same way whether or not an account exists (no enumeration).\n */\n sendLoginCode: (email: string) =>\n apiFetch<SendLoginCodeResult>('/accounts/code/send', {\n method: 'POST',\n body: JSON.stringify({ email }),\n }),\n\n /**\n * Verify the code. On success the account is signed in (created first if it\n * didn't exist), and the same session payload as login is returned.\n * `isNewAccount` tells the client whether to route into onboarding.\n */\n verifyLoginCode: (input: { email: string; code: string }) =>\n apiFetch<VerifyLoginCodeResult>('/accounts/code/verify', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n // ── Communication & privacy consent ────────────────────────────────\n /** Current consent preferences for the signed-in account. */\n getConsent: () => apiFetch<AccountConsent>('/accounts/me/consent'),\n\n /** Patch one or more consent switches. Server merges + re-stamps. */\n updateConsent: (data: Partial<ConsentInput>) =>\n apiFetch<AccountConsent>('/accounts/me/consent', {\n method: 'PATCH',\n body: JSON.stringify(data),\n }),\n\n /**\n * The second factor, for a sign-in that cleared its first.\n *\n * Takes a live authenticator code or one of the ten backup codes — those had\n * never been usable before, because nothing ever asked.\n */\n completeMfaChallenge: (input: { mfaToken: string; code: string }) =>\n apiFetch<AuthSessionPayload>('/accounts/mfa/challenge', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n // ── Passkeys ───────────────────────────────────────────────────────\n //\n // Enrolment is authenticated, signing in is not. You prove who you are with\n // an emailed code once, then attach a faster way back in from the device in\n // your hand.\n\n /** Options for creating a passkey. Requires a signed-in session. */\n beginPasskeyRegistration: () =>\n apiFetch<PublicKeyCredentialCreationOptionsJSON>(\n '/accounts/passkeys/register/begin',\n { method: 'POST' },\n ),\n\n /** Hand the created credential back to be verified and kept. */\n finishPasskeyRegistration: (input: { response: unknown; name?: string }) =>\n apiFetch<Passkey>('/accounts/passkeys/register/finish', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Start a passkey sign-in.\n *\n * `email` is optional and usually omitted: without it the browser offers\n * whatever it holds for this site, which is how the login gets to be one tap\n * with nothing typed. Passing it narrows the prompt to one account, which is\n * what the Welcome back card wants.\n */\n beginPasskeyLogin: (email?: string) =>\n apiFetch<BeginPasskeyLoginResult>('/accounts/passkeys/login/begin', {\n method: 'POST',\n body: JSON.stringify(email ? { email } : {}),\n }),\n\n finishPasskeyLogin: (input: { handle: string; response: unknown }) =>\n apiFetch<PasskeyLoginResult>('/accounts/passkeys/login/finish', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n listPasskeys: () => apiFetch<Passkey[]>('/accounts/passkeys'),\n\n renamePasskey: (id: string, name: string) =>\n apiFetch<{ success: true }>(`/accounts/passkeys/${id}`, {\n method: 'PATCH',\n body: JSON.stringify({ name }),\n }),\n\n removePasskey: (id: string) =>\n apiFetch<{ success: true }>(`/accounts/passkeys/${id}`, {\n method: 'DELETE',\n }),\n\n // ── Two-factor authentication (TOTP) ───────────────────────────────\n /**\n * Begin enrolment. Stores an encrypted secret with mfaEnabled=false and\n * returns the QR + secret to display. Not active until `verifyMfa` succeeds.\n */\n setupMfa: () =>\n apiFetch<MfaSetupResult>('/accounts/me/mfa/setup', { method: 'POST' }),\n\n /**\n * Confirm the first code to switch 2FA on. Returns the one-time backup\n * codes — shown ONCE; only their hashes are stored server-side.\n */\n verifyMfa: (code: string) =>\n apiFetch<MfaVerifyResult>('/accounts/me/mfa/verify', {\n method: 'POST',\n body: JSON.stringify({ code }),\n }),\n\n /** Turn 2FA off. Requires a valid current code (or a backup code). */\n disableMfa: (code: string) =>\n apiFetch<{ enabled: false }>('/accounts/me/mfa/disable', {\n method: 'POST',\n body: JSON.stringify({ code }),\n }),\n};\n\n/** Per-account communication + analytics consent. */\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/accounts.ts"],"sourcesContent":["import { apiFetch } from './core';\nimport type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/browser';\nimport type {\n SignupInput,\n SignupResult,\n UpdateAccountInput,\n ChangePasswordInput,\n ChangeEmailInput,\n AuthSessionPayload,\n GoogleSignInCallbackResult,\n GoogleLinkInput,\n AccountConsent,\n ConsentInput,\n MfaSetupResult,\n MfaVerifyResult,\n SendLoginCodeResult,\n VerifyLoginCodeResult,\n Passkey,\n BeginPasskeyLoginResult,\n PasskeyLoginResult,\n} from './types';\n\nexport const accountsApi = {\n /**\n * Create a new account (signup)\n */\n signup: (data: SignupInput) =>\n apiFetch<SignupResult>('/accounts', {\n method: 'POST',\n body: JSON.stringify(data),\n }),\n\n registerTeamInvite: (\n token: string,\n data: { name: string; password: string },\n ) =>\n apiFetch<AuthSessionPayload>(`/accounts/team-invites/${token}/register`, {\n method: 'POST',\n body: JSON.stringify(data),\n }),\n\n /**\n * Get current account (requires auth token)\n */\n getMe: (token: string) =>\n apiFetch<SignupResult['account']>('/accounts/me', { token }),\n\n /**\n * Update current account profile\n */\n updateMe: (data: UpdateAccountInput) =>\n apiFetch<SignupResult['account']>('/accounts/me', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Change password\n */\n changePassword: (data: ChangePasswordInput) =>\n apiFetch<{ success: boolean }>('/accounts/me/password', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Change email address\n */\n changeEmail: (data: ChangeEmailInput) =>\n apiFetch<{ success: boolean }>('/accounts/me/email', {\n method: 'PUT',\n body: JSON.stringify(data),\n }),\n\n /**\n * Delete account\n */\n deleteAccount: () =>\n apiFetch<{ success: boolean }>('/accounts/me', {\n method: 'DELETE',\n }),\n\n /**\n * List organizations I belong to\n */\n getMyOrganizations: (token: string) =>\n apiFetch<{ id: string; name: string; slug: string }[]>(\n '/accounts/me/organizations',\n { token },\n ),\n\n /**\n * Verify email address using token from verification email\n */\n verifyEmail: (token: string) =>\n apiFetch<{ message: string }>('/accounts/verify-email', {\n method: 'POST',\n body: JSON.stringify({ token }),\n }),\n\n /**\n * Resend verification email to the current account\n */\n resendVerification: () =>\n apiFetch<{ message: string }>('/accounts/resend-verification', {\n method: 'POST',\n }),\n\n forgotPassword: (email: string) =>\n apiFetch<{ message: string }>('/accounts/forgot-password', {\n method: 'POST',\n body: JSON.stringify({ email }),\n }),\n\n resetPassword: (token: string, newPassword: string) =>\n apiFetch<{ message: string }>('/accounts/reset-password', {\n method: 'POST',\n body: JSON.stringify({ token, newPassword }),\n }),\n\n /**\n * Get the Google OAuth consent URL. Caller navigates to `url`; Google\n * redirects back to the configured callback with `?code=&state=`.\n */\n googleAuthUrl: () =>\n apiFetch<{ url: string; state: string }>('/accounts/google/url'),\n\n /**\n * Exchange the OAuth code for an account + session. Returns one of:\n * - `signed-in` : existing google account, session issued\n * - `created` : brand-new account, session issued\n * - `collision` : email matches a password account; prompt for password\n * then call `googleLink` with `collision.accountId`.\n */\n googleCallback: (input: { code: string; state: string }) =>\n apiFetch<GoogleSignInCallbackResult>('/accounts/google/callback', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Confirm password ownership on collision and link Google to the account.\n * Returns a normal signed-in session payload.\n */\n googleLink: (input: GoogleLinkInput) =>\n apiFetch<AuthSessionPayload>('/accounts/google/link', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Passwordless: send a one-time login code to the email. Always resolves the\n * same way whether or not an account exists (no enumeration).\n */\n sendLoginCode: (email: string) =>\n apiFetch<SendLoginCodeResult>('/accounts/code/send', {\n method: 'POST',\n body: JSON.stringify({ email }),\n }),\n\n /**\n * Verify the code. On success the account is signed in (created first if it\n * didn't exist), and the same session payload as login is returned.\n * `isNewAccount` tells the client whether to route into onboarding.\n */\n verifyLoginCode: (input: { email: string; code: string }) =>\n apiFetch<VerifyLoginCodeResult>('/accounts/code/verify', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n // ── Communication & privacy consent ────────────────────────────────\n /** Current consent preferences for the signed-in account. */\n getConsent: () => apiFetch<AccountConsent>('/accounts/me/consent'),\n\n /** Patch one or more consent switches. Server merges + re-stamps. */\n updateConsent: (data: Partial<ConsentInput>) =>\n apiFetch<AccountConsent>('/accounts/me/consent', {\n method: 'PATCH',\n body: JSON.stringify(data),\n }),\n\n /**\n * The second factor, for a sign-in that cleared its first.\n *\n * Takes a live authenticator code or one of the ten backup codes — those had\n * never been usable before, because nothing ever asked.\n */\n completeMfaChallenge: (input: { mfaToken: string; code: string }) =>\n apiFetch<AuthSessionPayload>('/accounts/mfa/challenge', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n // ── Passkeys ───────────────────────────────────────────────────────\n //\n // Enrolment is authenticated, signing in is not. You prove who you are with\n // an emailed code once, then attach a faster way back in from the device in\n // your hand.\n\n /** Options for creating a passkey. Requires a signed-in session. */\n beginPasskeyRegistration: () =>\n apiFetch<PublicKeyCredentialCreationOptionsJSON>(\n '/accounts/passkeys/register/begin',\n { method: 'POST' },\n ),\n\n /** Hand the created credential back to be verified and kept. */\n finishPasskeyRegistration: (input: { response: unknown; name?: string }) =>\n apiFetch<Passkey>('/accounts/passkeys/register/finish', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n /**\n * Start a passkey sign-in.\n *\n * `email` is optional and usually omitted: without it the browser offers\n * whatever it holds for this site, which is how the login gets to be one tap\n * with nothing typed. Passing it narrows the prompt to one account, which is\n * what the Welcome back card wants.\n */\n beginPasskeyLogin: (email?: string) =>\n apiFetch<BeginPasskeyLoginResult>('/accounts/passkeys/login/begin', {\n method: 'POST',\n body: JSON.stringify(email ? { email } : {}),\n }),\n\n finishPasskeyLogin: (input: { handle: string; response: unknown }) =>\n apiFetch<PasskeyLoginResult>('/accounts/passkeys/login/finish', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n\n listPasskeys: () => apiFetch<Passkey[]>('/accounts/passkeys'),\n\n renamePasskey: (id: string, name: string) =>\n apiFetch<{ success: true }>(`/accounts/passkeys/${id}`, {\n method: 'PATCH',\n body: JSON.stringify({ name }),\n }),\n\n removePasskey: (id: string) =>\n apiFetch<{ success: true }>(`/accounts/passkeys/${id}`, {\n method: 'DELETE',\n }),\n\n // ── Two-factor authentication (TOTP) ───────────────────────────────\n /**\n * Begin enrolment. Stores an encrypted secret with mfaEnabled=false and\n * returns the QR + secret to display. Not active until `verifyMfa` succeeds.\n */\n setupMfa: () =>\n apiFetch<MfaSetupResult>('/accounts/me/mfa/setup', { method: 'POST' }),\n\n /**\n * Confirm the first code to switch 2FA on. Returns the one-time backup\n * codes — shown ONCE; only their hashes are stored server-side.\n */\n verifyMfa: (code: string) =>\n apiFetch<MfaVerifyResult>('/accounts/me/mfa/verify', {\n method: 'POST',\n body: JSON.stringify({ code }),\n }),\n\n /** Turn 2FA off. Requires a valid current code (or a backup code). */\n disableMfa: (code: string) =>\n apiFetch<{ enabled: false }>('/accounts/me/mfa/disable', {\n method: 'POST',\n body: JSON.stringify({ code }),\n }),\n};\n\n/** Per-account communication + analytics consent. */\n"],"mappings":"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","names":[]}
|
package/dist/api/analytics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getApiUrl, getAccountId, apiFetch } from "./core";
|
|
1
|
+
import { getApiUrl, getAccountId, getAuthToken, apiFetch } from "./core";
|
|
2
2
|
import { scopeParams } from "./analytics-patterns";
|
|
3
3
|
const analyticsApi = {
|
|
4
4
|
/**
|
|
@@ -365,6 +365,8 @@ const analyticsApi = {
|
|
|
365
365
|
const headers = { "X-Tenant-Slug": tenantSlug };
|
|
366
366
|
const accountId = getAccountId();
|
|
367
367
|
if (accountId) headers["x-account-id"] = accountId;
|
|
368
|
+
const token = getAuthToken();
|
|
369
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
368
370
|
const res = await fetch(`${getApiUrl()}/analytics/export?${params}`, {
|
|
369
371
|
headers,
|
|
370
372
|
credentials: "include"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/analytics.ts"],"sourcesContent":["import { getApiUrl, getAccountId, apiFetch } from './core';\nimport { scopeParams } from './analytics-patterns';\nimport type {\n CohortRow,\n SalesLedger,\n OrderReport,\n RevenueSplit,\n OverviewStats,\n SalesStats,\n TopContent,\n TopProduct,\n TrafficSource,\n DeviceStats,\n FunnelStats,\n BookingAnalytics,\n EventAnalytics,\n ProductPerformance,\n HomeSummary,\n OutstandingInvoices,\n PaymentsMix,\n Insight,\n MetricTree,\n Utilization,\n Rebooking,\n WinBack,\n LoyaltyEffect,\n DashboardStats,\n SearchTermStat,\n AttributionReport,\n AnalyticsQuery,\n RecentEvent,\n StockCover,\n BookingPerformance,\n Fulfilment,\n CollectionSpeed,\n DineIn,\n EventSales,\n Benchmarks,\n WeeklyDigest,\n} from './types';\n\nexport const analyticsApi = {\n /**\n * One event's ticketing: bookings, heads, revenue, check-in rate and the\n * split by ticket tier.\n *\n * The route has existed since event-tickets shipped and had no wrapper here\n * at all, so nine measurements the server computes on request reached no\n * screen. `contentId` is the event's Content row.\n */\n getEventAnalytics: (tenantSlug: string, contentId: string) =>\n apiFetch<EventAnalytics>(`/analytics/events/${contentId}`, { tenantSlug }),\n\n /**\n * What one item did, and where its buyers came from.\n *\n * The ordinary-product counterpart to `getEventAnalytics` above. Until this\n * existed the return leg of the loop was built for exactly one archetype: a\n * venue could see how a night went, and everybody else could open an item\n * they had created and find nothing that said what it earned.\n *\n * Its own request rather than a field on the product, because it is scoped\n * to a WINDOW and the product record is not — and because the item screen\n * should render its record immediately and let the money arrive after.\n */\n getProductPerformance: (\n tenantSlug: string,\n contentId: string,\n query: { startDate: string; endDate: string },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<ProductPerformance>(\n `/analytics/products/${contentId}?${params}`,\n { tenantSlug },\n );\n },\n\n /**\n * One measure, cut by one dimension, over a window.\n *\n * Grouped in the database. The view this replaces fetched 250 orders and\n * grouped them in the browser, so a busy quarter drew a chart from a slice.\n */\n getOrderReport: (\n tenantSlug: string,\n query: AnalyticsQuery & {\n dimension: string;\n measure: string;\n },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n dimension: query.dimension,\n measure: query.measure,\n ...(query.granularity && { granularity: query.granularity }),\n });\n return apiFetch<OrderReport>(`/analytics/order-report?${params}`, {\n tenantSlug,\n });\n },\n\n /** New against returning revenue, by first-paid-order. */\n getRevenueSplit: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<RevenueSplit>(`/analytics/revenue-split?${params}`, {\n tenantSlug,\n });\n },\n\n /** Gross to net over one window, from `orders` rather than from beacons. */\n getSalesLedger: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.granularity && { granularity: query.granularity }),\n ...scopeParams(query),\n });\n return apiFetch<SalesLedger>(`/analytics/sales-ledger?${params}`, {\n tenantSlug,\n });\n },\n\n getDashboard: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.granularity && { granularity: query.granularity }),\n ...(query.limit && { limit: String(query.limit) }),\n });\n return apiFetch<DashboardStats>(`/analytics/dashboard?${params}`, {\n tenantSlug,\n });\n },\n\n getOverview: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<OverviewStats>(`/analytics/overview?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * Retention by first-purchase month.\n *\n * No date range, deliberately: a cohort table reads backwards from today by\n * definition, and handing it the hub's period picker would let a merchant\n * ask for the six months ending last March, which is a different question\n * with the same shape. `months` is how many cohorts to draw, clamped 3 to 12\n * server-side because the grid is a triangle nobody can read at sixty.\n */\n getCohorts: (tenantSlug: string, months = 6) =>\n apiFetch<{ cohorts: CohortRow[] }>(`/analytics/cohorts?months=${months}`, {\n tenantSlug,\n }),\n\n getSales: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<SalesStats>(`/analytics/sales?${params}`, { tenantSlug });\n },\n\n /**\n * The last N events, newest first.\n *\n * The route has existed since analytics shipped and nothing in the dashboard\n * called it. `useLivePresence` does now: an event carries `sessionId` and\n * `createdAt`, so \"who is on the site this minute\" is a filter over this\n * payload rather than an endpoint of its own.\n *\n * `limit` matters more than it looks. The window is counted client-side, so\n * a busy storefront can have more events in five minutes than the limit\n * returns, and the count would silently cap. The caller detects that by\n * checking whether the OLDEST event returned is still inside the window.\n */\n getRecentEvents: (tenantSlug: string, limit = 60) =>\n apiFetch<RecentEvent[]>(`/analytics/recent-events?limit=${limit}`, {\n tenantSlug,\n }),\n\n getTopContent: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.limit && { limit: String(query.limit) }),\n });\n return apiFetch<TopContent[]>(`/analytics/top-content?${params}`, {\n tenantSlug,\n });\n },\n\n getTopProducts: (\n tenantSlug: string,\n query: AnalyticsQuery & { sortBy?: 'revenue' | 'units' },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.limit && { limit: String(query.limit) }),\n // Server-side, because it has to be the ORDER BY: re-sorting a\n // revenue-ranked page gives the units leaders AMONG the revenue leaders.\n ...(query.sortBy && { sortBy: query.sortBy }),\n });\n return apiFetch<TopProduct[]>(`/analytics/top-products?${params}`, {\n tenantSlug,\n });\n },\n\n getTrafficSources: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<TrafficSource[]>(`/analytics/traffic-sources?${params}`, {\n tenantSlug,\n });\n },\n\n getDevices: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<DeviceStats[]>(`/analytics/devices?${params}`, {\n tenantSlug,\n });\n },\n\n // Not part of `/analytics/dashboard`. That payload is already twenty-odd\n // aggregates fetched on every view of the hub, and only the traffic report\n // asks this question.\n getSearchTerms: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n limit: String(query.limit ?? 20),\n });\n return apiFetch<SearchTermStat[]>(`/analytics/search-terms?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * Revenue by where the orders came from.\n *\n * Its own request for the same reason search-terms is: `/analytics/dashboard`\n * is already twenty-odd aggregates on every view of the hub, and this is four\n * grouped queries only one screen asks for.\n *\n * `model` is sendable again, and this time the API whitelists it.\n *\n * It was dropped from this signature because the endpoint's DTO rejected it\n * outright — `property model should not exist` — so every request carrying\n * one 400d, which was every request, because the parameter defaulted. The\n * controller read it and Swagger documented the enum; only the DTO had never\n * heard of it.\n *\n * Sent only when a model is actually chosen, so the default request is\n * byte-identical to what a build without the fix expects. Callers must still\n * gate the CHOICE on `features.attributionModels` — an old API will 400 on\n * `first_touch`, and a control that errors is worse than no control.\n */\n getAttribution: (\n tenantSlug: string,\n query: AnalyticsQuery,\n model?: 'last_touch' | 'first_touch',\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(model ? { model } : {}),\n });\n return apiFetch<AttributionReport>(`/analytics/attribution?${params}`, {\n tenantSlug,\n });\n },\n\n getFunnel: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<FunnelStats>(`/analytics/funnel?${params}`, { tenantSlug });\n },\n\n getBookingAnalytics: (tenantSlug: string) => {\n return apiFetch<BookingAnalytics>(`/analytics/booking-stats`, {\n tenantSlug,\n });\n },\n\n /**\n * The home money band in one request.\n *\n * Replaces three: two full `/analytics/dashboard` payloads (current and\n * prior) plus an `/analytics/outstanding` route that does not exist on the\n * API and had been 404ing silently on every load. The band renders four\n * numbers and a series; this returns exactly that.\n */\n getHomeSummary: (\n tenantSlug: string,\n params: { startDate: string; endDate: string },\n ) =>\n apiFetch<HomeSummary>(\n `/analytics/home-summary?startDate=${params.startDate}&endDate=${params.endDate}`,\n { tenantSlug },\n ),\n\n /** When each tracked product runs out, and which are not moving. */\n getStockCover: (tenantSlug: string) =>\n apiFetch<StockCover>('/analytics/stock-cover', { tenantSlug }),\n\n /** No-show and cancellation rates, and by who was booked. */\n getBookingPerformance: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<BookingPerformance>(\n `/analytics/booking-performance?${params}`,\n { tenantSlug },\n );\n },\n\n /** Paid orders still to send, and how long sending takes. */\n getFulfilment: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Fulfilment>(`/analytics/fulfilment?${params}`, {\n tenantSlug,\n });\n },\n\n /** How many days invoices take to be paid. */\n getCollectionSpeed: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<CollectionSpeed>(`/analytics/collection-speed?${params}`, {\n tenantSlug,\n });\n },\n\n /** Tabs open now, average bill and how long tables are held. */\n getDineIn: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<DineIn>(`/analytics/dine-in?${params}`, { tenantSlug });\n },\n\n /** Tickets by type for each ticketed event, and who checked in. */\n getEventSales: (tenantSlug: string) =>\n apiFetch<EventSales>('/analytics/event-sales', { tenantSlug }),\n\n /** How this business compares with similar ones. Aggregates only. */\n getBenchmarks: (tenantSlug: string) =>\n apiFetch<Benchmarks>('/analytics/benchmarks', { tenantSlug }),\n\n /** The last seven days in a few plain lines, ready to read or send. */\n getWeeklyDigest: (tenantSlug: string) =>\n apiFetch<WeeklyDigest>('/analytics/weekly-digest', { tenantSlug }),\n\n getOutstanding: (tenantSlug: string, limit?: number) => {\n const params = new URLSearchParams(limit ? { limit: String(limit) } : {});\n const qs = params.toString();\n return apiFetch<OutstandingInvoices>(\n `/analytics/outstanding${qs ? `?${qs}` : ''}`,\n { tenantSlug },\n );\n },\n\n getPaymentsMix: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<PaymentsMix>(`/analytics/payments-mix?${params}`, {\n tenantSlug,\n });\n },\n\n getInsights: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Insight[]>(`/analytics/insights?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * The prior window is not sent: the API derives the equally-long stretch\n * immediately before this one, so the comparison cannot drift between\n * callers.\n */\n getMetricTree: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<MetricTree>(`/analytics/metric-tree?${params}`, {\n tenantSlug,\n });\n },\n\n getLoyaltyEffect: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<LoyaltyEffect>(`/analytics/loyalty-effect?${params}`, {\n tenantSlug,\n });\n },\n\n getRebooking: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Rebooking>(`/analytics/rebooking?${params}`, {\n tenantSlug,\n });\n },\n\n getWinBack: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<WinBack>(`/analytics/win-back?${params}`, { tenantSlug });\n },\n\n getUtilization: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Utilization>(`/analytics/utilization?${params}`, {\n tenantSlug,\n });\n },\n\n // CSV, not JSON — so it bypasses apiFetch's JSON parsing and returns raw text.\n exportCsv: async (\n tenantSlug: string,\n query: AnalyticsQuery,\n ): Promise<string> => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n const headers: Record<string, string> = { 'X-Tenant-Slug': tenantSlug };\n const accountId = getAccountId();\n if (accountId) headers['x-account-id'] = accountId;\n\n const res = await fetch(`${getApiUrl()}/analytics/export?${params}`, {\n headers,\n credentials: 'include',\n });\n if (!res.ok) throw new Error(`Export failed (HTTP ${res.status})`);\n return res.text();\n },\n};\n\n// ============================================\n// ORDERS API\n// ============================================\n"],"mappings":"AAAA,SAAS,WAAW,cAAc,gBAAgB;AAClD,SAAS,mBAAmB;AAwCrB,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS1B,mBAAmB,CAAC,YAAoB,cACtC,SAAyB,qBAAqB,SAAS,IAAI,EAAE,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc3E,uBAAuB,CACrB,YACA,WACA,UACG;AACH,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO;AAAA,MACL,uBAAuB,SAAS,IAAI,MAAM;AAAA,MAC1C,EAAE,WAAW;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,CACd,YACA,UAIG;AACH,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,eAAe,EAAE,aAAa,MAAM,YAAY;AAAA,IAC5D,CAAC;AACD,WAAO,SAAsB,2BAA2B,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,iBAAiB,CAAC,YAAoB,UAA0B;AAC9D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAuB,4BAA4B,MAAM,IAAI;AAAA,MAClE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,gBAAgB,CAAC,YAAoB,UAA0B;AAC7D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,eAAe,EAAE,aAAa,MAAM,YAAY;AAAA,MAC1D,GAAG,YAAY,KAAK;AAAA,IACtB,CAAC;AACD,WAAO,SAAsB,2BAA2B,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,cAAc,CAAC,YAAoB,UAA0B;AAC3D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,eAAe,EAAE,aAAa,MAAM,YAAY;AAAA,MAC1D,GAAI,MAAM,SAAS,EAAE,OAAO,OAAO,MAAM,KAAK,EAAE;AAAA,IAClD,CAAC;AACD,WAAO,SAAyB,wBAAwB,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAa,CAAC,YAAoB,UAA0B;AAC1D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAwB,uBAAuB,MAAM,IAAI;AAAA,MAC9D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAY,CAAC,YAAoB,SAAS,MACxC,SAAmC,6BAA6B,MAAM,IAAI;AAAA,IACxE;AAAA,EACF,CAAC;AAAA,EAEH,UAAU,CAAC,YAAoB,UAA0B;AACvD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAqB,oBAAoB,MAAM,IAAI,EAAE,WAAW,CAAC;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,iBAAiB,CAAC,YAAoB,QAAQ,OAC5C,SAAwB,kCAAkC,KAAK,IAAI;AAAA,IACjE;AAAA,EACF,CAAC;AAAA,EAEH,eAAe,CAAC,YAAoB,UAA0B;AAC5D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,SAAS,EAAE,OAAO,OAAO,MAAM,KAAK,EAAE;AAAA,IAClD,CAAC;AACD,WAAO,SAAuB,0BAA0B,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,gBAAgB,CACd,YACA,UACG;AACH,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,SAAS,EAAE,OAAO,OAAO,MAAM,KAAK,EAAE;AAAA;AAAA;AAAA,MAGhD,GAAI,MAAM,UAAU,EAAE,QAAQ,MAAM,OAAO;AAAA,IAC7C,CAAC;AACD,WAAO,SAAuB,2BAA2B,MAAM,IAAI;AAAA,MACjE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,mBAAmB,CAAC,YAAoB,UAA0B;AAChE,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAA0B,8BAA8B,MAAM,IAAI;AAAA,MACvE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,CAAC,YAAoB,UAA0B;AACzD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAwB,sBAAsB,MAAM,IAAI;AAAA,MAC7D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,CAAC,YAAoB,UAA0B;AAC7D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,OAAO,OAAO,MAAM,SAAS,EAAE;AAAA,IACjC,CAAC;AACD,WAAO,SAA2B,2BAA2B,MAAM,IAAI;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,gBAAgB,CACd,YACA,OACA,UACG;AACH,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B,CAAC;AACD,WAAO,SAA4B,0BAA0B,MAAM,IAAI;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,CAAC,YAAoB,UAA0B;AACxD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAsB,qBAAqB,MAAM,IAAI,EAAE,WAAW,CAAC;AAAA,EAC5E;AAAA,EAEA,qBAAqB,CAAC,eAAuB;AAC3C,WAAO,SAA2B,4BAA4B;AAAA,MAC5D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,gBAAgB,CACd,YACA,WAEA;AAAA,IACE,qCAAqC,OAAO,SAAS,YAAY,OAAO,OAAO;AAAA,IAC/E,EAAE,WAAW;AAAA,EACf;AAAA;AAAA,EAGF,eAAe,CAAC,eACd,SAAqB,0BAA0B,EAAE,WAAW,CAAC;AAAA;AAAA,EAG/D,uBAAuB,CAAC,YAAoB,UAA0B;AACpE,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO;AAAA,MACL,kCAAkC,MAAM;AAAA,MACxC,EAAE,WAAW;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,eAAe,CAAC,YAAoB,UAA0B;AAC5D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAqB,yBAAyB,MAAM,IAAI;AAAA,MAC7D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,oBAAoB,CAAC,YAAoB,UAA0B;AACjE,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAA0B,+BAA+B,MAAM,IAAI;AAAA,MACxE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,WAAW,CAAC,YAAoB,UAA0B;AACxD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAiB,sBAAsB,MAAM,IAAI,EAAE,WAAW,CAAC;AAAA,EACxE;AAAA;AAAA,EAGA,eAAe,CAAC,eACd,SAAqB,0BAA0B,EAAE,WAAW,CAAC;AAAA;AAAA,EAG/D,eAAe,CAAC,eACd,SAAqB,yBAAyB,EAAE,WAAW,CAAC;AAAA;AAAA,EAG9D,iBAAiB,CAAC,eAChB,SAAuB,4BAA4B,EAAE,WAAW,CAAC;AAAA,EAEnE,gBAAgB,CAAC,YAAoB,UAAmB;AACtD,UAAM,SAAS,IAAI,gBAAgB,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC;AACxE,UAAM,KAAK,OAAO,SAAS;AAC3B,WAAO;AAAA,MACL,yBAAyB,KAAK,IAAI,EAAE,KAAK,EAAE;AAAA,MAC3C,EAAE,WAAW;AAAA,IACf;AAAA,EACF;AAAA,EAEA,gBAAgB,CAAC,YAAoB,UAA0B;AAC7D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAsB,2BAA2B,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAa,CAAC,YAAoB,UAA0B;AAC1D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAoB,uBAAuB,MAAM,IAAI;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,CAAC,YAAoB,UAA0B;AAC5D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAqB,0BAA0B,MAAM,IAAI;AAAA,MAC9D;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,kBAAkB,CAAC,YAAoB,UAA0B;AAC/D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAwB,6BAA6B,MAAM,IAAI;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,cAAc,CAAC,YAAoB,UAA0B;AAC3D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAoB,wBAAwB,MAAM,IAAI;AAAA,MAC3D;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,CAAC,YAAoB,UAA0B;AACzD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAkB,uBAAuB,MAAM,IAAI,EAAE,WAAW,CAAC;AAAA,EAC1E;AAAA,EAEA,gBAAgB,CAAC,YAAoB,UAA0B;AAC7D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,SAAsB,0BAA0B,MAAM,IAAI;AAAA,MAC/D;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,WAAW,OACT,YACA,UACoB;AACpB,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,UAAM,UAAkC,EAAE,iBAAiB,WAAW;AACtE,UAAM,YAAY,aAAa;AAC/B,QAAI,UAAW,SAAQ,cAAc,IAAI;AAEzC,UAAM,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC,qBAAqB,MAAM,IAAI;AAAA,MACnE;AAAA,MACA,aAAa;AAAA,IACf,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,uBAAuB,IAAI,MAAM,GAAG;AACjE,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/api/analytics.ts"],"sourcesContent":["import { getApiUrl, getAccountId, getAuthToken, apiFetch } from './core';\nimport { scopeParams } from './analytics-patterns';\nimport type {\n CohortRow,\n SalesLedger,\n OrderReport,\n RevenueSplit,\n OverviewStats,\n SalesStats,\n TopContent,\n TopProduct,\n TrafficSource,\n DeviceStats,\n FunnelStats,\n BookingAnalytics,\n EventAnalytics,\n ProductPerformance,\n HomeSummary,\n OutstandingInvoices,\n PaymentsMix,\n Insight,\n MetricTree,\n Utilization,\n Rebooking,\n WinBack,\n LoyaltyEffect,\n DashboardStats,\n SearchTermStat,\n AttributionReport,\n AnalyticsQuery,\n RecentEvent,\n StockCover,\n BookingPerformance,\n Fulfilment,\n CollectionSpeed,\n DineIn,\n EventSales,\n Benchmarks,\n WeeklyDigest,\n} from './types';\n\nexport const analyticsApi = {\n /**\n * One event's ticketing: bookings, heads, revenue, check-in rate and the\n * split by ticket tier.\n *\n * The route has existed since event-tickets shipped and had no wrapper here\n * at all, so nine measurements the server computes on request reached no\n * screen. `contentId` is the event's Content row.\n */\n getEventAnalytics: (tenantSlug: string, contentId: string) =>\n apiFetch<EventAnalytics>(`/analytics/events/${contentId}`, { tenantSlug }),\n\n /**\n * What one item did, and where its buyers came from.\n *\n * The ordinary-product counterpart to `getEventAnalytics` above. Until this\n * existed the return leg of the loop was built for exactly one archetype: a\n * venue could see how a night went, and everybody else could open an item\n * they had created and find nothing that said what it earned.\n *\n * Its own request rather than a field on the product, because it is scoped\n * to a WINDOW and the product record is not — and because the item screen\n * should render its record immediately and let the money arrive after.\n */\n getProductPerformance: (\n tenantSlug: string,\n contentId: string,\n query: { startDate: string; endDate: string },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<ProductPerformance>(\n `/analytics/products/${contentId}?${params}`,\n { tenantSlug },\n );\n },\n\n /**\n * One measure, cut by one dimension, over a window.\n *\n * Grouped in the database. The view this replaces fetched 250 orders and\n * grouped them in the browser, so a busy quarter drew a chart from a slice.\n */\n getOrderReport: (\n tenantSlug: string,\n query: AnalyticsQuery & {\n dimension: string;\n measure: string;\n },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n dimension: query.dimension,\n measure: query.measure,\n ...(query.granularity && { granularity: query.granularity }),\n });\n return apiFetch<OrderReport>(`/analytics/order-report?${params}`, {\n tenantSlug,\n });\n },\n\n /** New against returning revenue, by first-paid-order. */\n getRevenueSplit: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<RevenueSplit>(`/analytics/revenue-split?${params}`, {\n tenantSlug,\n });\n },\n\n /** Gross to net over one window, from `orders` rather than from beacons. */\n getSalesLedger: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.granularity && { granularity: query.granularity }),\n ...scopeParams(query),\n });\n return apiFetch<SalesLedger>(`/analytics/sales-ledger?${params}`, {\n tenantSlug,\n });\n },\n\n getDashboard: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.granularity && { granularity: query.granularity }),\n ...(query.limit && { limit: String(query.limit) }),\n });\n return apiFetch<DashboardStats>(`/analytics/dashboard?${params}`, {\n tenantSlug,\n });\n },\n\n getOverview: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<OverviewStats>(`/analytics/overview?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * Retention by first-purchase month.\n *\n * No date range, deliberately: a cohort table reads backwards from today by\n * definition, and handing it the hub's period picker would let a merchant\n * ask for the six months ending last March, which is a different question\n * with the same shape. `months` is how many cohorts to draw, clamped 3 to 12\n * server-side because the grid is a triangle nobody can read at sixty.\n */\n getCohorts: (tenantSlug: string, months = 6) =>\n apiFetch<{ cohorts: CohortRow[] }>(`/analytics/cohorts?months=${months}`, {\n tenantSlug,\n }),\n\n getSales: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<SalesStats>(`/analytics/sales?${params}`, { tenantSlug });\n },\n\n /**\n * The last N events, newest first.\n *\n * The route has existed since analytics shipped and nothing in the dashboard\n * called it. `useLivePresence` does now: an event carries `sessionId` and\n * `createdAt`, so \"who is on the site this minute\" is a filter over this\n * payload rather than an endpoint of its own.\n *\n * `limit` matters more than it looks. The window is counted client-side, so\n * a busy storefront can have more events in five minutes than the limit\n * returns, and the count would silently cap. The caller detects that by\n * checking whether the OLDEST event returned is still inside the window.\n */\n getRecentEvents: (tenantSlug: string, limit = 60) =>\n apiFetch<RecentEvent[]>(`/analytics/recent-events?limit=${limit}`, {\n tenantSlug,\n }),\n\n getTopContent: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.limit && { limit: String(query.limit) }),\n });\n return apiFetch<TopContent[]>(`/analytics/top-content?${params}`, {\n tenantSlug,\n });\n },\n\n getTopProducts: (\n tenantSlug: string,\n query: AnalyticsQuery & { sortBy?: 'revenue' | 'units' },\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(query.limit && { limit: String(query.limit) }),\n // Server-side, because it has to be the ORDER BY: re-sorting a\n // revenue-ranked page gives the units leaders AMONG the revenue leaders.\n ...(query.sortBy && { sortBy: query.sortBy }),\n });\n return apiFetch<TopProduct[]>(`/analytics/top-products?${params}`, {\n tenantSlug,\n });\n },\n\n getTrafficSources: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<TrafficSource[]>(`/analytics/traffic-sources?${params}`, {\n tenantSlug,\n });\n },\n\n getDevices: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<DeviceStats[]>(`/analytics/devices?${params}`, {\n tenantSlug,\n });\n },\n\n // Not part of `/analytics/dashboard`. That payload is already twenty-odd\n // aggregates fetched on every view of the hub, and only the traffic report\n // asks this question.\n getSearchTerms: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n limit: String(query.limit ?? 20),\n });\n return apiFetch<SearchTermStat[]>(`/analytics/search-terms?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * Revenue by where the orders came from.\n *\n * Its own request for the same reason search-terms is: `/analytics/dashboard`\n * is already twenty-odd aggregates on every view of the hub, and this is four\n * grouped queries only one screen asks for.\n *\n * `model` is sendable again, and this time the API whitelists it.\n *\n * It was dropped from this signature because the endpoint's DTO rejected it\n * outright — `property model should not exist` — so every request carrying\n * one 400d, which was every request, because the parameter defaulted. The\n * controller read it and Swagger documented the enum; only the DTO had never\n * heard of it.\n *\n * Sent only when a model is actually chosen, so the default request is\n * byte-identical to what a build without the fix expects. Callers must still\n * gate the CHOICE on `features.attributionModels` — an old API will 400 on\n * `first_touch`, and a control that errors is worse than no control.\n */\n getAttribution: (\n tenantSlug: string,\n query: AnalyticsQuery,\n model?: 'last_touch' | 'first_touch',\n ) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n ...(model ? { model } : {}),\n });\n return apiFetch<AttributionReport>(`/analytics/attribution?${params}`, {\n tenantSlug,\n });\n },\n\n getFunnel: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<FunnelStats>(`/analytics/funnel?${params}`, { tenantSlug });\n },\n\n getBookingAnalytics: (tenantSlug: string) => {\n return apiFetch<BookingAnalytics>(`/analytics/booking-stats`, {\n tenantSlug,\n });\n },\n\n /**\n * The home money band in one request.\n *\n * Replaces three: two full `/analytics/dashboard` payloads (current and\n * prior) plus an `/analytics/outstanding` route that does not exist on the\n * API and had been 404ing silently on every load. The band renders four\n * numbers and a series; this returns exactly that.\n */\n getHomeSummary: (\n tenantSlug: string,\n params: { startDate: string; endDate: string },\n ) =>\n apiFetch<HomeSummary>(\n `/analytics/home-summary?startDate=${params.startDate}&endDate=${params.endDate}`,\n { tenantSlug },\n ),\n\n /** When each tracked product runs out, and which are not moving. */\n getStockCover: (tenantSlug: string) =>\n apiFetch<StockCover>('/analytics/stock-cover', { tenantSlug }),\n\n /** No-show and cancellation rates, and by who was booked. */\n getBookingPerformance: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<BookingPerformance>(\n `/analytics/booking-performance?${params}`,\n { tenantSlug },\n );\n },\n\n /** Paid orders still to send, and how long sending takes. */\n getFulfilment: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Fulfilment>(`/analytics/fulfilment?${params}`, {\n tenantSlug,\n });\n },\n\n /** How many days invoices take to be paid. */\n getCollectionSpeed: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<CollectionSpeed>(`/analytics/collection-speed?${params}`, {\n tenantSlug,\n });\n },\n\n /** Tabs open now, average bill and how long tables are held. */\n getDineIn: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<DineIn>(`/analytics/dine-in?${params}`, { tenantSlug });\n },\n\n /** Tickets by type for each ticketed event, and who checked in. */\n getEventSales: (tenantSlug: string) =>\n apiFetch<EventSales>('/analytics/event-sales', { tenantSlug }),\n\n /** How this business compares with similar ones. Aggregates only. */\n getBenchmarks: (tenantSlug: string) =>\n apiFetch<Benchmarks>('/analytics/benchmarks', { tenantSlug }),\n\n /** The last seven days in a few plain lines, ready to read or send. */\n getWeeklyDigest: (tenantSlug: string) =>\n apiFetch<WeeklyDigest>('/analytics/weekly-digest', { tenantSlug }),\n\n getOutstanding: (tenantSlug: string, limit?: number) => {\n const params = new URLSearchParams(limit ? { limit: String(limit) } : {});\n const qs = params.toString();\n return apiFetch<OutstandingInvoices>(\n `/analytics/outstanding${qs ? `?${qs}` : ''}`,\n { tenantSlug },\n );\n },\n\n getPaymentsMix: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<PaymentsMix>(`/analytics/payments-mix?${params}`, {\n tenantSlug,\n });\n },\n\n getInsights: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Insight[]>(`/analytics/insights?${params}`, {\n tenantSlug,\n });\n },\n\n /**\n * The prior window is not sent: the API derives the equally-long stretch\n * immediately before this one, so the comparison cannot drift between\n * callers.\n */\n getMetricTree: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<MetricTree>(`/analytics/metric-tree?${params}`, {\n tenantSlug,\n });\n },\n\n getLoyaltyEffect: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<LoyaltyEffect>(`/analytics/loyalty-effect?${params}`, {\n tenantSlug,\n });\n },\n\n getRebooking: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Rebooking>(`/analytics/rebooking?${params}`, {\n tenantSlug,\n });\n },\n\n getWinBack: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<WinBack>(`/analytics/win-back?${params}`, { tenantSlug });\n },\n\n getUtilization: (tenantSlug: string, query: AnalyticsQuery) => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n return apiFetch<Utilization>(`/analytics/utilization?${params}`, {\n tenantSlug,\n });\n },\n\n // CSV, not JSON — so it bypasses apiFetch's JSON parsing and returns raw text.\n exportCsv: async (\n tenantSlug: string,\n query: AnalyticsQuery,\n ): Promise<string> => {\n const params = new URLSearchParams({\n startDate: query.startDate,\n endDate: query.endDate,\n });\n const headers: Record<string, string> = { 'X-Tenant-Slug': tenantSlug };\n const accountId = getAccountId();\n if (accountId) headers['x-account-id'] = accountId;\n const token = getAuthToken();\n if (token) headers.Authorization = `Bearer ${token}`;\n\n const res = await fetch(`${getApiUrl()}/analytics/export?${params}`, {\n headers,\n credentials: 'include',\n });\n if (!res.ok) throw new Error(`Export failed (HTTP ${res.status})`);\n return res.text();\n },\n};\n\n// ============================================\n// ORDERS API\n// ============================================\n"],"mappings":"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","names":[]}
|
package/dist/api/core.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const IS_BROWSER: boolean;
|
|
|
10
10
|
import type { FetchOptions } from './types';
|
|
11
11
|
export declare const getApiUrl: () => string;
|
|
12
12
|
export declare function getAccountId(): string | null;
|
|
13
|
+
export declare function getAuthToken(): string | null;
|
|
13
14
|
export declare function getCsrfToken(): string | null;
|
|
14
15
|
/**
|
|
15
16
|
* Thrown when the API rejects the session (401). Typed so a caller can tell an
|
package/dist/api/core.js
CHANGED
|
@@ -2,9 +2,7 @@ const IS_BROWSER = typeof window !== "undefined";
|
|
|
2
2
|
import posthog from "posthog-js";
|
|
3
3
|
import { notifyWrite } from "./cache-bus";
|
|
4
4
|
import { announceFeatureLocked, featureNotInPlan } from "./feature-locked";
|
|
5
|
-
import { announcePlanRefusal, planRefusal } from "./plan-refusal";
|
|
6
5
|
import { isUnservedRoute, unservedMessage } from "./unserved-routes";
|
|
7
|
-
import { refreshSession, wantsRefresh } from "./session-refresh";
|
|
8
6
|
import {
|
|
9
7
|
isSessionExpiryResponse,
|
|
10
8
|
merchantIdentity,
|
|
@@ -29,6 +27,10 @@ function getAccountId() {
|
|
|
29
27
|
}
|
|
30
28
|
return null;
|
|
31
29
|
}
|
|
30
|
+
function getAuthToken() {
|
|
31
|
+
if (typeof window === "undefined") return null;
|
|
32
|
+
return localStorage.getItem("pb_token");
|
|
33
|
+
}
|
|
32
34
|
function getCsrfToken() {
|
|
33
35
|
if (typeof window === "undefined" || typeof document === "undefined")
|
|
34
36
|
return null;
|
|
@@ -88,7 +90,7 @@ async function apiFetch(endpoint, options = {}) {
|
|
|
88
90
|
if (tenantSlug) {
|
|
89
91
|
headers["X-Tenant-Slug"] = tenantSlug;
|
|
90
92
|
}
|
|
91
|
-
const merchant = merchantIdentity(endpoint, getAccountId(),
|
|
93
|
+
const merchant = merchantIdentity(endpoint, getAccountId(), getAuthToken());
|
|
92
94
|
const effectiveAccountId = accountId || merchant.accountId;
|
|
93
95
|
if (effectiveAccountId) {
|
|
94
96
|
headers["x-account-id"] = effectiveAccountId;
|
|
@@ -128,7 +130,7 @@ async function apiFetch(endpoint, options = {}) {
|
|
|
128
130
|
}
|
|
129
131
|
return send(url, fetchOptions, headers, isWrite && !onlyReads(endpoint));
|
|
130
132
|
}
|
|
131
|
-
async function send(url, fetchOptions, headers, isWrite
|
|
133
|
+
async function send(url, fetchOptions, headers, isWrite) {
|
|
132
134
|
const res = await fetch(url, {
|
|
133
135
|
...fetchOptions,
|
|
134
136
|
headers,
|
|
@@ -136,27 +138,12 @@ async function send(url, fetchOptions, headers, isWrite, retried = false) {
|
|
|
136
138
|
// Include cookies for CSRF
|
|
137
139
|
});
|
|
138
140
|
if (!res.ok) {
|
|
139
|
-
if (res.status === 401 && !retried && wantsRefresh(url)) {
|
|
140
|
-
const body = await res.clone().json().catch(() => null);
|
|
141
|
-
if (wantsRefresh(url, body) && await refreshSession(getApiUrl())) {
|
|
142
|
-
return send(url, fetchOptions, headers, isWrite, true);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
141
|
const lockedBody = res.status === 402 ? await res.clone().json().catch(() => null) : null;
|
|
146
142
|
const locked = featureNotInPlan(lockedBody);
|
|
147
143
|
if (locked) {
|
|
148
144
|
announceFeatureLocked(locked);
|
|
149
145
|
throw new ApiRequestError(lockedBody.message ?? "Not in your plan", 402, lockedBody);
|
|
150
146
|
}
|
|
151
|
-
const refusal = planRefusal(lockedBody);
|
|
152
|
-
if (refusal) {
|
|
153
|
-
announcePlanRefusal(refusal);
|
|
154
|
-
throw new ApiRequestError(
|
|
155
|
-
refusal.code === "LIMIT_REACHED" ? "You have reached the limit on your plan" : "This is not available on your plan",
|
|
156
|
-
402,
|
|
157
|
-
lockedBody
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
147
|
if (res.status === 402 && IS_BROWSER) {
|
|
161
148
|
window.location.href = "/dashboard/settings/billing";
|
|
162
149
|
throw new Error("Payment required");
|
|
@@ -216,6 +203,7 @@ export {
|
|
|
216
203
|
bouncedRecently,
|
|
217
204
|
getAccountId,
|
|
218
205
|
getApiUrl,
|
|
206
|
+
getAuthToken,
|
|
219
207
|
getCsrfToken,
|
|
220
208
|
isSessionExpiryResponse2 as isSessionExpiryResponse,
|
|
221
209
|
recordBounce
|