@teincfood/core 0.7.9 → 0.8.0

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.
Files changed (191) hide show
  1. package/README.md +26 -21
  2. package/dist/adapters/auth.d.ts +8 -0
  3. package/dist/adapters/auth.js +36 -0
  4. package/dist/adapters/devices.d.ts +3 -1
  5. package/dist/adapters/devices.js +187 -3
  6. package/dist/adapters/filesystem.d.ts +4 -0
  7. package/dist/adapters/kv.d.ts +10 -0
  8. package/dist/adapters/kv.js +23 -7
  9. package/dist/adapters/local-node.d.ts +4 -6
  10. package/dist/adapters/local-node.js +9 -32
  11. package/dist/adapters/notify.d.ts +12 -0
  12. package/dist/adapters/notify.js +33 -0
  13. package/dist/adapters/query-keys.d.ts +4 -0
  14. package/dist/adapters/query-keys.js +4 -0
  15. package/dist/adapters/services.d.ts +96 -15
  16. package/dist/adapters/services.js +241 -4
  17. package/dist/auth/session.d.ts +66 -0
  18. package/dist/auth/session.js +172 -0
  19. package/dist/catalog/hooks.d.ts +10 -0
  20. package/dist/catalog/hooks.js +82 -11
  21. package/dist/db/migrations.js +274 -0
  22. package/dist/db/operations.d.ts +53 -2
  23. package/dist/db/operations.js +177 -8
  24. package/dist/domain/business/index.d.ts +5 -0
  25. package/dist/domain/business/index.js +5 -0
  26. package/dist/domain/orders/lifecycle.d.ts +6 -0
  27. package/dist/domain/orders/lifecycle.js +6 -0
  28. package/dist/domain/orders/service.d.ts +24 -0
  29. package/dist/domain/orders/service.js +242 -0
  30. package/dist/domain/payments/index.d.ts +6 -0
  31. package/dist/domain/payments/index.js +5 -0
  32. package/dist/domain/reference/index.d.ts +8 -0
  33. package/dist/domain/reference/index.js +8 -0
  34. package/dist/e2e/harness.d.ts +1 -0
  35. package/dist/e2e/harness.js +61 -0
  36. package/dist/hooks/analytics.hooks.d.ts +25 -0
  37. package/dist/hooks/analytics.hooks.js +214 -0
  38. package/dist/hooks/business-hours.hooks.d.ts +8 -0
  39. package/dist/hooks/business-hours.hooks.js +45 -0
  40. package/dist/hooks/dashboard.hooks.d.ts +30 -0
  41. package/dist/hooks/dashboard.hooks.js +132 -0
  42. package/dist/hooks/delivery.hooks.d.ts +225 -0
  43. package/dist/hooks/delivery.hooks.js +80 -0
  44. package/dist/hooks/fleet.hooks.d.ts +37 -0
  45. package/dist/hooks/fleet.hooks.js +229 -0
  46. package/dist/hooks/image.hooks.d.ts +20 -0
  47. package/dist/hooks/image.hooks.js +62 -0
  48. package/dist/hooks/kiosk.hooks.d.ts +22 -5
  49. package/dist/hooks/kiosk.hooks.js +74 -5
  50. package/dist/hooks/members.hooks.d.ts +48 -0
  51. package/dist/hooks/members.hooks.js +302 -0
  52. package/dist/hooks/offline-orders.hooks.d.ts +9 -0
  53. package/dist/hooks/offline-orders.hooks.js +51 -0
  54. package/dist/hooks/orders.hooks.d.ts +18 -39
  55. package/dist/hooks/orders.hooks.js +124 -343
  56. package/dist/hooks/places.hooks.d.ts +21 -0
  57. package/dist/hooks/places.hooks.js +112 -0
  58. package/dist/hooks/realtime-stubs.d.ts +28 -0
  59. package/dist/hooks/realtime-stubs.js +85 -0
  60. package/dist/hooks/rider.hooks.d.ts +470 -0
  61. package/dist/hooks/rider.hooks.js +373 -0
  62. package/dist/hooks/use-infinite-list.d.ts +243 -0
  63. package/dist/hooks/use-infinite-list.js +58 -0
  64. package/dist/hooks/wallet.hooks.d.ts +219 -0
  65. package/dist/hooks/wallet.hooks.js +56 -0
  66. package/dist/image-cache/service.d.ts +8 -0
  67. package/dist/image-cache/service.js +15 -3
  68. package/dist/index.d.ts +56 -6
  69. package/dist/index.js +73 -6
  70. package/dist/local-node/adapters.d.ts +87 -0
  71. package/dist/local-node/adapters.js +4 -0
  72. package/dist/local-node/index.d.ts +5 -0
  73. package/dist/local-node/index.js +5 -0
  74. package/dist/local-node/security.d.ts +25 -0
  75. package/dist/local-node/security.js +149 -0
  76. package/dist/local-node/service.d.ts +83 -0
  77. package/dist/local-node/service.js +706 -0
  78. package/dist/local-node/store.d.ts +50 -0
  79. package/dist/local-node/store.js +284 -0
  80. package/dist/local-node/types.d.ts +92 -0
  81. package/dist/local-node/types.js +4 -0
  82. package/dist/printer/receipt.d.ts +4 -0
  83. package/dist/printer/receipt.js +75 -0
  84. package/dist/printer/service.js +1 -1
  85. package/dist/providers/query-client.d.ts +13 -0
  86. package/dist/providers/query-client.js +58 -0
  87. package/dist/realtime/phoenix-socket.js +30 -1
  88. package/dist/realtime/useEventsConnection.js +11 -2
  89. package/dist/reference/api.d.ts +2 -0
  90. package/dist/reference/api.js +2 -0
  91. package/dist/reference/capabilities.d.ts +12 -7
  92. package/dist/reference/capabilities.js +27 -13
  93. package/dist/reference/hooks.d.ts +34 -0
  94. package/dist/reference/hooks.js +124 -0
  95. package/dist/reference/menu-apply.d.ts +29 -0
  96. package/dist/reference/menu-apply.js +289 -0
  97. package/dist/reference/menu-record.d.ts +69 -0
  98. package/dist/reference/menu-record.js +163 -0
  99. package/dist/reference/operations.d.ts +3 -0
  100. package/dist/reference/operations.js +35 -1
  101. package/dist/reference/service.js +36 -9
  102. package/dist/reference/types.d.ts +1 -1
  103. package/dist/repositories/order.repository.d.ts +7 -0
  104. package/dist/repositories/order.repository.js +15 -2
  105. package/dist/repositories/pos.repository.d.ts +4 -0
  106. package/dist/repositories/pos.repository.js +4 -0
  107. package/dist/sync/backoff.d.ts +21 -0
  108. package/dist/sync/backoff.js +42 -0
  109. package/dist/sync/command-builder.js +10 -2
  110. package/dist/sync/command-queue.service.d.ts +12 -0
  111. package/dist/sync/command-queue.service.js +81 -16
  112. package/dist/sync/device-lease.service.d.ts +3 -3
  113. package/dist/sync/device-lease.service.js +80 -20
  114. package/dist/sync/engine.d.ts +3 -1
  115. package/dist/sync/engine.js +44 -4
  116. package/dist/sync/full-sync.d.ts +6 -0
  117. package/dist/sync/full-sync.js +72 -0
  118. package/dist/sync/health-probe.d.ts +24 -0
  119. package/dist/sync/health-probe.js +138 -0
  120. package/dist/sync/offline-capable.js +25 -0
  121. package/dist/sync/online-gates.d.ts +17 -0
  122. package/dist/sync/online-gates.js +55 -0
  123. package/dist/sync/order-number.d.ts +4 -4
  124. package/dist/sync/order-number.js +33 -13
  125. package/dist/sync/order-snapshots.service.js +18 -3
  126. package/dist/sync/pending-orders.service.d.ts +18 -0
  127. package/dist/sync/pending-orders.service.js +67 -1
  128. package/dist/sync/reconciliation/index.d.ts +5 -0
  129. package/dist/sync/reconciliation/index.js +5 -0
  130. package/dist/sync/snapshots/entity.d.ts +6 -0
  131. package/dist/sync/snapshots/entity.js +6 -0
  132. package/dist/sync/sync-jobs.service.d.ts +18 -0
  133. package/dist/sync/sync-jobs.service.js +539 -0
  134. package/dist/sync/transition-endpoints.d.ts +11 -0
  135. package/dist/sync/transition-endpoints.js +30 -0
  136. package/dist/sync/transport-local-node.js +3 -2
  137. package/dist/sync/transport-rest.d.ts +7 -0
  138. package/dist/sync/transport-rest.js +84 -26
  139. package/dist/sync/use-sync-reconciliation.js +15 -1
  140. package/dist/test-helpers/memory-driver.js +182 -4
  141. package/dist/test-helpers/seed-hours.d.ts +1 -0
  142. package/dist/test-helpers/seed-hours.js +23 -0
  143. package/dist/types/api/business.api.d.ts +42 -0
  144. package/dist/types/api/menu.api.d.ts +15 -0
  145. package/dist/types/api/seller.api.d.ts +20 -0
  146. package/dist/types/places.d.ts +19 -0
  147. package/dist/types/places.js +2 -0
  148. package/dist/types/rider.d.ts +67 -0
  149. package/dist/types/rider.js +2 -0
  150. package/dist/utils/attempt-stats.d.ts +23 -0
  151. package/dist/utils/attempt-stats.js +61 -0
  152. package/dist/utils/business-hours.d.ts +37 -0
  153. package/dist/utils/business-hours.js +96 -0
  154. package/dist/utils/debug-log.d.ts +23 -0
  155. package/dist/utils/debug-log.js +153 -0
  156. package/dist/utils/order-filter.d.ts +1 -1
  157. package/dist/utils/order-filter.js +1 -1
  158. package/dist/utils/order-transitions.d.ts +30 -0
  159. package/dist/utils/order-transitions.js +185 -0
  160. package/dist/utils/sync-logger.js +5 -4
  161. package/dist/validation/offline.d.ts +46 -0
  162. package/dist/validation/offline.js +79 -0
  163. package/package.json +6 -1
  164. package/dist/pricing/engine.test.d.ts +0 -1
  165. package/dist/pricing/engine.test.js +0 -142
  166. package/dist/reference/lww.test.d.ts +0 -1
  167. package/dist/reference/lww.test.js +0 -23
  168. package/dist/reference/operations.test.d.ts +0 -1
  169. package/dist/reference/operations.test.js +0 -66
  170. package/dist/standalone.integration.test.d.ts +0 -1
  171. package/dist/standalone.integration.test.js +0 -235
  172. package/dist/sync/device-lease.test.d.ts +0 -1
  173. package/dist/sync/device-lease.test.js +0 -38
  174. package/dist/sync/offline-integration.test.d.ts +0 -1
  175. package/dist/sync/offline-integration.test.js +0 -52
  176. package/dist/sync/order-number.test.d.ts +0 -1
  177. package/dist/sync/order-number.test.js +0 -51
  178. package/dist/sync/perms.test.d.ts +0 -1
  179. package/dist/sync/perms.test.js +0 -15
  180. package/dist/utils/currency.test.d.ts +0 -1
  181. package/dist/utils/currency.test.js +0 -17
  182. package/dist/utils/hlc.test.d.ts +0 -1
  183. package/dist/utils/hlc.test.js +0 -29
  184. package/dist/utils/phone.test.d.ts +0 -1
  185. package/dist/utils/phone.test.js +0 -19
  186. package/dist/utils/responsive.d.ts +0 -13
  187. package/dist/utils/responsive.js +0 -18
  188. package/dist/utils/uuid.test.d.ts +0 -1
  189. package/dist/utils/uuid.test.js +0 -13
  190. package/dist/utils/validation.test.d.ts +0 -1
  191. package/dist/utils/validation.test.js +0 -23
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Rider hooks — single source for both apps (previously verbatim copies).
3
+ * Reads serve cache instantly; network refreshes silently when online.
4
+ */
5
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
+ import { riderService } from "../adapters/services";
7
+ import { notifySuccess, notifyError } from "../adapters/notify";
8
+ import { assertOnlineFor } from "../sync/online-gates";
9
+ import { extractApiErrorMessage } from "../utils/error";
10
+ import { useInfiniteList, useIsEffectivelyOnline } from "./use-infinite-list";
11
+ // No SQLite rider mirror — the persisted query cache is the offline source.
12
+ // Reads never fire while unreachable; the OfflineBanner marks not-live.
13
+ function useOnline() {
14
+ return useIsEffectivelyOnline();
15
+ }
16
+ // Query keys
17
+ export const RIDER_KEYS = {
18
+ all: ["rider"],
19
+ profile: () => [...RIDER_KEYS.all, "profile"],
20
+ metrics: () => [...RIDER_KEYS.all, "metrics"],
21
+ activeDelivery: () => [...RIDER_KEYS.all, "active-delivery"],
22
+ deliveries: () => [...RIDER_KEYS.all, "deliveries"],
23
+ deliveryDetail: (id) => [...RIDER_KEYS.all, "delivery", id],
24
+ earnings: () => [...RIDER_KEYS.all, "earnings"],
25
+ earningsBreakdown: () => [...RIDER_KEYS.all, "earnings-breakdown"],
26
+ earningsSummary: (period) => [...RIDER_KEYS.all, "earnings-summary", period],
27
+ transactions: () => [...RIDER_KEYS.all, "transactions"],
28
+ statistics: () => [...RIDER_KEYS.all, "statistics"],
29
+ vehicleInfo: () => [...RIDER_KEYS.all, "vehicle"],
30
+ deliveryZones: () => [...RIDER_KEYS.all, "zones"],
31
+ navigationSettings: () => [...RIDER_KEYS.all, "navigation"],
32
+ navigationApps: () => [...RIDER_KEYS.all, "navigation-apps"],
33
+ };
34
+ // ─── Available Orders ───
35
+ export function useAvailableOrdersQuery(enabled = true) {
36
+ const online = useOnline();
37
+ return useQuery({
38
+ queryKey: [...RIDER_KEYS.all, "available-orders"],
39
+ queryFn: async () => {
40
+ const res = (await riderService.fetchAvailableOrders());
41
+ return res.data;
42
+ },
43
+ enabled: enabled && online,
44
+ staleTime: 15000,
45
+ retry: false,
46
+ networkMode: "offlineFirst",
47
+ });
48
+ }
49
+ export function useClaimOrderMutation() {
50
+ const queryClient = useQueryClient();
51
+ return useMutation({
52
+ mutationFn: (orderId) => riderService.acceptDelivery(orderId),
53
+ onSuccess: () => {
54
+ notifySuccess("Interest claimed! You'll be notified when ready.");
55
+ queryClient.invalidateQueries({ queryKey: [...RIDER_KEYS.all, "available-orders"] });
56
+ },
57
+ onError: (error) => {
58
+ notifyError(extractApiErrorMessage(error));
59
+ },
60
+ });
61
+ }
62
+ export function useSelfAssignMutation() {
63
+ const queryClient = useQueryClient();
64
+ return useMutation({
65
+ mutationFn: (orderId) => riderService.selfAssignDelivery(orderId),
66
+ onSuccess: () => {
67
+ notifySuccess("Delivery assigned! Confirm handover with the restaurant.");
68
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveries() });
69
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.activeDelivery() });
70
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.metrics() });
71
+ },
72
+ onError: (error) => {
73
+ notifyError(extractApiErrorMessage(error));
74
+ },
75
+ });
76
+ }
77
+ // ─── Metrics & Dashboard Hooks ───
78
+ export function useRiderProfileQuery(enabled = true) {
79
+ const online = useOnline();
80
+ return useQuery({
81
+ queryKey: RIDER_KEYS.profile(),
82
+ queryFn: async () => {
83
+ const res = (await riderService.fetchRiderProfile());
84
+ return res.data;
85
+ },
86
+ enabled: enabled && online,
87
+ staleTime: 30000,
88
+ retry: false,
89
+ networkMode: "offlineFirst",
90
+ });
91
+ }
92
+ export function useRiderMetricsQuery(enabled = true) {
93
+ const online = useOnline();
94
+ return useQuery({
95
+ queryKey: RIDER_KEYS.metrics(),
96
+ queryFn: async () => {
97
+ const res = (await riderService.fetchRiderDashboard());
98
+ return res.data;
99
+ },
100
+ enabled: enabled && online,
101
+ retry: false,
102
+ networkMode: "offlineFirst",
103
+ });
104
+ }
105
+ export function useActiveDeliveryQuery(enabled = true) {
106
+ const online = useOnline();
107
+ return useQuery({
108
+ queryKey: RIDER_KEYS.activeDelivery(),
109
+ queryFn: async () => {
110
+ const res = (await riderService.fetchActiveDelivery());
111
+ return res.data;
112
+ },
113
+ enabled: enabled && online,
114
+ retry: false,
115
+ networkMode: "offlineFirst",
116
+ });
117
+ }
118
+ // ─── Deliveries Hooks ───
119
+ export function useRiderDeliveriesQuery(enabled = true, filters) {
120
+ const online = useOnline();
121
+ return useInfiniteList({
122
+ key: [...RIDER_KEYS.deliveries(), filters],
123
+ enabled: enabled && online,
124
+ pageSize: 20,
125
+ onlineFetch: async (page, pageSize) => {
126
+ const res = (await riderService.fetchRiderDeliveries(page, pageSize, filters));
127
+ return res;
128
+ },
129
+ });
130
+ }
131
+ export function useRiderDeliveryDetailQuery(deliveryId, enabled = true) {
132
+ const online = useOnline();
133
+ return useQuery({
134
+ queryKey: RIDER_KEYS.deliveryDetail(deliveryId),
135
+ queryFn: () => riderService.fetchDeliveryDetail(deliveryId),
136
+ enabled: enabled && Boolean(deliveryId) && online,
137
+ retry: false,
138
+ networkMode: "offlineFirst",
139
+ });
140
+ }
141
+ export function useUpdateDeliveryStatusMutation() {
142
+ const queryClient = useQueryClient();
143
+ return useMutation({
144
+ mutationFn: ({ deliveryId, action }) => {
145
+ // Delivery state transitions require online (single source).
146
+ assertOnlineFor("order:create", { order_type: "delivery" });
147
+ const actions = {
148
+ "in-transit": riderService.markDeliveryInTransit,
149
+ arrived: riderService.markDeliveryArrived,
150
+ delivered: riderService.markDeliveryDelivered,
151
+ failed: riderService.markDeliveryFailed,
152
+ returned: riderService.markDeliveryReturned,
153
+ "confirm-pickup": riderService.confirmDeliveryPickup,
154
+ "confirm-handover": riderService.confirmDeliveryHandover,
155
+ };
156
+ const fn = actions[action];
157
+ if (!fn)
158
+ return Promise.reject(new Error(`Unknown action: ${action}`));
159
+ return fn(deliveryId);
160
+ },
161
+ onSuccess: (_, { deliveryId }) => {
162
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveries() });
163
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveryDetail(deliveryId) });
164
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.activeDelivery() });
165
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.metrics() });
166
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.earnings() });
167
+ },
168
+ });
169
+ }
170
+ export function useAcceptDeliveryMutation() {
171
+ const queryClient = useQueryClient();
172
+ return useMutation({
173
+ mutationFn: (orderId) => riderService.selfAssignDelivery(orderId),
174
+ onSuccess: () => {
175
+ queryClient.invalidateQueries({ queryKey: [...RIDER_KEYS.all, "available-orders"] });
176
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveries() });
177
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.activeDelivery() });
178
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.metrics() });
179
+ },
180
+ });
181
+ }
182
+ // ─── Earnings Hooks ───
183
+ export function useRiderEarningsQuery(enabled = true) {
184
+ const online = useOnline();
185
+ return useQuery({
186
+ queryKey: RIDER_KEYS.earnings(),
187
+ queryFn: () => riderService.fetchRiderEarnings(),
188
+ enabled: enabled && online,
189
+ retry: false,
190
+ networkMode: "offlineFirst",
191
+ });
192
+ }
193
+ export function useEarningsBreakdownQuery(enabled = true) {
194
+ const online = useOnline();
195
+ return useQuery({
196
+ queryKey: RIDER_KEYS.earningsBreakdown(),
197
+ queryFn: () => riderService.fetchEarningsBreakdown(),
198
+ enabled: enabled && online,
199
+ retry: false,
200
+ networkMode: "offlineFirst",
201
+ });
202
+ }
203
+ export function useRiderEarningsSummaryQuery(period, enabled = true) {
204
+ const online = useOnline();
205
+ return useQuery({
206
+ queryKey: RIDER_KEYS.earningsSummary(period),
207
+ queryFn: () => riderService.fetchRiderEarningsSummary(period),
208
+ enabled: enabled && online,
209
+ retry: false,
210
+ networkMode: "offlineFirst",
211
+ });
212
+ }
213
+ export function useRiderWalletHistoryQuery(enabled = true) {
214
+ const online = useOnline();
215
+ return useInfiniteList({
216
+ key: RIDER_KEYS.transactions(),
217
+ enabled: enabled && online,
218
+ pageSize: 20,
219
+ onlineFetch: async (page, pageSize) => {
220
+ const res = (await riderService.fetchWalletHistory(page, pageSize));
221
+ return { data: res.data.entries, pagination: res.pagination };
222
+ },
223
+ });
224
+ }
225
+ export function useRequestPayoutMutation() {
226
+ const queryClient = useQueryClient();
227
+ return useMutation({
228
+ mutationFn: (_amount) => Promise.reject(new Error("Payout not implemented on backend")),
229
+ onError: () => { },
230
+ onSuccess: () => {
231
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.earnings() });
232
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.transactions() });
233
+ },
234
+ });
235
+ }
236
+ // ─── Statistics Hooks ───
237
+ export function useRiderStatisticsQuery(enabled = true) {
238
+ const online = useOnline();
239
+ return useQuery({
240
+ queryKey: RIDER_KEYS.statistics(),
241
+ queryFn: () => riderService.fetchRiderStatistics(),
242
+ enabled: enabled && online,
243
+ retry: false,
244
+ networkMode: "offlineFirst",
245
+ });
246
+ }
247
+ // ─── Vehicle Info Hooks ───
248
+ export function useVehicleInfoQuery(enabled = true) {
249
+ const online = useOnline();
250
+ return useQuery({
251
+ queryKey: RIDER_KEYS.vehicleInfo(),
252
+ queryFn: () => riderService.fetchVehicleInfo(),
253
+ enabled: enabled && online,
254
+ retry: false,
255
+ networkMode: "offlineFirst",
256
+ });
257
+ }
258
+ export function useCreateVehicleMutation() {
259
+ const queryClient = useQueryClient();
260
+ return useMutation({
261
+ mutationFn: (vehicleInfo) => riderService.createVehicle(vehicleInfo),
262
+ onSuccess: () => {
263
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.vehicleInfo() });
264
+ },
265
+ onError: (error) => {
266
+ try {
267
+ console.debug("[useCreateVehicleMutation] error", error);
268
+ }
269
+ catch { }
270
+ },
271
+ });
272
+ }
273
+ // ─── Delivery Zones Hooks ───
274
+ export function useDeliveryZonesQuery(enabled = true) {
275
+ const online = useOnline();
276
+ return useQuery({
277
+ queryKey: RIDER_KEYS.deliveryZones(),
278
+ queryFn: () => riderService.fetchDeliveryZones(),
279
+ enabled: enabled && online,
280
+ retry: false,
281
+ networkMode: "offlineFirst",
282
+ });
283
+ }
284
+ export function useUpdateDeliveryZonesMutation() {
285
+ const queryClient = useQueryClient();
286
+ return useMutation({
287
+ mutationFn: (zones) => riderService.updateDeliveryZones(zones),
288
+ onSuccess: () => {
289
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveryZones() });
290
+ },
291
+ });
292
+ }
293
+ export function useToggleDeliveryZoneMutation() {
294
+ const queryClient = useQueryClient();
295
+ return useMutation({
296
+ mutationFn: ({ zoneId, enabled }) => riderService.toggleDeliveryZone(zoneId, enabled),
297
+ onSuccess: () => {
298
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.deliveryZones() });
299
+ },
300
+ });
301
+ }
302
+ // ─── Navigation Settings Hooks ───
303
+ export function useNavigationSettingsQuery(enabled = true) {
304
+ const online = useOnline();
305
+ return useQuery({
306
+ queryKey: RIDER_KEYS.navigationSettings(),
307
+ queryFn: () => riderService.fetchNavigationSettings(),
308
+ enabled: enabled && online,
309
+ retry: false,
310
+ networkMode: "offlineFirst",
311
+ });
312
+ }
313
+ export function useUpdateNavigationSettingsMutation() {
314
+ const queryClient = useQueryClient();
315
+ return useMutation({
316
+ mutationFn: (settings) => riderService.updateNavigationSettings(settings),
317
+ onSuccess: () => {
318
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.navigationSettings() });
319
+ },
320
+ });
321
+ }
322
+ // ─── Online Status Hooks ───
323
+ export function useUpdateOnlineStatusMutation() {
324
+ const queryClient = useQueryClient();
325
+ return useMutation({
326
+ mutationFn: (isOnline) => riderService.updateOnlineStatus(isOnline),
327
+ onSuccess: () => {
328
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.profile() });
329
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.metrics() });
330
+ },
331
+ onError: (error) => {
332
+ try {
333
+ console.debug("[useUpdateOnlineStatus] error", error);
334
+ }
335
+ catch { }
336
+ notifyError(extractApiErrorMessage(error));
337
+ },
338
+ });
339
+ }
340
+ // ─── Navigation Apps Hooks ───
341
+ export function useNavigationAppsQuery(enabled = true) {
342
+ const online = useOnline();
343
+ return useQuery({
344
+ queryKey: RIDER_KEYS.navigationApps(),
345
+ queryFn: () => riderService.fetchNavigationApps(),
346
+ enabled: enabled && online,
347
+ staleTime: Infinity,
348
+ retry: false,
349
+ networkMode: "offlineFirst",
350
+ });
351
+ }
352
+ // ─── Vehicle Types Hooks ───
353
+ export function useVehicleTypesQuery(enabled = true) {
354
+ const online = useOnline();
355
+ return useQuery({
356
+ queryKey: [...RIDER_KEYS.all, "vehicle-types"],
357
+ queryFn: () => riderService.fetchVehicleTypes(),
358
+ enabled: enabled && online,
359
+ staleTime: Infinity,
360
+ retry: false,
361
+ networkMode: "offlineFirst",
362
+ });
363
+ }
364
+ // ─── Toggle Online Status ───
365
+ export function useToggleOnlineMutation() {
366
+ const queryClient = useQueryClient();
367
+ return useMutation({
368
+ mutationFn: (isOnline) => riderService.updateRiderProfile({ is_online: isOnline }),
369
+ onSuccess: () => {
370
+ queryClient.invalidateQueries({ queryKey: RIDER_KEYS.profile() });
371
+ },
372
+ });
373
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Shared infinite-list primitive — the ONLY pagination implementation.
3
+ *
4
+ * Offline-first: SQLite/offline fetch serves instantly; the network fetch
5
+ * refreshes silently when effectively online and falls back to offline on
6
+ * error. Network is sync transport, never a render delay.
7
+ *
8
+ * Canonical pagination shape matches the backend (pagination.ex):
9
+ * { page, page_size, total_entries, total_pages }.
10
+ */
11
+ export declare function useIsEffectivelyOnline(): boolean;
12
+ export interface ListPage<T> {
13
+ data: T[];
14
+ pagination: {
15
+ page: number;
16
+ page_size: number;
17
+ total_pages: number;
18
+ total_entries?: number;
19
+ };
20
+ }
21
+ export interface InfiniteListOptions<T> {
22
+ key: readonly unknown[];
23
+ enabled?: boolean;
24
+ pageSize?: number;
25
+ staleTime?: number;
26
+ /**
27
+ * Always-available local fetch (SQLite). Serves instantly offline.
28
+ * Omit when there is no local source — offline then preserves stale
29
+ * cache (query errors, data stays) instead of serving empty.
30
+ */
31
+ offlineFetch?: (page: number, pageSize: number) => Promise<ListPage<T>>;
32
+ /** Network refresh. Failure falls back to offlineFetch, else stale cache. */
33
+ onlineFetch?: (page: number, pageSize: number) => Promise<ListPage<T>>;
34
+ }
35
+ export declare function nextPageParam(lastPage: unknown): number | undefined;
36
+ export declare function flattenPages<T>(pages: Array<{
37
+ data: T[];
38
+ }>): T[];
39
+ export declare function useInfiniteList<T>(options: InfiniteListOptions<T>): {
40
+ dataUpdatedAt: number;
41
+ errorUpdatedAt: number;
42
+ failureCount: number;
43
+ failureReason: Error | null;
44
+ errorUpdateCount: number;
45
+ isFetched: boolean;
46
+ isFetchedAfterMount: boolean;
47
+ isFetching: boolean;
48
+ isInitialLoading: boolean;
49
+ isPaused: boolean;
50
+ isRefetching: boolean;
51
+ isStale: boolean;
52
+ isEnabled: boolean;
53
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
54
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
55
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
56
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
57
+ hasNextPage: boolean;
58
+ hasPreviousPage: boolean;
59
+ isFetchingNextPage: boolean;
60
+ isFetchingPreviousPage: boolean;
61
+ error: Error;
62
+ isError: true;
63
+ isPending: false;
64
+ isLoading: false;
65
+ isLoadingError: true;
66
+ isRefetchError: false;
67
+ isFetchNextPageError: false;
68
+ isFetchPreviousPageError: false;
69
+ isSuccess: false;
70
+ isPlaceholderData: false;
71
+ status: 'error';
72
+ data: T[] | undefined;
73
+ } | {
74
+ dataUpdatedAt: number;
75
+ errorUpdatedAt: number;
76
+ failureCount: number;
77
+ failureReason: Error | null;
78
+ errorUpdateCount: number;
79
+ isFetched: boolean;
80
+ isFetchedAfterMount: boolean;
81
+ isFetching: boolean;
82
+ isInitialLoading: boolean;
83
+ isPaused: boolean;
84
+ isRefetching: boolean;
85
+ isStale: boolean;
86
+ isEnabled: boolean;
87
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
88
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
89
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
90
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
91
+ hasNextPage: boolean;
92
+ hasPreviousPage: boolean;
93
+ isFetchingNextPage: boolean;
94
+ isFetchingPreviousPage: boolean;
95
+ error: null;
96
+ isError: false;
97
+ isPending: true;
98
+ isLoading: true;
99
+ isLoadingError: false;
100
+ isRefetchError: false;
101
+ isFetchNextPageError: false;
102
+ isFetchPreviousPageError: false;
103
+ isSuccess: false;
104
+ isPlaceholderData: false;
105
+ status: 'pending';
106
+ data: T[] | undefined;
107
+ } | {
108
+ dataUpdatedAt: number;
109
+ errorUpdatedAt: number;
110
+ failureCount: number;
111
+ failureReason: Error | null;
112
+ errorUpdateCount: number;
113
+ isFetched: boolean;
114
+ isFetchedAfterMount: boolean;
115
+ isFetching: boolean;
116
+ isLoading: boolean;
117
+ isInitialLoading: boolean;
118
+ isPaused: boolean;
119
+ isRefetching: boolean;
120
+ isStale: boolean;
121
+ isEnabled: boolean;
122
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
123
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
124
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
125
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
126
+ hasNextPage: boolean;
127
+ hasPreviousPage: boolean;
128
+ isFetchingNextPage: boolean;
129
+ isFetchingPreviousPage: boolean;
130
+ error: null;
131
+ isError: false;
132
+ isPending: true;
133
+ isLoadingError: false;
134
+ isRefetchError: false;
135
+ isFetchNextPageError: false;
136
+ isFetchPreviousPageError: false;
137
+ isSuccess: false;
138
+ isPlaceholderData: false;
139
+ status: 'pending';
140
+ data: T[] | undefined;
141
+ } | {
142
+ dataUpdatedAt: number;
143
+ errorUpdatedAt: number;
144
+ failureCount: number;
145
+ failureReason: Error | null;
146
+ errorUpdateCount: number;
147
+ isFetched: boolean;
148
+ isFetchedAfterMount: boolean;
149
+ isFetching: boolean;
150
+ isInitialLoading: boolean;
151
+ isPaused: boolean;
152
+ isRefetching: boolean;
153
+ isStale: boolean;
154
+ isEnabled: boolean;
155
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
156
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
157
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
158
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
159
+ hasNextPage: boolean;
160
+ hasPreviousPage: boolean;
161
+ isFetchingNextPage: boolean;
162
+ isFetchingPreviousPage: boolean;
163
+ isError: false;
164
+ error: null;
165
+ isPending: false;
166
+ isLoading: false;
167
+ isLoadingError: false;
168
+ isRefetchError: false;
169
+ isSuccess: true;
170
+ isPlaceholderData: true;
171
+ isFetchNextPageError: false;
172
+ isFetchPreviousPageError: false;
173
+ status: 'success';
174
+ data: T[] | undefined;
175
+ } | {
176
+ dataUpdatedAt: number;
177
+ errorUpdatedAt: number;
178
+ failureCount: number;
179
+ failureReason: Error | null;
180
+ errorUpdateCount: number;
181
+ isFetched: boolean;
182
+ isFetchedAfterMount: boolean;
183
+ isFetching: boolean;
184
+ isInitialLoading: boolean;
185
+ isPaused: boolean;
186
+ isRefetching: boolean;
187
+ isStale: boolean;
188
+ isEnabled: boolean;
189
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
190
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
191
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
192
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
193
+ hasNextPage: boolean;
194
+ hasPreviousPage: boolean;
195
+ isFetchNextPageError: boolean;
196
+ isFetchingNextPage: boolean;
197
+ isFetchPreviousPageError: boolean;
198
+ isFetchingPreviousPage: boolean;
199
+ error: Error;
200
+ isError: true;
201
+ isPending: false;
202
+ isLoading: false;
203
+ isLoadingError: false;
204
+ isRefetchError: true;
205
+ isSuccess: false;
206
+ isPlaceholderData: false;
207
+ status: 'error';
208
+ data: T[] | undefined;
209
+ } | {
210
+ dataUpdatedAt: number;
211
+ errorUpdatedAt: number;
212
+ failureCount: number;
213
+ failureReason: Error | null;
214
+ errorUpdateCount: number;
215
+ isFetched: boolean;
216
+ isFetchedAfterMount: boolean;
217
+ isFetching: boolean;
218
+ isInitialLoading: boolean;
219
+ isPaused: boolean;
220
+ isRefetching: boolean;
221
+ isStale: boolean;
222
+ isEnabled: boolean;
223
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
224
+ fetchStatus: import("@tanstack/react-query").FetchStatus;
225
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
226
+ fetchPreviousPage: (options?: import("@tanstack/react-query").FetchPreviousPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<ListPage<T>, unknown>, Error>>;
227
+ hasNextPage: boolean;
228
+ hasPreviousPage: boolean;
229
+ isFetchingNextPage: boolean;
230
+ isFetchingPreviousPage: boolean;
231
+ error: null;
232
+ isError: false;
233
+ isPending: false;
234
+ isLoading: false;
235
+ isLoadingError: false;
236
+ isRefetchError: false;
237
+ isFetchNextPageError: false;
238
+ isFetchPreviousPageError: false;
239
+ isSuccess: true;
240
+ isPlaceholderData: false;
241
+ status: 'success';
242
+ data: T[] | undefined;
243
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared infinite-list primitive — the ONLY pagination implementation.
3
+ *
4
+ * Offline-first: SQLite/offline fetch serves instantly; the network fetch
5
+ * refreshes silently when effectively online and falls back to offline on
6
+ * error. Network is sync transport, never a render delay.
7
+ *
8
+ * Canonical pagination shape matches the backend (pagination.ex):
9
+ * { page, page_size, total_entries, total_pages }.
10
+ */
11
+ import { useInfiniteQuery } from "@tanstack/react-query";
12
+ import { useSyncExternalStore } from "react";
13
+ import { getConnectivityState, subscribeConnectivity } from "../adapters/connectivity";
14
+ import { DEFAULT_PAGE_SIZE } from "../utils/constants";
15
+ export function useIsEffectivelyOnline() {
16
+ const s = useSyncExternalStore(subscribeConnectivity, getConnectivityState, getConnectivityState);
17
+ return s.isOnline && s.isCloudReachable !== "unreachable";
18
+ }
19
+ export function nextPageParam(lastPage) {
20
+ const p = lastPage?.pagination;
21
+ return p && p.page < p.total_pages ? p.page + 1 : undefined;
22
+ }
23
+ export function flattenPages(pages) {
24
+ return pages.flatMap((p) => p.data ?? []);
25
+ }
26
+ export function useInfiniteList(options) {
27
+ const online = useIsEffectivelyOnline();
28
+ const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
29
+ const enabled = options.enabled ?? true;
30
+ const query = useInfiniteQuery({
31
+ queryKey: [...options.key],
32
+ queryFn: async ({ pageParam = 1 }) => {
33
+ const page = pageParam;
34
+ if (online && options.onlineFetch) {
35
+ try {
36
+ return await options.onlineFetch(page, pageSize);
37
+ }
38
+ catch {
39
+ // Network refresh failed — local source, else preserve stale cache.
40
+ if (options.offlineFetch)
41
+ return options.offlineFetch(page, pageSize);
42
+ throw new Error("Offline — showing cached data");
43
+ }
44
+ }
45
+ if (options.offlineFetch)
46
+ return options.offlineFetch(page, pageSize);
47
+ throw new Error("Offline — showing cached data");
48
+ },
49
+ initialPageParam: 1,
50
+ getNextPageParam: nextPageParam,
51
+ enabled,
52
+ staleTime: options.staleTime ?? 30000,
53
+ retry: false,
54
+ networkMode: "offlineFirst",
55
+ });
56
+ const data = query.data ? flattenPages(query.data.pages) : undefined;
57
+ return { ...query, data };
58
+ }