@teincfood/core 0.1.1 → 0.1.3
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/adapters/auth.js +2 -6
- package/dist/adapters/business.js +2 -6
- package/dist/adapters/connectivity.js +6 -14
- package/dist/adapters/devices.js +2 -6
- package/dist/adapters/http.js +4 -10
- package/dist/adapters/image-cache.d.ts +9 -3
- package/dist/adapters/image-cache.js +12 -8
- package/dist/adapters/kv-types.js +1 -2
- package/dist/adapters/kv.js +11 -24
- package/dist/adapters/local-node.js +3 -7
- package/dist/adapters/pending-orders.js +1 -4
- package/dist/adapters/query-keys.js +4 -7
- package/dist/adapters/services.js +9 -14
- package/dist/adapters/sqlite.js +3 -8
- package/dist/db/connection.js +15 -20
- package/dist/db/migrations.js +1 -4
- package/dist/db/operations.js +19 -36
- package/dist/index.d.ts +3 -0
- package/dist/index.js +43 -93
- package/dist/reference/api.js +7 -12
- package/dist/reference/events.js +25 -33
- package/dist/reference/operations.js +6 -14
- package/dist/reference/service.js +36 -39
- package/dist/reference/types.js +1 -2
- package/dist/repositories/order.repository.js +15 -18
- package/dist/repositories/pos.repository.js +6 -9
- package/dist/sync/command-builder.js +8 -12
- package/dist/sync/command-queue.service.js +35 -38
- package/dist/sync/engine.js +38 -42
- package/dist/sync/order-number.js +6 -11
- package/dist/sync/order-number.test.d.ts +1 -0
- package/dist/sync/order-number.test.js +30 -0
- package/dist/sync/order-snapshots.service.js +19 -22
- package/dist/sync/pending-orders.service.js +27 -33
- package/dist/sync/transport-local-node.js +16 -20
- package/dist/sync/transport-rest.js +18 -22
- package/dist/sync/types.js +1 -2
- package/dist/sync/use-sync-reconciliation.js +37 -40
- package/dist/types/api/auth.api.js +1 -2
- package/dist/types/api/business.api.js +1 -2
- package/dist/types/api/conversation.api.js +1 -2
- package/dist/types/api/error.api.js +1 -2
- package/dist/types/api/fleet.api.js +1 -2
- package/dist/types/api/index.js +11 -27
- package/dist/types/api/kiosk.api.js +1 -2
- package/dist/types/api/menu.api.js +1 -2
- package/dist/types/api/pagination.js +1 -2
- package/dist/types/api/rider.api.js +1 -2
- package/dist/types/api/seller.api.js +1 -2
- package/dist/types/api/wallet.api.js +1 -2
- package/dist/types/pos.types.js +1 -2
- package/dist/utils/constants.js +24 -27
- package/dist/utils/copyToClipBoard.js +2 -7
- package/dist/utils/currency.js +8 -16
- package/dist/utils/currency.test.d.ts +1 -0
- package/dist/utils/currency.test.js +17 -0
- package/dist/utils/error.js +4 -11
- package/dist/utils/formatCurrency.js +1 -4
- package/dist/utils/formatDate.js +2 -6
- package/dist/utils/phone.js +6 -13
- package/dist/utils/phone.test.d.ts +1 -0
- package/dist/utils/phone.test.js +19 -0
- package/dist/utils/responsive.js +1 -4
- package/dist/utils/storage.js +14 -28
- package/dist/utils/sync-logger.js +1 -4
- package/dist/utils/uuid.js +4 -10
- package/dist/utils/uuid.test.d.ts +1 -0
- package/dist/utils/uuid.test.js +13 -0
- package/dist/utils/validation.js +11 -24
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +23 -0
- package/package.json +16 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* REST transport implementation.
|
|
4
3
|
*
|
|
@@ -6,12 +5,10 @@
|
|
|
6
5
|
* resulting domain events. This transport is used when the cloud API is
|
|
7
6
|
* reachable.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const uuid_1 = require("../utils/uuid");
|
|
14
|
-
class RestTransport {
|
|
8
|
+
import { posService, sellerService, kioskService } from "../adapters/services";
|
|
9
|
+
import { syncLogger } from "../utils/sync-logger";
|
|
10
|
+
import { generateEventId } from "../utils/uuid";
|
|
11
|
+
export class RestTransport {
|
|
15
12
|
constructor() {
|
|
16
13
|
this.name = "rest";
|
|
17
14
|
}
|
|
@@ -25,20 +22,20 @@ class RestTransport {
|
|
|
25
22
|
return () => undefined;
|
|
26
23
|
}
|
|
27
24
|
async execute(command) {
|
|
28
|
-
|
|
25
|
+
syncLogger.transport(this, "executing", {
|
|
29
26
|
command_type: command.command_type,
|
|
30
27
|
command_id: command.command_id,
|
|
31
28
|
});
|
|
32
29
|
try {
|
|
33
30
|
const event = await this.dispatchCommand(command);
|
|
34
|
-
|
|
31
|
+
syncLogger.transport(this, "completed", {
|
|
35
32
|
command_type: command.command_type,
|
|
36
33
|
event_type: event?.event_type,
|
|
37
34
|
});
|
|
38
35
|
return event ? [event] : [];
|
|
39
36
|
}
|
|
40
37
|
catch (error) {
|
|
41
|
-
|
|
38
|
+
syncLogger.transport(this, "error", {
|
|
42
39
|
command_type: command.command_type,
|
|
43
40
|
command_id: command.command_id,
|
|
44
41
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -56,39 +53,39 @@ class RestTransport {
|
|
|
56
53
|
command_id: command.command_id,
|
|
57
54
|
local_order_number: payload.order_number,
|
|
58
55
|
};
|
|
59
|
-
const response = await
|
|
56
|
+
const response = await posService.createPOSOrder(business_id, posPayload);
|
|
60
57
|
return this.wrapResponse("order.created", response.data, entity_id, timestamp, command);
|
|
61
58
|
}
|
|
62
59
|
case "order:accept": {
|
|
63
|
-
const response = await
|
|
60
|
+
const response = await sellerService.acceptOrder(entity_id);
|
|
64
61
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
65
62
|
}
|
|
66
63
|
case "order:reject": {
|
|
67
|
-
const response = await
|
|
64
|
+
const response = await sellerService.rejectOrder(entity_id);
|
|
68
65
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
69
66
|
}
|
|
70
67
|
case "order:start_preparing": {
|
|
71
|
-
const response = await
|
|
68
|
+
const response = await sellerService.startPreparingOrder(entity_id);
|
|
72
69
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
73
70
|
}
|
|
74
71
|
case "order:mark_ready": {
|
|
75
|
-
const response = await
|
|
72
|
+
const response = await sellerService.markOrderReady(entity_id);
|
|
76
73
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
77
74
|
}
|
|
78
75
|
case "order:complete_pickup": {
|
|
79
|
-
const response = await
|
|
76
|
+
const response = await sellerService.completeOrderPickup(entity_id);
|
|
80
77
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
81
78
|
}
|
|
82
79
|
case "order:cancel": {
|
|
83
|
-
const response = await
|
|
80
|
+
const response = await sellerService.cancelOrder(entity_id);
|
|
84
81
|
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
85
82
|
}
|
|
86
83
|
case "delivery:assign": {
|
|
87
|
-
const response = await
|
|
84
|
+
const response = await kioskService.assignKioskOrderToRider(entity_id, payload.rider_id);
|
|
88
85
|
return this.wrapResponse("delivery.assigned", response, entity_id, timestamp, command);
|
|
89
86
|
}
|
|
90
87
|
case "delivery:confirm_handover": {
|
|
91
|
-
const response = await
|
|
88
|
+
const response = await sellerService.confirmHandover(entity_id);
|
|
92
89
|
return this.wrapResponse("delivery.status_changed", response, entity_id, timestamp, command);
|
|
93
90
|
}
|
|
94
91
|
default:
|
|
@@ -97,7 +94,7 @@ class RestTransport {
|
|
|
97
94
|
}
|
|
98
95
|
wrapResponse(eventType, data, entityId, timestamp, command) {
|
|
99
96
|
return {
|
|
100
|
-
event_id:
|
|
97
|
+
event_id: generateEventId(),
|
|
101
98
|
event_type: eventType,
|
|
102
99
|
event_version: "1.0",
|
|
103
100
|
entity_id: entityId ?? command.entity_id ?? data.id ?? "unknown",
|
|
@@ -108,5 +105,4 @@ class RestTransport {
|
|
|
108
105
|
};
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
|
-
|
|
112
|
-
exports.restTransport = new RestTransport();
|
|
108
|
+
export const restTransport = new RestTransport();
|
package/dist/sync/types.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Core types for the offline-first, event-driven Sync Engine.
|
|
4
3
|
*
|
|
@@ -10,4 +9,4 @@
|
|
|
10
9
|
*
|
|
11
10
|
* All timestamps are ISO strings. Monetary amounts remain in minor units.
|
|
12
11
|
*/
|
|
13
|
-
|
|
12
|
+
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Sync reconciliation bridge.
|
|
4
3
|
*
|
|
@@ -8,18 +7,16 @@
|
|
|
8
7
|
* - Marks reconciled optimistic events as synced to cloud.
|
|
9
8
|
* - Invalidates TanStack Query caches for affected entities.
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const query_keys_1 = require("../adapters/query-keys");
|
|
22
|
-
const events_1 = require("../reference/events");
|
|
10
|
+
import { useEffect } from "react";
|
|
11
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
12
|
+
import { syncEngine } from "./engine";
|
|
13
|
+
import { applyPendingOrderEvent, applyOrderStateChangeEvent, registerPendingOrdersInvalidator, } from "./pending-orders.service";
|
|
14
|
+
import { localNodeService } from "../adapters/local-node";
|
|
15
|
+
import { getSyncDatabase } from "../db/connection";
|
|
16
|
+
import { markEventsSynced } from "../db/operations";
|
|
17
|
+
import { syncLogger } from "../utils/sync-logger";
|
|
18
|
+
import { POS_KEYS, SELLER_KEYS, MENU_KEYS, REFERENCE_KEYS } from "../adapters/query-keys";
|
|
19
|
+
import { applyReferenceChangeEvent, registerReferenceInvalidator, registerReferenceEventEngine, REFERENCE_CHANGED_EVENT_TYPE, } from "../reference/events";
|
|
23
20
|
function isLocalOrderEvent(event) {
|
|
24
21
|
const payload = event.payload;
|
|
25
22
|
if (!payload)
|
|
@@ -27,73 +24,73 @@ function isLocalOrderEvent(event) {
|
|
|
27
24
|
const localOrderNumber = payload.local_order_number;
|
|
28
25
|
return typeof localOrderNumber === "string" && localOrderNumber.startsWith("TFL-");
|
|
29
26
|
}
|
|
30
|
-
function useSyncReconciliation() {
|
|
31
|
-
const queryClient =
|
|
32
|
-
|
|
27
|
+
export function useSyncReconciliation() {
|
|
28
|
+
const queryClient = useQueryClient();
|
|
29
|
+
useEffect(() => {
|
|
33
30
|
// Reference changes re-sync offline data and refresh caches on this device
|
|
34
31
|
// and (via the LAN relay) on every connected terminal.
|
|
35
|
-
const unregisterReferenceInvalidator =
|
|
36
|
-
queryClient.invalidateQueries({ queryKey:
|
|
37
|
-
queryClient.invalidateQueries({ queryKey:
|
|
38
|
-
queryClient.invalidateQueries({ queryKey:
|
|
39
|
-
queryClient.invalidateQueries({ queryKey:
|
|
32
|
+
const unregisterReferenceInvalidator = registerReferenceInvalidator(() => {
|
|
33
|
+
queryClient.invalidateQueries({ queryKey: MENU_KEYS.all });
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: REFERENCE_KEYS.all });
|
|
35
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
36
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
40
37
|
});
|
|
41
38
|
// Pending-order reconciliation invalidates POS + seller order lists so the
|
|
42
39
|
// server-confirmed order replaces the optimistic one. Invalidate from the
|
|
43
40
|
// broadest key: POS_KEYS.orders() without a businessId would produce
|
|
44
41
|
// ['pos','orders',undefined] which does not prefix-match business-scoped
|
|
45
42
|
// keys.
|
|
46
|
-
const unregisterInvalidator =
|
|
47
|
-
queryClient.invalidateQueries({ queryKey:
|
|
48
|
-
queryClient.invalidateQueries({ queryKey:
|
|
43
|
+
const unregisterInvalidator = registerPendingOrdersInvalidator(() => {
|
|
44
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
45
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
49
46
|
});
|
|
50
47
|
// Reference-change events are published through the Sync Engine but are
|
|
51
48
|
// not backed by a command, so the engine has no transport dependency on
|
|
52
49
|
// the reference module. Register the singleton once.
|
|
53
|
-
|
|
54
|
-
const unsubscribe =
|
|
55
|
-
|
|
50
|
+
registerReferenceEventEngine(syncEngine);
|
|
51
|
+
const unsubscribe = syncEngine.subscribe(async (event) => {
|
|
52
|
+
syncLogger.event(event, "received");
|
|
56
53
|
// The node relays engine events (e.g. order.created when a queued
|
|
57
54
|
// command is replayed) to LAN clients so they reconcile their pending
|
|
58
55
|
// orders and refresh caches.
|
|
59
|
-
if (
|
|
60
|
-
|
|
56
|
+
if (localNodeService.isLocalNode()) {
|
|
57
|
+
localNodeService.broadcastToClients(event);
|
|
61
58
|
}
|
|
62
59
|
// Reference data changed on this device but we are a LAN client (not the
|
|
63
60
|
// node): forward the event to the node so it rebroadcasts it to every
|
|
64
61
|
// other terminal (the node also applies it locally via its onRelayEvent
|
|
65
62
|
// hook). The node does not echo it back to this device.
|
|
66
|
-
if (event.event_type ===
|
|
67
|
-
!
|
|
68
|
-
|
|
63
|
+
if (event.event_type === REFERENCE_CHANGED_EVENT_TYPE &&
|
|
64
|
+
!localNodeService.isLocalNode()) {
|
|
65
|
+
localNodeService.getClient()?.publishEvent?.(event);
|
|
69
66
|
}
|
|
70
67
|
// Reference data changed (locally or echoed from the cloud): re-sync the
|
|
71
68
|
// affected keys and refresh caches on this device.
|
|
72
|
-
await
|
|
69
|
+
await applyReferenceChangeEvent(event, "local");
|
|
73
70
|
// Order/delivery state changed: refresh order caches so this terminal
|
|
74
71
|
// shows the same order states as the device that made the change.
|
|
75
|
-
await
|
|
72
|
+
await applyOrderStateChangeEvent(event, "local");
|
|
76
73
|
// Route both queued (optimistic) and reconciled (created) order events
|
|
77
74
|
// into the pending-orders store.
|
|
78
75
|
if (event.event_type === "order.create_queued" ||
|
|
79
76
|
event.event_type === "order.created") {
|
|
80
|
-
await
|
|
77
|
+
await applyPendingOrderEvent(event, "local");
|
|
81
78
|
}
|
|
82
79
|
if (event.event_type === "order.created" && isLocalOrderEvent(event)) {
|
|
83
80
|
try {
|
|
84
|
-
const db =
|
|
85
|
-
await
|
|
81
|
+
const db = getSyncDatabase();
|
|
82
|
+
await markEventsSynced(db, [event.event_id]);
|
|
86
83
|
}
|
|
87
84
|
catch (error) {
|
|
88
|
-
|
|
85
|
+
syncLogger.error("Reconciliation", "Failed to mark event synced", {
|
|
89
86
|
error: String(error),
|
|
90
87
|
});
|
|
91
88
|
}
|
|
92
89
|
const payload = event.payload;
|
|
93
90
|
const businessId = payload.business_id;
|
|
94
91
|
if (businessId) {
|
|
95
|
-
queryClient.invalidateQueries({ queryKey:
|
|
96
|
-
queryClient.invalidateQueries({ queryKey:
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.orders(businessId) });
|
|
93
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
97
94
|
}
|
|
98
95
|
}
|
|
99
96
|
});
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/dist/types/api/index.js
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./pagination"), exports);
|
|
18
|
-
__exportStar(require("./auth.api"), exports);
|
|
19
|
-
__exportStar(require("./rider.api"), exports);
|
|
20
|
-
__exportStar(require("./seller.api"), exports);
|
|
21
|
-
__exportStar(require("./fleet.api"), exports);
|
|
22
|
-
__exportStar(require("./kiosk.api"), exports);
|
|
23
|
-
__exportStar(require("./menu.api"), exports);
|
|
24
|
-
__exportStar(require("./business.api"), exports);
|
|
25
|
-
__exportStar(require("./wallet.api"), exports);
|
|
26
|
-
__exportStar(require("./conversation.api"), exports);
|
|
27
|
-
__exportStar(require("./error.api"), exports);
|
|
1
|
+
export * from './pagination';
|
|
2
|
+
export * from './auth.api';
|
|
3
|
+
export * from './rider.api';
|
|
4
|
+
export * from './seller.api';
|
|
5
|
+
export * from './fleet.api';
|
|
6
|
+
export * from './kiosk.api';
|
|
7
|
+
export * from './menu.api';
|
|
8
|
+
export * from './business.api';
|
|
9
|
+
export * from './wallet.api';
|
|
10
|
+
export * from './conversation.api';
|
|
11
|
+
export * from './error.api';
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/dist/types/pos.types.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Application-wide constants — plain config, no expo dependencies.
|
|
4
3
|
* Values can be overridden via createTeincCore config (future).
|
|
5
4
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.DEFAULTS = exports.appVersion = exports.CLOUD_COMMAND_TIMEOUT_MS = exports.DEFAULT_PAGE_SIZE = exports.OTP_RESEND_DELAY_SECONDS = exports.OTP_LENGTH = exports.CHAT_CONFIG = exports.CHAT_STORAGE_KEYS = exports.STORAGE_KEYS = exports.APP_VERSION = exports.APP_NAME = exports.WS_BASE_URL = exports.API_BASE_URL = void 0;
|
|
8
5
|
/** Base URL for the API — will be swapped per environment */
|
|
9
|
-
|
|
6
|
+
export const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL ??
|
|
10
7
|
process.env.API_URL ??
|
|
11
8
|
"http://localhost:4000/api/v1";
|
|
12
9
|
/** WebSocket URL for Phoenix Channels */
|
|
13
|
-
|
|
10
|
+
export const WS_BASE_URL = process.env.EXPO_PUBLIC_WS_URL ??
|
|
14
11
|
process.env.WS_URL ??
|
|
15
12
|
"ws://localhost:4000";
|
|
16
13
|
/** App metadata */
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export const APP_NAME = "Teinc Food Business";
|
|
15
|
+
export const APP_VERSION = "1.0.0";
|
|
19
16
|
/** Async storage keys */
|
|
20
|
-
|
|
17
|
+
export const STORAGE_KEYS = {
|
|
21
18
|
AUTH_TOKEN: "auth_token",
|
|
22
19
|
REFRESH_TOKEN: "refresh_token",
|
|
23
20
|
USER_PROFILE: "user_profile",
|
|
@@ -29,12 +26,12 @@ exports.STORAGE_KEYS = {
|
|
|
29
26
|
PRINTER_SETTINGS: "printer_settings",
|
|
30
27
|
};
|
|
31
28
|
/** Chat storage keys */
|
|
32
|
-
|
|
29
|
+
export const CHAT_STORAGE_KEYS = {
|
|
33
30
|
MESSAGES: "chat_messages",
|
|
34
31
|
OUTBOX: "chat_outbox",
|
|
35
32
|
};
|
|
36
33
|
/** Chat configuration */
|
|
37
|
-
|
|
34
|
+
export const CHAT_CONFIG = {
|
|
38
35
|
HEARTBEAT_INTERVAL_MS: 15000,
|
|
39
36
|
HEARTBEAT_TIMEOUT_MS: 10000,
|
|
40
37
|
RECONNECT_BASE_DELAY_MS: 1000,
|
|
@@ -44,29 +41,29 @@ exports.CHAT_CONFIG = {
|
|
|
44
41
|
MESSAGE_PAGE_SIZE: 50,
|
|
45
42
|
};
|
|
46
43
|
/** OTP configuration */
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
export const OTP_LENGTH = 4;
|
|
45
|
+
export const OTP_RESEND_DELAY_SECONDS = 60;
|
|
49
46
|
/** Pagination defaults */
|
|
50
|
-
|
|
47
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
51
48
|
/**
|
|
52
49
|
* Max time the Sync Engine waits for a cloud command before treating the cloud
|
|
53
50
|
* as unreachable and falling back to the Local Node or the offline outbox.
|
|
54
51
|
* Keeps the terminal responsive (fast) even when the internet is slow.
|
|
55
52
|
*/
|
|
56
|
-
|
|
53
|
+
export const CLOUD_COMMAND_TIMEOUT_MS = 2500;
|
|
57
54
|
// Expo version removed; expose static version for now
|
|
58
|
-
|
|
55
|
+
export const appVersion = APP_VERSION;
|
|
59
56
|
/** Default config bundle for createTeincCore overrides */
|
|
60
|
-
|
|
61
|
-
API_BASE_URL
|
|
62
|
-
WS_BASE_URL
|
|
63
|
-
APP_NAME
|
|
64
|
-
APP_VERSION
|
|
65
|
-
STORAGE_KEYS
|
|
66
|
-
CHAT_STORAGE_KEYS
|
|
67
|
-
CHAT_CONFIG
|
|
68
|
-
OTP_LENGTH
|
|
69
|
-
OTP_RESEND_DELAY_SECONDS
|
|
70
|
-
DEFAULT_PAGE_SIZE
|
|
71
|
-
CLOUD_COMMAND_TIMEOUT_MS
|
|
57
|
+
export const DEFAULTS = {
|
|
58
|
+
API_BASE_URL,
|
|
59
|
+
WS_BASE_URL,
|
|
60
|
+
APP_NAME,
|
|
61
|
+
APP_VERSION,
|
|
62
|
+
STORAGE_KEYS,
|
|
63
|
+
CHAT_STORAGE_KEYS,
|
|
64
|
+
CHAT_CONFIG,
|
|
65
|
+
OTP_LENGTH,
|
|
66
|
+
OTP_RESEND_DELAY_SECONDS,
|
|
67
|
+
DEFAULT_PAGE_SIZE,
|
|
68
|
+
CLOUD_COMMAND_TIMEOUT_MS,
|
|
72
69
|
};
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Clipboard helper — no-op stub in core. Host apps inject real clipboard.
|
|
4
3
|
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.copyToClipboard = void 0;
|
|
7
|
-
exports.setClipboardImpl = setClipboardImpl;
|
|
8
4
|
let clipboardImpl = null;
|
|
9
|
-
function setClipboardImpl(fn) {
|
|
5
|
+
export function setClipboardImpl(fn) {
|
|
10
6
|
clipboardImpl = fn;
|
|
11
7
|
}
|
|
12
|
-
const copyToClipboard = async (text) => {
|
|
8
|
+
export const copyToClipboard = async (text) => {
|
|
13
9
|
if (clipboardImpl) {
|
|
14
10
|
await clipboardImpl(text);
|
|
15
11
|
return;
|
|
@@ -20,4 +16,3 @@ const copyToClipboard = async (text) => {
|
|
|
20
16
|
}
|
|
21
17
|
// fallback: no-op
|
|
22
18
|
};
|
|
23
|
-
exports.copyToClipboard = copyToClipboard;
|
package/dist/utils/currency.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Currency Utility — no RN/store dependencies.
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
exports.DEFAULT_CURRENCY = void 0;
|
|
7
|
-
exports.getCurrencySymbol = getCurrencySymbol;
|
|
8
|
-
exports.formatPrice = formatPrice;
|
|
9
|
-
exports.formatPriceFromMinor = formatPriceFromMinor;
|
|
10
|
-
exports.useCurrency = useCurrency;
|
|
11
|
-
exports.useCurrencySymbol = useCurrencySymbol;
|
|
12
|
-
exports.DEFAULT_CURRENCY = "GHS";
|
|
4
|
+
export const DEFAULT_CURRENCY = "GHS";
|
|
13
5
|
const CURRENCY_SYMBOLS = {
|
|
14
6
|
GHS: "GH₵",
|
|
15
7
|
USD: "$",
|
|
@@ -18,20 +10,20 @@ const CURRENCY_SYMBOLS = {
|
|
|
18
10
|
EUR: "€",
|
|
19
11
|
GBP: "£",
|
|
20
12
|
};
|
|
21
|
-
function getCurrencySymbol(currency) {
|
|
13
|
+
export function getCurrencySymbol(currency) {
|
|
22
14
|
return CURRENCY_SYMBOLS[currency] || currency;
|
|
23
15
|
}
|
|
24
|
-
function formatPrice(amount, currency =
|
|
16
|
+
export function formatPrice(amount, currency = DEFAULT_CURRENCY) {
|
|
25
17
|
return `${getCurrencySymbol(currency)}${amount.toFixed(2)}`;
|
|
26
18
|
}
|
|
27
|
-
function formatPriceFromMinor(amountMinor, currency =
|
|
19
|
+
export function formatPriceFromMinor(amountMinor, currency = DEFAULT_CURRENCY) {
|
|
28
20
|
const amount = amountMinor / 100;
|
|
29
21
|
return formatPrice(amount, currency);
|
|
30
22
|
}
|
|
31
23
|
// Compatibility hooks — return default when no location adapter injected
|
|
32
|
-
function useCurrency() {
|
|
33
|
-
return
|
|
24
|
+
export function useCurrency() {
|
|
25
|
+
return DEFAULT_CURRENCY;
|
|
34
26
|
}
|
|
35
|
-
function useCurrencySymbol() {
|
|
36
|
-
return getCurrencySymbol(
|
|
27
|
+
export function useCurrencySymbol() {
|
|
28
|
+
return getCurrencySymbol(DEFAULT_CURRENCY);
|
|
37
29
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { formatPrice, formatPriceFromMinor, getCurrencySymbol, DEFAULT_CURRENCY } from "./currency";
|
|
3
|
+
describe("currency", () => {
|
|
4
|
+
it("returns symbol for known currencies", () => {
|
|
5
|
+
expect(getCurrencySymbol("GHS")).toBe("GH₵");
|
|
6
|
+
expect(getCurrencySymbol("USD")).toBe("$");
|
|
7
|
+
expect(getCurrencySymbol("UNKNOWN")).toBe("UNKNOWN");
|
|
8
|
+
});
|
|
9
|
+
it("formats price with default currency", () => {
|
|
10
|
+
expect(formatPrice(10.5)).toBe(`${getCurrencySymbol(DEFAULT_CURRENCY)}10.50`);
|
|
11
|
+
});
|
|
12
|
+
it("formats price from minor units", () => {
|
|
13
|
+
expect(formatPriceFromMinor(1050, "GHS")).toBe("GH₵10.50");
|
|
14
|
+
expect(formatPriceFromMinor(100, "USD")).toBe("$1.00");
|
|
15
|
+
expect(formatPriceFromMinor(0)).toBe(`${getCurrencySymbol(DEFAULT_CURRENCY)}0.00`);
|
|
16
|
+
});
|
|
17
|
+
});
|
package/dist/utils/error.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApiError = void 0;
|
|
4
|
-
exports.extractFromResponseBody = extractFromResponseBody;
|
|
5
|
-
exports.extractApiErrorMessage = extractApiErrorMessage;
|
|
6
|
-
exports.extractApiErrorCode = extractApiErrorCode;
|
|
7
|
-
function extractFromResponseBody(data) {
|
|
1
|
+
export function extractFromResponseBody(data) {
|
|
8
2
|
const errorField = data.error;
|
|
9
3
|
if (errorField && typeof errorField === "object") {
|
|
10
4
|
const msg = errorField.message;
|
|
@@ -24,7 +18,7 @@ function extractFromResponseBody(data) {
|
|
|
24
18
|
}
|
|
25
19
|
return null;
|
|
26
20
|
}
|
|
27
|
-
function extractApiErrorMessage(error) {
|
|
21
|
+
export function extractApiErrorMessage(error) {
|
|
28
22
|
if (typeof error === "string")
|
|
29
23
|
return error;
|
|
30
24
|
if (!error || typeof error !== "object")
|
|
@@ -41,7 +35,7 @@ function extractApiErrorMessage(error) {
|
|
|
41
35
|
}
|
|
42
36
|
return "An unexpected error occurred";
|
|
43
37
|
}
|
|
44
|
-
function extractApiErrorCode(error) {
|
|
38
|
+
export function extractApiErrorCode(error) {
|
|
45
39
|
if (!error || typeof error !== "object")
|
|
46
40
|
return undefined;
|
|
47
41
|
const err = error;
|
|
@@ -69,5 +63,4 @@ function extractApiErrorCode(error) {
|
|
|
69
63
|
return undefined;
|
|
70
64
|
}
|
|
71
65
|
// Re-export ApiError from http adapter for compatibility
|
|
72
|
-
|
|
73
|
-
Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return http_1.ApiError; } });
|
|
66
|
+
export { ApiError } from "../adapters/http";
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Format a number as a currency string.
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
exports.formatCurrency = formatCurrency;
|
|
7
|
-
function formatCurrency(amount, currency = "GHS") {
|
|
4
|
+
export function formatCurrency(amount, currency = "GHS") {
|
|
8
5
|
const amountMinor = Math.round(amount * 100);
|
|
9
6
|
// Simple region formatting — host apps can inject getRegionConfig via adapter if needed
|
|
10
7
|
const symbolMap = {
|
package/dist/utils/formatDate.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatDate = formatDate;
|
|
4
|
-
exports.formatRelativeTime = formatRelativeTime;
|
|
5
1
|
/**
|
|
6
2
|
* Format an ISO date string into a human-readable format.
|
|
7
3
|
*/
|
|
8
|
-
function formatDate(isoString, style = 'short') {
|
|
4
|
+
export function formatDate(isoString, style = 'short') {
|
|
9
5
|
const date = new Date(isoString);
|
|
10
6
|
if (style === 'long') {
|
|
11
7
|
return date.toLocaleDateString('en-US', {
|
|
@@ -23,7 +19,7 @@ function formatDate(isoString, style = 'short') {
|
|
|
23
19
|
/**
|
|
24
20
|
* Format a date as a relative time string (e.g., "20 min ago").
|
|
25
21
|
*/
|
|
26
|
-
function formatRelativeTime(isoString) {
|
|
22
|
+
export function formatRelativeTime(isoString) {
|
|
27
23
|
const now = Date.now();
|
|
28
24
|
const then = new Date(isoString).getTime();
|
|
29
25
|
const diffMs = now - then;
|