@kasuvia/sdk 0.0.1 → 0.0.4
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/README.md +1 -1
- package/dist/hooks/index.d.mts +429 -0
- package/dist/hooks/index.d.ts +429 -0
- package/dist/hooks/index.js +3073 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2962 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +279 -1152
- package/dist/index.d.ts +279 -1152
- package/dist/index.js +1471 -2208
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1451 -2107
- package/dist/index.mjs.map +1 -1
- package/dist/sdk-context-Cy51kEKT.d.mts +565 -0
- package/dist/sdk-context-Cy51kEKT.d.ts +565 -0
- package/package.json +6 -2
- package/src/config/index.ts +10 -91
- package/src/constants.ts +13 -0
- package/src/context/sdk-context.tsx +70 -0
- package/src/graphql/collections.graphql.ts +2 -10
- package/src/graphql/products.graphql.ts +215 -65
- package/src/graphql/wishlist.graphql.ts +7 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-auth.ts +82 -158
- package/src/hooks/use-bespoke-availability.ts +25 -8
- package/src/hooks/use-cart.ts +222 -119
- package/src/hooks/use-cms.ts +179 -86
- package/src/hooks/use-collections.ts +52 -15
- package/src/hooks/use-consultation-stream.ts +4 -3
- package/src/hooks/use-consultations.ts +120 -51
- package/src/hooks/use-countries.ts +35 -13
- package/src/hooks/use-currencies.ts +26 -13
- package/src/hooks/use-google-translate.ts +22 -8
- package/src/hooks/use-languages.ts +40 -20
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-order-stream.ts +9 -5
- package/src/hooks/use-orders.ts +99 -44
- package/src/hooks/use-payment.ts +108 -40
- package/src/hooks/use-products.ts +251 -53
- package/src/hooks/use-service-booking.ts +152 -90
- package/src/hooks/use-states.ts +35 -15
- package/src/hooks/use-wishlist.ts +92 -91
- package/src/index.ts +27 -28
- package/src/services/auth.service.ts +209 -167
- package/src/services/bespoke.service.ts +12 -4
- package/src/services/cart.service.ts +172 -70
- package/src/services/cms.service.ts +70 -35
- package/src/services/collection.service.ts +22 -5
- package/src/services/config.service.ts +34 -14
- package/src/services/consultation.service.ts +72 -18
- package/src/services/logistics.service.ts +35 -11
- package/src/services/notifications.service.ts +27 -12
- package/src/services/order.service.ts +74 -15
- package/src/services/payment.service.ts +86 -31
- package/src/services/product.service.ts +279 -39
- package/src/services/service-booking.service.ts +98 -26
- package/src/services/wishlist.service.ts +69 -25
- package/src/types/auth/index.ts +27 -4
- package/src/types/business/index.ts +7 -2
- package/src/types/payments/index.ts +3 -3
- package/src/types/services/index.ts +2 -5
- package/src/types/store/index.ts +44 -5
- package/src/utils/customer.ts +14 -0
- package/src/utils/error-handler.ts +10 -5
- package/src/utils/storage.ts +93 -0
- package/src/utils/validators.ts +85 -0
- package/src/client/authClient.ts +0 -135
- package/src/client/graphqlClient.ts +0 -53
- package/src/client/httpClient.ts +0 -298
package/dist/index.mjs
CHANGED
|
@@ -1,558 +1,42 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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;
|
|
1
|
+
// src/context/sdk-context.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var SdkContext = createContext(null);
|
|
5
|
+
function SdkProvider({ config, accessToken, children }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(SdkContext.Provider, { value: { config, accessToken }, children });
|
|
15
7
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return CACHE_TIERS[tier];
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// src/utils/logger.ts
|
|
22
|
-
var isDevelopment2 = true;
|
|
23
|
-
function setLoggerEnvironmentMode(development) {
|
|
24
|
-
isDevelopment2 = development;
|
|
8
|
+
function useSdkContext() {
|
|
9
|
+
return useContext(SdkContext);
|
|
25
10
|
}
|
|
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
11
|
|
|
86
|
-
// src/
|
|
87
|
-
var
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
12
|
+
// src/constants.ts
|
|
13
|
+
var STORAGE_KEYS = {
|
|
14
|
+
ACCESS_TOKEN: "kasuvia_access_token",
|
|
15
|
+
REFRESH_TOKEN: "kasuvia_refresh_token",
|
|
16
|
+
CHECKOUT_STATE: "kasuvia_checkout_state",
|
|
17
|
+
ORDERS_LINKED: "kasuvia_orders_linked",
|
|
18
|
+
STOREFRONT_STATE: "storefront_state_v3"
|
|
92
19
|
};
|
|
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
20
|
|
|
127
|
-
// src/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
}
|
|
21
|
+
// src/types/store/index.ts
|
|
22
|
+
var ServiceTypeEnum = /* @__PURE__ */ ((ServiceTypeEnum2) => {
|
|
23
|
+
ServiceTypeEnum2["APPOINTMENT"] = "appointment";
|
|
24
|
+
ServiceTypeEnum2["PROJECT"] = "project";
|
|
25
|
+
ServiceTypeEnum2["RECURRING"] = "recurring";
|
|
26
|
+
return ServiceTypeEnum2;
|
|
27
|
+
})(ServiceTypeEnum || {});
|
|
28
|
+
var ServiceLocationTypeEnum = /* @__PURE__ */ ((ServiceLocationTypeEnum2) => {
|
|
29
|
+
ServiceLocationTypeEnum2["REMOTE"] = "remote";
|
|
30
|
+
ServiceLocationTypeEnum2["PHYSICAL"] = "physical";
|
|
31
|
+
ServiceLocationTypeEnum2["BOTH"] = "both";
|
|
32
|
+
return ServiceLocationTypeEnum2;
|
|
33
|
+
})(ServiceLocationTypeEnum || {});
|
|
34
|
+
var ServiceRecurringIntervalEnum = /* @__PURE__ */ ((ServiceRecurringIntervalEnum2) => {
|
|
35
|
+
ServiceRecurringIntervalEnum2["WEEKLY"] = "weekly";
|
|
36
|
+
ServiceRecurringIntervalEnum2["MONTHLY"] = "monthly";
|
|
37
|
+
ServiceRecurringIntervalEnum2["YEARLY"] = "yearly";
|
|
38
|
+
return ServiceRecurringIntervalEnum2;
|
|
39
|
+
})(ServiceRecurringIntervalEnum || {});
|
|
556
40
|
|
|
557
41
|
// src/services/cart.service.ts
|
|
558
42
|
function parseItems(items) {
|
|
@@ -561,21 +45,25 @@ function parseItems(items) {
|
|
|
561
45
|
variant_attributes: item.variant_attributes && typeof item.variant_attributes === "string" ? JSON.parse(item.variant_attributes) : item.variant_attributes
|
|
562
46
|
}));
|
|
563
47
|
}
|
|
564
|
-
async function getGuestCart() {
|
|
565
|
-
|
|
566
|
-
if (!sid) return [];
|
|
48
|
+
async function getGuestCart(config, guestSessionId) {
|
|
49
|
+
if (!guestSessionId) return [];
|
|
567
50
|
try {
|
|
568
|
-
const
|
|
569
|
-
headers: {
|
|
51
|
+
const response = await fetch(`${config.backendUrls.store}/cart/`, {
|
|
52
|
+
headers: {
|
|
53
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
54
|
+
"X-Business-ID": config.businessId,
|
|
55
|
+
"X-Guest-Session": guestSessionId
|
|
56
|
+
}
|
|
570
57
|
});
|
|
58
|
+
if (!response.ok) return [];
|
|
59
|
+
const data = await response.json();
|
|
571
60
|
return parseItems(data.items || []);
|
|
572
61
|
} catch {
|
|
573
62
|
return [];
|
|
574
63
|
}
|
|
575
64
|
}
|
|
576
|
-
async function addToGuestCart(item) {
|
|
577
|
-
|
|
578
|
-
if (!sid) return [];
|
|
65
|
+
async function addToGuestCart(config, guestSessionId, item) {
|
|
66
|
+
if (!guestSessionId) return [];
|
|
579
67
|
const payload = {
|
|
580
68
|
product_id: item.product_id,
|
|
581
69
|
product_slug: item.product_slug,
|
|
@@ -588,66 +76,132 @@ async function addToGuestCart(item) {
|
|
|
588
76
|
custom_measurements: item.custom_measurements,
|
|
589
77
|
is_bespoke: item.is_bespoke
|
|
590
78
|
};
|
|
591
|
-
const
|
|
592
|
-
|
|
79
|
+
const response = await fetch(`${config.backendUrls.store}/cart/`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
84
|
+
"X-Business-ID": config.businessId,
|
|
85
|
+
"X-Guest-Session": guestSessionId
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify(payload)
|
|
593
88
|
});
|
|
89
|
+
if (!response.ok) return [];
|
|
90
|
+
const data = await response.json();
|
|
594
91
|
return parseItems(data.items || []);
|
|
595
92
|
}
|
|
596
|
-
async function updateGuestCartItemQuantity(itemId, quantity) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
93
|
+
async function updateGuestCartItemQuantity(config, guestSessionId, itemId, quantity) {
|
|
94
|
+
if (!guestSessionId) return [];
|
|
95
|
+
const response = await fetch(`${config.backendUrls.store}/cart/${itemId}`, {
|
|
96
|
+
method: "PATCH",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
100
|
+
"X-Business-ID": config.businessId,
|
|
101
|
+
"X-Guest-Session": guestSessionId
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({ quantity })
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) return [];
|
|
106
|
+
const data = await response.json();
|
|
604
107
|
return parseItems(data.items || []);
|
|
605
108
|
}
|
|
606
|
-
async function removeFromGuestCart(itemId) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
headers: {
|
|
109
|
+
async function removeFromGuestCart(config, guestSessionId, itemId) {
|
|
110
|
+
if (!guestSessionId) return [];
|
|
111
|
+
await fetch(`${config.backendUrls.store}/cart/${itemId}`, {
|
|
112
|
+
method: "DELETE",
|
|
113
|
+
headers: {
|
|
114
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
115
|
+
"X-Business-ID": config.businessId,
|
|
116
|
+
"X-Guest-Session": guestSessionId
|
|
117
|
+
}
|
|
611
118
|
});
|
|
612
|
-
return getGuestCart();
|
|
119
|
+
return getGuestCart(config, guestSessionId);
|
|
613
120
|
}
|
|
614
|
-
async function clearGuestCart() {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
headers: {
|
|
121
|
+
async function clearGuestCart(config, guestSessionId) {
|
|
122
|
+
if (!guestSessionId) return;
|
|
123
|
+
await fetch(`${config.backendUrls.store}/cart/clear`, {
|
|
124
|
+
method: "DELETE",
|
|
125
|
+
headers: {
|
|
126
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
127
|
+
"X-Business-ID": config.businessId,
|
|
128
|
+
"X-Guest-Session": guestSessionId
|
|
129
|
+
}
|
|
619
130
|
});
|
|
620
131
|
}
|
|
621
|
-
async function getCart() {
|
|
622
|
-
|
|
132
|
+
async function getCart(config, accessToken) {
|
|
133
|
+
if (!accessToken) {
|
|
134
|
+
return { id: "", items: [], subtotal: 0, total: 0, product_type: "physical" };
|
|
135
|
+
}
|
|
136
|
+
const response = await fetch(`${config.backendUrls.store}/cart/`, {
|
|
137
|
+
headers: {
|
|
138
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
139
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
140
|
+
"X-Business-ID": config.businessId
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) throw new Error("Failed to fetch cart");
|
|
144
|
+
const data = await response.json();
|
|
623
145
|
const parsedItems = parseItems(data.items || []);
|
|
624
146
|
return { ...data, items: parsedItems };
|
|
625
147
|
}
|
|
626
|
-
async function addToCart(item) {
|
|
627
|
-
const
|
|
148
|
+
async function addToCart(config, accessToken, item) {
|
|
149
|
+
const response = await fetch(`${config.backendUrls.store}/cart/`, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
154
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
155
|
+
"X-Business-ID": config.businessId
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(item)
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) throw new Error("Failed to add to cart");
|
|
160
|
+
const data = await response.json();
|
|
628
161
|
const parsedItems = parseItems(data.items || []);
|
|
629
162
|
return { ...data, items: parsedItems };
|
|
630
163
|
}
|
|
631
|
-
async function updateCartItem(itemId, quantity) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
{
|
|
635
|
-
|
|
164
|
+
async function updateCartItem(config, accessToken, itemId, quantity) {
|
|
165
|
+
const response = await fetch(`${config.backendUrls.store}/cart/${itemId}`, {
|
|
166
|
+
method: "PATCH",
|
|
167
|
+
headers: {
|
|
168
|
+
"Content-Type": "application/json",
|
|
169
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
170
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
171
|
+
"X-Business-ID": config.businessId
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify({ quantity })
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) throw new Error("Failed to update cart item");
|
|
176
|
+
const data = await response.json();
|
|
636
177
|
const parsedItems = parseItems(data.items || []);
|
|
637
178
|
return { ...data, items: parsedItems };
|
|
638
179
|
}
|
|
639
|
-
async function removeFromCart(itemId) {
|
|
640
|
-
await
|
|
641
|
-
|
|
180
|
+
async function removeFromCart(config, accessToken, itemId) {
|
|
181
|
+
await fetch(`${config.backendUrls.store}/cart/${itemId}`, {
|
|
182
|
+
method: "DELETE",
|
|
183
|
+
headers: {
|
|
184
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
185
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
186
|
+
"X-Business-ID": config.businessId
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return getCart(config, accessToken);
|
|
642
190
|
}
|
|
643
|
-
async function clearCart() {
|
|
644
|
-
await
|
|
191
|
+
async function clearCart(config, accessToken) {
|
|
192
|
+
await fetch(`${config.backendUrls.store}/cart/clear`, {
|
|
193
|
+
method: "DELETE",
|
|
194
|
+
headers: {
|
|
195
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
196
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
197
|
+
"X-Business-ID": config.businessId
|
|
198
|
+
}
|
|
199
|
+
});
|
|
645
200
|
}
|
|
646
|
-
async function mergeGuestCart() {
|
|
647
|
-
const
|
|
648
|
-
const guestItems = sid ? await getGuestCart() : [];
|
|
201
|
+
async function mergeGuestCart(config, accessToken, guestSessionId) {
|
|
202
|
+
const guestItems = guestSessionId ? await getGuestCart(config, guestSessionId) : [];
|
|
649
203
|
if (guestItems.length === 0) {
|
|
650
|
-
return getCart();
|
|
204
|
+
return getCart(config, accessToken);
|
|
651
205
|
}
|
|
652
206
|
const payloadItems = guestItems.map((item) => ({
|
|
653
207
|
product_id: item.product_id,
|
|
@@ -661,19 +215,29 @@ async function mergeGuestCart() {
|
|
|
661
215
|
custom_measurements: typeof item.custom_measurements === "string" ? item.custom_measurements : JSON.stringify(item.custom_measurements ?? {}),
|
|
662
216
|
is_bespoke: item.is_bespoke
|
|
663
217
|
}));
|
|
664
|
-
const
|
|
665
|
-
"
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
218
|
+
const headers = {
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
221
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
222
|
+
"X-Business-ID": config.businessId
|
|
223
|
+
};
|
|
224
|
+
if (guestSessionId) {
|
|
225
|
+
headers["X-Guest-Session"] = guestSessionId;
|
|
226
|
+
}
|
|
227
|
+
const response = await fetch(`${config.backendUrls.store}/cart/merge`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers,
|
|
230
|
+
body: JSON.stringify({ items: payloadItems })
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) throw new Error("Failed to merge guest cart");
|
|
233
|
+
const data = await response.json();
|
|
670
234
|
return { ...data, items: parseItems(data.items || []) };
|
|
671
235
|
}
|
|
672
|
-
async function mergeGuestCartIfPresent() {
|
|
236
|
+
async function mergeGuestCartIfPresent(config, accessToken, guestSessionId) {
|
|
673
237
|
try {
|
|
674
|
-
const guestItems = await getGuestCart();
|
|
238
|
+
const guestItems = await getGuestCart(config, guestSessionId);
|
|
675
239
|
if (guestItems.length === 0) return;
|
|
676
|
-
await mergeGuestCart();
|
|
240
|
+
await mergeGuestCart(config, accessToken, guestSessionId);
|
|
677
241
|
} catch (error) {
|
|
678
242
|
console.warn("[Cart] Failed to merge guest cart:", error);
|
|
679
243
|
}
|
|
@@ -747,88 +311,173 @@ function normaliseStorefrontOrder(raw) {
|
|
|
747
311
|
guestName: raw.guest_name
|
|
748
312
|
};
|
|
749
313
|
}
|
|
750
|
-
async function createOrder(payload) {
|
|
751
|
-
const
|
|
314
|
+
async function createOrder(config, accessToken, payload) {
|
|
315
|
+
const response = await fetch(`${config.backendUrls.store}/orders/storefront`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
320
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
321
|
+
"X-Business-ID": config.businessId
|
|
322
|
+
},
|
|
323
|
+
body: JSON.stringify(payload)
|
|
324
|
+
});
|
|
325
|
+
if (!response.ok) throw new Error("Failed to create order");
|
|
326
|
+
const data = await response.json();
|
|
752
327
|
return normaliseStorefrontOrder(data);
|
|
753
328
|
}
|
|
754
|
-
async function findOrder(params) {
|
|
755
|
-
const
|
|
329
|
+
async function findOrder(config, params) {
|
|
330
|
+
const queryParams = new URLSearchParams();
|
|
331
|
+
if (params.email) queryParams.set("email", params.email);
|
|
332
|
+
if (params.order_id) queryParams.set("order_id", params.order_id);
|
|
333
|
+
if (params.guest_token) queryParams.set("guest_token", params.guest_token);
|
|
334
|
+
const response = await fetch(`${config.backendUrls.store}/orders/find/?${queryParams.toString()}`, {
|
|
335
|
+
headers: {
|
|
336
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
337
|
+
"X-Business-ID": config.businessId
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) throw new Error("Failed to find order");
|
|
341
|
+
const data = await response.json();
|
|
756
342
|
return normaliseStorefrontOrder(data);
|
|
757
343
|
}
|
|
758
|
-
async function findOrderByToken(token) {
|
|
759
|
-
const
|
|
760
|
-
|
|
344
|
+
async function findOrderByToken(config, token) {
|
|
345
|
+
const response = await fetch(`${config.backendUrls.store}/orders/find/?guest_token=${token}`, {
|
|
346
|
+
headers: {
|
|
347
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
348
|
+
"X-Business-ID": config.businessId
|
|
349
|
+
}
|
|
761
350
|
});
|
|
351
|
+
if (!response.ok) throw new Error("Failed to find order by token");
|
|
352
|
+
const data = await response.json();
|
|
762
353
|
return normaliseStorefrontOrder(data);
|
|
763
354
|
}
|
|
764
|
-
async function getOrders(params) {
|
|
765
|
-
const
|
|
355
|
+
async function getOrders(config, accessToken, params) {
|
|
356
|
+
const queryParams = new URLSearchParams();
|
|
357
|
+
if (params?.email) queryParams.set("email", params.email);
|
|
358
|
+
if (params?.page) queryParams.set("page", params.page.toString());
|
|
359
|
+
if (params?.page_size) queryParams.set("page_size", params.page_size.toString());
|
|
360
|
+
const response = await fetch(`${config.backendUrls.store}/orders/?${queryParams.toString()}`, {
|
|
361
|
+
headers: {
|
|
362
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
363
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
364
|
+
"X-Business-ID": config.businessId
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
if (!response.ok) throw new Error("Failed to get orders");
|
|
368
|
+
const data = await response.json();
|
|
766
369
|
const items = Array.isArray(data) ? data : data.items ?? [];
|
|
767
370
|
return items.map(normaliseStorefrontOrder);
|
|
768
371
|
}
|
|
769
|
-
async function cancelOrder(orderId, reason) {
|
|
770
|
-
const
|
|
372
|
+
async function cancelOrder(config, accessToken, orderId, reason) {
|
|
373
|
+
const response = await fetch(`${config.backendUrls.store}/orders/${orderId}/cancel/`, {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: {
|
|
376
|
+
"Content-Type": "application/json",
|
|
377
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
378
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
379
|
+
"X-Business-ID": config.businessId
|
|
380
|
+
},
|
|
381
|
+
body: JSON.stringify({ reason })
|
|
382
|
+
});
|
|
383
|
+
if (!response.ok) throw new Error("Failed to cancel order");
|
|
384
|
+
const data = await response.json();
|
|
771
385
|
return normaliseStorefrontOrder(data);
|
|
772
386
|
}
|
|
773
|
-
async function trackOrder(orderId, token) {
|
|
774
|
-
const
|
|
775
|
-
|
|
387
|
+
async function trackOrder(config, orderId, token) {
|
|
388
|
+
const queryParams = token ? `?token=${token}` : "";
|
|
389
|
+
const response = await fetch(`${config.backendUrls.store}/orders/track/${orderId}${queryParams}`, {
|
|
390
|
+
headers: {
|
|
391
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
392
|
+
"X-Business-ID": config.businessId
|
|
393
|
+
}
|
|
776
394
|
});
|
|
395
|
+
if (!response.ok) throw new Error("Failed to track order");
|
|
396
|
+
const data = await response.json();
|
|
777
397
|
return normaliseStorefrontOrder(data);
|
|
778
398
|
}
|
|
779
399
|
|
|
780
400
|
// src/services/auth.service.ts
|
|
781
|
-
async function loginCustomer(payload) {
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
401
|
+
async function loginCustomer(config, payload) {
|
|
402
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/login/customer/`, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
headers: {
|
|
405
|
+
"Content-Type": "application/json",
|
|
406
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
407
|
+
"X-Business-ID": config.businessId
|
|
408
|
+
},
|
|
409
|
+
body: JSON.stringify({
|
|
410
|
+
...payload,
|
|
411
|
+
tenant_id: config.businessId
|
|
412
|
+
})
|
|
786
413
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
await
|
|
414
|
+
if (!response.ok) {
|
|
415
|
+
throw new Error("Login failed");
|
|
416
|
+
}
|
|
417
|
+
const data = await response.json();
|
|
791
418
|
return data;
|
|
792
419
|
}
|
|
793
|
-
async function registerCustomer(payload) {
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
420
|
+
async function registerCustomer(config, payload) {
|
|
421
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/register/customer/`, {
|
|
422
|
+
method: "POST",
|
|
423
|
+
headers: {
|
|
424
|
+
"Content-Type": "application/json",
|
|
425
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
426
|
+
"X-Business-ID": config.businessId
|
|
427
|
+
},
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
...payload,
|
|
430
|
+
tenant_id: config.businessId
|
|
431
|
+
})
|
|
798
432
|
});
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
await
|
|
433
|
+
if (!response.ok) {
|
|
434
|
+
throw new Error("Registration failed");
|
|
435
|
+
}
|
|
436
|
+
const data = await response.json();
|
|
803
437
|
return data;
|
|
804
438
|
}
|
|
805
|
-
async function
|
|
806
|
-
const
|
|
439
|
+
async function refreshToken(config, refresh) {
|
|
440
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/token/refresh/`, {
|
|
441
|
+
method: "POST",
|
|
442
|
+
headers: {
|
|
443
|
+
"Content-Type": "application/json"
|
|
444
|
+
},
|
|
445
|
+
body: JSON.stringify({ refresh })
|
|
446
|
+
});
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
throw new Error("Token refresh failed");
|
|
449
|
+
}
|
|
450
|
+
const data = await response.json();
|
|
807
451
|
return data;
|
|
808
452
|
}
|
|
809
|
-
async function logout() {
|
|
810
|
-
const refresh = tokenStore.getRefreshToken();
|
|
453
|
+
async function logout(config, refresh) {
|
|
811
454
|
const payload = refresh ? { refresh } : {};
|
|
812
455
|
try {
|
|
813
|
-
await
|
|
456
|
+
await fetch(`${config.backendUrls.auth}/auth/logout/`, {
|
|
457
|
+
method: "POST",
|
|
458
|
+
headers: {
|
|
459
|
+
"Content-Type": "application/json"
|
|
460
|
+
},
|
|
461
|
+
body: JSON.stringify(payload)
|
|
462
|
+
});
|
|
814
463
|
} catch {
|
|
815
|
-
} finally {
|
|
816
|
-
tokenStore.clearAll();
|
|
817
|
-
resetGraphQLClient();
|
|
818
464
|
}
|
|
819
465
|
}
|
|
820
|
-
async function verifySocialToken(provider, token) {
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
466
|
+
async function verifySocialToken(config, provider, token) {
|
|
467
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/social/verify/${provider}/`, {
|
|
468
|
+
method: "POST",
|
|
469
|
+
headers: {
|
|
470
|
+
"Content-Type": "application/json"
|
|
471
|
+
},
|
|
472
|
+
body: JSON.stringify({
|
|
825
473
|
id_token: token,
|
|
826
474
|
storefront_key: config.storefrontApiKey
|
|
827
|
-
}
|
|
828
|
-
);
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
475
|
+
})
|
|
476
|
+
});
|
|
477
|
+
if (!response.ok) {
|
|
478
|
+
throw new Error("Social token verification failed");
|
|
479
|
+
}
|
|
480
|
+
const data = await response.json();
|
|
832
481
|
return data;
|
|
833
482
|
}
|
|
834
483
|
function parseJwtPayload(token) {
|
|
@@ -862,7 +511,7 @@ function mapAuthUserToCustomer(data, provider) {
|
|
|
862
511
|
firstname,
|
|
863
512
|
lastname,
|
|
864
513
|
phone,
|
|
865
|
-
full_name:
|
|
514
|
+
full_name: buildFullName(firstname, lastname),
|
|
866
515
|
avatar: resolveAvatar(data.profile?.profile_picture),
|
|
867
516
|
provider: provider ?? resolveProvider(data)
|
|
868
517
|
};
|
|
@@ -870,7 +519,7 @@ function mapAuthUserToCustomer(data, provider) {
|
|
|
870
519
|
function buildCustomerFromClaims(claims) {
|
|
871
520
|
const provider = claims.is_google_auth_enabled ? "google" : claims.is_facebook_auth_enabled ? "facebook" : "email";
|
|
872
521
|
const jwtFullName = String(claims.full_name ?? claims.name ?? "").trim() || void 0;
|
|
873
|
-
const { firstname: splitFirst, lastname: splitLast } =
|
|
522
|
+
const { firstname: splitFirst, lastname: splitLast } = splitFullName(jwtFullName);
|
|
874
523
|
return {
|
|
875
524
|
id: String(claims.user_id ?? claims.sub ?? ""),
|
|
876
525
|
email: String(claims.email ?? ""),
|
|
@@ -882,86 +531,91 @@ function buildCustomerFromClaims(claims) {
|
|
|
882
531
|
provider
|
|
883
532
|
};
|
|
884
533
|
}
|
|
885
|
-
async function getMe() {
|
|
886
|
-
|
|
534
|
+
async function getMe(config, accessToken) {
|
|
535
|
+
if (!config || !accessToken) {
|
|
536
|
+
const response2 = await fetch("/api/auth/me", { credentials: "include" });
|
|
537
|
+
if (!response2.ok) throw new Error("Failed to fetch profile");
|
|
538
|
+
const data2 = await response2.json();
|
|
539
|
+
return data2.user;
|
|
540
|
+
}
|
|
541
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
542
|
+
headers: {
|
|
543
|
+
"Authorization": `Bearer ${accessToken}`
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
if (!response.ok) {
|
|
547
|
+
throw new Error("Failed to fetch profile");
|
|
548
|
+
}
|
|
549
|
+
const data = await response.json();
|
|
887
550
|
return mapAuthUserToCustomer(data);
|
|
888
551
|
}
|
|
889
|
-
async function getAuthUser() {
|
|
890
|
-
|
|
552
|
+
async function getAuthUser(config, accessToken) {
|
|
553
|
+
if (!config || !accessToken) {
|
|
554
|
+
const response2 = await fetch("/api/auth/me", { credentials: "include" });
|
|
555
|
+
if (!response2.ok) throw new Error("Failed to fetch auth user");
|
|
556
|
+
const data2 = await response2.json();
|
|
557
|
+
return data2.user;
|
|
558
|
+
}
|
|
559
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
560
|
+
headers: {
|
|
561
|
+
"Authorization": `Bearer ${accessToken}`
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
if (!response.ok) {
|
|
565
|
+
throw new Error("Failed to fetch auth user");
|
|
566
|
+
}
|
|
567
|
+
const data = await response.json();
|
|
891
568
|
return data;
|
|
892
569
|
}
|
|
893
|
-
async function updateProfile(
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
}
|
|
570
|
+
async function updateProfile(configOrPayload, accessTokenOrPayload, payloadParam) {
|
|
571
|
+
const isArg1Config = typeof configOrPayload === "object" && configOrPayload !== null && "businessId" in configOrPayload;
|
|
572
|
+
if (!isArg1Config) {
|
|
573
|
+
const payload2 = configOrPayload;
|
|
574
|
+
const response2 = await fetch("/api/auth/profile", {
|
|
575
|
+
method: "PUT",
|
|
576
|
+
headers: { "Content-Type": "application/json" },
|
|
577
|
+
credentials: "include",
|
|
578
|
+
body: JSON.stringify(payload2)
|
|
579
|
+
});
|
|
580
|
+
if (!response2.ok) throw new Error("Failed to update profile");
|
|
581
|
+
return response2.json();
|
|
922
582
|
}
|
|
923
|
-
|
|
583
|
+
const config = configOrPayload;
|
|
584
|
+
const accessToken = accessTokenOrPayload;
|
|
585
|
+
const payload = payloadParam;
|
|
586
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
587
|
+
method: "PUT",
|
|
588
|
+
headers: {
|
|
589
|
+
"Content-Type": "application/json",
|
|
590
|
+
"Authorization": `Bearer ${accessToken}`
|
|
591
|
+
},
|
|
592
|
+
body: JSON.stringify(payload)
|
|
593
|
+
});
|
|
594
|
+
if (!response.ok) {
|
|
595
|
+
throw new Error("Failed to update profile");
|
|
596
|
+
}
|
|
597
|
+
const data = await response.json();
|
|
598
|
+
return mapAuthUserToCustomer(data);
|
|
924
599
|
}
|
|
925
|
-
async function silentRefresh() {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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();
|
|
600
|
+
async function silentRefresh(config, refresh) {
|
|
601
|
+
try {
|
|
602
|
+
const data = await refreshToken(config, refresh);
|
|
603
|
+
if (!data.access || !data.refresh) {
|
|
949
604
|
return null;
|
|
950
|
-
} finally {
|
|
951
|
-
refreshPromise = null;
|
|
952
605
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
606
|
+
const claims = parseJwtPayload(data.access);
|
|
607
|
+
const user = buildCustomerFromClaims(claims);
|
|
608
|
+
const provider = user.provider;
|
|
609
|
+
return { user, provider, newAccessToken: data.access, newRefreshToken: data.refresh };
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
959
613
|
}
|
|
960
|
-
function
|
|
614
|
+
function buildFullName(firstname, lastname) {
|
|
961
615
|
if (firstname && lastname) return `${firstname} ${lastname}`;
|
|
962
616
|
return firstname || lastname || "";
|
|
963
617
|
}
|
|
964
|
-
function
|
|
618
|
+
function splitFullName(fullName) {
|
|
965
619
|
if (!fullName) return { firstname: "", lastname: "" };
|
|
966
620
|
const parts = fullName.trim().split(/\s+/);
|
|
967
621
|
if (parts.length === 1) {
|
|
@@ -972,8 +626,39 @@ function splitFullName2(fullName) {
|
|
|
972
626
|
lastname: parts.slice(1).join(" ")
|
|
973
627
|
};
|
|
974
628
|
}
|
|
629
|
+
function storeTokens(accessToken, refreshToken2) {
|
|
630
|
+
if (typeof window !== "undefined") {
|
|
631
|
+
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
|
|
632
|
+
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken2);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function clearTokens() {
|
|
636
|
+
if (typeof window !== "undefined") {
|
|
637
|
+
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
638
|
+
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function getStoredTokens() {
|
|
642
|
+
if (typeof window === "undefined") {
|
|
643
|
+
return { accessToken: null, refreshToken: null };
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
accessToken: localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN),
|
|
647
|
+
refreshToken: localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
975
650
|
|
|
976
651
|
// src/services/product.service.ts
|
|
652
|
+
import { GraphQLClient } from "graphql-request";
|
|
653
|
+
function createGraphQLClient(config) {
|
|
654
|
+
const headers = {
|
|
655
|
+
"Content-Type": "application/json",
|
|
656
|
+
"Accept": "application/json",
|
|
657
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
658
|
+
"X-Business-ID": config.businessId
|
|
659
|
+
};
|
|
660
|
+
return new GraphQLClient(`${config.backendUrls.store}/graphql`, { headers });
|
|
661
|
+
}
|
|
977
662
|
var GET_STOREFRONT_PRODUCTS = `
|
|
978
663
|
query GetStorefrontProducts($page: Int!, $pageSize: Int!, $collectionSlug: String, $search: String) {
|
|
979
664
|
storefront_products(page: $page, pageSize: $pageSize, collectionSlug: $collectionSlug, search: $search) {
|
|
@@ -990,7 +675,7 @@ var GET_STOREFRONT_PRODUCTS = `
|
|
|
990
675
|
is_bespoke
|
|
991
676
|
base_price
|
|
992
677
|
currency
|
|
993
|
-
images
|
|
678
|
+
images
|
|
994
679
|
tags
|
|
995
680
|
custom_fields { label unit required }
|
|
996
681
|
is_published
|
|
@@ -1013,7 +698,7 @@ var GET_STOREFRONT_PRODUCTS = `
|
|
|
1013
698
|
sku
|
|
1014
699
|
attributes
|
|
1015
700
|
price
|
|
1016
|
-
images
|
|
701
|
+
images
|
|
1017
702
|
weight_kg
|
|
1018
703
|
is_active
|
|
1019
704
|
inventory {
|
|
@@ -1040,7 +725,7 @@ var GET_STOREFRONT_PRODUCTS = `
|
|
|
1040
725
|
name
|
|
1041
726
|
slug
|
|
1042
727
|
description
|
|
1043
|
-
image
|
|
728
|
+
image
|
|
1044
729
|
collection_type
|
|
1045
730
|
rules
|
|
1046
731
|
status
|
|
@@ -1071,7 +756,7 @@ var GET_STOREFRONT_PRODUCT_BY_SLUG = `
|
|
|
1071
756
|
is_bespoke
|
|
1072
757
|
base_price
|
|
1073
758
|
currency
|
|
1074
|
-
images
|
|
759
|
+
images
|
|
1075
760
|
tags
|
|
1076
761
|
custom_fields { label unit required }
|
|
1077
762
|
is_published
|
|
@@ -1094,7 +779,7 @@ var GET_STOREFRONT_PRODUCT_BY_SLUG = `
|
|
|
1094
779
|
sku
|
|
1095
780
|
attributes
|
|
1096
781
|
price
|
|
1097
|
-
images
|
|
782
|
+
images
|
|
1098
783
|
weight_kg
|
|
1099
784
|
is_active
|
|
1100
785
|
inventory {
|
|
@@ -1121,7 +806,7 @@ var GET_STOREFRONT_PRODUCT_BY_SLUG = `
|
|
|
1121
806
|
name
|
|
1122
807
|
slug
|
|
1123
808
|
description
|
|
1124
|
-
image
|
|
809
|
+
image
|
|
1125
810
|
collection_type
|
|
1126
811
|
rules
|
|
1127
812
|
status
|
|
@@ -1148,7 +833,7 @@ var GET_STOREFRONT_LATEST = `
|
|
|
1148
833
|
is_bespoke
|
|
1149
834
|
base_price
|
|
1150
835
|
currency
|
|
1151
|
-
images
|
|
836
|
+
images
|
|
1152
837
|
tags
|
|
1153
838
|
custom_fields { label unit required }
|
|
1154
839
|
is_published
|
|
@@ -1171,7 +856,7 @@ var GET_STOREFRONT_LATEST = `
|
|
|
1171
856
|
sku
|
|
1172
857
|
attributes
|
|
1173
858
|
price
|
|
1174
|
-
images
|
|
859
|
+
images
|
|
1175
860
|
weight_kg
|
|
1176
861
|
is_active
|
|
1177
862
|
inventory {
|
|
@@ -1198,7 +883,7 @@ var GET_STOREFRONT_LATEST = `
|
|
|
1198
883
|
name
|
|
1199
884
|
slug
|
|
1200
885
|
description
|
|
1201
|
-
image
|
|
886
|
+
image
|
|
1202
887
|
collection_type
|
|
1203
888
|
rules
|
|
1204
889
|
status
|
|
@@ -1230,7 +915,7 @@ var GET_STOREFRONT_POPULAR = `
|
|
|
1230
915
|
is_bespoke
|
|
1231
916
|
base_price
|
|
1232
917
|
currency
|
|
1233
|
-
images
|
|
918
|
+
images
|
|
1234
919
|
tags
|
|
1235
920
|
custom_fields { label unit required }
|
|
1236
921
|
is_published
|
|
@@ -1253,7 +938,7 @@ var GET_STOREFRONT_POPULAR = `
|
|
|
1253
938
|
sku
|
|
1254
939
|
attributes
|
|
1255
940
|
price
|
|
1256
|
-
images
|
|
941
|
+
images
|
|
1257
942
|
weight_kg
|
|
1258
943
|
is_active
|
|
1259
944
|
inventory {
|
|
@@ -1280,7 +965,7 @@ var GET_STOREFRONT_POPULAR = `
|
|
|
1280
965
|
name
|
|
1281
966
|
slug
|
|
1282
967
|
description
|
|
1283
|
-
image
|
|
968
|
+
image
|
|
1284
969
|
collection_type
|
|
1285
970
|
rules
|
|
1286
971
|
status
|
|
@@ -1312,7 +997,7 @@ var GET_STOREFRONT_FEATURED = `
|
|
|
1312
997
|
is_bespoke
|
|
1313
998
|
base_price
|
|
1314
999
|
currency
|
|
1315
|
-
images
|
|
1000
|
+
images
|
|
1316
1001
|
tags
|
|
1317
1002
|
custom_fields { label unit required }
|
|
1318
1003
|
is_published
|
|
@@ -1335,7 +1020,7 @@ var GET_STOREFRONT_FEATURED = `
|
|
|
1335
1020
|
sku
|
|
1336
1021
|
attributes
|
|
1337
1022
|
price
|
|
1338
|
-
images
|
|
1023
|
+
images
|
|
1339
1024
|
weight_kg
|
|
1340
1025
|
is_active
|
|
1341
1026
|
inventory {
|
|
@@ -1362,7 +1047,7 @@ var GET_STOREFRONT_FEATURED = `
|
|
|
1362
1047
|
name
|
|
1363
1048
|
slug
|
|
1364
1049
|
description
|
|
1365
|
-
image
|
|
1050
|
+
image
|
|
1366
1051
|
collection_type
|
|
1367
1052
|
rules
|
|
1368
1053
|
status
|
|
@@ -1394,7 +1079,180 @@ var GET_STOREFRONT_NEW = `
|
|
|
1394
1079
|
is_bespoke
|
|
1395
1080
|
base_price
|
|
1396
1081
|
currency
|
|
1397
|
-
|
|
1082
|
+
sale_price
|
|
1083
|
+
sale_start
|
|
1084
|
+
sale_end
|
|
1085
|
+
images
|
|
1086
|
+
tags
|
|
1087
|
+
custom_fields { label unit required }
|
|
1088
|
+
is_published
|
|
1089
|
+
is_featured
|
|
1090
|
+
is_popular
|
|
1091
|
+
is_new
|
|
1092
|
+
is_temporary
|
|
1093
|
+
digital_file_key
|
|
1094
|
+
download_limit
|
|
1095
|
+
download_expires_at
|
|
1096
|
+
weight_kg
|
|
1097
|
+
length_cm
|
|
1098
|
+
width_cm
|
|
1099
|
+
height_cm
|
|
1100
|
+
created_at
|
|
1101
|
+
updated_at
|
|
1102
|
+
variants {
|
|
1103
|
+
id
|
|
1104
|
+
product_id
|
|
1105
|
+
sku
|
|
1106
|
+
attributes
|
|
1107
|
+
price
|
|
1108
|
+
images
|
|
1109
|
+
weight_kg
|
|
1110
|
+
is_active
|
|
1111
|
+
inventory {
|
|
1112
|
+
id
|
|
1113
|
+
product_id
|
|
1114
|
+
variant_id
|
|
1115
|
+
quantity
|
|
1116
|
+
reserved
|
|
1117
|
+
reorder_point
|
|
1118
|
+
reorder_quantity
|
|
1119
|
+
track_quantity
|
|
1120
|
+
allow_backorder
|
|
1121
|
+
available
|
|
1122
|
+
is_in_stock
|
|
1123
|
+
is_low_stock
|
|
1124
|
+
updated_at
|
|
1125
|
+
}
|
|
1126
|
+
created_at
|
|
1127
|
+
updated_at
|
|
1128
|
+
}
|
|
1129
|
+
collections {
|
|
1130
|
+
id
|
|
1131
|
+
business_id
|
|
1132
|
+
name
|
|
1133
|
+
slug
|
|
1134
|
+
description
|
|
1135
|
+
image
|
|
1136
|
+
collection_type
|
|
1137
|
+
rules
|
|
1138
|
+
status
|
|
1139
|
+
sort_order
|
|
1140
|
+
created_at
|
|
1141
|
+
updated_at
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
total
|
|
1145
|
+
page
|
|
1146
|
+
page_size
|
|
1147
|
+
total_pages
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
`;
|
|
1151
|
+
var GET_STOREFRONT_ON_SALE = `
|
|
1152
|
+
query GetStorefrontOnSale($page: Int!, $pageSize: Int!) {
|
|
1153
|
+
storefront_on_sale(page: $page, pageSize: $pageSize) {
|
|
1154
|
+
items {
|
|
1155
|
+
id
|
|
1156
|
+
business_id
|
|
1157
|
+
name
|
|
1158
|
+
slug
|
|
1159
|
+
description
|
|
1160
|
+
brand
|
|
1161
|
+
product_type
|
|
1162
|
+
requires_shipping
|
|
1163
|
+
is_consumable
|
|
1164
|
+
is_bespoke
|
|
1165
|
+
base_price
|
|
1166
|
+
currency
|
|
1167
|
+
sale_price
|
|
1168
|
+
sale_start
|
|
1169
|
+
sale_end
|
|
1170
|
+
images
|
|
1171
|
+
tags
|
|
1172
|
+
custom_fields { label unit required }
|
|
1173
|
+
is_published
|
|
1174
|
+
is_featured
|
|
1175
|
+
is_popular
|
|
1176
|
+
is_new
|
|
1177
|
+
is_temporary
|
|
1178
|
+
digital_file_key
|
|
1179
|
+
download_limit
|
|
1180
|
+
download_expires_at
|
|
1181
|
+
weight_kg
|
|
1182
|
+
length_cm
|
|
1183
|
+
width_cm
|
|
1184
|
+
height_cm
|
|
1185
|
+
created_at
|
|
1186
|
+
updated_at
|
|
1187
|
+
variants {
|
|
1188
|
+
id
|
|
1189
|
+
product_id
|
|
1190
|
+
sku
|
|
1191
|
+
attributes
|
|
1192
|
+
price
|
|
1193
|
+
images
|
|
1194
|
+
weight_kg
|
|
1195
|
+
is_active
|
|
1196
|
+
inventory {
|
|
1197
|
+
id
|
|
1198
|
+
product_id
|
|
1199
|
+
variant_id
|
|
1200
|
+
quantity
|
|
1201
|
+
reserved
|
|
1202
|
+
reorder_point
|
|
1203
|
+
reorder_quantity
|
|
1204
|
+
track_quantity
|
|
1205
|
+
allow_backorder
|
|
1206
|
+
available
|
|
1207
|
+
is_in_stock
|
|
1208
|
+
is_low_stock
|
|
1209
|
+
updated_at
|
|
1210
|
+
}
|
|
1211
|
+
created_at
|
|
1212
|
+
updated_at
|
|
1213
|
+
}
|
|
1214
|
+
collections {
|
|
1215
|
+
id
|
|
1216
|
+
business_id
|
|
1217
|
+
name
|
|
1218
|
+
slug
|
|
1219
|
+
description
|
|
1220
|
+
image
|
|
1221
|
+
collection_type
|
|
1222
|
+
rules
|
|
1223
|
+
status
|
|
1224
|
+
sort_order
|
|
1225
|
+
created_at
|
|
1226
|
+
updated_at
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
total
|
|
1230
|
+
page
|
|
1231
|
+
page_size
|
|
1232
|
+
total_pages
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
`;
|
|
1236
|
+
var GET_STOREFRONT_LOW_STOCK = `
|
|
1237
|
+
query GetStorefrontLowStock($page: Int!, $pageSize: Int!) {
|
|
1238
|
+
storefront_low_stock(page: $page, pageSize: $pageSize) {
|
|
1239
|
+
items {
|
|
1240
|
+
id
|
|
1241
|
+
business_id
|
|
1242
|
+
name
|
|
1243
|
+
slug
|
|
1244
|
+
description
|
|
1245
|
+
brand
|
|
1246
|
+
product_type
|
|
1247
|
+
requires_shipping
|
|
1248
|
+
is_consumable
|
|
1249
|
+
is_bespoke
|
|
1250
|
+
base_price
|
|
1251
|
+
currency
|
|
1252
|
+
sale_price
|
|
1253
|
+
sale_start
|
|
1254
|
+
sale_end
|
|
1255
|
+
images
|
|
1398
1256
|
tags
|
|
1399
1257
|
custom_fields { label unit required }
|
|
1400
1258
|
is_published
|
|
@@ -1417,7 +1275,7 @@ var GET_STOREFRONT_NEW = `
|
|
|
1417
1275
|
sku
|
|
1418
1276
|
attributes
|
|
1419
1277
|
price
|
|
1420
|
-
images
|
|
1278
|
+
images
|
|
1421
1279
|
weight_kg
|
|
1422
1280
|
is_active
|
|
1423
1281
|
inventory {
|
|
@@ -1444,7 +1302,7 @@ var GET_STOREFRONT_NEW = `
|
|
|
1444
1302
|
name
|
|
1445
1303
|
slug
|
|
1446
1304
|
description
|
|
1447
|
-
image
|
|
1305
|
+
image
|
|
1448
1306
|
collection_type
|
|
1449
1307
|
rules
|
|
1450
1308
|
status
|
|
@@ -1476,7 +1334,7 @@ var GET_STOREFRONT_RECOMMENDED = `
|
|
|
1476
1334
|
is_bespoke
|
|
1477
1335
|
base_price
|
|
1478
1336
|
currency
|
|
1479
|
-
images
|
|
1337
|
+
images
|
|
1480
1338
|
tags
|
|
1481
1339
|
custom_fields { label unit required }
|
|
1482
1340
|
is_published
|
|
@@ -1499,7 +1357,7 @@ var GET_STOREFRONT_RECOMMENDED = `
|
|
|
1499
1357
|
sku
|
|
1500
1358
|
attributes
|
|
1501
1359
|
price
|
|
1502
|
-
images
|
|
1360
|
+
images
|
|
1503
1361
|
weight_kg
|
|
1504
1362
|
is_active
|
|
1505
1363
|
inventory {
|
|
@@ -1526,7 +1384,7 @@ var GET_STOREFRONT_RECOMMENDED = `
|
|
|
1526
1384
|
name
|
|
1527
1385
|
slug
|
|
1528
1386
|
description
|
|
1529
|
-
image
|
|
1387
|
+
image
|
|
1530
1388
|
collection_type
|
|
1531
1389
|
rules
|
|
1532
1390
|
status
|
|
@@ -1542,8 +1400,8 @@ var GET_STOREFRONT_RECOMMENDED = `
|
|
|
1542
1400
|
}
|
|
1543
1401
|
}
|
|
1544
1402
|
`;
|
|
1545
|
-
async function getStorefrontProducts(params) {
|
|
1546
|
-
const client =
|
|
1403
|
+
async function getStorefrontProducts(config, params) {
|
|
1404
|
+
const client = createGraphQLClient(config);
|
|
1547
1405
|
const variables = {
|
|
1548
1406
|
page: params?.page || 1,
|
|
1549
1407
|
pageSize: params?.pageSize || 20,
|
|
@@ -1553,13 +1411,13 @@ async function getStorefrontProducts(params) {
|
|
|
1553
1411
|
const data = await client.request(GET_STOREFRONT_PRODUCTS, variables);
|
|
1554
1412
|
return data.storefront_products;
|
|
1555
1413
|
}
|
|
1556
|
-
async function getStorefrontProductBySlug(slug) {
|
|
1557
|
-
const client =
|
|
1414
|
+
async function getStorefrontProductBySlug(config, slug) {
|
|
1415
|
+
const client = createGraphQLClient(config);
|
|
1558
1416
|
const data = await client.request(GET_STOREFRONT_PRODUCT_BY_SLUG, { slug });
|
|
1559
1417
|
return data.storefront_product_by_slug;
|
|
1560
1418
|
}
|
|
1561
|
-
async function getLatestProducts(params) {
|
|
1562
|
-
const client =
|
|
1419
|
+
async function getLatestProducts(config, params) {
|
|
1420
|
+
const client = createGraphQLClient(config);
|
|
1563
1421
|
const variables = {
|
|
1564
1422
|
page: params?.page || 1,
|
|
1565
1423
|
pageSize: params?.pageSize || 4
|
|
@@ -1567,8 +1425,8 @@ async function getLatestProducts(params) {
|
|
|
1567
1425
|
const data = await client.request(GET_STOREFRONT_LATEST, variables);
|
|
1568
1426
|
return data.storefront_latest;
|
|
1569
1427
|
}
|
|
1570
|
-
async function getPopularProducts(params) {
|
|
1571
|
-
const client =
|
|
1428
|
+
async function getPopularProducts(config, params) {
|
|
1429
|
+
const client = createGraphQLClient(config);
|
|
1572
1430
|
const variables = {
|
|
1573
1431
|
page: params?.page || 1,
|
|
1574
1432
|
pageSize: params?.pageSize || 4
|
|
@@ -1576,8 +1434,8 @@ async function getPopularProducts(params) {
|
|
|
1576
1434
|
const data = await client.request(GET_STOREFRONT_POPULAR, variables);
|
|
1577
1435
|
return data.storefront_popular;
|
|
1578
1436
|
}
|
|
1579
|
-
async function getFeaturedProducts(params) {
|
|
1580
|
-
const client =
|
|
1437
|
+
async function getFeaturedProducts(config, params) {
|
|
1438
|
+
const client = createGraphQLClient(config);
|
|
1581
1439
|
const variables = {
|
|
1582
1440
|
page: params?.page || 1,
|
|
1583
1441
|
pageSize: params?.pageSize || 4
|
|
@@ -1585,8 +1443,8 @@ async function getFeaturedProducts(params) {
|
|
|
1585
1443
|
const data = await client.request(GET_STOREFRONT_FEATURED, variables);
|
|
1586
1444
|
return data.storefront_featured;
|
|
1587
1445
|
}
|
|
1588
|
-
async function getNewProducts(params) {
|
|
1589
|
-
const client =
|
|
1446
|
+
async function getNewProducts(config, params) {
|
|
1447
|
+
const client = createGraphQLClient(config);
|
|
1590
1448
|
const variables = {
|
|
1591
1449
|
page: params?.page || 1,
|
|
1592
1450
|
pageSize: params?.pageSize || 4
|
|
@@ -1594,8 +1452,26 @@ async function getNewProducts(params) {
|
|
|
1594
1452
|
const data = await client.request(GET_STOREFRONT_NEW, variables);
|
|
1595
1453
|
return data.storefront_new;
|
|
1596
1454
|
}
|
|
1597
|
-
async function
|
|
1598
|
-
const client =
|
|
1455
|
+
async function getOnSaleProducts(config, params) {
|
|
1456
|
+
const client = createGraphQLClient(config);
|
|
1457
|
+
const variables = {
|
|
1458
|
+
page: params?.page || 1,
|
|
1459
|
+
pageSize: params?.pageSize || 4
|
|
1460
|
+
};
|
|
1461
|
+
const data = await client.request(GET_STOREFRONT_ON_SALE, variables);
|
|
1462
|
+
return data.storefront_on_sale;
|
|
1463
|
+
}
|
|
1464
|
+
async function getLowStockProducts(config, params) {
|
|
1465
|
+
const client = createGraphQLClient(config);
|
|
1466
|
+
const variables = {
|
|
1467
|
+
page: params?.page || 1,
|
|
1468
|
+
pageSize: params?.pageSize || 4
|
|
1469
|
+
};
|
|
1470
|
+
const data = await client.request(GET_STOREFRONT_LOW_STOCK, variables);
|
|
1471
|
+
return data.storefront_low_stock;
|
|
1472
|
+
}
|
|
1473
|
+
async function getRecommendedProducts(config, params) {
|
|
1474
|
+
const client = createGraphQLClient(config);
|
|
1599
1475
|
const variables = {
|
|
1600
1476
|
page: params?.page || 1,
|
|
1601
1477
|
pageSize: params?.pageSize || 4,
|
|
@@ -1606,6 +1482,7 @@ async function getRecommendedProducts(params) {
|
|
|
1606
1482
|
}
|
|
1607
1483
|
|
|
1608
1484
|
// src/services/collection.service.ts
|
|
1485
|
+
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
1609
1486
|
var GET_STOREFRONT_COLLECTIONS = `
|
|
1610
1487
|
query GetStorefrontCollections($page: Int!, $pageSize: Int!) {
|
|
1611
1488
|
storefront_collections(page: $page, pageSize: $pageSize) {
|
|
@@ -1615,7 +1492,7 @@ var GET_STOREFRONT_COLLECTIONS = `
|
|
|
1615
1492
|
name
|
|
1616
1493
|
slug
|
|
1617
1494
|
description
|
|
1618
|
-
image
|
|
1495
|
+
image
|
|
1619
1496
|
collection_type
|
|
1620
1497
|
rules
|
|
1621
1498
|
status
|
|
@@ -1638,7 +1515,7 @@ var GET_STOREFRONT_COLLECTION_BY_SLUG = `
|
|
|
1638
1515
|
name
|
|
1639
1516
|
slug
|
|
1640
1517
|
description
|
|
1641
|
-
image
|
|
1518
|
+
image
|
|
1642
1519
|
collection_type
|
|
1643
1520
|
rules
|
|
1644
1521
|
status
|
|
@@ -1648,8 +1525,17 @@ var GET_STOREFRONT_COLLECTION_BY_SLUG = `
|
|
|
1648
1525
|
}
|
|
1649
1526
|
}
|
|
1650
1527
|
`;
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1528
|
+
function createGraphQLClient2(config) {
|
|
1529
|
+
const headers = {
|
|
1530
|
+
"Content-Type": "application/json",
|
|
1531
|
+
"Accept": "application/json",
|
|
1532
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1533
|
+
"X-Business-ID": config.businessId
|
|
1534
|
+
};
|
|
1535
|
+
return new GraphQLClient2(`${config.backendUrls.store}/graphql`, { headers });
|
|
1536
|
+
}
|
|
1537
|
+
async function getStorefrontCollections(config, params) {
|
|
1538
|
+
const client = createGraphQLClient2(config);
|
|
1653
1539
|
const variables = {
|
|
1654
1540
|
page: params?.page || 1,
|
|
1655
1541
|
pageSize: params?.pageSize || 20
|
|
@@ -1657,16 +1543,17 @@ async function getStorefrontCollections(params) {
|
|
|
1657
1543
|
const data = await client.request(GET_STOREFRONT_COLLECTIONS, variables);
|
|
1658
1544
|
return data.storefront_collections;
|
|
1659
1545
|
}
|
|
1660
|
-
async function getStorefrontCollectionBySlug(slug) {
|
|
1661
|
-
const client =
|
|
1546
|
+
async function getStorefrontCollectionBySlug(config, slug) {
|
|
1547
|
+
const client = createGraphQLClient2(config);
|
|
1662
1548
|
const data = await client.request(GET_STOREFRONT_COLLECTION_BY_SLUG, { slug });
|
|
1663
1549
|
return data.storefront_collection_by_slug;
|
|
1664
1550
|
}
|
|
1665
1551
|
|
|
1666
1552
|
// src/services/wishlist.service.ts
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1553
|
+
import { GraphQLClient as GraphQLClient3 } from "graphql-request";
|
|
1554
|
+
var GET_STOREFRONT_WISHLIST = `
|
|
1555
|
+
query GetStorefrontWishlist($page: Int!, $pageSize: Int!) {
|
|
1556
|
+
storefront_wishlist(page: $page, pageSize: $pageSize) {
|
|
1670
1557
|
items {
|
|
1671
1558
|
id
|
|
1672
1559
|
product_id
|
|
@@ -1675,6 +1562,7 @@ var GET_WISHLIST = `
|
|
|
1675
1562
|
product_name
|
|
1676
1563
|
product_slug
|
|
1677
1564
|
product_price
|
|
1565
|
+
currency
|
|
1678
1566
|
product_image
|
|
1679
1567
|
}
|
|
1680
1568
|
total
|
|
@@ -1684,79 +1572,136 @@ var GET_WISHLIST = `
|
|
|
1684
1572
|
}
|
|
1685
1573
|
}
|
|
1686
1574
|
`;
|
|
1687
|
-
var
|
|
1688
|
-
query
|
|
1689
|
-
|
|
1575
|
+
var CHECK_IN_STOREFRONT_WISHLIST = `
|
|
1576
|
+
query CheckInStorefrontWishlist($productId: String!) {
|
|
1577
|
+
check_in_storefront_wishlist(productId: $productId) {
|
|
1690
1578
|
in_wishlist
|
|
1691
1579
|
}
|
|
1692
1580
|
}
|
|
1693
1581
|
`;
|
|
1694
|
-
|
|
1695
|
-
const
|
|
1582
|
+
function createGraphQLClient3(config, accessToken) {
|
|
1583
|
+
const headers = {
|
|
1584
|
+
"Content-Type": "application/json",
|
|
1585
|
+
"Accept": "application/json",
|
|
1586
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1587
|
+
"X-Business-ID": config.businessId
|
|
1588
|
+
};
|
|
1589
|
+
if (accessToken) {
|
|
1590
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
1591
|
+
}
|
|
1592
|
+
return new GraphQLClient3(`${config.backendUrls.store}/graphql`, { headers });
|
|
1593
|
+
}
|
|
1594
|
+
async function getWishlist(config, accessToken, params) {
|
|
1595
|
+
const client = createGraphQLClient3(config, accessToken);
|
|
1696
1596
|
const variables = {
|
|
1697
1597
|
page: params?.page || 1,
|
|
1698
1598
|
pageSize: params?.pageSize || 20
|
|
1699
1599
|
};
|
|
1700
|
-
const data = await client.request(
|
|
1701
|
-
return data.
|
|
1702
|
-
}
|
|
1703
|
-
async function checkInWishlist(productId) {
|
|
1704
|
-
const client =
|
|
1705
|
-
const data = await client.request(
|
|
1706
|
-
return data.
|
|
1707
|
-
}
|
|
1708
|
-
async function addToWishlist(productId, notes) {
|
|
1709
|
-
const
|
|
1710
|
-
|
|
1711
|
-
|
|
1600
|
+
const data = await client.request(GET_STOREFRONT_WISHLIST, variables);
|
|
1601
|
+
return data.storefront_wishlist;
|
|
1602
|
+
}
|
|
1603
|
+
async function checkInWishlist(config, accessToken, productId) {
|
|
1604
|
+
const client = createGraphQLClient3(config, accessToken);
|
|
1605
|
+
const data = await client.request(CHECK_IN_STOREFRONT_WISHLIST, { productId });
|
|
1606
|
+
return data.check_in_storefront_wishlist;
|
|
1607
|
+
}
|
|
1608
|
+
async function addToWishlist(config, accessToken, productId, notes) {
|
|
1609
|
+
const response = await fetch(`${config.backendUrls.store}/wishlist/`, {
|
|
1610
|
+
method: "POST",
|
|
1611
|
+
headers: {
|
|
1612
|
+
"Content-Type": "application/json",
|
|
1613
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1614
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1615
|
+
"X-Business-ID": config.businessId
|
|
1616
|
+
},
|
|
1617
|
+
body: JSON.stringify({
|
|
1618
|
+
product_id: productId,
|
|
1619
|
+
notes: notes || void 0
|
|
1620
|
+
})
|
|
1712
1621
|
});
|
|
1713
|
-
|
|
1622
|
+
if (!response.ok) throw new Error("Failed to add to wishlist");
|
|
1623
|
+
return response.json();
|
|
1714
1624
|
}
|
|
1715
|
-
async function removeFromWishlist(itemId) {
|
|
1716
|
-
await
|
|
1625
|
+
async function removeFromWishlist(config, accessToken, itemId) {
|
|
1626
|
+
const response = await fetch(`${config.backendUrls.store}/wishlist/${itemId}`, {
|
|
1627
|
+
method: "DELETE",
|
|
1628
|
+
headers: {
|
|
1629
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1630
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1631
|
+
"X-Business-ID": config.businessId
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
if (!response.ok) throw new Error("Failed to remove from wishlist");
|
|
1717
1635
|
}
|
|
1718
|
-
async function removeProductFromWishlist(productId) {
|
|
1719
|
-
await
|
|
1636
|
+
async function removeProductFromWishlist(config, accessToken, productId) {
|
|
1637
|
+
const response = await fetch(`${config.backendUrls.store}/wishlist/product/${productId}`, {
|
|
1638
|
+
method: "DELETE",
|
|
1639
|
+
headers: {
|
|
1640
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1641
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1642
|
+
"X-Business-ID": config.businessId
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
if (!response.ok) throw new Error("Failed to remove product from wishlist");
|
|
1720
1646
|
}
|
|
1721
1647
|
|
|
1722
1648
|
// src/services/payment.service.ts
|
|
1723
|
-
async function createDedicatedAccount(request) {
|
|
1649
|
+
async function createDedicatedAccount(config, accessToken, request) {
|
|
1724
1650
|
const isValidUUID = (str) => {
|
|
1725
1651
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1726
1652
|
return uuidRegex.test(str);
|
|
1727
1653
|
};
|
|
1728
|
-
const
|
|
1729
|
-
"
|
|
1730
|
-
{
|
|
1654
|
+
const response = await fetch(`${config.backendUrls.payment}/payments/dedicated-account`, {
|
|
1655
|
+
method: "POST",
|
|
1656
|
+
headers: {
|
|
1657
|
+
"Content-Type": "application/json",
|
|
1658
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1659
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1660
|
+
"X-Business-ID": config.businessId
|
|
1661
|
+
},
|
|
1662
|
+
body: JSON.stringify({
|
|
1731
1663
|
order_id: request.order_id && isValidUUID(request.order_id) ? request.order_id : void 0,
|
|
1732
1664
|
amount: request.amount,
|
|
1733
1665
|
customer_email: request.customer_email,
|
|
1734
1666
|
customer_name: request.customer_name,
|
|
1735
1667
|
currency: request.currency,
|
|
1736
1668
|
idempotency_key: request.idempotency_key
|
|
1737
|
-
}
|
|
1738
|
-
);
|
|
1669
|
+
})
|
|
1670
|
+
});
|
|
1671
|
+
if (!response.ok) throw new Error("Failed to create dedicated account");
|
|
1672
|
+
const data = await response.json();
|
|
1739
1673
|
return data;
|
|
1740
1674
|
}
|
|
1741
|
-
async function initiatePayment(request) {
|
|
1675
|
+
async function initiatePayment(config, accessToken, request) {
|
|
1742
1676
|
const isValidUUID = (str) => {
|
|
1743
1677
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1744
1678
|
return uuidRegex.test(str);
|
|
1745
1679
|
};
|
|
1746
1680
|
const validOrderId = request.order_id && isValidUUID(request.order_id) ? request.order_id : void 0;
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1681
|
+
const response = await fetch(`${config.backendUrls.payment}/payments/storefront/initiate`, {
|
|
1682
|
+
method: "POST",
|
|
1683
|
+
headers: {
|
|
1684
|
+
"Content-Type": "application/json",
|
|
1685
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1686
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1687
|
+
"X-Business-ID": config.businessId
|
|
1688
|
+
},
|
|
1689
|
+
body: JSON.stringify({
|
|
1690
|
+
amount: request.amount,
|
|
1691
|
+
currency: request.currency,
|
|
1755
1692
|
order_id: validOrderId,
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1693
|
+
quote_id: request.quote_id,
|
|
1694
|
+
metadata_json: {
|
|
1695
|
+
customer_email: request.customer_email,
|
|
1696
|
+
customer_name: request.customer_name ?? "",
|
|
1697
|
+
order_id: validOrderId,
|
|
1698
|
+
...request.metadata ?? {}
|
|
1699
|
+
},
|
|
1700
|
+
idempotency_key: request.idempotency_key
|
|
1701
|
+
})
|
|
1759
1702
|
});
|
|
1703
|
+
if (!response.ok) throw new Error("Failed to initiate payment");
|
|
1704
|
+
const data = await response.json();
|
|
1760
1705
|
return {
|
|
1761
1706
|
id: data.id,
|
|
1762
1707
|
gateway_ref: data.gateway_ref,
|
|
@@ -1765,20 +1710,53 @@ async function initiatePayment(request) {
|
|
|
1765
1710
|
checkout_url: data.metadata_json?.checkout_url ?? ""
|
|
1766
1711
|
};
|
|
1767
1712
|
}
|
|
1768
|
-
async function requestQuote(amount) {
|
|
1769
|
-
const
|
|
1713
|
+
async function requestQuote(config, amount) {
|
|
1714
|
+
const response = await fetch(`${config.backendUrls.payment}/payments/quote`, {
|
|
1715
|
+
method: "POST",
|
|
1716
|
+
headers: {
|
|
1717
|
+
"Content-Type": "application/json",
|
|
1718
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1719
|
+
"X-Business-ID": config.businessId
|
|
1720
|
+
},
|
|
1721
|
+
body: JSON.stringify({ amount })
|
|
1722
|
+
});
|
|
1723
|
+
if (!response.ok) throw new Error("Failed to request quote");
|
|
1724
|
+
const data = await response.json();
|
|
1770
1725
|
return data;
|
|
1771
1726
|
}
|
|
1772
|
-
async function verifyTransaction(reference) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1727
|
+
async function verifyTransaction(configOrReference, reference) {
|
|
1728
|
+
if (typeof configOrReference === "string") {
|
|
1729
|
+
const ref2 = configOrReference;
|
|
1730
|
+
const response2 = await fetch(`/api/payments/verify/${ref2}`, {
|
|
1731
|
+
method: "POST",
|
|
1732
|
+
headers: { "Content-Type": "application/json" }
|
|
1733
|
+
});
|
|
1734
|
+
if (!response2.ok) throw new Error("Failed to verify transaction");
|
|
1735
|
+
return response2.json();
|
|
1736
|
+
}
|
|
1737
|
+
const config = configOrReference;
|
|
1738
|
+
const ref = reference || "";
|
|
1739
|
+
const response = await fetch(`${config.backendUrls.payment}/payments/verify/${ref}`, {
|
|
1740
|
+
method: "POST",
|
|
1741
|
+
headers: {
|
|
1742
|
+
"Content-Type": "application/json",
|
|
1743
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1744
|
+
"X-Business-ID": config.businessId
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
if (!response.ok) throw new Error("Failed to verify transaction");
|
|
1748
|
+
const data = await response.json();
|
|
1776
1749
|
return data;
|
|
1777
1750
|
}
|
|
1778
|
-
async function getTransactionStatus(transactionId) {
|
|
1779
|
-
const
|
|
1780
|
-
|
|
1781
|
-
|
|
1751
|
+
async function getTransactionStatus(config, transactionId) {
|
|
1752
|
+
const response = await fetch(`${config.backendUrls.payment}/payments/transactions/${transactionId}`, {
|
|
1753
|
+
headers: {
|
|
1754
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1755
|
+
"X-Business-ID": config.businessId
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
if (!response.ok) throw new Error("Failed to get transaction status");
|
|
1759
|
+
const data = await response.json();
|
|
1782
1760
|
return data;
|
|
1783
1761
|
}
|
|
1784
1762
|
var paymentService = {
|
|
@@ -1790,28 +1768,81 @@ var paymentService = {
|
|
|
1790
1768
|
};
|
|
1791
1769
|
|
|
1792
1770
|
// src/services/consultation.service.ts
|
|
1793
|
-
async function createConsultation(payload) {
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1771
|
+
async function createConsultation(config, accessToken, payload) {
|
|
1772
|
+
const response = await fetch(`${config.backendUrls.store}/social/consultations/`, {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
headers: {
|
|
1775
|
+
"Content-Type": "application/json",
|
|
1776
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1777
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1778
|
+
"X-Business-ID": config.businessId
|
|
1779
|
+
},
|
|
1780
|
+
body: JSON.stringify(payload)
|
|
1781
|
+
});
|
|
1782
|
+
if (!response.ok) throw new Error("Failed to create consultation");
|
|
1783
|
+
return response.json();
|
|
1800
1784
|
}
|
|
1801
|
-
async function
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1785
|
+
async function getConsultations(config, accessToken) {
|
|
1786
|
+
const response = await fetch(`${config.backendUrls.store}/social/consultations/`, {
|
|
1787
|
+
headers: {
|
|
1788
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1789
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1790
|
+
"X-Business-ID": config.businessId
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
if (!response.ok) throw new Error("Failed to get consultations");
|
|
1794
|
+
return response.json();
|
|
1804
1795
|
}
|
|
1805
|
-
async function
|
|
1806
|
-
await
|
|
1796
|
+
async function getConsultation(config, accessToken, id) {
|
|
1797
|
+
const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}`, {
|
|
1798
|
+
headers: {
|
|
1799
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1800
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1801
|
+
"X-Business-ID": config.businessId
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
if (!response.ok) throw new Error("Failed to get consultation");
|
|
1805
|
+
return response.json();
|
|
1807
1806
|
}
|
|
1808
|
-
async function
|
|
1809
|
-
|
|
1810
|
-
|
|
1807
|
+
async function sendMessage(config, accessToken, id, payload) {
|
|
1808
|
+
await fetch(`${config.backendUrls.store}/social/consultations/${id}/messages`, {
|
|
1809
|
+
method: "POST",
|
|
1810
|
+
headers: {
|
|
1811
|
+
"Content-Type": "application/json",
|
|
1812
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1813
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1814
|
+
"X-Business-ID": config.businessId
|
|
1815
|
+
},
|
|
1816
|
+
body: JSON.stringify(payload)
|
|
1817
|
+
});
|
|
1811
1818
|
}
|
|
1812
|
-
async function
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1819
|
+
async function acceptQuote(config, accessToken, id, payload) {
|
|
1820
|
+
const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}/accept-quote`, {
|
|
1821
|
+
method: "POST",
|
|
1822
|
+
headers: {
|
|
1823
|
+
"Content-Type": "application/json",
|
|
1824
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1825
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1826
|
+
"X-Business-ID": config.businessId
|
|
1827
|
+
},
|
|
1828
|
+
body: JSON.stringify(payload)
|
|
1829
|
+
});
|
|
1830
|
+
if (!response.ok) throw new Error("Failed to accept quote");
|
|
1831
|
+
return response.json();
|
|
1832
|
+
}
|
|
1833
|
+
async function rejectQuote(config, accessToken, id, reason) {
|
|
1834
|
+
const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}/reject-quote`, {
|
|
1835
|
+
method: "POST",
|
|
1836
|
+
headers: {
|
|
1837
|
+
"Content-Type": "application/json",
|
|
1838
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1839
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1840
|
+
"X-Business-ID": config.businessId
|
|
1841
|
+
},
|
|
1842
|
+
body: JSON.stringify({ reason })
|
|
1843
|
+
});
|
|
1844
|
+
if (!response.ok) throw new Error("Failed to reject quote");
|
|
1845
|
+
return response.json();
|
|
1815
1846
|
}
|
|
1816
1847
|
var consultationService = {
|
|
1817
1848
|
createConsultation,
|
|
@@ -1823,20 +1854,41 @@ var consultationService = {
|
|
|
1823
1854
|
};
|
|
1824
1855
|
|
|
1825
1856
|
// src/services/logistics.service.ts
|
|
1826
|
-
async function getShippingRates(payload) {
|
|
1827
|
-
const
|
|
1828
|
-
"
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1857
|
+
async function getShippingRates(config, payload) {
|
|
1858
|
+
const response = await fetch(`${config.backendUrls.store}/logistics/shipping/rates`, {
|
|
1859
|
+
method: "POST",
|
|
1860
|
+
headers: {
|
|
1861
|
+
"Content-Type": "application/json",
|
|
1862
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1863
|
+
"X-Business-ID": config.businessId
|
|
1864
|
+
},
|
|
1865
|
+
body: JSON.stringify(payload)
|
|
1866
|
+
});
|
|
1867
|
+
if (!response.ok) throw new Error("Failed to get shipping rates");
|
|
1868
|
+
return response.json();
|
|
1832
1869
|
}
|
|
1833
|
-
async function getSellerShippingAddress() {
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1870
|
+
async function getSellerShippingAddress(config) {
|
|
1871
|
+
const response = await fetch(`${config.backendUrls.store}/logistics/shipping-address`, {
|
|
1872
|
+
headers: {
|
|
1873
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1874
|
+
"X-Business-ID": config.businessId
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
if (!response.ok) throw new Error("Failed to get seller shipping address");
|
|
1878
|
+
return response.json();
|
|
1836
1879
|
}
|
|
1837
|
-
async function validateShippingAddress(payload) {
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1880
|
+
async function validateShippingAddress(config, payload) {
|
|
1881
|
+
const response = await fetch(`${config.backendUrls.store}/logistics/shipping-address/validate`, {
|
|
1882
|
+
method: "POST",
|
|
1883
|
+
headers: {
|
|
1884
|
+
"Content-Type": "application/json",
|
|
1885
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1886
|
+
"X-Business-ID": config.businessId
|
|
1887
|
+
},
|
|
1888
|
+
body: JSON.stringify(payload)
|
|
1889
|
+
});
|
|
1890
|
+
if (!response.ok) throw new Error("Failed to validate shipping address");
|
|
1891
|
+
return response.json();
|
|
1840
1892
|
}
|
|
1841
1893
|
var logisticsService = {
|
|
1842
1894
|
getShippingRates,
|
|
@@ -1845,54 +1897,124 @@ var logisticsService = {
|
|
|
1845
1897
|
};
|
|
1846
1898
|
|
|
1847
1899
|
// src/services/bespoke.service.ts
|
|
1848
|
-
async function checkAvailability(product_id) {
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1900
|
+
async function checkAvailability(config, product_id) {
|
|
1901
|
+
const response = await fetch(`${config.backendUrls.store}/bespoke/availability/${product_id}`, {
|
|
1902
|
+
headers: {
|
|
1903
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1904
|
+
"X-Business-ID": config.businessId
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
if (!response.ok) throw new Error("Failed to check bespoke availability");
|
|
1908
|
+
return response.json();
|
|
1851
1909
|
}
|
|
1852
1910
|
var bespokeService = {
|
|
1853
1911
|
checkAvailability
|
|
1854
1912
|
};
|
|
1855
1913
|
|
|
1856
1914
|
// src/services/service-booking.service.ts
|
|
1857
|
-
async function getAvailableSlots(product_id, date_from, date_to) {
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
{
|
|
1915
|
+
async function getAvailableSlots(config, product_id, date_from, date_to) {
|
|
1916
|
+
const response = await fetch(
|
|
1917
|
+
`${config.backendUrls.store}/service/available-slots/${product_id}?date_from=${date_from}&date_to=${date_to}`,
|
|
1918
|
+
{
|
|
1919
|
+
headers: {
|
|
1920
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1921
|
+
"X-Business-ID": config.businessId
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1861
1924
|
);
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1925
|
+
if (!response.ok) throw new Error("Failed to get available slots");
|
|
1926
|
+
return response.json();
|
|
1927
|
+
}
|
|
1928
|
+
async function bookAppointment(config, accessToken, payload) {
|
|
1929
|
+
const response = await fetch(`${config.backendUrls.store}/service/appointments/`, {
|
|
1930
|
+
method: "POST",
|
|
1931
|
+
headers: {
|
|
1932
|
+
"Content-Type": "application/json",
|
|
1933
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1934
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1935
|
+
"X-Business-ID": config.businessId
|
|
1936
|
+
},
|
|
1937
|
+
body: JSON.stringify(payload)
|
|
1938
|
+
});
|
|
1939
|
+
if (!response.ok) throw new Error("Failed to book appointment");
|
|
1940
|
+
return response.json();
|
|
1867
1941
|
}
|
|
1868
|
-
async function getAppointments() {
|
|
1869
|
-
const
|
|
1870
|
-
|
|
1942
|
+
async function getAppointments(config, accessToken) {
|
|
1943
|
+
const response = await fetch(`${config.backendUrls.store}/service/bookings/my`, {
|
|
1944
|
+
headers: {
|
|
1945
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1946
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1947
|
+
"X-Business-ID": config.businessId
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
if (!response.ok) throw new Error("Failed to get appointments");
|
|
1951
|
+
return response.json();
|
|
1871
1952
|
}
|
|
1872
|
-
async function getAppointment(id) {
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1953
|
+
async function getAppointment(config, accessToken, id) {
|
|
1954
|
+
const response = await fetch(`${config.backendUrls.store}/service/bookings/${id}`, {
|
|
1955
|
+
headers: {
|
|
1956
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1957
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1958
|
+
"X-Business-ID": config.businessId
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1961
|
+
if (!response.ok) throw new Error("Failed to get appointment");
|
|
1962
|
+
return response.json();
|
|
1875
1963
|
}
|
|
1876
|
-
async function cancelAppointment(id) {
|
|
1877
|
-
await
|
|
1964
|
+
async function cancelAppointment(config, accessToken, id) {
|
|
1965
|
+
await fetch(`${config.backendUrls.store}/service/bookings/${id}`, {
|
|
1966
|
+
method: "DELETE",
|
|
1967
|
+
headers: {
|
|
1968
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1969
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1970
|
+
"X-Business-ID": config.businessId
|
|
1971
|
+
}
|
|
1972
|
+
});
|
|
1878
1973
|
}
|
|
1879
|
-
async function getDeliverables(order_id) {
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1974
|
+
async function getDeliverables(config, accessToken, order_id) {
|
|
1975
|
+
const response = await fetch(`${config.backendUrls.store}/service/projects/${order_id}/deliverables/`, {
|
|
1976
|
+
headers: {
|
|
1977
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1978
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1979
|
+
"X-Business-ID": config.businessId
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
if (!response.ok) throw new Error("Failed to get deliverables");
|
|
1983
|
+
return response.json();
|
|
1882
1984
|
}
|
|
1883
|
-
async function approveDeliverable(order_id, deliverable_id) {
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
{
|
|
1985
|
+
async function approveDeliverable(config, accessToken, order_id, deliverable_id) {
|
|
1986
|
+
const response = await fetch(
|
|
1987
|
+
`${config.backendUrls.store}/service/projects/${order_id}/deliverables/${deliverable_id}/approve`,
|
|
1988
|
+
{
|
|
1989
|
+
method: "POST",
|
|
1990
|
+
headers: {
|
|
1991
|
+
"Content-Type": "application/json",
|
|
1992
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
1993
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
1994
|
+
"X-Business-ID": config.businessId
|
|
1995
|
+
},
|
|
1996
|
+
body: JSON.stringify({ approved: true })
|
|
1997
|
+
}
|
|
1887
1998
|
);
|
|
1888
|
-
|
|
1999
|
+
if (!response.ok) throw new Error("Failed to approve deliverable");
|
|
2000
|
+
return response.json();
|
|
1889
2001
|
}
|
|
1890
|
-
async function requestRevision(order_id, deliverable_id, feedback) {
|
|
1891
|
-
const
|
|
1892
|
-
|
|
1893
|
-
{
|
|
2002
|
+
async function requestRevision(config, accessToken, order_id, deliverable_id, feedback) {
|
|
2003
|
+
const response = await fetch(
|
|
2004
|
+
`${config.backendUrls.store}/service/projects/${order_id}/deliverables/${deliverable_id}/revision`,
|
|
2005
|
+
{
|
|
2006
|
+
method: "POST",
|
|
2007
|
+
headers: {
|
|
2008
|
+
"Content-Type": "application/json",
|
|
2009
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2010
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2011
|
+
"X-Business-ID": config.businessId
|
|
2012
|
+
},
|
|
2013
|
+
body: JSON.stringify({ approved: false, feedback })
|
|
2014
|
+
}
|
|
1894
2015
|
);
|
|
1895
|
-
|
|
2016
|
+
if (!response.ok) throw new Error("Failed to request revision");
|
|
2017
|
+
return response.json();
|
|
1896
2018
|
}
|
|
1897
2019
|
var serviceBookingService = {
|
|
1898
2020
|
getAvailableSlots,
|
|
@@ -1906,19 +2028,31 @@ var serviceBookingService = {
|
|
|
1906
2028
|
};
|
|
1907
2029
|
|
|
1908
2030
|
// src/services/notifications.service.ts
|
|
1909
|
-
async function sendContactEmail(payload) {
|
|
1910
|
-
const
|
|
1911
|
-
"
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
);
|
|
1921
|
-
|
|
2031
|
+
async function sendContactEmail(config, payload) {
|
|
2032
|
+
const response = await fetch(`${config.backendUrls.notifications}/contact/send-contact-email`, {
|
|
2033
|
+
method: "POST",
|
|
2034
|
+
headers: {
|
|
2035
|
+
"Content-Type": "application/json",
|
|
2036
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2037
|
+
"X-Business-ID": config.businessId
|
|
2038
|
+
},
|
|
2039
|
+
body: JSON.stringify(payload)
|
|
2040
|
+
});
|
|
2041
|
+
if (!response.ok) throw new Error("Failed to send contact email");
|
|
2042
|
+
return response.json();
|
|
2043
|
+
}
|
|
2044
|
+
async function sendContactConfirmation(config, payload) {
|
|
2045
|
+
const response = await fetch(`${config.backendUrls.notifications}/contact/send-contact-confirmation`, {
|
|
2046
|
+
method: "POST",
|
|
2047
|
+
headers: {
|
|
2048
|
+
"Content-Type": "application/json",
|
|
2049
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2050
|
+
"X-Business-ID": config.businessId
|
|
2051
|
+
},
|
|
2052
|
+
body: JSON.stringify(payload)
|
|
2053
|
+
});
|
|
2054
|
+
if (!response.ok) throw new Error("Failed to send contact confirmation");
|
|
2055
|
+
return response.json();
|
|
1922
2056
|
}
|
|
1923
2057
|
var notificationsService = {
|
|
1924
2058
|
sendContactEmail,
|
|
@@ -1939,17 +2073,35 @@ async function googleTranslate(text, target, source = "en") {
|
|
|
1939
2073
|
}
|
|
1940
2074
|
|
|
1941
2075
|
// src/services/config.service.ts
|
|
1942
|
-
async function getSupportedLanguages() {
|
|
1943
|
-
const
|
|
1944
|
-
|
|
2076
|
+
async function getSupportedLanguages(config) {
|
|
2077
|
+
const response = await fetch(`${config.backendUrls.payment}/config/supported-languages`, {
|
|
2078
|
+
headers: {
|
|
2079
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2080
|
+
"X-Business-ID": config.businessId
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
if (!response.ok) throw new Error("Failed to get supported languages");
|
|
2084
|
+
return response.json();
|
|
1945
2085
|
}
|
|
1946
|
-
async function getSupportedCurrencies() {
|
|
1947
|
-
const
|
|
1948
|
-
|
|
2086
|
+
async function getSupportedCurrencies(config) {
|
|
2087
|
+
const response = await fetch(`${config.backendUrls.payment}/config/supported-currencies`, {
|
|
2088
|
+
headers: {
|
|
2089
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2090
|
+
"X-Business-ID": config.businessId
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
if (!response.ok) throw new Error("Failed to get supported currencies");
|
|
2094
|
+
return response.json();
|
|
1949
2095
|
}
|
|
1950
|
-
async function getBusinessConfig() {
|
|
1951
|
-
const
|
|
1952
|
-
|
|
2096
|
+
async function getBusinessConfig(config) {
|
|
2097
|
+
const response = await fetch(`${config.backendUrls.auth}/internal/businesses/${config.businessId}/`, {
|
|
2098
|
+
headers: {
|
|
2099
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2100
|
+
"X-Business-ID": config.businessId
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
if (!response.ok) throw new Error("Failed to get business config");
|
|
2104
|
+
return response.json();
|
|
1953
2105
|
}
|
|
1954
2106
|
var configService = {
|
|
1955
2107
|
getSupportedLanguages,
|
|
@@ -1958,1194 +2110,454 @@ var configService = {
|
|
|
1958
2110
|
};
|
|
1959
2111
|
|
|
1960
2112
|
// src/services/cms.service.ts
|
|
1961
|
-
async function listCmsEntries(modelSlug, params) {
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2113
|
+
async function listCmsEntries(config, modelSlug, params) {
|
|
2114
|
+
const queryParams = new URLSearchParams();
|
|
2115
|
+
queryParams.set("page", (params?.page || 1).toString());
|
|
2116
|
+
queryParams.set("page_size", (params?.page_size || 50).toString());
|
|
2117
|
+
const response = await fetch(
|
|
2118
|
+
`${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries?${queryParams.toString()}`,
|
|
1966
2119
|
{
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2120
|
+
headers: {
|
|
2121
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2122
|
+
"X-Business-ID": config.businessId
|
|
1970
2123
|
}
|
|
1971
2124
|
}
|
|
1972
2125
|
);
|
|
1973
|
-
|
|
2126
|
+
if (!response.ok) throw new Error("Failed to list CMS entries");
|
|
2127
|
+
return response.json();
|
|
1974
2128
|
}
|
|
1975
|
-
async function getCmsEntry(modelSlug, entryId) {
|
|
1976
|
-
const
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
const config = getRuntimeConfig();
|
|
1985
|
-
const { businessId } = config;
|
|
1986
|
-
const response = await cmsAxios.post(
|
|
1987
|
-
`/sdk/${businessId}/${modelSlug}/entries`,
|
|
1988
|
-
params
|
|
2129
|
+
async function getCmsEntry(config, modelSlug, entryId) {
|
|
2130
|
+
const response = await fetch(
|
|
2131
|
+
`${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
|
|
2132
|
+
{
|
|
2133
|
+
headers: {
|
|
2134
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2135
|
+
"X-Business-ID": config.businessId
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
1989
2138
|
);
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2139
|
+
if (!response.ok) throw new Error("Failed to get CMS entry");
|
|
2140
|
+
return response.json();
|
|
2141
|
+
}
|
|
2142
|
+
async function createCmsEntry(config, accessToken, modelSlug, params) {
|
|
2143
|
+
const response = await fetch(
|
|
2144
|
+
`${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries`,
|
|
2145
|
+
{
|
|
2146
|
+
method: "POST",
|
|
2147
|
+
headers: {
|
|
2148
|
+
"Content-Type": "application/json",
|
|
2149
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2150
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2151
|
+
"X-Business-ID": config.businessId
|
|
2152
|
+
},
|
|
2153
|
+
body: JSON.stringify(params)
|
|
2154
|
+
}
|
|
1998
2155
|
);
|
|
1999
|
-
|
|
2156
|
+
if (!response.ok) throw new Error("Failed to create CMS entry");
|
|
2157
|
+
return response.json();
|
|
2000
2158
|
}
|
|
2001
|
-
async function
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2159
|
+
async function updateCmsEntry(config, accessToken, modelSlug, entryId, params) {
|
|
2160
|
+
const response = await fetch(
|
|
2161
|
+
`${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
|
|
2162
|
+
{
|
|
2163
|
+
method: "PUT",
|
|
2164
|
+
headers: {
|
|
2165
|
+
"Content-Type": "application/json",
|
|
2166
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2167
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2168
|
+
"X-Business-ID": config.businessId
|
|
2169
|
+
},
|
|
2170
|
+
body: JSON.stringify(params)
|
|
2171
|
+
}
|
|
2006
2172
|
);
|
|
2173
|
+
if (!response.ok) throw new Error("Failed to update CMS entry");
|
|
2174
|
+
return response.json();
|
|
2007
2175
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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
|
-
}
|
|
2176
|
+
async function deleteCmsEntry(config, accessToken, modelSlug, entryId) {
|
|
2177
|
+
const response = await fetch(
|
|
2178
|
+
`${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
|
|
2179
|
+
{
|
|
2180
|
+
method: "DELETE",
|
|
2181
|
+
headers: {
|
|
2182
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2183
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
2184
|
+
"X-Business-ID": config.businessId
|
|
2048
2185
|
}
|
|
2049
2186
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
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
|
-
};
|
|
2187
|
+
);
|
|
2188
|
+
if (!response.ok) throw new Error("Failed to delete CMS entry");
|
|
2129
2189
|
}
|
|
2130
2190
|
|
|
2131
|
-
// src/
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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
|
-
});
|
|
2191
|
+
// src/utils/cache.ts
|
|
2192
|
+
var CACHE_TIERS = {
|
|
2193
|
+
none: { staleTime: 0, gcTime: 0 },
|
|
2194
|
+
infinity: { staleTime: Infinity, gcTime: 0 },
|
|
2195
|
+
low: { staleTime: 1e3 * 30, gcTime: 1e3 * 60 },
|
|
2196
|
+
// 30s / 1m
|
|
2197
|
+
medium: { staleTime: 1e3 * 60 * 5, gcTime: 1e3 * 60 * 10 },
|
|
2198
|
+
// 5m / 10m
|
|
2199
|
+
high: { staleTime: 1e3 * 60 * 30, gcTime: 1e3 * 60 * 60 }
|
|
2200
|
+
// 30m / 1h
|
|
2201
|
+
};
|
|
2202
|
+
var isDevelopment = true;
|
|
2203
|
+
function setEnvironmentMode(development) {
|
|
2204
|
+
isDevelopment = development;
|
|
2178
2205
|
}
|
|
2206
|
+
var getCacheTier = (tier) => {
|
|
2207
|
+
if (isDevelopment) return CACHE_TIERS.none;
|
|
2208
|
+
return CACHE_TIERS[tier];
|
|
2209
|
+
};
|
|
2179
2210
|
|
|
2180
|
-
// src/
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
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
|
-
});
|
|
2211
|
+
// src/utils/logger.ts
|
|
2212
|
+
var isDevelopment2 = true;
|
|
2213
|
+
function setLoggerEnvironmentMode(development) {
|
|
2214
|
+
isDevelopment2 = development;
|
|
2258
2215
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
onSuccess: () => {
|
|
2264
|
-
queryClient.invalidateQueries({ queryKey: ["auth-user"] });
|
|
2265
|
-
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2216
|
+
var logger = {
|
|
2217
|
+
log: (...args) => {
|
|
2218
|
+
if (isDevelopment2) {
|
|
2219
|
+
console.log(...args);
|
|
2266
2220
|
}
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
return useMutation3({
|
|
2272
|
-
mutationFn: registerCustomer,
|
|
2273
|
-
onSuccess: () => {
|
|
2274
|
-
queryClient.invalidateQueries({ queryKey: ["auth-user"] });
|
|
2275
|
-
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
|
2221
|
+
},
|
|
2222
|
+
warn: (...args) => {
|
|
2223
|
+
if (isDevelopment2) {
|
|
2224
|
+
console.warn(...args);
|
|
2276
2225
|
}
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2226
|
+
},
|
|
2227
|
+
/**
|
|
2228
|
+
* Logs a warning to the console in development only.
|
|
2229
|
+
* Alias for warn() for Python logging compatibility.
|
|
2230
|
+
* In production, this is a no-op.
|
|
2231
|
+
*/
|
|
2232
|
+
warning: (...args) => {
|
|
2233
|
+
if (isDevelopment2) {
|
|
2234
|
+
console.warn(...args);
|
|
2285
2235
|
}
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
return useMutation3({
|
|
2291
|
-
mutationFn: logout,
|
|
2292
|
-
onSuccess: () => {
|
|
2293
|
-
queryClient.clear();
|
|
2236
|
+
},
|
|
2237
|
+
error: (...args) => {
|
|
2238
|
+
if (isDevelopment2) {
|
|
2239
|
+
console.error(...args);
|
|
2294
2240
|
}
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
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();
|
|
2241
|
+
},
|
|
2242
|
+
info: (...args) => {
|
|
2243
|
+
if (isDevelopment2) {
|
|
2244
|
+
console.info(...args);
|
|
2314
2245
|
}
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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);
|
|
2246
|
+
},
|
|
2247
|
+
createScope: (scope) => ({
|
|
2248
|
+
log: (...args) => {
|
|
2249
|
+
if (isDevelopment2) {
|
|
2250
|
+
console.log(`[${scope}]`, ...args);
|
|
2251
|
+
}
|
|
2532
2252
|
},
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
});
|
|
2538
|
-
}
|
|
2539
|
-
function useRemoveFromWishlist() {
|
|
2540
|
-
const queryClient = useQueryClient4();
|
|
2541
|
-
return useMutation4({
|
|
2542
|
-
mutationFn: (itemId) => {
|
|
2543
|
-
return removeFromWishlist(itemId);
|
|
2253
|
+
warn: (...args) => {
|
|
2254
|
+
if (isDevelopment2) {
|
|
2255
|
+
console.warn(`[${scope}]`, ...args);
|
|
2256
|
+
}
|
|
2544
2257
|
},
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
});
|
|
2550
|
-
}
|
|
2551
|
-
function useRemoveProductFromWishlist() {
|
|
2552
|
-
const queryClient = useQueryClient4();
|
|
2553
|
-
return useMutation4({
|
|
2554
|
-
mutationFn: (productId) => {
|
|
2555
|
-
return removeProductFromWishlist(productId);
|
|
2258
|
+
warning: (...args) => {
|
|
2259
|
+
if (isDevelopment2) {
|
|
2260
|
+
console.warn(`[${scope}]`, ...args);
|
|
2261
|
+
}
|
|
2556
2262
|
},
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2263
|
+
error: (...args) => {
|
|
2264
|
+
if (isDevelopment2) {
|
|
2265
|
+
console.error(`[${scope}]`, ...args);
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
info: (...args) => {
|
|
2269
|
+
if (isDevelopment2) {
|
|
2270
|
+
console.info(`[${scope}]`, ...args);
|
|
2271
|
+
}
|
|
2560
2272
|
}
|
|
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
|
-
}
|
|
2273
|
+
})
|
|
2274
|
+
};
|
|
2573
2275
|
|
|
2574
|
-
// src/
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
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
|
-
});
|
|
2276
|
+
// src/utils/sanitize-url.ts
|
|
2277
|
+
var InvalidBaseURLError = class extends Error {
|
|
2278
|
+
constructor(message) {
|
|
2279
|
+
super(message);
|
|
2280
|
+
this.name = "InvalidBaseURLError";
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
var ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "ws:", "wss:"]);
|
|
2284
|
+
function sanitizeBaseUrl(url, name = "URL") {
|
|
2285
|
+
if (url == null) {
|
|
2286
|
+
throw new InvalidBaseURLError(`${name} is not set`);
|
|
2287
|
+
}
|
|
2288
|
+
const cleaned0 = url.trim();
|
|
2289
|
+
if (!cleaned0) {
|
|
2290
|
+
throw new InvalidBaseURLError(`${name} is empty`);
|
|
2291
|
+
}
|
|
2292
|
+
const cleaned = cleaned0.replace(/\/+$/, "");
|
|
2293
|
+
let parsed;
|
|
2294
|
+
try {
|
|
2295
|
+
parsed = new URL(cleaned);
|
|
2296
|
+
} catch {
|
|
2297
|
+
throw new InvalidBaseURLError(
|
|
2298
|
+
`${name} must start with http://, https://, ws://, or wss://, got: ${url}`
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
|
|
2302
|
+
throw new InvalidBaseURLError(
|
|
2303
|
+
`${name} must start with http://, https://, ws://, or wss://, got: ${url}`
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
if (!parsed.hostname) {
|
|
2307
|
+
throw new InvalidBaseURLError(`${name} is missing a host: ${url}`);
|
|
2308
|
+
}
|
|
2309
|
+
if (parsed.search || parsed.hash) {
|
|
2310
|
+
throw new InvalidBaseURLError(
|
|
2311
|
+
`${name} should not contain a query or fragment: ${url}`
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
return cleaned;
|
|
2638
2315
|
}
|
|
2639
2316
|
|
|
2640
|
-
// src/
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
return
|
|
2644
|
-
queryKey: ["consultations"],
|
|
2645
|
-
queryFn: () => consultationService.getConsultations()
|
|
2646
|
-
});
|
|
2317
|
+
// src/utils/customer.ts
|
|
2318
|
+
function buildFullName2(firstname, lastname) {
|
|
2319
|
+
const name = [firstname, lastname].filter(Boolean).join(" ").trim();
|
|
2320
|
+
return name || void 0;
|
|
2647
2321
|
}
|
|
2648
|
-
function
|
|
2649
|
-
return
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2322
|
+
function splitFullName2(fullName) {
|
|
2323
|
+
if (!fullName?.trim()) return { firstname: "", lastname: "" };
|
|
2324
|
+
const parts = fullName.trim().split(/\s+/);
|
|
2325
|
+
return {
|
|
2326
|
+
firstname: parts[0] ?? "",
|
|
2327
|
+
lastname: parts.slice(1).join(" ")
|
|
2328
|
+
};
|
|
2654
2329
|
}
|
|
2655
|
-
function
|
|
2656
|
-
const
|
|
2657
|
-
return
|
|
2658
|
-
mutationFn: (payload) => consultationService.createConsultation(payload),
|
|
2659
|
-
onSuccess: () => {
|
|
2660
|
-
queryClient.invalidateQueries({ queryKey: ["consultations"] });
|
|
2661
|
-
}
|
|
2662
|
-
});
|
|
2330
|
+
function getCustomerDisplayName(user) {
|
|
2331
|
+
const fromParts = buildFullName2(user.firstname, user.lastname);
|
|
2332
|
+
return fromParts || user.full_name || user.email.split("@")[0];
|
|
2663
2333
|
}
|
|
2664
|
-
function
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
});
|
|
2334
|
+
function getStorefrontUrl(businessId, customDomain) {
|
|
2335
|
+
if (customDomain && customDomain.trim()) {
|
|
2336
|
+
const cleanDomain = customDomain.trim().replace(/^https?:\/\//, "");
|
|
2337
|
+
return `https://${cleanDomain}`;
|
|
2338
|
+
}
|
|
2339
|
+
const cleanBid = (businessId || "store").toLowerCase().trim();
|
|
2340
|
+
return `https://${cleanBid}.kasuvia.com`;
|
|
2672
2341
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2342
|
+
|
|
2343
|
+
// src/utils/checkout.ts
|
|
2344
|
+
function buildDeliveryAddress(customer, location) {
|
|
2345
|
+
return {
|
|
2346
|
+
first_name: customer.first_name,
|
|
2347
|
+
last_name: customer.last_name,
|
|
2348
|
+
phone: customer.phone,
|
|
2349
|
+
...location
|
|
2350
|
+
};
|
|
2681
2351
|
}
|
|
2682
|
-
function
|
|
2683
|
-
|
|
2684
|
-
return useMutation6({
|
|
2685
|
-
mutationFn: (reason) => consultationService.rejectQuote(consultationId, reason),
|
|
2686
|
-
onSuccess: () => {
|
|
2687
|
-
queryClient.invalidateQueries({ queryKey: ["consultation", consultationId] });
|
|
2688
|
-
}
|
|
2689
|
-
});
|
|
2352
|
+
function getCheckoutCustomerName(customer) {
|
|
2353
|
+
return buildFullName2(customer.first_name, customer.last_name) ?? customer.email.split("@")[0];
|
|
2690
2354
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
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 };
|
|
2355
|
+
function enrichCompletedOrder(order, options) {
|
|
2356
|
+
const customerName = getCheckoutCustomerName(options.customer);
|
|
2357
|
+
return {
|
|
2358
|
+
...order,
|
|
2359
|
+
customerName: order.customerName || customerName,
|
|
2360
|
+
customerEmail: order.customerEmail || options.customer.email,
|
|
2361
|
+
shippingAddress: order.shippingAddress ?? (options.productType === "physical" && options.location ? buildDeliveryAddress(options.customer, options.location) : void 0),
|
|
2362
|
+
bespokeBrief: order.bespokeBrief ?? options.bespokeBrief
|
|
2363
|
+
};
|
|
2777
2364
|
}
|
|
2778
2365
|
|
|
2779
|
-
// src/
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
}
|
|
2791
|
-
|
|
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;
|
|
2366
|
+
// src/utils/error-handler.ts
|
|
2367
|
+
function getErrorMessage(error, fallback = "Something went wrong") {
|
|
2368
|
+
if (error instanceof Error) {
|
|
2369
|
+
return error.message;
|
|
2370
|
+
}
|
|
2371
|
+
if (error instanceof Response) {
|
|
2372
|
+
return `HTTP ${error.status}: ${error.statusText}`;
|
|
2373
|
+
}
|
|
2374
|
+
if (typeof error === "object" && error !== null) {
|
|
2375
|
+
const err = error;
|
|
2376
|
+
return err.detail || err.message || fallback;
|
|
2377
|
+
}
|
|
2378
|
+
return fallback;
|
|
2881
2379
|
}
|
|
2882
2380
|
|
|
2883
|
-
// src/
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
useEffect4(() => {
|
|
2888
|
-
setMounted(true);
|
|
2889
|
-
}, []);
|
|
2890
|
-
return mounted;
|
|
2381
|
+
// src/utils/storage.ts
|
|
2382
|
+
function getPrefixedKey(businessId, key) {
|
|
2383
|
+
const cleanKey = key.startsWith("kasuvia_") ? key.substring(5) : key;
|
|
2384
|
+
return businessId ? `kasuvia-sf-${businessId}-${cleanKey}` : `kasuvia-sf-anon-${cleanKey}`;
|
|
2891
2385
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
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(() => {
|
|
2386
|
+
var sessionStore = {
|
|
2387
|
+
get: (businessIdOrKey, key) => {
|
|
2388
|
+
if (typeof window === "undefined") return null;
|
|
2389
|
+
const bId = key ? businessIdOrKey : "";
|
|
2390
|
+
const k = key || businessIdOrKey;
|
|
2391
|
+
return sessionStorage.getItem(getPrefixedKey(bId, k));
|
|
2392
|
+
},
|
|
2393
|
+
set: (businessIdOrKey, keyOrVal, val) => {
|
|
2911
2394
|
if (typeof window === "undefined") return;
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
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(() => {
|
|
2395
|
+
const bId = val !== void 0 ? businessIdOrKey : "";
|
|
2396
|
+
const k = val !== void 0 ? keyOrVal : businessIdOrKey;
|
|
2397
|
+
const v = val !== void 0 ? val : keyOrVal;
|
|
2398
|
+
sessionStorage.setItem(getPrefixedKey(bId, k), v);
|
|
2399
|
+
},
|
|
2400
|
+
remove: (businessIdOrKey, key) => {
|
|
2924
2401
|
if (typeof window === "undefined") return;
|
|
2925
|
-
const
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
}
|
|
2938
|
-
|
|
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;
|
|
2402
|
+
const bId = key ? businessIdOrKey : "";
|
|
2403
|
+
const k = key || businessIdOrKey;
|
|
2404
|
+
sessionStorage.removeItem(getPrefixedKey(bId, k));
|
|
2405
|
+
},
|
|
2406
|
+
getJSON: (businessIdOrKey, key) => {
|
|
2407
|
+
if (typeof window === "undefined") return null;
|
|
2408
|
+
const bId = key ? businessIdOrKey : "";
|
|
2409
|
+
const k = key || businessIdOrKey;
|
|
2410
|
+
const value = sessionStorage.getItem(getPrefixedKey(bId, k));
|
|
2411
|
+
if (!value) return null;
|
|
2412
|
+
try {
|
|
2413
|
+
return JSON.parse(value);
|
|
2414
|
+
} catch {
|
|
2415
|
+
return null;
|
|
2952
2416
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
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(() => {
|
|
2417
|
+
},
|
|
2418
|
+
setJSON: (businessIdOrKey, keyOrVal, val) => {
|
|
2964
2419
|
if (typeof window === "undefined") return;
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
const
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
if (
|
|
2997
|
-
|
|
2420
|
+
const bId = val !== void 0 ? businessIdOrKey : "";
|
|
2421
|
+
const k = val !== void 0 ? keyOrVal : businessIdOrKey;
|
|
2422
|
+
const v = val !== void 0 ? val : keyOrVal;
|
|
2423
|
+
sessionStorage.setItem(getPrefixedKey(bId, k), JSON.stringify(v));
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
var localStore = {
|
|
2427
|
+
get: (businessIdOrKey, key) => {
|
|
2428
|
+
if (typeof window === "undefined") return null;
|
|
2429
|
+
const bId = key ? businessIdOrKey : "";
|
|
2430
|
+
const k = key || businessIdOrKey;
|
|
2431
|
+
return localStorage.getItem(getPrefixedKey(bId, k));
|
|
2432
|
+
},
|
|
2433
|
+
set: (businessIdOrKey, keyOrVal, val) => {
|
|
2434
|
+
if (typeof window === "undefined") return;
|
|
2435
|
+
const bId = val !== void 0 ? businessIdOrKey : "";
|
|
2436
|
+
const k = val !== void 0 ? keyOrVal : businessIdOrKey;
|
|
2437
|
+
const v = val !== void 0 ? val : keyOrVal;
|
|
2438
|
+
localStorage.setItem(getPrefixedKey(bId, k), v);
|
|
2439
|
+
},
|
|
2440
|
+
remove: (businessIdOrKey, key) => {
|
|
2441
|
+
if (typeof window === "undefined") return;
|
|
2442
|
+
const bId = key ? businessIdOrKey : "";
|
|
2443
|
+
const k = key || businessIdOrKey;
|
|
2444
|
+
localStorage.removeItem(getPrefixedKey(bId, k));
|
|
2445
|
+
},
|
|
2446
|
+
getJSON: (businessIdOrKey, key) => {
|
|
2447
|
+
if (typeof window === "undefined") return null;
|
|
2448
|
+
const bId = key ? businessIdOrKey : "";
|
|
2449
|
+
const k = key || businessIdOrKey;
|
|
2450
|
+
const value = localStorage.getItem(getPrefixedKey(bId, k));
|
|
2451
|
+
if (!value) return null;
|
|
2452
|
+
try {
|
|
2453
|
+
return JSON.parse(value);
|
|
2454
|
+
} catch {
|
|
2455
|
+
return null;
|
|
2998
2456
|
}
|
|
2999
|
-
},
|
|
3000
|
-
|
|
3001
|
-
|
|
2457
|
+
},
|
|
2458
|
+
setJSON: (businessIdOrKey, keyOrVal, val) => {
|
|
2459
|
+
if (typeof window === "undefined") return;
|
|
2460
|
+
const bId = val !== void 0 ? businessIdOrKey : "";
|
|
2461
|
+
const k = val !== void 0 ? keyOrVal : businessIdOrKey;
|
|
2462
|
+
const v = val !== void 0 ? val : keyOrVal;
|
|
2463
|
+
localStorage.setItem(getPrefixedKey(bId, k), JSON.stringify(v));
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
3002
2466
|
|
|
3003
|
-
// src/
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
-
});
|
|
2467
|
+
// src/utils/validators.ts
|
|
2468
|
+
function validateEmail(email) {
|
|
2469
|
+
if (!email || email.trim() === "") {
|
|
2470
|
+
return "Email is required";
|
|
2471
|
+
}
|
|
2472
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2473
|
+
if (!emailRegex.test(email)) {
|
|
2474
|
+
return "Please enter a valid email address";
|
|
2475
|
+
}
|
|
2476
|
+
return null;
|
|
3045
2477
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
2478
|
+
function validatePassword(password) {
|
|
2479
|
+
if (!password || password.trim() === "") {
|
|
2480
|
+
return "Password is required";
|
|
2481
|
+
}
|
|
2482
|
+
if (password.length < 8) {
|
|
2483
|
+
return "Password must be at least 8 characters";
|
|
2484
|
+
}
|
|
2485
|
+
if (password.length > 72) {
|
|
2486
|
+
return "Password cannot exceed 72 characters";
|
|
2487
|
+
}
|
|
2488
|
+
if (!/[A-Z]/.test(password)) {
|
|
2489
|
+
return "Password must contain at least one uppercase letter";
|
|
2490
|
+
}
|
|
2491
|
+
if (!/[a-z]/.test(password)) {
|
|
2492
|
+
return "Password must contain at least one lowercase letter";
|
|
2493
|
+
}
|
|
2494
|
+
if (!/[0-9]/.test(password)) {
|
|
2495
|
+
return "Password must contain at least one number";
|
|
2496
|
+
}
|
|
2497
|
+
return null;
|
|
3064
2498
|
}
|
|
3065
|
-
function
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
2499
|
+
function validateName(name, fieldName = "Name") {
|
|
2500
|
+
if (!name || name.trim() === "") {
|
|
2501
|
+
return `${fieldName} is required`;
|
|
2502
|
+
}
|
|
2503
|
+
if (name.trim().length < 2) {
|
|
2504
|
+
return `${fieldName} must be at least 2 characters`;
|
|
2505
|
+
}
|
|
2506
|
+
if (name.trim().length > 50) {
|
|
2507
|
+
return `${fieldName} cannot exceed 50 characters`;
|
|
2508
|
+
}
|
|
2509
|
+
const nameRegex = /^[a-zA-Z\s\-'\u00C0-\u017F]+$/;
|
|
2510
|
+
if (!nameRegex.test(name.trim())) {
|
|
2511
|
+
return `${fieldName} can only contain letters, spaces, hyphens, and apostrophes`;
|
|
2512
|
+
}
|
|
2513
|
+
return null;
|
|
3077
2514
|
}
|
|
3078
|
-
function
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
queryClient.invalidateQueries({ queryKey: ["cms-entries", modelSlug] });
|
|
3084
|
-
}
|
|
3085
|
-
});
|
|
3086
|
-
return {
|
|
3087
|
-
createEntry: mutation.mutate,
|
|
3088
|
-
isLoading: mutation.isPending,
|
|
3089
|
-
error: mutation.error
|
|
3090
|
-
};
|
|
2515
|
+
function validateRequired(value, fieldName) {
|
|
2516
|
+
if (!value || value.trim() === "") {
|
|
2517
|
+
return `${fieldName} is required`;
|
|
2518
|
+
}
|
|
2519
|
+
return null;
|
|
3091
2520
|
}
|
|
3092
|
-
function
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
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
|
-
};
|
|
2521
|
+
function validateConfirmPassword(password, confirmPassword) {
|
|
2522
|
+
if (!confirmPassword) return null;
|
|
2523
|
+
if (confirmPassword !== password) {
|
|
2524
|
+
return "Passwords do not match";
|
|
2525
|
+
}
|
|
2526
|
+
return null;
|
|
3106
2527
|
}
|
|
3107
|
-
function
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
});
|
|
3116
|
-
return {
|
|
3117
|
-
deleteEntry: mutation.mutate,
|
|
3118
|
-
isLoading: mutation.isPending,
|
|
3119
|
-
error: mutation.error
|
|
3120
|
-
};
|
|
2528
|
+
function validateMessage(message) {
|
|
2529
|
+
if (!message || message.trim() === "") {
|
|
2530
|
+
return "Message is required";
|
|
2531
|
+
}
|
|
2532
|
+
if (message.trim().length < 10) {
|
|
2533
|
+
return "Message must be at least 10 characters";
|
|
2534
|
+
}
|
|
2535
|
+
return null;
|
|
3121
2536
|
}
|
|
3122
2537
|
export {
|
|
3123
|
-
AuthCtx,
|
|
3124
|
-
AuthProvider,
|
|
3125
|
-
CartCtx,
|
|
3126
|
-
CartProvider,
|
|
3127
2538
|
InvalidBaseURLError,
|
|
2539
|
+
STORAGE_KEYS,
|
|
2540
|
+
SdkProvider,
|
|
2541
|
+
ServiceLocationTypeEnum,
|
|
2542
|
+
ServiceRecurringIntervalEnum,
|
|
2543
|
+
ServiceTypeEnum,
|
|
3128
2544
|
acceptQuote,
|
|
3129
2545
|
addToCart,
|
|
3130
2546
|
addToGuestCart,
|
|
3131
2547
|
addToWishlist,
|
|
3132
2548
|
approveDeliverable,
|
|
3133
|
-
authAxios,
|
|
3134
2549
|
bespokeService,
|
|
3135
2550
|
bookAppointment,
|
|
3136
2551
|
buildDeliveryAddress,
|
|
3137
|
-
buildFullName,
|
|
2552
|
+
buildFullName2 as buildFullName,
|
|
3138
2553
|
cancelAppointment,
|
|
3139
2554
|
cancelOrder,
|
|
3140
2555
|
checkAvailability,
|
|
3141
2556
|
checkInWishlist,
|
|
3142
2557
|
clearCart,
|
|
3143
2558
|
clearGuestCart,
|
|
3144
|
-
|
|
3145
|
-
cmsAxios,
|
|
3146
|
-
collectionKeys,
|
|
2559
|
+
clearTokens,
|
|
3147
2560
|
configService,
|
|
3148
|
-
configureHttpClients,
|
|
3149
2561
|
consultationService,
|
|
3150
2562
|
createCmsEntry,
|
|
3151
2563
|
createConsultation,
|
|
@@ -3159,7 +2571,6 @@ export {
|
|
|
3159
2571
|
getAppointments,
|
|
3160
2572
|
getAuthUser,
|
|
3161
2573
|
getAvailableSlots,
|
|
3162
|
-
getBackendUrl,
|
|
3163
2574
|
getBusinessConfig,
|
|
3164
2575
|
getCacheTier,
|
|
3165
2576
|
getCart,
|
|
@@ -3167,43 +2578,38 @@ export {
|
|
|
3167
2578
|
getCmsEntry,
|
|
3168
2579
|
getConsultation,
|
|
3169
2580
|
getConsultations,
|
|
3170
|
-
getCountries,
|
|
3171
2581
|
getCustomerDisplayName,
|
|
3172
2582
|
getDeliverables,
|
|
3173
2583
|
getErrorMessage,
|
|
3174
2584
|
getFeaturedProducts,
|
|
3175
|
-
getGraphQLClient,
|
|
3176
2585
|
getGuestCart,
|
|
3177
2586
|
getGuestCartCount,
|
|
3178
2587
|
getGuestCartProductType,
|
|
3179
2588
|
getGuestCartSubtotal,
|
|
3180
|
-
getGuestSessionId,
|
|
3181
2589
|
getLatestProducts,
|
|
2590
|
+
getLowStockProducts,
|
|
3182
2591
|
getMe,
|
|
3183
2592
|
getNewProducts,
|
|
3184
|
-
|
|
2593
|
+
getOnSaleProducts,
|
|
3185
2594
|
getOrders,
|
|
3186
2595
|
getPopularProducts,
|
|
3187
2596
|
getRecommendedProducts,
|
|
3188
|
-
getRuntimeConfig,
|
|
3189
2597
|
getSellerShippingAddress,
|
|
3190
2598
|
getShippingRates,
|
|
3191
|
-
|
|
2599
|
+
getStoredTokens,
|
|
3192
2600
|
getStorefrontCollectionBySlug,
|
|
3193
2601
|
getStorefrontCollections,
|
|
3194
2602
|
getStorefrontProductBySlug,
|
|
3195
2603
|
getStorefrontProducts,
|
|
2604
|
+
getStorefrontUrl,
|
|
3196
2605
|
getSupportedCurrencies,
|
|
3197
|
-
getSupportedCurrenciesList,
|
|
3198
2606
|
getSupportedLanguages,
|
|
3199
|
-
getSupportedLanguagesList,
|
|
3200
2607
|
getTransactionStatus,
|
|
3201
2608
|
getWishlist,
|
|
3202
2609
|
googleTranslate,
|
|
3203
|
-
initSdk,
|
|
3204
2610
|
initiatePayment,
|
|
3205
|
-
isInitialized,
|
|
3206
2611
|
listCmsEntries,
|
|
2612
|
+
localStore,
|
|
3207
2613
|
logger,
|
|
3208
2614
|
loginCustomer,
|
|
3209
2615
|
logisticsService,
|
|
@@ -3212,10 +2618,7 @@ export {
|
|
|
3212
2618
|
mergeGuestCart,
|
|
3213
2619
|
mergeGuestCartIfPresent,
|
|
3214
2620
|
notificationsService,
|
|
3215
|
-
paymentAxios,
|
|
3216
2621
|
paymentService,
|
|
3217
|
-
productKeys,
|
|
3218
|
-
publicStoreAxios,
|
|
3219
2622
|
refreshToken,
|
|
3220
2623
|
registerCustomer,
|
|
3221
2624
|
rejectQuote,
|
|
@@ -3225,90 +2628,31 @@ export {
|
|
|
3225
2628
|
removeProductFromWishlist,
|
|
3226
2629
|
requestQuote,
|
|
3227
2630
|
requestRevision,
|
|
3228
|
-
resetConfig,
|
|
3229
|
-
resetGraphQLClient,
|
|
3230
2631
|
sanitizeBaseUrl,
|
|
3231
2632
|
sendContactConfirmation,
|
|
3232
2633
|
sendContactEmail,
|
|
3233
2634
|
sendMessage,
|
|
3234
2635
|
serviceBookingService,
|
|
2636
|
+
sessionStore,
|
|
3235
2637
|
setEnvironmentMode,
|
|
3236
2638
|
setLoggerEnvironmentMode,
|
|
3237
2639
|
silentRefresh,
|
|
3238
|
-
splitFullName,
|
|
3239
|
-
storeManagementAxios,
|
|
2640
|
+
splitFullName2 as splitFullName,
|
|
3240
2641
|
storeTokens,
|
|
3241
|
-
tokenStore,
|
|
3242
2642
|
trackOrder,
|
|
3243
2643
|
updateCartItem,
|
|
3244
2644
|
updateCmsEntry,
|
|
3245
2645
|
updateGuestCartItemQuantity,
|
|
3246
2646
|
updateProfile,
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
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,
|
|
2647
|
+
useSdkContext,
|
|
2648
|
+
validateConfirmPassword,
|
|
2649
|
+
validateEmail,
|
|
2650
|
+
validateMessage,
|
|
2651
|
+
validateName,
|
|
2652
|
+
validatePassword,
|
|
2653
|
+
validateRequired,
|
|
3309
2654
|
validateShippingAddress,
|
|
3310
2655
|
verifySocialToken,
|
|
3311
|
-
verifyTransaction
|
|
3312
|
-
wishlistKeys
|
|
2656
|
+
verifyTransaction
|
|
3313
2657
|
};
|
|
3314
2658
|
//# sourceMappingURL=index.mjs.map
|