@kasuvia/sdk 0.0.1
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/LICENSE +201 -0
- package/README.md +112 -0
- package/dist/index.d.mts +1891 -0
- package/dist/index.d.ts +1891 -0
- package/dist/index.js +3540 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3314 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
- package/src/client/authClient.ts +135 -0
- package/src/client/graphqlClient.ts +53 -0
- package/src/client/httpClient.ts +298 -0
- package/src/config/index.ts +102 -0
- package/src/graphql/collections.graphql.ts +56 -0
- package/src/graphql/index.ts +7 -0
- package/src/graphql/products.graphql.ts +710 -0
- package/src/graphql/wishlist.graphql.ts +33 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/use-auth.ts +182 -0
- package/src/hooks/use-bespoke-availability.ts +20 -0
- package/src/hooks/use-cart.ts +207 -0
- package/src/hooks/use-cms.ts +165 -0
- package/src/hooks/use-collections.ts +40 -0
- package/src/hooks/use-consultation-stream.ts +141 -0
- package/src/hooks/use-consultations.ts +76 -0
- package/src/hooks/use-countries.ts +24 -0
- package/src/hooks/use-currencies.ts +27 -0
- package/src/hooks/use-google-translate.ts +164 -0
- package/src/hooks/use-is-mounted.ts +9 -0
- package/src/hooks/use-languages.ts +33 -0
- package/src/hooks/use-order-stream.ts +195 -0
- package/src/hooks/use-orders.ts +64 -0
- package/src/hooks/use-payment.ts +68 -0
- package/src/hooks/use-products.ts +113 -0
- package/src/hooks/use-service-booking.ts +118 -0
- package/src/hooks/use-states.ts +28 -0
- package/src/hooks/use-wishlist.ts +121 -0
- package/src/index.ts +65 -0
- package/src/services/app.service.ts +17 -0
- package/src/services/auth.service.ts +332 -0
- package/src/services/bespoke.service.ts +17 -0
- package/src/services/cart.service.ts +245 -0
- package/src/services/cms.service.ts +112 -0
- package/src/services/collection.service.ts +77 -0
- package/src/services/config.service.ts +42 -0
- package/src/services/consultation.service.ts +51 -0
- package/src/services/index.ts +21 -0
- package/src/services/logistics.service.ts +95 -0
- package/src/services/notifications.service.ts +50 -0
- package/src/services/order.service.ts +169 -0
- package/src/services/payment.service.ts +127 -0
- package/src/services/product.service.ts +678 -0
- package/src/services/service-booking.service.ts +86 -0
- package/src/services/wishlist.service.ts +84 -0
- package/src/types/auth/index.ts +126 -0
- package/src/types/bespoke/index.ts +39 -0
- package/src/types/business/index.ts +6 -0
- package/src/types/cms/index.ts +77 -0
- package/src/types/consultations/index.ts +79 -0
- package/src/types/index.ts +27 -0
- package/src/types/media/index.ts +41 -0
- package/src/types/payments/index.ts +120 -0
- package/src/types/services/index.ts +80 -0
- package/src/types/store/index.ts +282 -0
- package/src/types/store/shipping.ts +38 -0
- package/src/types/ui/index.ts +32 -0
- package/src/utils/cache.ts +33 -0
- package/src/utils/checkout.ts +39 -0
- package/src/utils/customer.ts +20 -0
- package/src/utils/error-handler.ts +14 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/logger.ts +79 -0
- package/src/utils/sanitize-url.ts +55 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3314 @@
|
|
|
1
|
+
// src/utils/cache.ts
|
|
2
|
+
var CACHE_TIERS = {
|
|
3
|
+
none: { staleTime: 0, gcTime: 0 },
|
|
4
|
+
infinity: { staleTime: Infinity, gcTime: 0 },
|
|
5
|
+
low: { staleTime: 1e3 * 30, gcTime: 1e3 * 60 },
|
|
6
|
+
// 30s / 1m
|
|
7
|
+
medium: { staleTime: 1e3 * 60 * 5, gcTime: 1e3 * 60 * 10 },
|
|
8
|
+
// 5m / 10m
|
|
9
|
+
high: { staleTime: 1e3 * 60 * 30, gcTime: 1e3 * 60 * 60 }
|
|
10
|
+
// 30m / 1h
|
|
11
|
+
};
|
|
12
|
+
var isDevelopment = true;
|
|
13
|
+
function setEnvironmentMode(development) {
|
|
14
|
+
isDevelopment = development;
|
|
15
|
+
}
|
|
16
|
+
var getCacheTier = (tier) => {
|
|
17
|
+
if (isDevelopment) return CACHE_TIERS.none;
|
|
18
|
+
return CACHE_TIERS[tier];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/utils/logger.ts
|
|
22
|
+
var isDevelopment2 = true;
|
|
23
|
+
function setLoggerEnvironmentMode(development) {
|
|
24
|
+
isDevelopment2 = development;
|
|
25
|
+
}
|
|
26
|
+
var logger = {
|
|
27
|
+
log: (...args) => {
|
|
28
|
+
if (isDevelopment2) {
|
|
29
|
+
console.log(...args);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
warn: (...args) => {
|
|
33
|
+
if (isDevelopment2) {
|
|
34
|
+
console.warn(...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Logs a warning to the console in development only.
|
|
39
|
+
* Alias for warn() for Python logging compatibility.
|
|
40
|
+
* In production, this is a no-op.
|
|
41
|
+
*/
|
|
42
|
+
warning: (...args) => {
|
|
43
|
+
if (isDevelopment2) {
|
|
44
|
+
console.warn(...args);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
error: (...args) => {
|
|
48
|
+
if (isDevelopment2) {
|
|
49
|
+
console.error(...args);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
info: (...args) => {
|
|
53
|
+
if (isDevelopment2) {
|
|
54
|
+
console.info(...args);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
createScope: (scope) => ({
|
|
58
|
+
log: (...args) => {
|
|
59
|
+
if (isDevelopment2) {
|
|
60
|
+
console.log(`[${scope}]`, ...args);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
warn: (...args) => {
|
|
64
|
+
if (isDevelopment2) {
|
|
65
|
+
console.warn(`[${scope}]`, ...args);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
warning: (...args) => {
|
|
69
|
+
if (isDevelopment2) {
|
|
70
|
+
console.warn(`[${scope}]`, ...args);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
error: (...args) => {
|
|
74
|
+
if (isDevelopment2) {
|
|
75
|
+
console.error(`[${scope}]`, ...args);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
info: (...args) => {
|
|
79
|
+
if (isDevelopment2) {
|
|
80
|
+
console.info(`[${scope}]`, ...args);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/utils/sanitize-url.ts
|
|
87
|
+
var InvalidBaseURLError = class extends Error {
|
|
88
|
+
constructor(message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "InvalidBaseURLError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "ws:", "wss:"]);
|
|
94
|
+
function sanitizeBaseUrl(url, name = "URL") {
|
|
95
|
+
if (url == null) {
|
|
96
|
+
throw new InvalidBaseURLError(`${name} is not set`);
|
|
97
|
+
}
|
|
98
|
+
const cleaned0 = url.trim();
|
|
99
|
+
if (!cleaned0) {
|
|
100
|
+
throw new InvalidBaseURLError(`${name} is empty`);
|
|
101
|
+
}
|
|
102
|
+
const cleaned = cleaned0.replace(/\/+$/, "");
|
|
103
|
+
let parsed;
|
|
104
|
+
try {
|
|
105
|
+
parsed = new URL(cleaned);
|
|
106
|
+
} catch {
|
|
107
|
+
throw new InvalidBaseURLError(
|
|
108
|
+
`${name} must start with http://, https://, ws://, or wss://, got: ${url}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
|
|
112
|
+
throw new InvalidBaseURLError(
|
|
113
|
+
`${name} must start with http://, https://, ws://, or wss://, got: ${url}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (!parsed.hostname) {
|
|
117
|
+
throw new InvalidBaseURLError(`${name} is missing a host: ${url}`);
|
|
118
|
+
}
|
|
119
|
+
if (parsed.search || parsed.hash) {
|
|
120
|
+
throw new InvalidBaseURLError(
|
|
121
|
+
`${name} should not contain a query or fragment: ${url}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return cleaned;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/utils/customer.ts
|
|
128
|
+
function buildFullName(firstname, lastname) {
|
|
129
|
+
const name = [firstname, lastname].filter(Boolean).join(" ").trim();
|
|
130
|
+
return name || void 0;
|
|
131
|
+
}
|
|
132
|
+
function splitFullName(fullName) {
|
|
133
|
+
if (!fullName?.trim()) return { firstname: "", lastname: "" };
|
|
134
|
+
const parts = fullName.trim().split(/\s+/);
|
|
135
|
+
return {
|
|
136
|
+
firstname: parts[0] ?? "",
|
|
137
|
+
lastname: parts.slice(1).join(" ")
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function getCustomerDisplayName(user) {
|
|
141
|
+
const fromParts = buildFullName(user.firstname, user.lastname);
|
|
142
|
+
return fromParts || user.full_name || user.email.split("@")[0];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/utils/checkout.ts
|
|
146
|
+
function buildDeliveryAddress(customer, location) {
|
|
147
|
+
return {
|
|
148
|
+
first_name: customer.first_name,
|
|
149
|
+
last_name: customer.last_name,
|
|
150
|
+
phone: customer.phone,
|
|
151
|
+
...location
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function getCheckoutCustomerName(customer) {
|
|
155
|
+
return buildFullName(customer.first_name, customer.last_name) ?? customer.email.split("@")[0];
|
|
156
|
+
}
|
|
157
|
+
function enrichCompletedOrder(order, options) {
|
|
158
|
+
const customerName = getCheckoutCustomerName(options.customer);
|
|
159
|
+
return {
|
|
160
|
+
...order,
|
|
161
|
+
customerName: order.customerName || customerName,
|
|
162
|
+
customerEmail: order.customerEmail || options.customer.email,
|
|
163
|
+
shippingAddress: order.shippingAddress ?? (options.productType === "physical" && options.location ? buildDeliveryAddress(options.customer, options.location) : void 0),
|
|
164
|
+
bespokeBrief: order.bespokeBrief ?? options.bespokeBrief
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/utils/error-handler.ts
|
|
169
|
+
import axios from "axios";
|
|
170
|
+
function getErrorMessage(error, fallback = "Something went wrong") {
|
|
171
|
+
if (axios.isAxiosError(error)) {
|
|
172
|
+
return error.response?.data?.detail || error.response?.data?.message || error.message || fallback;
|
|
173
|
+
}
|
|
174
|
+
if (error instanceof Error) {
|
|
175
|
+
return error.message;
|
|
176
|
+
}
|
|
177
|
+
return fallback;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/config/index.ts
|
|
181
|
+
var runtimeConfig = null;
|
|
182
|
+
var BACKEND_URLS = {
|
|
183
|
+
auth: "https://127.0.0.1:8000/api/v1",
|
|
184
|
+
store: "https://127.0.0.1:8001/api/v1",
|
|
185
|
+
payment: "https://127.0.0.1:8002/api/v1",
|
|
186
|
+
notifications: "https://127.0.0.1:8007/api/v1",
|
|
187
|
+
cms: "https://127.0.0.1:8003/api/v1"
|
|
188
|
+
};
|
|
189
|
+
function initSdk(config) {
|
|
190
|
+
runtimeConfig = {
|
|
191
|
+
businessId: config.businessId,
|
|
192
|
+
storefrontApiKey: config.storefrontApiKey
|
|
193
|
+
};
|
|
194
|
+
const isDevelopment3 = typeof window === "undefined" && globalThis.process?.env?.NODE_ENV === "development";
|
|
195
|
+
setEnvironmentMode(isDevelopment3);
|
|
196
|
+
setLoggerEnvironmentMode(isDevelopment3);
|
|
197
|
+
logger.log("SDK initialized with businessId:", config.businessId);
|
|
198
|
+
}
|
|
199
|
+
function getRuntimeConfig() {
|
|
200
|
+
if (!runtimeConfig) {
|
|
201
|
+
throw new Error("SDK not initialized. Call initSdk() before using SDK functions.");
|
|
202
|
+
}
|
|
203
|
+
return runtimeConfig;
|
|
204
|
+
}
|
|
205
|
+
function getBackendUrl(service) {
|
|
206
|
+
return BACKEND_URLS[service];
|
|
207
|
+
}
|
|
208
|
+
function isInitialized() {
|
|
209
|
+
return runtimeConfig !== null;
|
|
210
|
+
}
|
|
211
|
+
function resetConfig() {
|
|
212
|
+
runtimeConfig = null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client/httpClient.ts
|
|
216
|
+
import axios3 from "axios";
|
|
217
|
+
|
|
218
|
+
// src/client/authClient.ts
|
|
219
|
+
import axios2 from "axios";
|
|
220
|
+
var _accessToken = null;
|
|
221
|
+
var _isRefreshing = false;
|
|
222
|
+
var _pendingQueue = [];
|
|
223
|
+
function getStorageKey() {
|
|
224
|
+
if (!isInitialized()) return "kasuvia-sf-anon-refresh";
|
|
225
|
+
const config = getRuntimeConfig();
|
|
226
|
+
return `kasuvia-sf-${config.businessId}-refresh`;
|
|
227
|
+
}
|
|
228
|
+
function drainQueue(error, newToken = null) {
|
|
229
|
+
_pendingQueue.forEach(({ resolve, reject }) => {
|
|
230
|
+
if (newToken) resolve(newToken);
|
|
231
|
+
else reject(error);
|
|
232
|
+
});
|
|
233
|
+
_pendingQueue = [];
|
|
234
|
+
}
|
|
235
|
+
var tokenStore = {
|
|
236
|
+
getAccessToken: () => _accessToken,
|
|
237
|
+
setAccessToken: (token) => {
|
|
238
|
+
_accessToken = token;
|
|
239
|
+
},
|
|
240
|
+
getRefreshToken: () => {
|
|
241
|
+
if (typeof window === "undefined") return null;
|
|
242
|
+
const key = getStorageKey();
|
|
243
|
+
return localStorage.getItem(key) || sessionStorage.getItem(key);
|
|
244
|
+
},
|
|
245
|
+
setRefreshToken: (token, remember = true) => {
|
|
246
|
+
if (typeof window === "undefined") return;
|
|
247
|
+
const key = getStorageKey();
|
|
248
|
+
localStorage.removeItem(key);
|
|
249
|
+
sessionStorage.removeItem(key);
|
|
250
|
+
if (token) {
|
|
251
|
+
const store = remember ? localStorage : sessionStorage;
|
|
252
|
+
store.setItem(key, token);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
clearAll: () => {
|
|
256
|
+
_accessToken = null;
|
|
257
|
+
if (typeof window !== "undefined") {
|
|
258
|
+
const key = getStorageKey();
|
|
259
|
+
localStorage.removeItem(key);
|
|
260
|
+
sessionStorage.removeItem(key);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
hasSession: () => {
|
|
264
|
+
if (typeof window === "undefined") return false;
|
|
265
|
+
const key = getStorageKey();
|
|
266
|
+
return !!(localStorage.getItem(key) || sessionStorage.getItem(key));
|
|
267
|
+
},
|
|
268
|
+
isPersistent: () => {
|
|
269
|
+
if (typeof window === "undefined") return true;
|
|
270
|
+
const key = getStorageKey();
|
|
271
|
+
return !!localStorage.getItem(key);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var refreshAxios = axios2.create({
|
|
275
|
+
baseURL: getBackendUrl("auth"),
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": "application/json",
|
|
278
|
+
Accept: "application/json"
|
|
279
|
+
},
|
|
280
|
+
withCredentials: true,
|
|
281
|
+
timeout: 3e4
|
|
282
|
+
});
|
|
283
|
+
async function refreshToken() {
|
|
284
|
+
if (_isRefreshing) {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
_pendingQueue.push({ resolve, reject });
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
_isRefreshing = true;
|
|
290
|
+
const refresh = tokenStore.getRefreshToken();
|
|
291
|
+
if (!refresh) {
|
|
292
|
+
_isRefreshing = false;
|
|
293
|
+
tokenStore.clearAll();
|
|
294
|
+
throw new Error("[kasuvia SDK] No refresh token available");
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const { data } = await refreshAxios.post("/auth/token/refresh/", { refresh });
|
|
298
|
+
const newAccess = data.access;
|
|
299
|
+
tokenStore.setAccessToken(newAccess);
|
|
300
|
+
if (data.refresh) {
|
|
301
|
+
tokenStore.setRefreshToken(data.refresh, tokenStore.isPersistent());
|
|
302
|
+
}
|
|
303
|
+
drainQueue(null, newAccess);
|
|
304
|
+
return newAccess;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
drainQueue(err);
|
|
307
|
+
tokenStore.clearAll();
|
|
308
|
+
throw err;
|
|
309
|
+
} finally {
|
|
310
|
+
_isRefreshing = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/client/httpClient.ts
|
|
315
|
+
var _guestSessionId = null;
|
|
316
|
+
function getOrCreateGuestSessionId() {
|
|
317
|
+
if (_guestSessionId) return _guestSessionId;
|
|
318
|
+
const storageKey = isInitialized() ? `kasuvia-guest-${getRuntimeConfig().businessId}` : "kasuvia-guest-anon";
|
|
319
|
+
if (typeof window !== "undefined") {
|
|
320
|
+
_guestSessionId = sessionStorage.getItem(storageKey) || crypto.randomUUID();
|
|
321
|
+
sessionStorage.setItem(storageKey, _guestSessionId);
|
|
322
|
+
} else {
|
|
323
|
+
_guestSessionId = crypto.randomUUID();
|
|
324
|
+
}
|
|
325
|
+
return _guestSessionId;
|
|
326
|
+
}
|
|
327
|
+
function getGuestSessionId() {
|
|
328
|
+
if (!_guestSessionId && typeof window !== "undefined") {
|
|
329
|
+
const storageKey = isInitialized() ? `kasuvia-guest-${getRuntimeConfig().businessId}` : "kasuvia-guest-anon";
|
|
330
|
+
_guestSessionId = sessionStorage.getItem(storageKey);
|
|
331
|
+
}
|
|
332
|
+
return _guestSessionId;
|
|
333
|
+
}
|
|
334
|
+
function clearGuestSessionId() {
|
|
335
|
+
_guestSessionId = null;
|
|
336
|
+
if (typeof window !== "undefined") {
|
|
337
|
+
const storageKey = isInitialized() ? `kasuvia-guest-${getRuntimeConfig().businessId}` : "kasuvia-guest-anon";
|
|
338
|
+
sessionStorage.removeItem(storageKey);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
var storeManagementAxios = axios3.create({
|
|
342
|
+
baseURL: getBackendUrl("store"),
|
|
343
|
+
headers: {
|
|
344
|
+
"Content-Type": "application/json",
|
|
345
|
+
Accept: "application/json"
|
|
346
|
+
},
|
|
347
|
+
timeout: 3e4
|
|
348
|
+
});
|
|
349
|
+
storeManagementAxios.interceptors.request.use((cfg) => {
|
|
350
|
+
const config = getRuntimeConfig();
|
|
351
|
+
const token = tokenStore.getAccessToken();
|
|
352
|
+
if (token) {
|
|
353
|
+
cfg.headers = cfg.headers ?? {};
|
|
354
|
+
cfg.headers["Authorization"] = `Bearer ${token}`;
|
|
355
|
+
}
|
|
356
|
+
cfg.headers = cfg.headers ?? {};
|
|
357
|
+
cfg.headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
358
|
+
cfg.headers["X-Business-ID"] = config.businessId;
|
|
359
|
+
if (!token) {
|
|
360
|
+
const sid = getOrCreateGuestSessionId();
|
|
361
|
+
if (sid) cfg.headers["X-Guest-Session"] = sid;
|
|
362
|
+
}
|
|
363
|
+
return cfg;
|
|
364
|
+
});
|
|
365
|
+
storeManagementAxios.interceptors.response.use(
|
|
366
|
+
(res) => res,
|
|
367
|
+
async (err) => {
|
|
368
|
+
const originalRequest = err.config;
|
|
369
|
+
if (!originalRequest) return Promise.reject(err);
|
|
370
|
+
const status = err.response?.status;
|
|
371
|
+
const isUnauthorized = status === 401;
|
|
372
|
+
const isRefreshEndpoint = originalRequest.url?.includes("/auth/token/refresh/");
|
|
373
|
+
const alreadyRetried = originalRequest._retry === true;
|
|
374
|
+
if (isUnauthorized && isRefreshEndpoint) {
|
|
375
|
+
tokenStore.clearAll();
|
|
376
|
+
return Promise.reject(err);
|
|
377
|
+
}
|
|
378
|
+
if (!isUnauthorized || alreadyRetried) return Promise.reject(err);
|
|
379
|
+
try {
|
|
380
|
+
const newToken = await refreshToken();
|
|
381
|
+
originalRequest._retry = true;
|
|
382
|
+
if (originalRequest.headers) {
|
|
383
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
384
|
+
}
|
|
385
|
+
return storeManagementAxios(originalRequest);
|
|
386
|
+
} catch (refreshErr) {
|
|
387
|
+
tokenStore.clearAll();
|
|
388
|
+
return Promise.reject(refreshErr);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
var publicStoreAxios = axios3.create({
|
|
393
|
+
baseURL: getBackendUrl("store"),
|
|
394
|
+
headers: {
|
|
395
|
+
"Content-Type": "application/json",
|
|
396
|
+
Accept: "application/json"
|
|
397
|
+
},
|
|
398
|
+
timeout: 3e4
|
|
399
|
+
});
|
|
400
|
+
publicStoreAxios.interceptors.request.use((cfg) => {
|
|
401
|
+
const config = getRuntimeConfig();
|
|
402
|
+
cfg.headers = cfg.headers ?? {};
|
|
403
|
+
cfg.headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
404
|
+
cfg.headers["X-Business-ID"] = config.businessId;
|
|
405
|
+
return cfg;
|
|
406
|
+
});
|
|
407
|
+
publicStoreAxios.interceptors.response.use(
|
|
408
|
+
(res) => res,
|
|
409
|
+
(err) => Promise.reject(err)
|
|
410
|
+
);
|
|
411
|
+
var authAxios = axios3.create({
|
|
412
|
+
baseURL: getBackendUrl("auth"),
|
|
413
|
+
headers: {
|
|
414
|
+
"Content-Type": "application/json",
|
|
415
|
+
Accept: "application/json"
|
|
416
|
+
},
|
|
417
|
+
withCredentials: true,
|
|
418
|
+
timeout: 3e4
|
|
419
|
+
});
|
|
420
|
+
authAxios.interceptors.request.use((cfg) => {
|
|
421
|
+
const config = getRuntimeConfig();
|
|
422
|
+
const token = tokenStore.getAccessToken();
|
|
423
|
+
if (token) {
|
|
424
|
+
cfg.headers = cfg.headers ?? {};
|
|
425
|
+
cfg.headers["Authorization"] = `Bearer ${token}`;
|
|
426
|
+
}
|
|
427
|
+
cfg.headers = cfg.headers ?? {};
|
|
428
|
+
cfg.headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
429
|
+
cfg.headers["X-Business-ID"] = config.businessId;
|
|
430
|
+
return cfg;
|
|
431
|
+
});
|
|
432
|
+
authAxios.interceptors.response.use(
|
|
433
|
+
(res) => res,
|
|
434
|
+
async (err) => {
|
|
435
|
+
const originalRequest = err.config;
|
|
436
|
+
if (!originalRequest) return Promise.reject(err);
|
|
437
|
+
const status = err.response?.status;
|
|
438
|
+
const isUnauthorized = status === 401;
|
|
439
|
+
const isRefreshEndpoint = originalRequest.url?.includes("/auth/token/refresh/");
|
|
440
|
+
const alreadyRetried = originalRequest._retry === true;
|
|
441
|
+
if (isUnauthorized && isRefreshEndpoint) {
|
|
442
|
+
tokenStore.clearAll();
|
|
443
|
+
return Promise.reject(err);
|
|
444
|
+
}
|
|
445
|
+
if (!isUnauthorized || alreadyRetried) return Promise.reject(err);
|
|
446
|
+
try {
|
|
447
|
+
const newToken = await refreshToken();
|
|
448
|
+
originalRequest._retry = true;
|
|
449
|
+
if (originalRequest.headers) {
|
|
450
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
451
|
+
}
|
|
452
|
+
return authAxios(originalRequest);
|
|
453
|
+
} catch (refreshErr) {
|
|
454
|
+
tokenStore.clearAll();
|
|
455
|
+
return Promise.reject(refreshErr);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
var paymentAxios = axios3.create({
|
|
460
|
+
baseURL: getBackendUrl("payment"),
|
|
461
|
+
headers: {
|
|
462
|
+
"Content-Type": "application/json",
|
|
463
|
+
Accept: "application/json"
|
|
464
|
+
},
|
|
465
|
+
timeout: 3e4
|
|
466
|
+
});
|
|
467
|
+
paymentAxios.interceptors.request.use((cfg) => {
|
|
468
|
+
const config = getRuntimeConfig();
|
|
469
|
+
cfg.headers = cfg.headers ?? {};
|
|
470
|
+
cfg.headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
471
|
+
cfg.headers["X-Business-ID"] = config.businessId;
|
|
472
|
+
return cfg;
|
|
473
|
+
});
|
|
474
|
+
paymentAxios.interceptors.response.use(
|
|
475
|
+
(res) => res,
|
|
476
|
+
(err) => Promise.reject(err)
|
|
477
|
+
);
|
|
478
|
+
var notificationsAxios = axios3.create({
|
|
479
|
+
baseURL: getBackendUrl("notifications"),
|
|
480
|
+
headers: {
|
|
481
|
+
"Content-Type": "application/json",
|
|
482
|
+
Accept: "application/json"
|
|
483
|
+
},
|
|
484
|
+
timeout: 3e4
|
|
485
|
+
});
|
|
486
|
+
notificationsAxios.interceptors.response.use(
|
|
487
|
+
(res) => res,
|
|
488
|
+
(err) => Promise.reject(err)
|
|
489
|
+
);
|
|
490
|
+
var cmsAxios = axios3.create({
|
|
491
|
+
baseURL: getBackendUrl("cms"),
|
|
492
|
+
headers: {
|
|
493
|
+
"Content-Type": "application/json",
|
|
494
|
+
Accept: "application/json"
|
|
495
|
+
},
|
|
496
|
+
timeout: 3e4
|
|
497
|
+
});
|
|
498
|
+
cmsAxios.interceptors.request.use((cfg) => {
|
|
499
|
+
const config = getRuntimeConfig();
|
|
500
|
+
cfg.headers = cfg.headers ?? {};
|
|
501
|
+
cfg.headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
502
|
+
cfg.headers["X-Business-ID"] = config.businessId;
|
|
503
|
+
return cfg;
|
|
504
|
+
});
|
|
505
|
+
cmsAxios.interceptors.response.use(
|
|
506
|
+
(res) => res,
|
|
507
|
+
(err) => Promise.reject(err)
|
|
508
|
+
);
|
|
509
|
+
var refreshAxios2 = axios3.create({
|
|
510
|
+
baseURL: getBackendUrl("auth"),
|
|
511
|
+
headers: {
|
|
512
|
+
"Content-Type": "application/json",
|
|
513
|
+
Accept: "application/json"
|
|
514
|
+
},
|
|
515
|
+
withCredentials: true,
|
|
516
|
+
timeout: 3e4
|
|
517
|
+
});
|
|
518
|
+
function configureHttpClients() {
|
|
519
|
+
if (!isInitialized()) return;
|
|
520
|
+
const storeUrl = getBackendUrl("store");
|
|
521
|
+
const authUrl = getBackendUrl("auth");
|
|
522
|
+
const paymentUrl = getBackendUrl("payment");
|
|
523
|
+
const notificationsUrl = getBackendUrl("notifications");
|
|
524
|
+
const cmsUrl = getBackendUrl("cms");
|
|
525
|
+
storeManagementAxios.defaults.baseURL = storeUrl;
|
|
526
|
+
publicStoreAxios.defaults.baseURL = storeUrl;
|
|
527
|
+
authAxios.defaults.baseURL = authUrl;
|
|
528
|
+
paymentAxios.defaults.baseURL = paymentUrl;
|
|
529
|
+
notificationsAxios.defaults.baseURL = notificationsUrl;
|
|
530
|
+
cmsAxios.defaults.baseURL = cmsUrl;
|
|
531
|
+
refreshAxios2.defaults.baseURL = authUrl;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/client/graphqlClient.ts
|
|
535
|
+
import { GraphQLClient } from "graphql-request";
|
|
536
|
+
var GRAPHQL_URL = `${getBackendUrl("store")}/graphql`;
|
|
537
|
+
function buildHeaders() {
|
|
538
|
+
const headers = {
|
|
539
|
+
"Content-Type": "application/json",
|
|
540
|
+
Accept: "application/json"
|
|
541
|
+
};
|
|
542
|
+
if (isInitialized()) {
|
|
543
|
+
const config = getRuntimeConfig();
|
|
544
|
+
headers["X-Storefront-Key"] = config.storefrontApiKey;
|
|
545
|
+
headers["X-Business-ID"] = config.businessId;
|
|
546
|
+
}
|
|
547
|
+
const token = tokenStore.getAccessToken();
|
|
548
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
549
|
+
return headers;
|
|
550
|
+
}
|
|
551
|
+
function getGraphQLClient() {
|
|
552
|
+
return new GraphQLClient(GRAPHQL_URL, { headers: buildHeaders() });
|
|
553
|
+
}
|
|
554
|
+
function resetGraphQLClient() {
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/services/cart.service.ts
|
|
558
|
+
function parseItems(items) {
|
|
559
|
+
return items.map((item) => ({
|
|
560
|
+
...item,
|
|
561
|
+
variant_attributes: item.variant_attributes && typeof item.variant_attributes === "string" ? JSON.parse(item.variant_attributes) : item.variant_attributes
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
async function getGuestCart() {
|
|
565
|
+
const sid = getGuestSessionId();
|
|
566
|
+
if (!sid) return [];
|
|
567
|
+
try {
|
|
568
|
+
const { data } = await storeManagementAxios.get("cart/", {
|
|
569
|
+
headers: { "X-Guest-Session": sid }
|
|
570
|
+
});
|
|
571
|
+
return parseItems(data.items || []);
|
|
572
|
+
} catch {
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async function addToGuestCart(item) {
|
|
577
|
+
const sid = getOrCreateGuestSessionId();
|
|
578
|
+
if (!sid) return [];
|
|
579
|
+
const payload = {
|
|
580
|
+
product_id: item.product_id,
|
|
581
|
+
product_slug: item.product_slug,
|
|
582
|
+
product_name: item.product_name,
|
|
583
|
+
image: item.image,
|
|
584
|
+
variant_id: item.variant_id,
|
|
585
|
+
variant_attributes: typeof item.variant_attributes === "string" ? item.variant_attributes : JSON.stringify(item.variant_attributes),
|
|
586
|
+
unit_price: item.unit_price,
|
|
587
|
+
quantity: item.quantity,
|
|
588
|
+
custom_measurements: item.custom_measurements,
|
|
589
|
+
is_bespoke: item.is_bespoke
|
|
590
|
+
};
|
|
591
|
+
const { data } = await storeManagementAxios.post("cart/", payload, {
|
|
592
|
+
headers: { "X-Guest-Session": sid }
|
|
593
|
+
});
|
|
594
|
+
return parseItems(data.items || []);
|
|
595
|
+
}
|
|
596
|
+
async function updateGuestCartItemQuantity(itemId, quantity) {
|
|
597
|
+
const sid = getGuestSessionId();
|
|
598
|
+
if (!sid) return [];
|
|
599
|
+
const { data } = await storeManagementAxios.patch(
|
|
600
|
+
`cart/${itemId}`,
|
|
601
|
+
{ quantity },
|
|
602
|
+
{ headers: { "X-Guest-Session": sid } }
|
|
603
|
+
);
|
|
604
|
+
return parseItems(data.items || []);
|
|
605
|
+
}
|
|
606
|
+
async function removeFromGuestCart(itemId) {
|
|
607
|
+
const sid = getGuestSessionId();
|
|
608
|
+
if (!sid) return [];
|
|
609
|
+
await storeManagementAxios.delete(`cart/${itemId}`, {
|
|
610
|
+
headers: { "X-Guest-Session": sid }
|
|
611
|
+
});
|
|
612
|
+
return getGuestCart();
|
|
613
|
+
}
|
|
614
|
+
async function clearGuestCart() {
|
|
615
|
+
const sid = getGuestSessionId();
|
|
616
|
+
if (!sid) return;
|
|
617
|
+
await storeManagementAxios.delete("cart/clear", {
|
|
618
|
+
headers: { "X-Guest-Session": sid }
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
async function getCart() {
|
|
622
|
+
const { data } = await storeManagementAxios.get("cart/");
|
|
623
|
+
const parsedItems = parseItems(data.items || []);
|
|
624
|
+
return { ...data, items: parsedItems };
|
|
625
|
+
}
|
|
626
|
+
async function addToCart(item) {
|
|
627
|
+
const { data } = await storeManagementAxios.post("cart/", item);
|
|
628
|
+
const parsedItems = parseItems(data.items || []);
|
|
629
|
+
return { ...data, items: parsedItems };
|
|
630
|
+
}
|
|
631
|
+
async function updateCartItem(itemId, quantity) {
|
|
632
|
+
const { data } = await storeManagementAxios.patch(
|
|
633
|
+
`cart/${itemId}`,
|
|
634
|
+
{ quantity }
|
|
635
|
+
);
|
|
636
|
+
const parsedItems = parseItems(data.items || []);
|
|
637
|
+
return { ...data, items: parsedItems };
|
|
638
|
+
}
|
|
639
|
+
async function removeFromCart(itemId) {
|
|
640
|
+
await storeManagementAxios.delete(`cart/${itemId}`);
|
|
641
|
+
return getCart();
|
|
642
|
+
}
|
|
643
|
+
async function clearCart() {
|
|
644
|
+
await storeManagementAxios.delete("cart/clear");
|
|
645
|
+
}
|
|
646
|
+
async function mergeGuestCart() {
|
|
647
|
+
const sid = getGuestSessionId();
|
|
648
|
+
const guestItems = sid ? await getGuestCart() : [];
|
|
649
|
+
if (guestItems.length === 0) {
|
|
650
|
+
return getCart();
|
|
651
|
+
}
|
|
652
|
+
const payloadItems = guestItems.map((item) => ({
|
|
653
|
+
product_id: item.product_id,
|
|
654
|
+
product_slug: item.product_slug,
|
|
655
|
+
product_name: item.product_name,
|
|
656
|
+
image: item.image,
|
|
657
|
+
variant_id: item.variant_id,
|
|
658
|
+
variant_attributes: typeof item.variant_attributes === "string" ? item.variant_attributes : JSON.stringify(item.variant_attributes ?? {}),
|
|
659
|
+
unit_price: item.unit_price,
|
|
660
|
+
quantity: item.quantity,
|
|
661
|
+
custom_measurements: typeof item.custom_measurements === "string" ? item.custom_measurements : JSON.stringify(item.custom_measurements ?? {}),
|
|
662
|
+
is_bespoke: item.is_bespoke
|
|
663
|
+
}));
|
|
664
|
+
const { data } = await storeManagementAxios.post(
|
|
665
|
+
"cart/merge",
|
|
666
|
+
{ items: payloadItems },
|
|
667
|
+
sid ? { headers: { "X-Guest-Session": sid } } : {}
|
|
668
|
+
);
|
|
669
|
+
clearGuestSessionId();
|
|
670
|
+
return { ...data, items: parseItems(data.items || []) };
|
|
671
|
+
}
|
|
672
|
+
async function mergeGuestCartIfPresent() {
|
|
673
|
+
try {
|
|
674
|
+
const guestItems = await getGuestCart();
|
|
675
|
+
if (guestItems.length === 0) return;
|
|
676
|
+
await mergeGuestCart();
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.warn("[Cart] Failed to merge guest cart:", error);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function getGuestCartCount() {
|
|
682
|
+
return 0;
|
|
683
|
+
}
|
|
684
|
+
function getGuestCartSubtotal() {
|
|
685
|
+
return 0;
|
|
686
|
+
}
|
|
687
|
+
function getGuestCartProductType() {
|
|
688
|
+
return "physical";
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/services/order.service.ts
|
|
692
|
+
function deriveCustomerName(raw) {
|
|
693
|
+
if (raw.customer_name) return raw.customer_name;
|
|
694
|
+
if (raw.guest_name) return raw.guest_name;
|
|
695
|
+
const addr = typeof raw.shipping_address === "object" && raw.shipping_address !== null ? raw.shipping_address : null;
|
|
696
|
+
if (addr) {
|
|
697
|
+
const full = `${addr.first_name ?? ""} ${addr.last_name ?? ""}`.trim();
|
|
698
|
+
if (full) return full;
|
|
699
|
+
}
|
|
700
|
+
const email = raw.guest_email ?? raw.customer_email ?? "";
|
|
701
|
+
if (email) return email.split("@")[0];
|
|
702
|
+
return "";
|
|
703
|
+
}
|
|
704
|
+
function normaliseStorefrontOrder(raw) {
|
|
705
|
+
const items = (raw.items ?? []).map((item) => ({
|
|
706
|
+
id: item.id,
|
|
707
|
+
productName: item.product_name ?? item.productName ?? "",
|
|
708
|
+
image: item.image ?? "",
|
|
709
|
+
quantity: item.quantity ?? 1,
|
|
710
|
+
price: item.unit_price ?? item.price ?? 0
|
|
711
|
+
}));
|
|
712
|
+
const addr = typeof raw.shipping_address === "object" && raw.shipping_address !== null ? raw.shipping_address : null;
|
|
713
|
+
return {
|
|
714
|
+
id: raw.id,
|
|
715
|
+
display_id: raw.display_id,
|
|
716
|
+
items,
|
|
717
|
+
total: raw.total_amount ?? raw.total ?? 0,
|
|
718
|
+
status: raw.status,
|
|
719
|
+
customerEmail: raw.guest_email ?? raw.customer_email ?? "",
|
|
720
|
+
customerName: deriveCustomerName(raw),
|
|
721
|
+
createdAt: raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
|
|
722
|
+
updatedAt: raw.updated_at ? new Date(raw.updated_at).getTime() : Date.now(),
|
|
723
|
+
deliveryMethod: raw.delivery_method,
|
|
724
|
+
trackingNumber: raw.tracking_number,
|
|
725
|
+
deliveryAddress: typeof raw.shipping_address === "string" ? raw.shipping_address : addr ? `${addr.street}, ${addr.city}, ${addr.state}, ${addr.country}` : void 0,
|
|
726
|
+
shippingAddress: addr ? {
|
|
727
|
+
first_name: addr.first_name ?? "",
|
|
728
|
+
last_name: addr.last_name ?? "",
|
|
729
|
+
phone: addr.phone ?? "",
|
|
730
|
+
street: addr.street ?? "",
|
|
731
|
+
city: addr.city ?? "",
|
|
732
|
+
state: addr.state ?? "",
|
|
733
|
+
lga: addr.lga ?? "",
|
|
734
|
+
country: addr.country ?? "",
|
|
735
|
+
postal: addr.postal ?? ""
|
|
736
|
+
} : void 0,
|
|
737
|
+
cancellationReason: raw.cancellation_reason,
|
|
738
|
+
hasBespoke: raw.product_type === "bespoke",
|
|
739
|
+
bespokeStage: raw.bespoke_stage,
|
|
740
|
+
bespokeBrief: raw.bespoke_brief,
|
|
741
|
+
deliveryStatus: raw.delivery_status,
|
|
742
|
+
carrierName: raw.carrier_name,
|
|
743
|
+
terminalShipmentId: raw.terminal_shipment_id,
|
|
744
|
+
guestToken: raw.guest_token,
|
|
745
|
+
guestEmail: raw.guest_email,
|
|
746
|
+
guestPhone: raw.guest_phone,
|
|
747
|
+
guestName: raw.guest_name
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
async function createOrder(payload) {
|
|
751
|
+
const { data } = await storeManagementAxios.post("orders/storefront", payload);
|
|
752
|
+
return normaliseStorefrontOrder(data);
|
|
753
|
+
}
|
|
754
|
+
async function findOrder(params) {
|
|
755
|
+
const { data } = await publicStoreAxios.get("orders/find/", { params });
|
|
756
|
+
return normaliseStorefrontOrder(data);
|
|
757
|
+
}
|
|
758
|
+
async function findOrderByToken(token) {
|
|
759
|
+
const { data } = await publicStoreAxios.get("orders/find/", {
|
|
760
|
+
params: { guest_token: token }
|
|
761
|
+
});
|
|
762
|
+
return normaliseStorefrontOrder(data);
|
|
763
|
+
}
|
|
764
|
+
async function getOrders(params) {
|
|
765
|
+
const { data } = await storeManagementAxios.get("orders/", { params });
|
|
766
|
+
const items = Array.isArray(data) ? data : data.items ?? [];
|
|
767
|
+
return items.map(normaliseStorefrontOrder);
|
|
768
|
+
}
|
|
769
|
+
async function cancelOrder(orderId, reason) {
|
|
770
|
+
const { data } = await storeManagementAxios.post(`orders/${orderId}/cancel/`, { reason });
|
|
771
|
+
return normaliseStorefrontOrder(data);
|
|
772
|
+
}
|
|
773
|
+
async function trackOrder(orderId, token) {
|
|
774
|
+
const { data } = await publicStoreAxios.get(`orders/track/${orderId}`, {
|
|
775
|
+
params: token ? { token } : void 0
|
|
776
|
+
});
|
|
777
|
+
return normaliseStorefrontOrder(data);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/services/auth.service.ts
|
|
781
|
+
async function loginCustomer(payload) {
|
|
782
|
+
const config = getRuntimeConfig();
|
|
783
|
+
const { data } = await authAxios.post("/auth/login/customer/", {
|
|
784
|
+
...payload,
|
|
785
|
+
tenant_id: config.businessId
|
|
786
|
+
});
|
|
787
|
+
tokenStore.setAccessToken(data.access);
|
|
788
|
+
tokenStore.setRefreshToken(data.refresh);
|
|
789
|
+
resetGraphQLClient();
|
|
790
|
+
await mergeGuestCartIfPresent();
|
|
791
|
+
return data;
|
|
792
|
+
}
|
|
793
|
+
async function registerCustomer(payload) {
|
|
794
|
+
const config = getRuntimeConfig();
|
|
795
|
+
const { data } = await authAxios.post("/auth/register/customer/", {
|
|
796
|
+
...payload,
|
|
797
|
+
tenant_id: config.businessId
|
|
798
|
+
});
|
|
799
|
+
tokenStore.setAccessToken(data.access);
|
|
800
|
+
tokenStore.setRefreshToken(data.refresh);
|
|
801
|
+
resetGraphQLClient();
|
|
802
|
+
await mergeGuestCartIfPresent();
|
|
803
|
+
return data;
|
|
804
|
+
}
|
|
805
|
+
async function refreshToken2(refresh) {
|
|
806
|
+
const { data } = await refreshAxios2.post("/auth/token/refresh/", { refresh });
|
|
807
|
+
return data;
|
|
808
|
+
}
|
|
809
|
+
async function logout() {
|
|
810
|
+
const refresh = tokenStore.getRefreshToken();
|
|
811
|
+
const payload = refresh ? { refresh } : {};
|
|
812
|
+
try {
|
|
813
|
+
await authAxios.post("/auth/logout/", payload);
|
|
814
|
+
} catch {
|
|
815
|
+
} finally {
|
|
816
|
+
tokenStore.clearAll();
|
|
817
|
+
resetGraphQLClient();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function verifySocialToken(provider, token) {
|
|
821
|
+
const config = getRuntimeConfig();
|
|
822
|
+
const { data } = await authAxios.post(
|
|
823
|
+
`/auth/social/verify/${provider}/`,
|
|
824
|
+
{
|
|
825
|
+
id_token: token,
|
|
826
|
+
storefront_key: config.storefrontApiKey
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
tokenStore.setAccessToken(data.access);
|
|
830
|
+
tokenStore.setRefreshToken(data.refresh);
|
|
831
|
+
resetGraphQLClient();
|
|
832
|
+
return data;
|
|
833
|
+
}
|
|
834
|
+
function parseJwtPayload(token) {
|
|
835
|
+
try {
|
|
836
|
+
return JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
837
|
+
} catch {
|
|
838
|
+
return {};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function resolveProvider(data) {
|
|
842
|
+
if (data.is_google_auth_enabled) return "google";
|
|
843
|
+
if (data.is_facebook_auth_enabled) return "facebook";
|
|
844
|
+
return "email";
|
|
845
|
+
}
|
|
846
|
+
function resolveAvatar(profilePicture) {
|
|
847
|
+
if (!profilePicture) return null;
|
|
848
|
+
if (typeof profilePicture === "string") return profilePicture;
|
|
849
|
+
if (typeof profilePicture === "object" && profilePicture !== null && "url" in profilePicture) {
|
|
850
|
+
const url = profilePicture.url;
|
|
851
|
+
return typeof url === "string" ? url : null;
|
|
852
|
+
}
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
function mapAuthUserToCustomer(data, provider) {
|
|
856
|
+
const firstname = data.profile?.firstname?.trim() || void 0;
|
|
857
|
+
const lastname = data.profile?.lastname?.trim() || void 0;
|
|
858
|
+
const phone = data.profile?.phone?.trim() || void 0;
|
|
859
|
+
return {
|
|
860
|
+
id: String(data.id),
|
|
861
|
+
email: data.email,
|
|
862
|
+
firstname,
|
|
863
|
+
lastname,
|
|
864
|
+
phone,
|
|
865
|
+
full_name: buildFullName2(firstname, lastname),
|
|
866
|
+
avatar: resolveAvatar(data.profile?.profile_picture),
|
|
867
|
+
provider: provider ?? resolveProvider(data)
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function buildCustomerFromClaims(claims) {
|
|
871
|
+
const provider = claims.is_google_auth_enabled ? "google" : claims.is_facebook_auth_enabled ? "facebook" : "email";
|
|
872
|
+
const jwtFullName = String(claims.full_name ?? claims.name ?? "").trim() || void 0;
|
|
873
|
+
const { firstname: splitFirst, lastname: splitLast } = splitFullName2(jwtFullName);
|
|
874
|
+
return {
|
|
875
|
+
id: String(claims.user_id ?? claims.sub ?? ""),
|
|
876
|
+
email: String(claims.email ?? ""),
|
|
877
|
+
firstname: splitFirst || void 0,
|
|
878
|
+
lastname: splitLast || void 0,
|
|
879
|
+
phone: String(claims.phone ?? "").trim() || void 0,
|
|
880
|
+
full_name: jwtFullName,
|
|
881
|
+
avatar: claims.avatar,
|
|
882
|
+
provider
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
async function getMe() {
|
|
886
|
+
const { data } = await authAxios.get("/auth/profile/");
|
|
887
|
+
return mapAuthUserToCustomer(data);
|
|
888
|
+
}
|
|
889
|
+
async function getAuthUser() {
|
|
890
|
+
const { data } = await authAxios.get("/auth/profile/");
|
|
891
|
+
return data;
|
|
892
|
+
}
|
|
893
|
+
async function updateProfile(payload) {
|
|
894
|
+
const { data } = await authAxios.put("/auth/profile/", payload);
|
|
895
|
+
return mapAuthUserToCustomer(data);
|
|
896
|
+
}
|
|
897
|
+
var refreshPromise = null;
|
|
898
|
+
function classifyError(error) {
|
|
899
|
+
const err = error;
|
|
900
|
+
const isNetworkError = !!(!err.response && (err.code === "ECONNABORTED" || err.code === "ERR_NETWORK" || err.message?.includes("Network Error")));
|
|
901
|
+
const isAuthError = !!(err.response?.status === 401 || err.response?.status === 403);
|
|
902
|
+
const isBlacklistedError = !!(err.response?.data?.detail === "Token is blacklisted" || err.message === "Token is blacklisted");
|
|
903
|
+
return { isNetworkError, isAuthError, isBlacklistedError };
|
|
904
|
+
}
|
|
905
|
+
async function retryWithBackoff(fn, maxRetries = 2, baseDelay = 1e3) {
|
|
906
|
+
let lastError;
|
|
907
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
908
|
+
try {
|
|
909
|
+
return await fn();
|
|
910
|
+
} catch (error) {
|
|
911
|
+
lastError = error;
|
|
912
|
+
const { isNetworkError, isAuthError } = classifyError(error);
|
|
913
|
+
if (!isNetworkError || isAuthError) {
|
|
914
|
+
throw error;
|
|
915
|
+
}
|
|
916
|
+
if (attempt === maxRetries) {
|
|
917
|
+
throw error;
|
|
918
|
+
}
|
|
919
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
920
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
throw lastError;
|
|
924
|
+
}
|
|
925
|
+
async function silentRefresh() {
|
|
926
|
+
const refresh = tokenStore.getRefreshToken();
|
|
927
|
+
if (!refresh) return null;
|
|
928
|
+
if (refreshPromise) return refreshPromise;
|
|
929
|
+
refreshPromise = (async () => {
|
|
930
|
+
try {
|
|
931
|
+
const tokens = await retryWithBackoff(() => refreshToken2(refresh), 2, 1e3);
|
|
932
|
+
tokenStore.setAccessToken(tokens.access);
|
|
933
|
+
if (tokens.refresh) tokenStore.setRefreshToken(tokens.refresh, tokenStore.isPersistent());
|
|
934
|
+
const claims = parseJwtPayload(tokens.access);
|
|
935
|
+
const user = buildCustomerFromClaims(claims);
|
|
936
|
+
return { user, provider: user.provider };
|
|
937
|
+
} catch (error) {
|
|
938
|
+
const { isNetworkError, isAuthError, isBlacklistedError } = classifyError(error);
|
|
939
|
+
if (isNetworkError) {
|
|
940
|
+
logger.warn("[silentRefresh] Network error during token refresh (offline/CORS), clearing session:", error);
|
|
941
|
+
} else if (isBlacklistedError) {
|
|
942
|
+
logger.log("[silentRefresh] Token was blacklisted (likely from logout), clearing session");
|
|
943
|
+
} else if (isAuthError) {
|
|
944
|
+
logger.log("[silentRefresh] Token refresh failed with auth error (expired/invalid), clearing session");
|
|
945
|
+
} else {
|
|
946
|
+
logger.error("[silentRefresh] Unexpected error during token refresh, clearing session:", error);
|
|
947
|
+
}
|
|
948
|
+
tokenStore.clearAll();
|
|
949
|
+
return null;
|
|
950
|
+
} finally {
|
|
951
|
+
refreshPromise = null;
|
|
952
|
+
}
|
|
953
|
+
})();
|
|
954
|
+
return refreshPromise;
|
|
955
|
+
}
|
|
956
|
+
function storeTokens(access, refresh, remember = true) {
|
|
957
|
+
tokenStore.setAccessToken(access);
|
|
958
|
+
tokenStore.setRefreshToken(refresh, remember);
|
|
959
|
+
}
|
|
960
|
+
function buildFullName2(firstname, lastname) {
|
|
961
|
+
if (firstname && lastname) return `${firstname} ${lastname}`;
|
|
962
|
+
return firstname || lastname || "";
|
|
963
|
+
}
|
|
964
|
+
function splitFullName2(fullName) {
|
|
965
|
+
if (!fullName) return { firstname: "", lastname: "" };
|
|
966
|
+
const parts = fullName.trim().split(/\s+/);
|
|
967
|
+
if (parts.length === 1) {
|
|
968
|
+
return { firstname: parts[0], lastname: "" };
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
firstname: parts[0],
|
|
972
|
+
lastname: parts.slice(1).join(" ")
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/services/product.service.ts
|
|
977
|
+
var GET_STOREFRONT_PRODUCTS = `
|
|
978
|
+
query GetStorefrontProducts($page: Int!, $pageSize: Int!, $collectionSlug: String, $search: String) {
|
|
979
|
+
storefront_products(page: $page, pageSize: $pageSize, collectionSlug: $collectionSlug, search: $search) {
|
|
980
|
+
items {
|
|
981
|
+
id
|
|
982
|
+
business_id
|
|
983
|
+
name
|
|
984
|
+
slug
|
|
985
|
+
description
|
|
986
|
+
brand
|
|
987
|
+
product_type
|
|
988
|
+
requires_shipping
|
|
989
|
+
is_consumable
|
|
990
|
+
is_bespoke
|
|
991
|
+
base_price
|
|
992
|
+
currency
|
|
993
|
+
images { url alt id }
|
|
994
|
+
tags
|
|
995
|
+
custom_fields { label unit required }
|
|
996
|
+
is_published
|
|
997
|
+
is_featured
|
|
998
|
+
is_popular
|
|
999
|
+
is_new
|
|
1000
|
+
is_temporary
|
|
1001
|
+
digital_file_key
|
|
1002
|
+
download_limit
|
|
1003
|
+
download_expires_at
|
|
1004
|
+
weight_kg
|
|
1005
|
+
length_cm
|
|
1006
|
+
width_cm
|
|
1007
|
+
height_cm
|
|
1008
|
+
created_at
|
|
1009
|
+
updated_at
|
|
1010
|
+
variants {
|
|
1011
|
+
id
|
|
1012
|
+
product_id
|
|
1013
|
+
sku
|
|
1014
|
+
attributes
|
|
1015
|
+
price
|
|
1016
|
+
images { url alt id }
|
|
1017
|
+
weight_kg
|
|
1018
|
+
is_active
|
|
1019
|
+
inventory {
|
|
1020
|
+
id
|
|
1021
|
+
product_id
|
|
1022
|
+
variant_id
|
|
1023
|
+
quantity
|
|
1024
|
+
reserved
|
|
1025
|
+
reorder_point
|
|
1026
|
+
reorder_quantity
|
|
1027
|
+
track_quantity
|
|
1028
|
+
allow_backorder
|
|
1029
|
+
available
|
|
1030
|
+
is_in_stock
|
|
1031
|
+
is_low_stock
|
|
1032
|
+
updated_at
|
|
1033
|
+
}
|
|
1034
|
+
created_at
|
|
1035
|
+
updated_at
|
|
1036
|
+
}
|
|
1037
|
+
collections {
|
|
1038
|
+
id
|
|
1039
|
+
business_id
|
|
1040
|
+
name
|
|
1041
|
+
slug
|
|
1042
|
+
description
|
|
1043
|
+
image { url alt id }
|
|
1044
|
+
collection_type
|
|
1045
|
+
rules
|
|
1046
|
+
status
|
|
1047
|
+
sort_order
|
|
1048
|
+
created_at
|
|
1049
|
+
updated_at
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
total
|
|
1053
|
+
page
|
|
1054
|
+
page_size
|
|
1055
|
+
total_pages
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
`;
|
|
1059
|
+
var GET_STOREFRONT_PRODUCT_BY_SLUG = `
|
|
1060
|
+
query GetStorefrontProductBySlug($slug: String!) {
|
|
1061
|
+
storefront_product_by_slug(slug: $slug) {
|
|
1062
|
+
id
|
|
1063
|
+
business_id
|
|
1064
|
+
name
|
|
1065
|
+
slug
|
|
1066
|
+
description
|
|
1067
|
+
brand
|
|
1068
|
+
product_type
|
|
1069
|
+
requires_shipping
|
|
1070
|
+
is_consumable
|
|
1071
|
+
is_bespoke
|
|
1072
|
+
base_price
|
|
1073
|
+
currency
|
|
1074
|
+
images { url alt id }
|
|
1075
|
+
tags
|
|
1076
|
+
custom_fields { label unit required }
|
|
1077
|
+
is_published
|
|
1078
|
+
is_featured
|
|
1079
|
+
is_popular
|
|
1080
|
+
is_new
|
|
1081
|
+
is_temporary
|
|
1082
|
+
digital_file_key
|
|
1083
|
+
download_limit
|
|
1084
|
+
download_expires_at
|
|
1085
|
+
weight_kg
|
|
1086
|
+
length_cm
|
|
1087
|
+
width_cm
|
|
1088
|
+
height_cm
|
|
1089
|
+
created_at
|
|
1090
|
+
updated_at
|
|
1091
|
+
variants {
|
|
1092
|
+
id
|
|
1093
|
+
product_id
|
|
1094
|
+
sku
|
|
1095
|
+
attributes
|
|
1096
|
+
price
|
|
1097
|
+
images { url alt id }
|
|
1098
|
+
weight_kg
|
|
1099
|
+
is_active
|
|
1100
|
+
inventory {
|
|
1101
|
+
id
|
|
1102
|
+
product_id
|
|
1103
|
+
variant_id
|
|
1104
|
+
quantity
|
|
1105
|
+
reserved
|
|
1106
|
+
reorder_point
|
|
1107
|
+
reorder_quantity
|
|
1108
|
+
track_quantity
|
|
1109
|
+
allow_backorder
|
|
1110
|
+
available
|
|
1111
|
+
is_in_stock
|
|
1112
|
+
is_low_stock
|
|
1113
|
+
updated_at
|
|
1114
|
+
}
|
|
1115
|
+
created_at
|
|
1116
|
+
updated_at
|
|
1117
|
+
}
|
|
1118
|
+
collections {
|
|
1119
|
+
id
|
|
1120
|
+
business_id
|
|
1121
|
+
name
|
|
1122
|
+
slug
|
|
1123
|
+
description
|
|
1124
|
+
image { url alt id }
|
|
1125
|
+
collection_type
|
|
1126
|
+
rules
|
|
1127
|
+
status
|
|
1128
|
+
sort_order
|
|
1129
|
+
created_at
|
|
1130
|
+
updated_at
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
`;
|
|
1135
|
+
var GET_STOREFRONT_LATEST = `
|
|
1136
|
+
query GetStorefrontLatest($page: Int!, $pageSize: Int!) {
|
|
1137
|
+
storefront_latest(page: $page, pageSize: $pageSize) {
|
|
1138
|
+
items {
|
|
1139
|
+
id
|
|
1140
|
+
business_id
|
|
1141
|
+
name
|
|
1142
|
+
slug
|
|
1143
|
+
description
|
|
1144
|
+
brand
|
|
1145
|
+
product_type
|
|
1146
|
+
requires_shipping
|
|
1147
|
+
is_consumable
|
|
1148
|
+
is_bespoke
|
|
1149
|
+
base_price
|
|
1150
|
+
currency
|
|
1151
|
+
images { url alt id }
|
|
1152
|
+
tags
|
|
1153
|
+
custom_fields { label unit required }
|
|
1154
|
+
is_published
|
|
1155
|
+
is_featured
|
|
1156
|
+
is_popular
|
|
1157
|
+
is_new
|
|
1158
|
+
is_temporary
|
|
1159
|
+
digital_file_key
|
|
1160
|
+
download_limit
|
|
1161
|
+
download_expires_at
|
|
1162
|
+
weight_kg
|
|
1163
|
+
length_cm
|
|
1164
|
+
width_cm
|
|
1165
|
+
height_cm
|
|
1166
|
+
created_at
|
|
1167
|
+
updated_at
|
|
1168
|
+
variants {
|
|
1169
|
+
id
|
|
1170
|
+
product_id
|
|
1171
|
+
sku
|
|
1172
|
+
attributes
|
|
1173
|
+
price
|
|
1174
|
+
images { url alt id }
|
|
1175
|
+
weight_kg
|
|
1176
|
+
is_active
|
|
1177
|
+
inventory {
|
|
1178
|
+
id
|
|
1179
|
+
product_id
|
|
1180
|
+
variant_id
|
|
1181
|
+
quantity
|
|
1182
|
+
reserved
|
|
1183
|
+
reorder_point
|
|
1184
|
+
reorder_quantity
|
|
1185
|
+
track_quantity
|
|
1186
|
+
allow_backorder
|
|
1187
|
+
available
|
|
1188
|
+
is_in_stock
|
|
1189
|
+
is_low_stock
|
|
1190
|
+
updated_at
|
|
1191
|
+
}
|
|
1192
|
+
created_at
|
|
1193
|
+
updated_at
|
|
1194
|
+
}
|
|
1195
|
+
collections {
|
|
1196
|
+
id
|
|
1197
|
+
business_id
|
|
1198
|
+
name
|
|
1199
|
+
slug
|
|
1200
|
+
description
|
|
1201
|
+
image { url alt id }
|
|
1202
|
+
collection_type
|
|
1203
|
+
rules
|
|
1204
|
+
status
|
|
1205
|
+
sort_order
|
|
1206
|
+
created_at
|
|
1207
|
+
updated_at
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
total
|
|
1211
|
+
page
|
|
1212
|
+
page_size
|
|
1213
|
+
total_pages
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
`;
|
|
1217
|
+
var GET_STOREFRONT_POPULAR = `
|
|
1218
|
+
query GetStorefrontPopular($page: Int!, $pageSize: Int!) {
|
|
1219
|
+
storefront_popular(page: $page, pageSize: $pageSize) {
|
|
1220
|
+
items {
|
|
1221
|
+
id
|
|
1222
|
+
business_id
|
|
1223
|
+
name
|
|
1224
|
+
slug
|
|
1225
|
+
description
|
|
1226
|
+
brand
|
|
1227
|
+
product_type
|
|
1228
|
+
requires_shipping
|
|
1229
|
+
is_consumable
|
|
1230
|
+
is_bespoke
|
|
1231
|
+
base_price
|
|
1232
|
+
currency
|
|
1233
|
+
images { url alt id }
|
|
1234
|
+
tags
|
|
1235
|
+
custom_fields { label unit required }
|
|
1236
|
+
is_published
|
|
1237
|
+
is_featured
|
|
1238
|
+
is_popular
|
|
1239
|
+
is_new
|
|
1240
|
+
is_temporary
|
|
1241
|
+
digital_file_key
|
|
1242
|
+
download_limit
|
|
1243
|
+
download_expires_at
|
|
1244
|
+
weight_kg
|
|
1245
|
+
length_cm
|
|
1246
|
+
width_cm
|
|
1247
|
+
height_cm
|
|
1248
|
+
created_at
|
|
1249
|
+
updated_at
|
|
1250
|
+
variants {
|
|
1251
|
+
id
|
|
1252
|
+
product_id
|
|
1253
|
+
sku
|
|
1254
|
+
attributes
|
|
1255
|
+
price
|
|
1256
|
+
images { url alt id }
|
|
1257
|
+
weight_kg
|
|
1258
|
+
is_active
|
|
1259
|
+
inventory {
|
|
1260
|
+
id
|
|
1261
|
+
product_id
|
|
1262
|
+
variant_id
|
|
1263
|
+
quantity
|
|
1264
|
+
reserved
|
|
1265
|
+
reorder_point
|
|
1266
|
+
reorder_quantity
|
|
1267
|
+
track_quantity
|
|
1268
|
+
allow_backorder
|
|
1269
|
+
available
|
|
1270
|
+
is_in_stock
|
|
1271
|
+
is_low_stock
|
|
1272
|
+
updated_at
|
|
1273
|
+
}
|
|
1274
|
+
created_at
|
|
1275
|
+
updated_at
|
|
1276
|
+
}
|
|
1277
|
+
collections {
|
|
1278
|
+
id
|
|
1279
|
+
business_id
|
|
1280
|
+
name
|
|
1281
|
+
slug
|
|
1282
|
+
description
|
|
1283
|
+
image { url alt id }
|
|
1284
|
+
collection_type
|
|
1285
|
+
rules
|
|
1286
|
+
status
|
|
1287
|
+
sort_order
|
|
1288
|
+
created_at
|
|
1289
|
+
updated_at
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
total
|
|
1293
|
+
page
|
|
1294
|
+
page_size
|
|
1295
|
+
total_pages
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
`;
|
|
1299
|
+
var GET_STOREFRONT_FEATURED = `
|
|
1300
|
+
query GetStorefrontFeatured($page: Int!, $pageSize: Int!) {
|
|
1301
|
+
storefront_featured(page: $page, pageSize: $pageSize) {
|
|
1302
|
+
items {
|
|
1303
|
+
id
|
|
1304
|
+
business_id
|
|
1305
|
+
name
|
|
1306
|
+
slug
|
|
1307
|
+
description
|
|
1308
|
+
brand
|
|
1309
|
+
product_type
|
|
1310
|
+
requires_shipping
|
|
1311
|
+
is_consumable
|
|
1312
|
+
is_bespoke
|
|
1313
|
+
base_price
|
|
1314
|
+
currency
|
|
1315
|
+
images { url alt id }
|
|
1316
|
+
tags
|
|
1317
|
+
custom_fields { label unit required }
|
|
1318
|
+
is_published
|
|
1319
|
+
is_featured
|
|
1320
|
+
is_popular
|
|
1321
|
+
is_new
|
|
1322
|
+
is_temporary
|
|
1323
|
+
digital_file_key
|
|
1324
|
+
download_limit
|
|
1325
|
+
download_expires_at
|
|
1326
|
+
weight_kg
|
|
1327
|
+
length_cm
|
|
1328
|
+
width_cm
|
|
1329
|
+
height_cm
|
|
1330
|
+
created_at
|
|
1331
|
+
updated_at
|
|
1332
|
+
variants {
|
|
1333
|
+
id
|
|
1334
|
+
product_id
|
|
1335
|
+
sku
|
|
1336
|
+
attributes
|
|
1337
|
+
price
|
|
1338
|
+
images { url alt id }
|
|
1339
|
+
weight_kg
|
|
1340
|
+
is_active
|
|
1341
|
+
inventory {
|
|
1342
|
+
id
|
|
1343
|
+
product_id
|
|
1344
|
+
variant_id
|
|
1345
|
+
quantity
|
|
1346
|
+
reserved
|
|
1347
|
+
reorder_point
|
|
1348
|
+
reorder_quantity
|
|
1349
|
+
track_quantity
|
|
1350
|
+
allow_backorder
|
|
1351
|
+
available
|
|
1352
|
+
is_in_stock
|
|
1353
|
+
is_low_stock
|
|
1354
|
+
updated_at
|
|
1355
|
+
}
|
|
1356
|
+
created_at
|
|
1357
|
+
updated_at
|
|
1358
|
+
}
|
|
1359
|
+
collections {
|
|
1360
|
+
id
|
|
1361
|
+
business_id
|
|
1362
|
+
name
|
|
1363
|
+
slug
|
|
1364
|
+
description
|
|
1365
|
+
image { url alt id }
|
|
1366
|
+
collection_type
|
|
1367
|
+
rules
|
|
1368
|
+
status
|
|
1369
|
+
sort_order
|
|
1370
|
+
created_at
|
|
1371
|
+
updated_at
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
total
|
|
1375
|
+
page
|
|
1376
|
+
page_size
|
|
1377
|
+
total_pages
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
`;
|
|
1381
|
+
var GET_STOREFRONT_NEW = `
|
|
1382
|
+
query GetStorefrontNew($page: Int!, $pageSize: Int!) {
|
|
1383
|
+
storefront_new(page: $page, pageSize: $pageSize) {
|
|
1384
|
+
items {
|
|
1385
|
+
id
|
|
1386
|
+
business_id
|
|
1387
|
+
name
|
|
1388
|
+
slug
|
|
1389
|
+
description
|
|
1390
|
+
brand
|
|
1391
|
+
product_type
|
|
1392
|
+
requires_shipping
|
|
1393
|
+
is_consumable
|
|
1394
|
+
is_bespoke
|
|
1395
|
+
base_price
|
|
1396
|
+
currency
|
|
1397
|
+
images { url alt id }
|
|
1398
|
+
tags
|
|
1399
|
+
custom_fields { label unit required }
|
|
1400
|
+
is_published
|
|
1401
|
+
is_featured
|
|
1402
|
+
is_popular
|
|
1403
|
+
is_new
|
|
1404
|
+
is_temporary
|
|
1405
|
+
digital_file_key
|
|
1406
|
+
download_limit
|
|
1407
|
+
download_expires_at
|
|
1408
|
+
weight_kg
|
|
1409
|
+
length_cm
|
|
1410
|
+
width_cm
|
|
1411
|
+
height_cm
|
|
1412
|
+
created_at
|
|
1413
|
+
updated_at
|
|
1414
|
+
variants {
|
|
1415
|
+
id
|
|
1416
|
+
product_id
|
|
1417
|
+
sku
|
|
1418
|
+
attributes
|
|
1419
|
+
price
|
|
1420
|
+
images { url alt id }
|
|
1421
|
+
weight_kg
|
|
1422
|
+
is_active
|
|
1423
|
+
inventory {
|
|
1424
|
+
id
|
|
1425
|
+
product_id
|
|
1426
|
+
variant_id
|
|
1427
|
+
quantity
|
|
1428
|
+
reserved
|
|
1429
|
+
reorder_point
|
|
1430
|
+
reorder_quantity
|
|
1431
|
+
track_quantity
|
|
1432
|
+
allow_backorder
|
|
1433
|
+
available
|
|
1434
|
+
is_in_stock
|
|
1435
|
+
is_low_stock
|
|
1436
|
+
updated_at
|
|
1437
|
+
}
|
|
1438
|
+
created_at
|
|
1439
|
+
updated_at
|
|
1440
|
+
}
|
|
1441
|
+
collections {
|
|
1442
|
+
id
|
|
1443
|
+
business_id
|
|
1444
|
+
name
|
|
1445
|
+
slug
|
|
1446
|
+
description
|
|
1447
|
+
image { url alt id }
|
|
1448
|
+
collection_type
|
|
1449
|
+
rules
|
|
1450
|
+
status
|
|
1451
|
+
sort_order
|
|
1452
|
+
created_at
|
|
1453
|
+
updated_at
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
total
|
|
1457
|
+
page
|
|
1458
|
+
page_size
|
|
1459
|
+
total_pages
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
`;
|
|
1463
|
+
var GET_STOREFRONT_RECOMMENDED = `
|
|
1464
|
+
query GetStorefrontRecommended($page: Int!, $pageSize: Int!, $excludeProductId: String) {
|
|
1465
|
+
storefront_recommended(page: $page, pageSize: $pageSize, excludeProductId: $excludeProductId) {
|
|
1466
|
+
items {
|
|
1467
|
+
id
|
|
1468
|
+
business_id
|
|
1469
|
+
name
|
|
1470
|
+
slug
|
|
1471
|
+
description
|
|
1472
|
+
brand
|
|
1473
|
+
product_type
|
|
1474
|
+
requires_shipping
|
|
1475
|
+
is_consumable
|
|
1476
|
+
is_bespoke
|
|
1477
|
+
base_price
|
|
1478
|
+
currency
|
|
1479
|
+
images { url alt id }
|
|
1480
|
+
tags
|
|
1481
|
+
custom_fields { label unit required }
|
|
1482
|
+
is_published
|
|
1483
|
+
is_featured
|
|
1484
|
+
is_popular
|
|
1485
|
+
is_new
|
|
1486
|
+
is_temporary
|
|
1487
|
+
digital_file_key
|
|
1488
|
+
download_limit
|
|
1489
|
+
download_expires_at
|
|
1490
|
+
weight_kg
|
|
1491
|
+
length_cm
|
|
1492
|
+
width_cm
|
|
1493
|
+
height_cm
|
|
1494
|
+
created_at
|
|
1495
|
+
updated_at
|
|
1496
|
+
variants {
|
|
1497
|
+
id
|
|
1498
|
+
product_id
|
|
1499
|
+
sku
|
|
1500
|
+
attributes
|
|
1501
|
+
price
|
|
1502
|
+
images { url alt id }
|
|
1503
|
+
weight_kg
|
|
1504
|
+
is_active
|
|
1505
|
+
inventory {
|
|
1506
|
+
id
|
|
1507
|
+
product_id
|
|
1508
|
+
variant_id
|
|
1509
|
+
quantity
|
|
1510
|
+
reserved
|
|
1511
|
+
reorder_point
|
|
1512
|
+
reorder_quantity
|
|
1513
|
+
track_quantity
|
|
1514
|
+
allow_backorder
|
|
1515
|
+
available
|
|
1516
|
+
is_in_stock
|
|
1517
|
+
is_low_stock
|
|
1518
|
+
updated_at
|
|
1519
|
+
}
|
|
1520
|
+
created_at
|
|
1521
|
+
updated_at
|
|
1522
|
+
}
|
|
1523
|
+
collections {
|
|
1524
|
+
id
|
|
1525
|
+
business_id
|
|
1526
|
+
name
|
|
1527
|
+
slug
|
|
1528
|
+
description
|
|
1529
|
+
image { url alt id }
|
|
1530
|
+
collection_type
|
|
1531
|
+
rules
|
|
1532
|
+
status
|
|
1533
|
+
sort_order
|
|
1534
|
+
created_at
|
|
1535
|
+
updated_at
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
total
|
|
1539
|
+
page
|
|
1540
|
+
page_size
|
|
1541
|
+
total_pages
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
`;
|
|
1545
|
+
async function getStorefrontProducts(params) {
|
|
1546
|
+
const client = getGraphQLClient();
|
|
1547
|
+
const variables = {
|
|
1548
|
+
page: params?.page || 1,
|
|
1549
|
+
pageSize: params?.pageSize || 20,
|
|
1550
|
+
collectionSlug: params?.collectionSlug,
|
|
1551
|
+
search: params?.search
|
|
1552
|
+
};
|
|
1553
|
+
const data = await client.request(GET_STOREFRONT_PRODUCTS, variables);
|
|
1554
|
+
return data.storefront_products;
|
|
1555
|
+
}
|
|
1556
|
+
async function getStorefrontProductBySlug(slug) {
|
|
1557
|
+
const client = getGraphQLClient();
|
|
1558
|
+
const data = await client.request(GET_STOREFRONT_PRODUCT_BY_SLUG, { slug });
|
|
1559
|
+
return data.storefront_product_by_slug;
|
|
1560
|
+
}
|
|
1561
|
+
async function getLatestProducts(params) {
|
|
1562
|
+
const client = getGraphQLClient();
|
|
1563
|
+
const variables = {
|
|
1564
|
+
page: params?.page || 1,
|
|
1565
|
+
pageSize: params?.pageSize || 4
|
|
1566
|
+
};
|
|
1567
|
+
const data = await client.request(GET_STOREFRONT_LATEST, variables);
|
|
1568
|
+
return data.storefront_latest;
|
|
1569
|
+
}
|
|
1570
|
+
async function getPopularProducts(params) {
|
|
1571
|
+
const client = getGraphQLClient();
|
|
1572
|
+
const variables = {
|
|
1573
|
+
page: params?.page || 1,
|
|
1574
|
+
pageSize: params?.pageSize || 4
|
|
1575
|
+
};
|
|
1576
|
+
const data = await client.request(GET_STOREFRONT_POPULAR, variables);
|
|
1577
|
+
return data.storefront_popular;
|
|
1578
|
+
}
|
|
1579
|
+
async function getFeaturedProducts(params) {
|
|
1580
|
+
const client = getGraphQLClient();
|
|
1581
|
+
const variables = {
|
|
1582
|
+
page: params?.page || 1,
|
|
1583
|
+
pageSize: params?.pageSize || 4
|
|
1584
|
+
};
|
|
1585
|
+
const data = await client.request(GET_STOREFRONT_FEATURED, variables);
|
|
1586
|
+
return data.storefront_featured;
|
|
1587
|
+
}
|
|
1588
|
+
async function getNewProducts(params) {
|
|
1589
|
+
const client = getGraphQLClient();
|
|
1590
|
+
const variables = {
|
|
1591
|
+
page: params?.page || 1,
|
|
1592
|
+
pageSize: params?.pageSize || 4
|
|
1593
|
+
};
|
|
1594
|
+
const data = await client.request(GET_STOREFRONT_NEW, variables);
|
|
1595
|
+
return data.storefront_new;
|
|
1596
|
+
}
|
|
1597
|
+
async function getRecommendedProducts(params) {
|
|
1598
|
+
const client = getGraphQLClient();
|
|
1599
|
+
const variables = {
|
|
1600
|
+
page: params?.page || 1,
|
|
1601
|
+
pageSize: params?.pageSize || 4,
|
|
1602
|
+
excludeProductId: params?.excludeProductId
|
|
1603
|
+
};
|
|
1604
|
+
const data = await client.request(GET_STOREFRONT_RECOMMENDED, variables);
|
|
1605
|
+
return data.storefront_recommended;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// src/services/collection.service.ts
|
|
1609
|
+
var GET_STOREFRONT_COLLECTIONS = `
|
|
1610
|
+
query GetStorefrontCollections($page: Int!, $pageSize: Int!) {
|
|
1611
|
+
storefront_collections(page: $page, pageSize: $pageSize) {
|
|
1612
|
+
items {
|
|
1613
|
+
id
|
|
1614
|
+
business_id
|
|
1615
|
+
name
|
|
1616
|
+
slug
|
|
1617
|
+
description
|
|
1618
|
+
image { url alt id }
|
|
1619
|
+
collection_type
|
|
1620
|
+
rules
|
|
1621
|
+
status
|
|
1622
|
+
sort_order
|
|
1623
|
+
created_at
|
|
1624
|
+
updated_at
|
|
1625
|
+
}
|
|
1626
|
+
total
|
|
1627
|
+
page
|
|
1628
|
+
page_size
|
|
1629
|
+
total_pages
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
`;
|
|
1633
|
+
var GET_STOREFRONT_COLLECTION_BY_SLUG = `
|
|
1634
|
+
query GetStorefrontCollectionBySlug($slug: String!) {
|
|
1635
|
+
storefront_collection_by_slug(slug: $slug) {
|
|
1636
|
+
id
|
|
1637
|
+
business_id
|
|
1638
|
+
name
|
|
1639
|
+
slug
|
|
1640
|
+
description
|
|
1641
|
+
image { url alt id }
|
|
1642
|
+
collection_type
|
|
1643
|
+
rules
|
|
1644
|
+
status
|
|
1645
|
+
sort_order
|
|
1646
|
+
created_at
|
|
1647
|
+
updated_at
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
`;
|
|
1651
|
+
async function getStorefrontCollections(params) {
|
|
1652
|
+
const client = getGraphQLClient();
|
|
1653
|
+
const variables = {
|
|
1654
|
+
page: params?.page || 1,
|
|
1655
|
+
pageSize: params?.pageSize || 20
|
|
1656
|
+
};
|
|
1657
|
+
const data = await client.request(GET_STOREFRONT_COLLECTIONS, variables);
|
|
1658
|
+
return data.storefront_collections;
|
|
1659
|
+
}
|
|
1660
|
+
async function getStorefrontCollectionBySlug(slug) {
|
|
1661
|
+
const client = getGraphQLClient();
|
|
1662
|
+
const data = await client.request(GET_STOREFRONT_COLLECTION_BY_SLUG, { slug });
|
|
1663
|
+
return data.storefront_collection_by_slug;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/services/wishlist.service.ts
|
|
1667
|
+
var GET_WISHLIST = `
|
|
1668
|
+
query GetWishlist($page: Int!, $pageSize: Int!) {
|
|
1669
|
+
wishlist(page: $page, pageSize: $pageSize) {
|
|
1670
|
+
items {
|
|
1671
|
+
id
|
|
1672
|
+
product_id
|
|
1673
|
+
notes
|
|
1674
|
+
created_at
|
|
1675
|
+
product_name
|
|
1676
|
+
product_slug
|
|
1677
|
+
product_price
|
|
1678
|
+
product_image
|
|
1679
|
+
}
|
|
1680
|
+
total
|
|
1681
|
+
page
|
|
1682
|
+
page_size
|
|
1683
|
+
total_pages
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
`;
|
|
1687
|
+
var CHECK_IN_WISHLIST = `
|
|
1688
|
+
query CheckInWishlist($productId: String!) {
|
|
1689
|
+
check_in_wishlist(productId: $productId) {
|
|
1690
|
+
in_wishlist
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
`;
|
|
1694
|
+
async function getWishlist(params) {
|
|
1695
|
+
const client = getGraphQLClient();
|
|
1696
|
+
const variables = {
|
|
1697
|
+
page: params?.page || 1,
|
|
1698
|
+
pageSize: params?.pageSize || 20
|
|
1699
|
+
};
|
|
1700
|
+
const data = await client.request(GET_WISHLIST, variables);
|
|
1701
|
+
return data.wishlist;
|
|
1702
|
+
}
|
|
1703
|
+
async function checkInWishlist(productId) {
|
|
1704
|
+
const client = getGraphQLClient();
|
|
1705
|
+
const data = await client.request(CHECK_IN_WISHLIST, { productId });
|
|
1706
|
+
return data.check_in_wishlist;
|
|
1707
|
+
}
|
|
1708
|
+
async function addToWishlist(productId, notes) {
|
|
1709
|
+
const { data } = await storeManagementAxios.post("wishlist/", {
|
|
1710
|
+
product_id: productId,
|
|
1711
|
+
notes: notes || void 0
|
|
1712
|
+
});
|
|
1713
|
+
return data;
|
|
1714
|
+
}
|
|
1715
|
+
async function removeFromWishlist(itemId) {
|
|
1716
|
+
await storeManagementAxios.delete(`wishlist/${itemId}`);
|
|
1717
|
+
}
|
|
1718
|
+
async function removeProductFromWishlist(productId) {
|
|
1719
|
+
await storeManagementAxios.delete(`wishlist/product/${productId}`);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// src/services/payment.service.ts
|
|
1723
|
+
async function createDedicatedAccount(request) {
|
|
1724
|
+
const isValidUUID = (str) => {
|
|
1725
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1726
|
+
return uuidRegex.test(str);
|
|
1727
|
+
};
|
|
1728
|
+
const { data } = await paymentAxios.post(
|
|
1729
|
+
"payments/dedicated-account",
|
|
1730
|
+
{
|
|
1731
|
+
order_id: request.order_id && isValidUUID(request.order_id) ? request.order_id : void 0,
|
|
1732
|
+
amount: request.amount,
|
|
1733
|
+
customer_email: request.customer_email,
|
|
1734
|
+
customer_name: request.customer_name,
|
|
1735
|
+
currency: request.currency,
|
|
1736
|
+
idempotency_key: request.idempotency_key
|
|
1737
|
+
}
|
|
1738
|
+
);
|
|
1739
|
+
return data;
|
|
1740
|
+
}
|
|
1741
|
+
async function initiatePayment(request) {
|
|
1742
|
+
const isValidUUID = (str) => {
|
|
1743
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1744
|
+
return uuidRegex.test(str);
|
|
1745
|
+
};
|
|
1746
|
+
const validOrderId = request.order_id && isValidUUID(request.order_id) ? request.order_id : void 0;
|
|
1747
|
+
const { data } = await paymentAxios.post("payments/storefront/initiate", {
|
|
1748
|
+
amount: request.amount,
|
|
1749
|
+
currency: request.currency,
|
|
1750
|
+
order_id: validOrderId,
|
|
1751
|
+
quote_id: request.quote_id,
|
|
1752
|
+
metadata_json: {
|
|
1753
|
+
customer_email: request.customer_email,
|
|
1754
|
+
customer_name: request.customer_name ?? "",
|
|
1755
|
+
order_id: validOrderId,
|
|
1756
|
+
...request.metadata ?? {}
|
|
1757
|
+
},
|
|
1758
|
+
idempotency_key: request.idempotency_key
|
|
1759
|
+
});
|
|
1760
|
+
return {
|
|
1761
|
+
id: data.id,
|
|
1762
|
+
gateway_ref: data.gateway_ref,
|
|
1763
|
+
gateway: data.gateway,
|
|
1764
|
+
status: data.status,
|
|
1765
|
+
checkout_url: data.metadata_json?.checkout_url ?? ""
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
async function requestQuote(amount) {
|
|
1769
|
+
const { data } = await paymentAxios.post("payments/quote", { amount });
|
|
1770
|
+
return data;
|
|
1771
|
+
}
|
|
1772
|
+
async function verifyTransaction(reference) {
|
|
1773
|
+
const { data } = await paymentAxios.post(
|
|
1774
|
+
`payments/verify/${reference}`
|
|
1775
|
+
);
|
|
1776
|
+
return data;
|
|
1777
|
+
}
|
|
1778
|
+
async function getTransactionStatus(transactionId) {
|
|
1779
|
+
const { data } = await paymentAxios.get(
|
|
1780
|
+
`payments/transactions/${transactionId}`
|
|
1781
|
+
);
|
|
1782
|
+
return data;
|
|
1783
|
+
}
|
|
1784
|
+
var paymentService = {
|
|
1785
|
+
createDedicatedAccount,
|
|
1786
|
+
initiatePayment,
|
|
1787
|
+
requestQuote,
|
|
1788
|
+
verifyTransaction,
|
|
1789
|
+
getTransactionStatus
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
// src/services/consultation.service.ts
|
|
1793
|
+
async function createConsultation(payload) {
|
|
1794
|
+
const { data } = await storeManagementAxios.post("social/consultations/", payload);
|
|
1795
|
+
return data;
|
|
1796
|
+
}
|
|
1797
|
+
async function getConsultations() {
|
|
1798
|
+
const { data } = await storeManagementAxios.get("social/consultations/");
|
|
1799
|
+
return data;
|
|
1800
|
+
}
|
|
1801
|
+
async function getConsultation(id) {
|
|
1802
|
+
const { data } = await storeManagementAxios.get(`social/consultations/${id}`);
|
|
1803
|
+
return data;
|
|
1804
|
+
}
|
|
1805
|
+
async function sendMessage(id, payload) {
|
|
1806
|
+
await storeManagementAxios.post(`social/consultations/${id}/messages`, payload);
|
|
1807
|
+
}
|
|
1808
|
+
async function acceptQuote(id, payload) {
|
|
1809
|
+
const { data } = await storeManagementAxios.post(`social/consultations/${id}/accept-quote`, payload);
|
|
1810
|
+
return data;
|
|
1811
|
+
}
|
|
1812
|
+
async function rejectQuote(id, reason) {
|
|
1813
|
+
const { data } = await storeManagementAxios.post(`social/consultations/${id}/reject-quote`, { reason });
|
|
1814
|
+
return data;
|
|
1815
|
+
}
|
|
1816
|
+
var consultationService = {
|
|
1817
|
+
createConsultation,
|
|
1818
|
+
getConsultations,
|
|
1819
|
+
getConsultation,
|
|
1820
|
+
sendMessage,
|
|
1821
|
+
acceptQuote,
|
|
1822
|
+
rejectQuote
|
|
1823
|
+
};
|
|
1824
|
+
|
|
1825
|
+
// src/services/logistics.service.ts
|
|
1826
|
+
async function getShippingRates(payload) {
|
|
1827
|
+
const { data } = await storeManagementAxios.post(
|
|
1828
|
+
"logistics/shipping/rates",
|
|
1829
|
+
payload
|
|
1830
|
+
);
|
|
1831
|
+
return data;
|
|
1832
|
+
}
|
|
1833
|
+
async function getSellerShippingAddress() {
|
|
1834
|
+
const { data } = await storeManagementAxios.get("logistics/shipping-address");
|
|
1835
|
+
return data;
|
|
1836
|
+
}
|
|
1837
|
+
async function validateShippingAddress(payload) {
|
|
1838
|
+
const { data } = await storeManagementAxios.post("logistics/shipping-address/validate", payload);
|
|
1839
|
+
return data;
|
|
1840
|
+
}
|
|
1841
|
+
var logisticsService = {
|
|
1842
|
+
getShippingRates,
|
|
1843
|
+
getSellerShippingAddress,
|
|
1844
|
+
validateShippingAddress
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
// src/services/bespoke.service.ts
|
|
1848
|
+
async function checkAvailability(product_id) {
|
|
1849
|
+
const { data } = await storeManagementAxios.get(`bespoke/availability/${product_id}`);
|
|
1850
|
+
return data;
|
|
1851
|
+
}
|
|
1852
|
+
var bespokeService = {
|
|
1853
|
+
checkAvailability
|
|
1854
|
+
};
|
|
1855
|
+
|
|
1856
|
+
// src/services/service-booking.service.ts
|
|
1857
|
+
async function getAvailableSlots(product_id, date_from, date_to) {
|
|
1858
|
+
const { data } = await storeManagementAxios.get(
|
|
1859
|
+
`service/available-slots/${product_id}`,
|
|
1860
|
+
{ params: { date_from, date_to } }
|
|
1861
|
+
);
|
|
1862
|
+
return data;
|
|
1863
|
+
}
|
|
1864
|
+
async function bookAppointment(payload) {
|
|
1865
|
+
const { data } = await storeManagementAxios.post("service/appointments/", payload);
|
|
1866
|
+
return data;
|
|
1867
|
+
}
|
|
1868
|
+
async function getAppointments() {
|
|
1869
|
+
const { data } = await storeManagementAxios.get("service/bookings/my");
|
|
1870
|
+
return data;
|
|
1871
|
+
}
|
|
1872
|
+
async function getAppointment(id) {
|
|
1873
|
+
const { data } = await storeManagementAxios.get(`service/bookings/${id}`);
|
|
1874
|
+
return data;
|
|
1875
|
+
}
|
|
1876
|
+
async function cancelAppointment(id) {
|
|
1877
|
+
await storeManagementAxios.delete(`service/bookings/${id}`);
|
|
1878
|
+
}
|
|
1879
|
+
async function getDeliverables(order_id) {
|
|
1880
|
+
const { data } = await storeManagementAxios.get(`service/projects/${order_id}/deliverables/`);
|
|
1881
|
+
return data;
|
|
1882
|
+
}
|
|
1883
|
+
async function approveDeliverable(order_id, deliverable_id) {
|
|
1884
|
+
const { data } = await storeManagementAxios.post(
|
|
1885
|
+
`service/projects/${order_id}/deliverables/${deliverable_id}/approve`,
|
|
1886
|
+
{ approved: true }
|
|
1887
|
+
);
|
|
1888
|
+
return data;
|
|
1889
|
+
}
|
|
1890
|
+
async function requestRevision(order_id, deliverable_id, feedback) {
|
|
1891
|
+
const { data } = await storeManagementAxios.post(
|
|
1892
|
+
`service/projects/${order_id}/deliverables/${deliverable_id}/revision`,
|
|
1893
|
+
{ approved: false, feedback }
|
|
1894
|
+
);
|
|
1895
|
+
return data;
|
|
1896
|
+
}
|
|
1897
|
+
var serviceBookingService = {
|
|
1898
|
+
getAvailableSlots,
|
|
1899
|
+
bookAppointment,
|
|
1900
|
+
getAppointments,
|
|
1901
|
+
getAppointment,
|
|
1902
|
+
cancelAppointment,
|
|
1903
|
+
getDeliverables,
|
|
1904
|
+
approveDeliverable,
|
|
1905
|
+
requestRevision
|
|
1906
|
+
};
|
|
1907
|
+
|
|
1908
|
+
// src/services/notifications.service.ts
|
|
1909
|
+
async function sendContactEmail(payload) {
|
|
1910
|
+
const { data } = await notificationsAxios.post(
|
|
1911
|
+
"contact/send-contact-email",
|
|
1912
|
+
payload
|
|
1913
|
+
);
|
|
1914
|
+
return data;
|
|
1915
|
+
}
|
|
1916
|
+
async function sendContactConfirmation(payload) {
|
|
1917
|
+
const { data } = await notificationsAxios.post(
|
|
1918
|
+
"contact/send-contact-confirmation",
|
|
1919
|
+
payload
|
|
1920
|
+
);
|
|
1921
|
+
return data;
|
|
1922
|
+
}
|
|
1923
|
+
var notificationsService = {
|
|
1924
|
+
sendContactEmail,
|
|
1925
|
+
sendContactConfirmation
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
// src/services/app.service.ts
|
|
1929
|
+
async function googleTranslate(text, target, source = "en") {
|
|
1930
|
+
if (!text || target === source) return text;
|
|
1931
|
+
try {
|
|
1932
|
+
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
|
|
1933
|
+
const res = await fetch(url);
|
|
1934
|
+
const data = await res.json();
|
|
1935
|
+
return (data?.[0] || []).map((s) => s[0]).join("");
|
|
1936
|
+
} catch {
|
|
1937
|
+
return text;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// src/services/config.service.ts
|
|
1942
|
+
async function getSupportedLanguages() {
|
|
1943
|
+
const { data } = await paymentAxios.get("/config/supported-languages");
|
|
1944
|
+
return data;
|
|
1945
|
+
}
|
|
1946
|
+
async function getSupportedCurrencies() {
|
|
1947
|
+
const { data } = await paymentAxios.get("/config/supported-currencies");
|
|
1948
|
+
return data;
|
|
1949
|
+
}
|
|
1950
|
+
async function getBusinessConfig() {
|
|
1951
|
+
const { data } = await publicStoreAxios.get("business/profile");
|
|
1952
|
+
return data;
|
|
1953
|
+
}
|
|
1954
|
+
var configService = {
|
|
1955
|
+
getSupportedLanguages,
|
|
1956
|
+
getSupportedCurrencies,
|
|
1957
|
+
getBusinessConfig
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
// src/services/cms.service.ts
|
|
1961
|
+
async function listCmsEntries(modelSlug, params) {
|
|
1962
|
+
const config = getRuntimeConfig();
|
|
1963
|
+
const { businessId } = config;
|
|
1964
|
+
const response = await cmsAxios.get(
|
|
1965
|
+
`/sdk/${businessId}/${modelSlug}/entries`,
|
|
1966
|
+
{
|
|
1967
|
+
params: {
|
|
1968
|
+
page: params?.page || 1,
|
|
1969
|
+
page_size: params?.page_size || 50
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
);
|
|
1973
|
+
return response.data;
|
|
1974
|
+
}
|
|
1975
|
+
async function getCmsEntry(modelSlug, entryId) {
|
|
1976
|
+
const config = getRuntimeConfig();
|
|
1977
|
+
const { businessId } = config;
|
|
1978
|
+
const response = await cmsAxios.get(
|
|
1979
|
+
`/sdk/${businessId}/${modelSlug}/entries/${entryId}`
|
|
1980
|
+
);
|
|
1981
|
+
return response.data;
|
|
1982
|
+
}
|
|
1983
|
+
async function createCmsEntry(modelSlug, params) {
|
|
1984
|
+
const config = getRuntimeConfig();
|
|
1985
|
+
const { businessId } = config;
|
|
1986
|
+
const response = await cmsAxios.post(
|
|
1987
|
+
`/sdk/${businessId}/${modelSlug}/entries`,
|
|
1988
|
+
params
|
|
1989
|
+
);
|
|
1990
|
+
return response.data;
|
|
1991
|
+
}
|
|
1992
|
+
async function updateCmsEntry(modelSlug, entryId, params) {
|
|
1993
|
+
const config = getRuntimeConfig();
|
|
1994
|
+
const { businessId } = config;
|
|
1995
|
+
const response = await cmsAxios.put(
|
|
1996
|
+
`/sdk/${businessId}/${modelSlug}/entries/${entryId}`,
|
|
1997
|
+
params
|
|
1998
|
+
);
|
|
1999
|
+
return response.data;
|
|
2000
|
+
}
|
|
2001
|
+
async function deleteCmsEntry(modelSlug, entryId) {
|
|
2002
|
+
const config = getRuntimeConfig();
|
|
2003
|
+
const { businessId } = config;
|
|
2004
|
+
await cmsAxios.delete(
|
|
2005
|
+
`/sdk/${businessId}/${modelSlug}/entries/${entryId}`
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// src/hooks/use-cart.ts
|
|
2010
|
+
import React, { useContext, useState } from "react";
|
|
2011
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2012
|
+
import axios4 from "axios";
|
|
2013
|
+
var CartCtx = React.createContext ? React.createContext(null) : null;
|
|
2014
|
+
function CartProvider({ children }) {
|
|
2015
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2016
|
+
if (!CartCtx) return children;
|
|
2017
|
+
return React.createElement(CartCtx.Provider, { value: { isOpen, setOpen: setIsOpen } }, children);
|
|
2018
|
+
}
|
|
2019
|
+
function useCart() {
|
|
2020
|
+
const queryClient = useQueryClient();
|
|
2021
|
+
const context = CartCtx ? useContext(CartCtx) : null;
|
|
2022
|
+
const isOpen = context ? context.isOpen : false;
|
|
2023
|
+
const setOpen = context ? context.setOpen : () => {
|
|
2024
|
+
};
|
|
2025
|
+
const [productTypeMismatch, setProductTypeMismatch] = useState(null);
|
|
2026
|
+
const cartQuery = useQuery({
|
|
2027
|
+
queryKey: ["cart"],
|
|
2028
|
+
queryFn: getCart,
|
|
2029
|
+
enabled: isInitialized()
|
|
2030
|
+
});
|
|
2031
|
+
const addToCartMutation = useMutation({
|
|
2032
|
+
mutationFn: addToCart,
|
|
2033
|
+
onSuccess: () => {
|
|
2034
|
+
setProductTypeMismatch(null);
|
|
2035
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2036
|
+
},
|
|
2037
|
+
onError: (error) => {
|
|
2038
|
+
if (axios4.isAxiosError(error) && error.response?.status === 409) {
|
|
2039
|
+
const responseData = error.response?.data;
|
|
2040
|
+
const errorDetail = responseData?.detail;
|
|
2041
|
+
if (errorDetail?.code === "CART_PRODUCT_TYPE_MISMATCH") {
|
|
2042
|
+
setProductTypeMismatch({
|
|
2043
|
+
message: errorDetail.message ?? "Your cart contains incompatible items.",
|
|
2044
|
+
existing_type: errorDetail.existing_type ?? "physical",
|
|
2045
|
+
new_type: errorDetail.new_type ?? "physical"
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
const updateCartItemMutation = useMutation({
|
|
2052
|
+
mutationFn: ({ itemId, quantity }) => updateCartItem(itemId, quantity),
|
|
2053
|
+
onSuccess: () => {
|
|
2054
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
const removeFromCartMutation = useMutation({
|
|
2058
|
+
mutationFn: removeFromCart,
|
|
2059
|
+
onSuccess: () => {
|
|
2060
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
const clearCartMutation = useMutation({
|
|
2064
|
+
mutationFn: clearCart,
|
|
2065
|
+
onSuccess: () => {
|
|
2066
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
const mergeGuestCartMutation = useMutation({
|
|
2070
|
+
mutationFn: mergeGuestCartIfPresent,
|
|
2071
|
+
onSuccess: () => {
|
|
2072
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
const addItem = (product, variantId, quantity = 1, customMeasurements) => {
|
|
2076
|
+
const variant = variantId ? product.variants.find((v) => v.id === variantId) : void 0;
|
|
2077
|
+
return addToCartMutation.mutate({
|
|
2078
|
+
product_id: product.id,
|
|
2079
|
+
product_slug: product.slug,
|
|
2080
|
+
product_name: product.name,
|
|
2081
|
+
image: product.images[0]?.url,
|
|
2082
|
+
variant_id: variantId,
|
|
2083
|
+
variant_attributes: variant?.attributes ? JSON.stringify(variant.attributes) : void 0,
|
|
2084
|
+
unit_price: variant?.price ?? product.base_price,
|
|
2085
|
+
quantity,
|
|
2086
|
+
custom_measurements: customMeasurements ? JSON.stringify(customMeasurements) : void 0,
|
|
2087
|
+
is_bespoke: product.is_bespoke
|
|
2088
|
+
});
|
|
2089
|
+
};
|
|
2090
|
+
const items = cartQuery.data?.items || [];
|
|
2091
|
+
const subtotal = cartQuery.data?.subtotal || 0;
|
|
2092
|
+
const count = items.reduce((total, item) => total + item.quantity, 0);
|
|
2093
|
+
const updateQty = (itemId, quantity) => {
|
|
2094
|
+
updateCartItemMutation.mutate({ itemId, quantity });
|
|
2095
|
+
};
|
|
2096
|
+
const removeItem = (itemId) => {
|
|
2097
|
+
removeFromCartMutation.mutate(itemId);
|
|
2098
|
+
};
|
|
2099
|
+
const handleClearCartAndAdd = () => {
|
|
2100
|
+
clearCartMutation.mutate();
|
|
2101
|
+
setProductTypeMismatch(null);
|
|
2102
|
+
};
|
|
2103
|
+
const handleKeepCurrentCart = () => {
|
|
2104
|
+
setProductTypeMismatch(null);
|
|
2105
|
+
};
|
|
2106
|
+
return {
|
|
2107
|
+
cart: cartQuery.data,
|
|
2108
|
+
items,
|
|
2109
|
+
count,
|
|
2110
|
+
isOpen,
|
|
2111
|
+
setOpen,
|
|
2112
|
+
isLoading: cartQuery.isLoading,
|
|
2113
|
+
error: cartQuery.error,
|
|
2114
|
+
addToCart: addToCartMutation.mutate,
|
|
2115
|
+
updateCartItem: updateCartItemMutation.mutate,
|
|
2116
|
+
removeFromCart: removeFromCartMutation.mutate,
|
|
2117
|
+
clearCart: clearCartMutation.mutate,
|
|
2118
|
+
clear: clearCartMutation.mutate,
|
|
2119
|
+
// Alias for storefront compatibility
|
|
2120
|
+
mergeGuestCart: mergeGuestCartMutation.mutate,
|
|
2121
|
+
addItem,
|
|
2122
|
+
updateQty,
|
|
2123
|
+
removeItem,
|
|
2124
|
+
subtotal,
|
|
2125
|
+
productTypeMismatch,
|
|
2126
|
+
handleClearCartAndAdd,
|
|
2127
|
+
handleKeepCurrentCart
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/hooks/use-orders.ts
|
|
2132
|
+
import { useQuery as useQuery2, useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
2133
|
+
function useOrders(params) {
|
|
2134
|
+
return useQuery2({
|
|
2135
|
+
queryKey: ["orders", params],
|
|
2136
|
+
queryFn: () => getOrders(params)
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
function useOrdersList(params) {
|
|
2140
|
+
return useOrders(params);
|
|
2141
|
+
}
|
|
2142
|
+
function useFindOrder(params) {
|
|
2143
|
+
return useQuery2({
|
|
2144
|
+
queryKey: ["order", params],
|
|
2145
|
+
queryFn: () => findOrder(params),
|
|
2146
|
+
enabled: !!params.email && !!params.order_id
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
function useOrder(params) {
|
|
2150
|
+
return useFindOrder(params);
|
|
2151
|
+
}
|
|
2152
|
+
function useOrderTracking(orderId, token) {
|
|
2153
|
+
return useQuery2({
|
|
2154
|
+
queryKey: ["order-tracking", orderId, token],
|
|
2155
|
+
queryFn: () => trackOrder(orderId, token),
|
|
2156
|
+
enabled: !!orderId,
|
|
2157
|
+
refetchInterval: 3e4
|
|
2158
|
+
// Refetch every 30 seconds for tracking updates
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
function useCreateOrder() {
|
|
2162
|
+
const queryClient = useQueryClient2();
|
|
2163
|
+
return useMutation2({
|
|
2164
|
+
mutationFn: createOrder,
|
|
2165
|
+
onSuccess: () => {
|
|
2166
|
+
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
function useCancelOrder() {
|
|
2171
|
+
const queryClient = useQueryClient2();
|
|
2172
|
+
return useMutation2({
|
|
2173
|
+
mutationFn: ({ orderId, reason }) => cancelOrder(orderId, reason),
|
|
2174
|
+
onSuccess: () => {
|
|
2175
|
+
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// src/hooks/use-auth.ts
|
|
2181
|
+
import React2, { useContext as useContext2, useState as useState2, useEffect, useCallback } from "react";
|
|
2182
|
+
import { useQuery as useQuery3, useMutation as useMutation3, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
2183
|
+
var AuthCtx = React2.createContext ? React2.createContext(null) : null;
|
|
2184
|
+
function AuthProvider({ children }) {
|
|
2185
|
+
const [customer, setCustomer] = useState2(null);
|
|
2186
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
2187
|
+
const [isModalOpen, setIsModalOpen] = useState2(false);
|
|
2188
|
+
useEffect(() => {
|
|
2189
|
+
let voided = false;
|
|
2190
|
+
(async () => {
|
|
2191
|
+
setIsLoading(true);
|
|
2192
|
+
try {
|
|
2193
|
+
const result = await silentRefresh();
|
|
2194
|
+
if (!voided && result) {
|
|
2195
|
+
setCustomer(result.user);
|
|
2196
|
+
resetGraphQLClient();
|
|
2197
|
+
}
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
logger.error("[AuthProvider] Session restore failed:", error);
|
|
2200
|
+
if (!voided) setCustomer(null);
|
|
2201
|
+
} finally {
|
|
2202
|
+
if (!voided) setIsLoading(false);
|
|
2203
|
+
}
|
|
2204
|
+
})();
|
|
2205
|
+
return () => {
|
|
2206
|
+
voided = true;
|
|
2207
|
+
};
|
|
2208
|
+
}, []);
|
|
2209
|
+
const openModal = useCallback(() => setIsModalOpen(true), []);
|
|
2210
|
+
const closeModal = useCallback(() => {
|
|
2211
|
+
setIsModalOpen(false);
|
|
2212
|
+
if (typeof window !== "undefined" && (window.location.hash === "#login" || window.location.hash === "#signup")) {
|
|
2213
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
2214
|
+
}
|
|
2215
|
+
}, []);
|
|
2216
|
+
useEffect(() => {
|
|
2217
|
+
const handleHash = () => {
|
|
2218
|
+
if (typeof window === "undefined") return;
|
|
2219
|
+
if (window.location.hash === "#login" || window.location.hash === "#signup") {
|
|
2220
|
+
if (!isLoading && customer) {
|
|
2221
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
2222
|
+
} else if (!isLoading && !customer) {
|
|
2223
|
+
setIsModalOpen(true);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
handleHash();
|
|
2228
|
+
window.addEventListener("hashchange", handleHash);
|
|
2229
|
+
return () => window.removeEventListener("hashchange", handleHash);
|
|
2230
|
+
}, [customer, isLoading]);
|
|
2231
|
+
useEffect(() => {
|
|
2232
|
+
resetGraphQLClient();
|
|
2233
|
+
}, [customer]);
|
|
2234
|
+
const handleLogout = useCallback(async () => {
|
|
2235
|
+
await logout();
|
|
2236
|
+
setCustomer(null);
|
|
2237
|
+
setIsModalOpen(false);
|
|
2238
|
+
resetGraphQLClient();
|
|
2239
|
+
}, []);
|
|
2240
|
+
const value = {
|
|
2241
|
+
customer,
|
|
2242
|
+
isLoading,
|
|
2243
|
+
isModalOpen,
|
|
2244
|
+
openModal,
|
|
2245
|
+
closeModal,
|
|
2246
|
+
setCustomer,
|
|
2247
|
+
logout: handleLogout
|
|
2248
|
+
};
|
|
2249
|
+
if (!AuthCtx) return children;
|
|
2250
|
+
return React2.createElement(AuthCtx.Provider, { value }, children);
|
|
2251
|
+
}
|
|
2252
|
+
function useAuthUser() {
|
|
2253
|
+
return useQuery3({
|
|
2254
|
+
queryKey: ["auth-user"],
|
|
2255
|
+
queryFn: getAuthUser,
|
|
2256
|
+
enabled: !!tokenStore.getAccessToken()
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
function useLogin() {
|
|
2260
|
+
const queryClient = useQueryClient3();
|
|
2261
|
+
return useMutation3({
|
|
2262
|
+
mutationFn: loginCustomer,
|
|
2263
|
+
onSuccess: () => {
|
|
2264
|
+
queryClient.invalidateQueries({ queryKey: ["auth-user"] });
|
|
2265
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
function useRegister() {
|
|
2270
|
+
const queryClient = useQueryClient3();
|
|
2271
|
+
return useMutation3({
|
|
2272
|
+
mutationFn: registerCustomer,
|
|
2273
|
+
onSuccess: () => {
|
|
2274
|
+
queryClient.invalidateQueries({ queryKey: ["auth-user"] });
|
|
2275
|
+
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
function useUpdateProfile() {
|
|
2280
|
+
const queryClient = useQueryClient3();
|
|
2281
|
+
return useMutation3({
|
|
2282
|
+
mutationFn: updateProfile,
|
|
2283
|
+
onSuccess: () => {
|
|
2284
|
+
queryClient.invalidateQueries({ queryKey: ["auth-user"] });
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
function useLogout() {
|
|
2289
|
+
const queryClient = useQueryClient3();
|
|
2290
|
+
return useMutation3({
|
|
2291
|
+
mutationFn: logout,
|
|
2292
|
+
onSuccess: () => {
|
|
2293
|
+
queryClient.clear();
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
function useAuth() {
|
|
2298
|
+
const context = AuthCtx ? useContext2(AuthCtx) : null;
|
|
2299
|
+
if (context) return context;
|
|
2300
|
+
const authUserQuery = useAuthUser();
|
|
2301
|
+
const logoutMutation = useLogout();
|
|
2302
|
+
return {
|
|
2303
|
+
customer: authUserQuery.data,
|
|
2304
|
+
isLoading: authUserQuery.isLoading,
|
|
2305
|
+
isModalOpen: false,
|
|
2306
|
+
openModal: () => {
|
|
2307
|
+
},
|
|
2308
|
+
closeModal: () => {
|
|
2309
|
+
},
|
|
2310
|
+
setCustomer: () => {
|
|
2311
|
+
},
|
|
2312
|
+
logout: async () => {
|
|
2313
|
+
await logoutMutation.mutateAsync();
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// src/hooks/use-countries.ts
|
|
2319
|
+
import { useQuery as useQuery4 } from "@tanstack/react-query";
|
|
2320
|
+
async function getCountries() {
|
|
2321
|
+
const { data } = await authAxios.get("auth/countries/");
|
|
2322
|
+
return data;
|
|
2323
|
+
}
|
|
2324
|
+
function useCountries() {
|
|
2325
|
+
return useQuery4({
|
|
2326
|
+
queryKey: ["countries"],
|
|
2327
|
+
queryFn: () => getCountries(),
|
|
2328
|
+
retry: 1,
|
|
2329
|
+
refetchOnWindowFocus: false,
|
|
2330
|
+
refetchOnReconnect: false,
|
|
2331
|
+
staleTime: 24 * 60 * 60 * 1e3
|
|
2332
|
+
// 24 hours
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/hooks/use-states.ts
|
|
2337
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
2338
|
+
async function getStates(countryIso) {
|
|
2339
|
+
if (!countryIso) return [];
|
|
2340
|
+
const { data } = await authAxios.get("auth/countries/", {
|
|
2341
|
+
params: { iso: countryIso }
|
|
2342
|
+
});
|
|
2343
|
+
return data;
|
|
2344
|
+
}
|
|
2345
|
+
function useStates(countryIso) {
|
|
2346
|
+
return useQuery5({
|
|
2347
|
+
queryKey: ["states", countryIso],
|
|
2348
|
+
queryFn: () => getStates(countryIso),
|
|
2349
|
+
enabled: !!countryIso,
|
|
2350
|
+
retry: 1,
|
|
2351
|
+
refetchOnWindowFocus: false,
|
|
2352
|
+
refetchOnReconnect: false,
|
|
2353
|
+
staleTime: 24 * 60 * 60 * 1e3
|
|
2354
|
+
// 24 hours
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// src/hooks/use-languages.ts
|
|
2359
|
+
import { useQuery as useQuery6 } from "@tanstack/react-query";
|
|
2360
|
+
async function getSupportedLanguagesList() {
|
|
2361
|
+
const data = await getSupportedLanguages();
|
|
2362
|
+
return data.languages.map((language) => ({
|
|
2363
|
+
code: language.code,
|
|
2364
|
+
appCode: language.code,
|
|
2365
|
+
label: language.label,
|
|
2366
|
+
native: language.label,
|
|
2367
|
+
flag: language.flag
|
|
2368
|
+
}));
|
|
2369
|
+
}
|
|
2370
|
+
function useSupportedLanguages() {
|
|
2371
|
+
return useQuery6({
|
|
2372
|
+
queryKey: ["supported-languages"],
|
|
2373
|
+
queryFn: () => getSupportedLanguagesList(),
|
|
2374
|
+
enabled: isInitialized(),
|
|
2375
|
+
retry: 1,
|
|
2376
|
+
...getCacheTier("high")
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/hooks/use-currencies.ts
|
|
2381
|
+
import { useQuery as useQuery7 } from "@tanstack/react-query";
|
|
2382
|
+
async function getSupportedCurrenciesList() {
|
|
2383
|
+
const data = await getSupportedCurrencies();
|
|
2384
|
+
return data.currencies || [];
|
|
2385
|
+
}
|
|
2386
|
+
function useCurrencies() {
|
|
2387
|
+
return useQuery7({
|
|
2388
|
+
queryKey: ["supported-currencies"],
|
|
2389
|
+
queryFn: () => getSupportedCurrenciesList(),
|
|
2390
|
+
enabled: isInitialized(),
|
|
2391
|
+
retry: 1,
|
|
2392
|
+
...getCacheTier("high")
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// src/hooks/use-bespoke-availability.ts
|
|
2397
|
+
import { useQuery as useQuery8 } from "@tanstack/react-query";
|
|
2398
|
+
function useBespokeAvailability(product_id) {
|
|
2399
|
+
return useQuery8({
|
|
2400
|
+
queryKey: ["bespoke-availability", product_id],
|
|
2401
|
+
queryFn: () => bespokeService.checkAvailability(product_id),
|
|
2402
|
+
enabled: !!product_id,
|
|
2403
|
+
retry: false
|
|
2404
|
+
// Don't retry if no config exists (returns 404)
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
// src/hooks/use-collections.ts
|
|
2409
|
+
import { useQuery as useQuery9 } from "@tanstack/react-query";
|
|
2410
|
+
var collectionKeys = {
|
|
2411
|
+
all: ["core", "collections"],
|
|
2412
|
+
list: (params) => ["core", "collections", "list", params ?? {}],
|
|
2413
|
+
slug: (slug) => ["core", "collections", "slug", slug]
|
|
2414
|
+
};
|
|
2415
|
+
function useStorefrontCollections(params) {
|
|
2416
|
+
return useQuery9({
|
|
2417
|
+
queryKey: collectionKeys.list(params),
|
|
2418
|
+
queryFn: () => getStorefrontCollections(params),
|
|
2419
|
+
// medium — collections change infrequently
|
|
2420
|
+
...getCacheTier("medium")
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
function useStorefrontCollection(slug) {
|
|
2424
|
+
return useQuery9({
|
|
2425
|
+
queryKey: collectionKeys.slug(slug),
|
|
2426
|
+
queryFn: () => getStorefrontCollectionBySlug(slug),
|
|
2427
|
+
// medium — collection details change infrequently
|
|
2428
|
+
...getCacheTier("medium"),
|
|
2429
|
+
enabled: !!slug
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
// src/hooks/use-products.ts
|
|
2434
|
+
import { useQuery as useQuery10 } from "@tanstack/react-query";
|
|
2435
|
+
var productKeys = {
|
|
2436
|
+
all: ["products"],
|
|
2437
|
+
list: (params) => [...productKeys.all, "list", params],
|
|
2438
|
+
detail: (slug) => [...productKeys.all, "detail", slug],
|
|
2439
|
+
recommended: (params) => [...productKeys.all, "recommended", params],
|
|
2440
|
+
latest: (params) => [...productKeys.all, "latest", params],
|
|
2441
|
+
popular: (params) => [...productKeys.all, "popular", params],
|
|
2442
|
+
featured: (params) => [...productKeys.all, "featured", params],
|
|
2443
|
+
new: (params) => [...productKeys.all, "new", params]
|
|
2444
|
+
};
|
|
2445
|
+
function useLatestProducts(params) {
|
|
2446
|
+
return useQuery10({
|
|
2447
|
+
queryKey: productKeys.latest(params),
|
|
2448
|
+
queryFn: () => getLatestProducts(params),
|
|
2449
|
+
// medium — latest products change infrequently
|
|
2450
|
+
...getCacheTier("medium")
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
function usePopularProducts(params) {
|
|
2454
|
+
return useQuery10({
|
|
2455
|
+
queryKey: productKeys.popular(params),
|
|
2456
|
+
queryFn: () => getPopularProducts(params),
|
|
2457
|
+
// medium — popular products change infrequently
|
|
2458
|
+
...getCacheTier("medium")
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
function useFeaturedProducts(params) {
|
|
2462
|
+
return useQuery10({
|
|
2463
|
+
queryKey: productKeys.featured(params),
|
|
2464
|
+
queryFn: () => getFeaturedProducts(params),
|
|
2465
|
+
// medium — featured products change infrequently
|
|
2466
|
+
...getCacheTier("medium")
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
function useNewProducts(params) {
|
|
2470
|
+
return useQuery10({
|
|
2471
|
+
queryKey: productKeys.new(params),
|
|
2472
|
+
queryFn: () => getNewProducts(params),
|
|
2473
|
+
// medium — new products change infrequently
|
|
2474
|
+
...getCacheTier("medium")
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
function useStorefrontProducts(params) {
|
|
2478
|
+
return useQuery10({
|
|
2479
|
+
queryKey: productKeys.list(params),
|
|
2480
|
+
queryFn: () => getStorefrontProducts(params),
|
|
2481
|
+
// medium — product catalog changes infrequently
|
|
2482
|
+
...getCacheTier("medium")
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
function useProduct(slug) {
|
|
2486
|
+
return useQuery10({
|
|
2487
|
+
queryKey: productKeys.detail(slug),
|
|
2488
|
+
queryFn: () => getStorefrontProductBySlug(slug),
|
|
2489
|
+
// medium — product details change infrequently
|
|
2490
|
+
...getCacheTier("medium"),
|
|
2491
|
+
enabled: !!slug
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
function useRecommendedProducts(params) {
|
|
2495
|
+
return useQuery10({
|
|
2496
|
+
queryKey: productKeys.recommended(params),
|
|
2497
|
+
queryFn: () => getRecommendedProducts(params),
|
|
2498
|
+
// medium — recommendations change infrequently
|
|
2499
|
+
...getCacheTier("medium")
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// src/hooks/use-wishlist.ts
|
|
2504
|
+
import { useQuery as useQuery11, useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
2505
|
+
var wishlistKeys = {
|
|
2506
|
+
all: ["wishlist"],
|
|
2507
|
+
list: () => [...wishlistKeys.all, "list"],
|
|
2508
|
+
check: (productId) => [...wishlistKeys.all, "check", productId]
|
|
2509
|
+
};
|
|
2510
|
+
function useWishlist(params) {
|
|
2511
|
+
return useQuery11({
|
|
2512
|
+
queryKey: [...wishlistKeys.list(), params],
|
|
2513
|
+
queryFn: () => getWishlist(params),
|
|
2514
|
+
// medium — wishlist changes infrequently
|
|
2515
|
+
...getCacheTier("medium")
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
function useCheckInWishlist(productId) {
|
|
2519
|
+
return useQuery11({
|
|
2520
|
+
queryKey: wishlistKeys.check(productId),
|
|
2521
|
+
queryFn: () => checkInWishlist(productId),
|
|
2522
|
+
// low — wishlist check changes frequently
|
|
2523
|
+
...getCacheTier("low"),
|
|
2524
|
+
enabled: !!productId
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
function useAddToWishlist() {
|
|
2528
|
+
const queryClient = useQueryClient4();
|
|
2529
|
+
return useMutation4({
|
|
2530
|
+
mutationFn: ({ productId, notes }) => {
|
|
2531
|
+
return addToWishlist(productId, notes);
|
|
2532
|
+
},
|
|
2533
|
+
onSuccess: () => {
|
|
2534
|
+
queryClient.invalidateQueries({ queryKey: wishlistKeys.all });
|
|
2535
|
+
queryClient.invalidateQueries({ queryKey: ["wishlist", "check"] });
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
function useRemoveFromWishlist() {
|
|
2540
|
+
const queryClient = useQueryClient4();
|
|
2541
|
+
return useMutation4({
|
|
2542
|
+
mutationFn: (itemId) => {
|
|
2543
|
+
return removeFromWishlist(itemId);
|
|
2544
|
+
},
|
|
2545
|
+
onSuccess: () => {
|
|
2546
|
+
queryClient.invalidateQueries({ queryKey: wishlistKeys.all });
|
|
2547
|
+
queryClient.invalidateQueries({ queryKey: ["wishlist", "check"] });
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
function useRemoveProductFromWishlist() {
|
|
2552
|
+
const queryClient = useQueryClient4();
|
|
2553
|
+
return useMutation4({
|
|
2554
|
+
mutationFn: (productId) => {
|
|
2555
|
+
return removeProductFromWishlist(productId);
|
|
2556
|
+
},
|
|
2557
|
+
onSuccess: () => {
|
|
2558
|
+
queryClient.invalidateQueries({ queryKey: wishlistKeys.all });
|
|
2559
|
+
queryClient.invalidateQueries({ queryKey: ["wishlist", "check"] });
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
function useWishlistCount() {
|
|
2564
|
+
const backendQuery = useQuery11({
|
|
2565
|
+
queryKey: wishlistKeys.list(),
|
|
2566
|
+
queryFn: () => getWishlist(),
|
|
2567
|
+
// low — wishlist count changes frequently
|
|
2568
|
+
...getCacheTier("low"),
|
|
2569
|
+
select: (data) => data.total
|
|
2570
|
+
});
|
|
2571
|
+
return backendQuery;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
// src/hooks/use-service-booking.ts
|
|
2575
|
+
import { useQuery as useQuery12, useMutation as useMutation5, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
2576
|
+
function useAvailableSlots(product_id, date_from, date_to) {
|
|
2577
|
+
return useQuery12({
|
|
2578
|
+
queryKey: ["available-slots", product_id, date_from, date_to],
|
|
2579
|
+
queryFn: () => serviceBookingService.getAvailableSlots(product_id, date_from, date_to),
|
|
2580
|
+
enabled: !!product_id && !!date_from && !!date_to
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
function useBookAppointment() {
|
|
2584
|
+
const queryClient = useQueryClient5();
|
|
2585
|
+
return useMutation5({
|
|
2586
|
+
mutationFn: serviceBookingService.bookAppointment,
|
|
2587
|
+
onSuccess: () => {
|
|
2588
|
+
queryClient.invalidateQueries({ queryKey: ["appointments"] });
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
function useAppointments() {
|
|
2593
|
+
return useQuery12({
|
|
2594
|
+
queryKey: ["appointments"],
|
|
2595
|
+
queryFn: serviceBookingService.getAppointments
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
function useAppointment(id) {
|
|
2599
|
+
return useQuery12({
|
|
2600
|
+
queryKey: ["appointment", id],
|
|
2601
|
+
queryFn: () => serviceBookingService.getAppointment(id),
|
|
2602
|
+
enabled: !!id
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
function useCancelAppointment() {
|
|
2606
|
+
const queryClient = useQueryClient5();
|
|
2607
|
+
return useMutation5({
|
|
2608
|
+
mutationFn: (id) => serviceBookingService.cancelAppointment(id),
|
|
2609
|
+
onSuccess: () => {
|
|
2610
|
+
queryClient.invalidateQueries({ queryKey: ["appointments"] });
|
|
2611
|
+
}
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
function useDeliverables(order_id) {
|
|
2615
|
+
return useQuery12({
|
|
2616
|
+
queryKey: ["deliverables", order_id],
|
|
2617
|
+
queryFn: () => serviceBookingService.getDeliverables(order_id),
|
|
2618
|
+
enabled: !!order_id
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
function useApproveDeliverable(order_id, deliverable_id) {
|
|
2622
|
+
const queryClient = useQueryClient5();
|
|
2623
|
+
return useMutation5({
|
|
2624
|
+
mutationFn: () => serviceBookingService.approveDeliverable(order_id, deliverable_id),
|
|
2625
|
+
onSuccess: () => {
|
|
2626
|
+
queryClient.invalidateQueries({ queryKey: ["deliverables", order_id] });
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
function useRequestRevision(order_id, deliverable_id) {
|
|
2631
|
+
const queryClient = useQueryClient5();
|
|
2632
|
+
return useMutation5({
|
|
2633
|
+
mutationFn: (feedback) => serviceBookingService.requestRevision(order_id, deliverable_id, feedback),
|
|
2634
|
+
onSuccess: () => {
|
|
2635
|
+
queryClient.invalidateQueries({ queryKey: ["deliverables", order_id] });
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// src/hooks/use-consultations.ts
|
|
2641
|
+
import { useQuery as useQuery13, useMutation as useMutation6, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
2642
|
+
function useConsultations() {
|
|
2643
|
+
return useQuery13({
|
|
2644
|
+
queryKey: ["consultations"],
|
|
2645
|
+
queryFn: () => consultationService.getConsultations()
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
function useConsultation(id) {
|
|
2649
|
+
return useQuery13({
|
|
2650
|
+
queryKey: ["consultation", id],
|
|
2651
|
+
queryFn: () => consultationService.getConsultation(id),
|
|
2652
|
+
enabled: !!id
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
function useCreateConsultation() {
|
|
2656
|
+
const queryClient = useQueryClient6();
|
|
2657
|
+
return useMutation6({
|
|
2658
|
+
mutationFn: (payload) => consultationService.createConsultation(payload),
|
|
2659
|
+
onSuccess: () => {
|
|
2660
|
+
queryClient.invalidateQueries({ queryKey: ["consultations"] });
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
function useSendMessage(consultationId) {
|
|
2665
|
+
const queryClient = useQueryClient6();
|
|
2666
|
+
return useMutation6({
|
|
2667
|
+
mutationFn: (payload) => consultationService.sendMessage(consultationId, payload),
|
|
2668
|
+
onSuccess: () => {
|
|
2669
|
+
queryClient.invalidateQueries({ queryKey: ["consultation", consultationId] });
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
function useAcceptQuote(consultationId) {
|
|
2674
|
+
const queryClient = useQueryClient6();
|
|
2675
|
+
return useMutation6({
|
|
2676
|
+
mutationFn: (payload) => consultationService.acceptQuote(consultationId, payload),
|
|
2677
|
+
onSuccess: () => {
|
|
2678
|
+
queryClient.invalidateQueries({ queryKey: ["consultation", consultationId] });
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
function useRejectQuote(consultationId) {
|
|
2683
|
+
const queryClient = useQueryClient6();
|
|
2684
|
+
return useMutation6({
|
|
2685
|
+
mutationFn: (reason) => consultationService.rejectQuote(consultationId, reason),
|
|
2686
|
+
onSuccess: () => {
|
|
2687
|
+
queryClient.invalidateQueries({ queryKey: ["consultation", consultationId] });
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
// src/hooks/use-consultation-stream.ts
|
|
2693
|
+
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
2694
|
+
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
|
2695
|
+
function useConsultationStream({
|
|
2696
|
+
consultationId,
|
|
2697
|
+
onMessage,
|
|
2698
|
+
onQuote,
|
|
2699
|
+
onStatusChange,
|
|
2700
|
+
onError
|
|
2701
|
+
}) {
|
|
2702
|
+
const abortControllerRef = useRef(null);
|
|
2703
|
+
const [isConnected, setIsConnected] = useState3(false);
|
|
2704
|
+
const [lastEvent, setLastEvent] = useState3(null);
|
|
2705
|
+
useEffect2(() => {
|
|
2706
|
+
if (!consultationId) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const abortController = new AbortController();
|
|
2710
|
+
abortControllerRef.current = abortController;
|
|
2711
|
+
const paymentUrl = getBackendUrl("payment");
|
|
2712
|
+
const streamUrl = `${paymentUrl}/events/stream/consultation/${consultationId}`;
|
|
2713
|
+
fetchEventSource(streamUrl, {
|
|
2714
|
+
method: "GET",
|
|
2715
|
+
signal: abortController.signal,
|
|
2716
|
+
onmessage: (event) => {
|
|
2717
|
+
try {
|
|
2718
|
+
if (!event.data || event.data.trim() === "") {
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const data = JSON.parse(event.data);
|
|
2722
|
+
logger.log("[ConsultationStream] Received event:", data);
|
|
2723
|
+
setLastEvent(data);
|
|
2724
|
+
if (data.event === "consultation.message") {
|
|
2725
|
+
logger.log("[ConsultationStream] Message detected, calling onMessage");
|
|
2726
|
+
if (onMessage) {
|
|
2727
|
+
onMessage(data);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
if (data.event === "consultation.quote") {
|
|
2731
|
+
logger.log("[ConsultationStream] Quote detected, calling onQuote");
|
|
2732
|
+
if (onQuote) {
|
|
2733
|
+
onQuote(data);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (data.event === "consultation.status") {
|
|
2737
|
+
logger.log("[ConsultationStream] Status change detected, calling onStatusChange");
|
|
2738
|
+
if (onStatusChange) {
|
|
2739
|
+
onStatusChange(data);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
logger.error("[ConsultationStream] Error parsing event:", error);
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2746
|
+
onerror: (error) => {
|
|
2747
|
+
if (abortController.signal.aborted) {
|
|
2748
|
+
logger.log("[ConsultationStream] Connection closed via AbortController");
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
logger.error("[ConsultationStream] Connection error:", error);
|
|
2752
|
+
setIsConnected(false);
|
|
2753
|
+
if (onError) {
|
|
2754
|
+
onError(error);
|
|
2755
|
+
}
|
|
2756
|
+
},
|
|
2757
|
+
onopen: async (response) => {
|
|
2758
|
+
if (response.ok && response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
2759
|
+
logger.log("[ConsultationStream] Connection established for consultation:", consultationId);
|
|
2760
|
+
setIsConnected(true);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
throw new Error(`Unexpected response: ${response.status}`);
|
|
2764
|
+
}
|
|
2765
|
+
}).catch((error) => {
|
|
2766
|
+
if (abortController.signal.aborted || error.name === "AbortError") {
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
logger.error("[ConsultationStream] fetchEventSource failed:", error);
|
|
2770
|
+
});
|
|
2771
|
+
return () => {
|
|
2772
|
+
abortController.abort();
|
|
2773
|
+
setIsConnected(false);
|
|
2774
|
+
};
|
|
2775
|
+
}, [consultationId, onMessage, onQuote, onStatusChange, onError]);
|
|
2776
|
+
return { isConnected, lastEvent };
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// src/hooks/use-order-stream.ts
|
|
2780
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
2781
|
+
import { fetchEventSource as fetchEventSource2 } from "@microsoft/fetch-event-source";
|
|
2782
|
+
function useOrderStream({
|
|
2783
|
+
orderId,
|
|
2784
|
+
onPaymentComplete,
|
|
2785
|
+
onDigitalReady,
|
|
2786
|
+
onBespokeUpdate,
|
|
2787
|
+
onConsultationUpdate,
|
|
2788
|
+
onServiceBookingUpdate,
|
|
2789
|
+
onError
|
|
2790
|
+
}) {
|
|
2791
|
+
const abortControllerRef = useRef2(null);
|
|
2792
|
+
useEffect3(() => {
|
|
2793
|
+
if (!orderId) {
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
const abortController = new AbortController();
|
|
2797
|
+
abortControllerRef.current = abortController;
|
|
2798
|
+
const paymentUrl = getBackendUrl("payment");
|
|
2799
|
+
const streamUrl = `${paymentUrl}/events/stream/order/${encodeURIComponent(orderId)}`;
|
|
2800
|
+
fetchEventSource2(streamUrl, {
|
|
2801
|
+
method: "GET",
|
|
2802
|
+
signal: abortController.signal,
|
|
2803
|
+
onmessage: (event) => {
|
|
2804
|
+
try {
|
|
2805
|
+
if (!event.data || event.data.trim() === "") {
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
const data = JSON.parse(event.data);
|
|
2809
|
+
logger.log("[OrderStream] Received event:", data);
|
|
2810
|
+
if (data.event === "payment.success") {
|
|
2811
|
+
logger.log("[OrderStream] Payment success detected, calling onPaymentComplete");
|
|
2812
|
+
if (onPaymentComplete) {
|
|
2813
|
+
onPaymentComplete(data);
|
|
2814
|
+
}
|
|
2815
|
+
abortController.abort();
|
|
2816
|
+
}
|
|
2817
|
+
if (data.event === "digital.ready") {
|
|
2818
|
+
logger.log("[OrderStream] Digital delivery ready detected, calling onDigitalReady");
|
|
2819
|
+
if (onDigitalReady) {
|
|
2820
|
+
onDigitalReady(data);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
if (data.event === "bespoke.update") {
|
|
2824
|
+
logger.log("[OrderStream] Bespoke update detected, calling onBespokeUpdate");
|
|
2825
|
+
if (onBespokeUpdate) {
|
|
2826
|
+
onBespokeUpdate(data);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
if (data.event === "consultation.update") {
|
|
2830
|
+
logger.log("[OrderStream] Consultation update detected, calling onConsultationUpdate");
|
|
2831
|
+
if (onConsultationUpdate) {
|
|
2832
|
+
onConsultationUpdate(data);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
if (data.event === "service_booking.update") {
|
|
2836
|
+
logger.log("[OrderStream] Service booking update detected, calling onServiceBookingUpdate");
|
|
2837
|
+
if (onServiceBookingUpdate) {
|
|
2838
|
+
onServiceBookingUpdate(data);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
} catch (error) {
|
|
2842
|
+
logger.error("[OrderStream] Error parsing event:", error);
|
|
2843
|
+
}
|
|
2844
|
+
},
|
|
2845
|
+
onerror: (error) => {
|
|
2846
|
+
if (abortController.signal.aborted) {
|
|
2847
|
+
logger.log("[OrderStream] Connection closed via AbortController");
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
if (error.message?.includes("404") || error.message?.includes("Not Found")) {
|
|
2851
|
+
logger.warn("[OrderStream] Stream endpoint unavailable for order:", orderId);
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
logger.error("[OrderStream] Connection error:", error);
|
|
2855
|
+
if (onError) {
|
|
2856
|
+
onError(error);
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
onopen: async (response) => {
|
|
2860
|
+
if (response.ok && response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
2861
|
+
logger.log("[OrderStream] Connection established for order:", orderId);
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
if (response.status === 404) {
|
|
2865
|
+
logger.warn("[OrderStream] Stream endpoint unavailable for order:", orderId);
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
throw new Error(`Unexpected response: ${response.status}`);
|
|
2869
|
+
}
|
|
2870
|
+
}).catch((error) => {
|
|
2871
|
+
if (abortController.signal.aborted || error.name === "AbortError") {
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
logger.error("[OrderStream] fetchEventSource failed:", error);
|
|
2875
|
+
});
|
|
2876
|
+
return () => {
|
|
2877
|
+
abortController.abort();
|
|
2878
|
+
};
|
|
2879
|
+
}, [orderId, onPaymentComplete, onDigitalReady, onBespokeUpdate, onConsultationUpdate, onServiceBookingUpdate, onError]);
|
|
2880
|
+
return null;
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
// src/hooks/use-is-mounted.ts
|
|
2884
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
2885
|
+
function useIsMounted() {
|
|
2886
|
+
const [mounted, setMounted] = useState4(false);
|
|
2887
|
+
useEffect4(() => {
|
|
2888
|
+
setMounted(true);
|
|
2889
|
+
}, []);
|
|
2890
|
+
return mounted;
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// src/hooks/use-google-translate.ts
|
|
2894
|
+
import { useEffect as useEffect5, useCallback as useCallback2, useState as useState5, useRef as useRef3 } from "react";
|
|
2895
|
+
var SCRIPT_ID = "google-translate-script";
|
|
2896
|
+
var ELEMENT_ID = "google_translate_element";
|
|
2897
|
+
function fireChange(googleCode) {
|
|
2898
|
+
const combo = document.querySelector(".goog-te-combo");
|
|
2899
|
+
if (combo) {
|
|
2900
|
+
combo.value = googleCode;
|
|
2901
|
+
combo.dispatchEvent(new Event("change"));
|
|
2902
|
+
return true;
|
|
2903
|
+
}
|
|
2904
|
+
return false;
|
|
2905
|
+
}
|
|
2906
|
+
function useGoogleTranslate(appLang) {
|
|
2907
|
+
const [isLoaded, setIsLoaded] = useState5(false);
|
|
2908
|
+
const initRef = useRef3(false);
|
|
2909
|
+
const { data: supportedLangs = [] } = useSupportedLanguages();
|
|
2910
|
+
const clearCookies = useCallback2(() => {
|
|
2911
|
+
if (typeof window === "undefined") return;
|
|
2912
|
+
const domains = [
|
|
2913
|
+
window.location.hostname,
|
|
2914
|
+
"." + window.location.hostname,
|
|
2915
|
+
"." + window.location.hostname.split(".").slice(-2).join(".")
|
|
2916
|
+
];
|
|
2917
|
+
const path = "/";
|
|
2918
|
+
document.cookie = `googtrans=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path};`;
|
|
2919
|
+
domains.forEach((domain) => {
|
|
2920
|
+
document.cookie = `googtrans=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}; domain=${domain};`;
|
|
2921
|
+
});
|
|
2922
|
+
}, []);
|
|
2923
|
+
const resetToEnglish = useCallback2(() => {
|
|
2924
|
+
if (typeof window === "undefined") return;
|
|
2925
|
+
const googtrans = document.cookie.split("; ").find((row) => row.startsWith("googtrans="))?.split("=")[1];
|
|
2926
|
+
const domains = [
|
|
2927
|
+
window.location.hostname,
|
|
2928
|
+
"." + window.location.hostname,
|
|
2929
|
+
window.location.hostname.split(".").slice(-2).join("."),
|
|
2930
|
+
"." + window.location.hostname.split(".").slice(-2).join(".")
|
|
2931
|
+
];
|
|
2932
|
+
const clear = (name) => {
|
|
2933
|
+
document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
|
|
2934
|
+
domains.forEach((d) => {
|
|
2935
|
+
document.cookie = `${name}=; Path=/; Domain=${d}; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
|
|
2936
|
+
});
|
|
2937
|
+
};
|
|
2938
|
+
clear("googtrans");
|
|
2939
|
+
if (googtrans || document.documentElement.classList.contains("translated-ltr") || document.querySelector(".goog-te-banner-frame")) {
|
|
2940
|
+
window.location.reload();
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
document.body.style.top = "0";
|
|
2944
|
+
document.documentElement.classList.remove("translated-ltr", "translated-rtl");
|
|
2945
|
+
const container = document.getElementById(":1.container");
|
|
2946
|
+
if (container) container.style.display = "none";
|
|
2947
|
+
}, []);
|
|
2948
|
+
const changeLanguage = useCallback2((nextAppCode) => {
|
|
2949
|
+
if (nextAppCode === "en") {
|
|
2950
|
+
resetToEnglish();
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const googleCode = nextAppCode;
|
|
2954
|
+
const container = document.getElementById(":1.container");
|
|
2955
|
+
if (container) container.style.display = "block";
|
|
2956
|
+
let tries = 0;
|
|
2957
|
+
const tick = () => {
|
|
2958
|
+
if (fireChange(googleCode)) return;
|
|
2959
|
+
if (tries++ < 30) setTimeout(tick, 200);
|
|
2960
|
+
};
|
|
2961
|
+
tick();
|
|
2962
|
+
}, [resetToEnglish]);
|
|
2963
|
+
useEffect5(() => {
|
|
2964
|
+
if (typeof window === "undefined") return;
|
|
2965
|
+
if (appLang === "en" && !initRef.current) return;
|
|
2966
|
+
if (initRef.current) return;
|
|
2967
|
+
if (supportedLangs.length === 0) return;
|
|
2968
|
+
initRef.current = true;
|
|
2969
|
+
if (!document.getElementById(ELEMENT_ID)) {
|
|
2970
|
+
const host = document.createElement("div");
|
|
2971
|
+
host.id = ELEMENT_ID;
|
|
2972
|
+
host.style.display = "none";
|
|
2973
|
+
document.body.appendChild(host);
|
|
2974
|
+
}
|
|
2975
|
+
const includedLanguages = supportedLangs.map((l) => l.code).join(",");
|
|
2976
|
+
window.googleTranslateElementInit = () => {
|
|
2977
|
+
if (window.google?.translate?.TranslateElement) {
|
|
2978
|
+
new window.google.translate.TranslateElement(
|
|
2979
|
+
{ pageLanguage: "en", includedLanguages, autoDisplay: false },
|
|
2980
|
+
ELEMENT_ID
|
|
2981
|
+
);
|
|
2982
|
+
setIsLoaded(true);
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
if (!document.getElementById(SCRIPT_ID)) {
|
|
2986
|
+
const script = document.createElement("script");
|
|
2987
|
+
script.id = SCRIPT_ID;
|
|
2988
|
+
script.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
|
|
2989
|
+
script.async = true;
|
|
2990
|
+
document.body.appendChild(script);
|
|
2991
|
+
} else {
|
|
2992
|
+
if (window.google?.translate) window.googleTranslateElementInit?.();
|
|
2993
|
+
}
|
|
2994
|
+
}, [appLang, supportedLangs]);
|
|
2995
|
+
useEffect5(() => {
|
|
2996
|
+
if (isLoaded || appLang === "en" && initRef.current) {
|
|
2997
|
+
changeLanguage(appLang);
|
|
2998
|
+
}
|
|
2999
|
+
}, [appLang, isLoaded, changeLanguage]);
|
|
3000
|
+
return { isLoaded };
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// src/hooks/use-payment.ts
|
|
3004
|
+
import { useQuery as useQuery14, useMutation as useMutation7, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
|
|
3005
|
+
function useCreateDVA() {
|
|
3006
|
+
const queryClient = useQueryClient7();
|
|
3007
|
+
return useMutation7({
|
|
3008
|
+
mutationFn: (request) => paymentService.createDedicatedAccount(request),
|
|
3009
|
+
onSuccess: () => {
|
|
3010
|
+
queryClient.invalidateQueries({ queryKey: ["payment"] });
|
|
3011
|
+
}
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
function useInitiatePayment() {
|
|
3015
|
+
return useMutation7({
|
|
3016
|
+
mutationFn: (request) => paymentService.initiatePayment(request)
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
function useVerifyTransaction() {
|
|
3020
|
+
return useMutation7({
|
|
3021
|
+
mutationFn: (reference) => paymentService.verifyTransaction(reference)
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
function useTransactionStatus(transactionId, enabled = false) {
|
|
3025
|
+
return useQuery14({
|
|
3026
|
+
queryKey: ["transaction", "status", transactionId],
|
|
3027
|
+
queryFn: () => paymentService.getTransactionStatus(transactionId),
|
|
3028
|
+
enabled: isInitialized() && enabled,
|
|
3029
|
+
...getCacheTier("none"),
|
|
3030
|
+
refetchInterval: 3e3
|
|
3031
|
+
// Poll every 3 seconds
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
function usePollTransactionStatus(reference, enabled = false) {
|
|
3035
|
+
return useQuery14({
|
|
3036
|
+
queryKey: ["transaction", "poll", reference],
|
|
3037
|
+
queryFn: () => paymentService.verifyTransaction(reference),
|
|
3038
|
+
enabled: isInitialized() && enabled,
|
|
3039
|
+
...getCacheTier("none"),
|
|
3040
|
+
refetchInterval: 2e3,
|
|
3041
|
+
// Poll every 2 seconds
|
|
3042
|
+
retry: 10
|
|
3043
|
+
// Retry up to 10 times
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/hooks/use-cms.ts
|
|
3048
|
+
import { useQuery as useQuery15, useMutation as useMutation8, useQueryClient as useQueryClient8 } from "@tanstack/react-query";
|
|
3049
|
+
function useCmsEntries(modelSlug, params) {
|
|
3050
|
+
const query = useQuery15({
|
|
3051
|
+
queryKey: ["cms-entries", modelSlug, params],
|
|
3052
|
+
queryFn: () => listCmsEntries(modelSlug, params),
|
|
3053
|
+
enabled: isInitialized() && !!modelSlug
|
|
3054
|
+
});
|
|
3055
|
+
return {
|
|
3056
|
+
entries: query.data?.entries || [],
|
|
3057
|
+
total: query.data?.total || 0,
|
|
3058
|
+
page: query.data?.page || 1,
|
|
3059
|
+
page_size: query.data?.page_size || 50,
|
|
3060
|
+
isLoading: query.isLoading,
|
|
3061
|
+
error: query.error,
|
|
3062
|
+
refetch: query.refetch
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
function useCmsEntry(modelSlug, entryId) {
|
|
3066
|
+
const query = useQuery15({
|
|
3067
|
+
queryKey: ["cms-entry", modelSlug, entryId],
|
|
3068
|
+
queryFn: () => getCmsEntry(modelSlug, entryId),
|
|
3069
|
+
enabled: isInitialized() && !!modelSlug && !!entryId
|
|
3070
|
+
});
|
|
3071
|
+
return {
|
|
3072
|
+
entry: query.data,
|
|
3073
|
+
isLoading: query.isLoading,
|
|
3074
|
+
error: query.error,
|
|
3075
|
+
refetch: query.refetch
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
function useCreateCmsEntry(modelSlug) {
|
|
3079
|
+
const queryClient = useQueryClient8();
|
|
3080
|
+
const mutation = useMutation8({
|
|
3081
|
+
mutationFn: (params) => createCmsEntry(modelSlug, params),
|
|
3082
|
+
onSuccess: () => {
|
|
3083
|
+
queryClient.invalidateQueries({ queryKey: ["cms-entries", modelSlug] });
|
|
3084
|
+
}
|
|
3085
|
+
});
|
|
3086
|
+
return {
|
|
3087
|
+
createEntry: mutation.mutate,
|
|
3088
|
+
isLoading: mutation.isPending,
|
|
3089
|
+
error: mutation.error
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
function useUpdateCmsEntry(modelSlug) {
|
|
3093
|
+
const queryClient = useQueryClient8();
|
|
3094
|
+
const mutation = useMutation8({
|
|
3095
|
+
mutationFn: ({ entryId, params }) => updateCmsEntry(modelSlug, entryId, params),
|
|
3096
|
+
onSuccess: () => {
|
|
3097
|
+
queryClient.invalidateQueries({ queryKey: ["cms-entries", modelSlug] });
|
|
3098
|
+
queryClient.invalidateQueries({ queryKey: ["cms-entry", modelSlug] });
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
3101
|
+
return {
|
|
3102
|
+
updateEntry: (entryId, params) => mutation.mutate({ entryId, params }),
|
|
3103
|
+
isLoading: mutation.isPending,
|
|
3104
|
+
error: mutation.error
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
function useDeleteCmsEntry(modelSlug) {
|
|
3108
|
+
const queryClient = useQueryClient8();
|
|
3109
|
+
const mutation = useMutation8({
|
|
3110
|
+
mutationFn: (entryId) => deleteCmsEntry(modelSlug, entryId),
|
|
3111
|
+
onSuccess: () => {
|
|
3112
|
+
queryClient.invalidateQueries({ queryKey: ["cms-entries", modelSlug] });
|
|
3113
|
+
queryClient.invalidateQueries({ queryKey: ["cms-entry", modelSlug] });
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
return {
|
|
3117
|
+
deleteEntry: mutation.mutate,
|
|
3118
|
+
isLoading: mutation.isPending,
|
|
3119
|
+
error: mutation.error
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
export {
|
|
3123
|
+
AuthCtx,
|
|
3124
|
+
AuthProvider,
|
|
3125
|
+
CartCtx,
|
|
3126
|
+
CartProvider,
|
|
3127
|
+
InvalidBaseURLError,
|
|
3128
|
+
acceptQuote,
|
|
3129
|
+
addToCart,
|
|
3130
|
+
addToGuestCart,
|
|
3131
|
+
addToWishlist,
|
|
3132
|
+
approveDeliverable,
|
|
3133
|
+
authAxios,
|
|
3134
|
+
bespokeService,
|
|
3135
|
+
bookAppointment,
|
|
3136
|
+
buildDeliveryAddress,
|
|
3137
|
+
buildFullName,
|
|
3138
|
+
cancelAppointment,
|
|
3139
|
+
cancelOrder,
|
|
3140
|
+
checkAvailability,
|
|
3141
|
+
checkInWishlist,
|
|
3142
|
+
clearCart,
|
|
3143
|
+
clearGuestCart,
|
|
3144
|
+
clearGuestSessionId,
|
|
3145
|
+
cmsAxios,
|
|
3146
|
+
collectionKeys,
|
|
3147
|
+
configService,
|
|
3148
|
+
configureHttpClients,
|
|
3149
|
+
consultationService,
|
|
3150
|
+
createCmsEntry,
|
|
3151
|
+
createConsultation,
|
|
3152
|
+
createDedicatedAccount,
|
|
3153
|
+
createOrder,
|
|
3154
|
+
deleteCmsEntry,
|
|
3155
|
+
enrichCompletedOrder,
|
|
3156
|
+
findOrder,
|
|
3157
|
+
findOrderByToken,
|
|
3158
|
+
getAppointment,
|
|
3159
|
+
getAppointments,
|
|
3160
|
+
getAuthUser,
|
|
3161
|
+
getAvailableSlots,
|
|
3162
|
+
getBackendUrl,
|
|
3163
|
+
getBusinessConfig,
|
|
3164
|
+
getCacheTier,
|
|
3165
|
+
getCart,
|
|
3166
|
+
getCheckoutCustomerName,
|
|
3167
|
+
getCmsEntry,
|
|
3168
|
+
getConsultation,
|
|
3169
|
+
getConsultations,
|
|
3170
|
+
getCountries,
|
|
3171
|
+
getCustomerDisplayName,
|
|
3172
|
+
getDeliverables,
|
|
3173
|
+
getErrorMessage,
|
|
3174
|
+
getFeaturedProducts,
|
|
3175
|
+
getGraphQLClient,
|
|
3176
|
+
getGuestCart,
|
|
3177
|
+
getGuestCartCount,
|
|
3178
|
+
getGuestCartProductType,
|
|
3179
|
+
getGuestCartSubtotal,
|
|
3180
|
+
getGuestSessionId,
|
|
3181
|
+
getLatestProducts,
|
|
3182
|
+
getMe,
|
|
3183
|
+
getNewProducts,
|
|
3184
|
+
getOrCreateGuestSessionId,
|
|
3185
|
+
getOrders,
|
|
3186
|
+
getPopularProducts,
|
|
3187
|
+
getRecommendedProducts,
|
|
3188
|
+
getRuntimeConfig,
|
|
3189
|
+
getSellerShippingAddress,
|
|
3190
|
+
getShippingRates,
|
|
3191
|
+
getStates,
|
|
3192
|
+
getStorefrontCollectionBySlug,
|
|
3193
|
+
getStorefrontCollections,
|
|
3194
|
+
getStorefrontProductBySlug,
|
|
3195
|
+
getStorefrontProducts,
|
|
3196
|
+
getSupportedCurrencies,
|
|
3197
|
+
getSupportedCurrenciesList,
|
|
3198
|
+
getSupportedLanguages,
|
|
3199
|
+
getSupportedLanguagesList,
|
|
3200
|
+
getTransactionStatus,
|
|
3201
|
+
getWishlist,
|
|
3202
|
+
googleTranslate,
|
|
3203
|
+
initSdk,
|
|
3204
|
+
initiatePayment,
|
|
3205
|
+
isInitialized,
|
|
3206
|
+
listCmsEntries,
|
|
3207
|
+
logger,
|
|
3208
|
+
loginCustomer,
|
|
3209
|
+
logisticsService,
|
|
3210
|
+
logout,
|
|
3211
|
+
mapAuthUserToCustomer,
|
|
3212
|
+
mergeGuestCart,
|
|
3213
|
+
mergeGuestCartIfPresent,
|
|
3214
|
+
notificationsService,
|
|
3215
|
+
paymentAxios,
|
|
3216
|
+
paymentService,
|
|
3217
|
+
productKeys,
|
|
3218
|
+
publicStoreAxios,
|
|
3219
|
+
refreshToken,
|
|
3220
|
+
registerCustomer,
|
|
3221
|
+
rejectQuote,
|
|
3222
|
+
removeFromCart,
|
|
3223
|
+
removeFromGuestCart,
|
|
3224
|
+
removeFromWishlist,
|
|
3225
|
+
removeProductFromWishlist,
|
|
3226
|
+
requestQuote,
|
|
3227
|
+
requestRevision,
|
|
3228
|
+
resetConfig,
|
|
3229
|
+
resetGraphQLClient,
|
|
3230
|
+
sanitizeBaseUrl,
|
|
3231
|
+
sendContactConfirmation,
|
|
3232
|
+
sendContactEmail,
|
|
3233
|
+
sendMessage,
|
|
3234
|
+
serviceBookingService,
|
|
3235
|
+
setEnvironmentMode,
|
|
3236
|
+
setLoggerEnvironmentMode,
|
|
3237
|
+
silentRefresh,
|
|
3238
|
+
splitFullName,
|
|
3239
|
+
storeManagementAxios,
|
|
3240
|
+
storeTokens,
|
|
3241
|
+
tokenStore,
|
|
3242
|
+
trackOrder,
|
|
3243
|
+
updateCartItem,
|
|
3244
|
+
updateCmsEntry,
|
|
3245
|
+
updateGuestCartItemQuantity,
|
|
3246
|
+
updateProfile,
|
|
3247
|
+
useAcceptQuote,
|
|
3248
|
+
useAddToWishlist,
|
|
3249
|
+
useAppointment,
|
|
3250
|
+
useAppointments,
|
|
3251
|
+
useApproveDeliverable,
|
|
3252
|
+
useAuth,
|
|
3253
|
+
useAuthUser,
|
|
3254
|
+
useAvailableSlots,
|
|
3255
|
+
useBespokeAvailability,
|
|
3256
|
+
useBookAppointment,
|
|
3257
|
+
useCancelAppointment,
|
|
3258
|
+
useCancelOrder,
|
|
3259
|
+
useCart,
|
|
3260
|
+
useCheckInWishlist,
|
|
3261
|
+
useCmsEntries,
|
|
3262
|
+
useCmsEntry,
|
|
3263
|
+
useConsultation,
|
|
3264
|
+
useConsultationStream,
|
|
3265
|
+
useConsultations,
|
|
3266
|
+
useCountries,
|
|
3267
|
+
useCreateCmsEntry,
|
|
3268
|
+
useCreateConsultation,
|
|
3269
|
+
useCreateDVA,
|
|
3270
|
+
useCreateOrder,
|
|
3271
|
+
useCurrencies,
|
|
3272
|
+
useDeleteCmsEntry,
|
|
3273
|
+
useDeliverables,
|
|
3274
|
+
useFeaturedProducts,
|
|
3275
|
+
useFindOrder,
|
|
3276
|
+
useGoogleTranslate,
|
|
3277
|
+
useInitiatePayment,
|
|
3278
|
+
useIsMounted,
|
|
3279
|
+
useLatestProducts,
|
|
3280
|
+
useLogin,
|
|
3281
|
+
useLogout,
|
|
3282
|
+
useNewProducts,
|
|
3283
|
+
useOrder,
|
|
3284
|
+
useOrderStream,
|
|
3285
|
+
useOrderTracking,
|
|
3286
|
+
useOrders,
|
|
3287
|
+
useOrdersList,
|
|
3288
|
+
usePollTransactionStatus,
|
|
3289
|
+
usePopularProducts,
|
|
3290
|
+
useProduct,
|
|
3291
|
+
useRecommendedProducts,
|
|
3292
|
+
useRegister,
|
|
3293
|
+
useRejectQuote,
|
|
3294
|
+
useRemoveFromWishlist,
|
|
3295
|
+
useRemoveProductFromWishlist,
|
|
3296
|
+
useRequestRevision,
|
|
3297
|
+
useSendMessage,
|
|
3298
|
+
useStates,
|
|
3299
|
+
useStorefrontCollection,
|
|
3300
|
+
useStorefrontCollections,
|
|
3301
|
+
useStorefrontProducts,
|
|
3302
|
+
useSupportedLanguages,
|
|
3303
|
+
useTransactionStatus,
|
|
3304
|
+
useUpdateCmsEntry,
|
|
3305
|
+
useUpdateProfile,
|
|
3306
|
+
useVerifyTransaction,
|
|
3307
|
+
useWishlist,
|
|
3308
|
+
useWishlistCount,
|
|
3309
|
+
validateShippingAddress,
|
|
3310
|
+
verifySocialToken,
|
|
3311
|
+
verifyTransaction,
|
|
3312
|
+
wishlistKeys
|
|
3313
|
+
};
|
|
3314
|
+
//# sourceMappingURL=index.mjs.map
|