react-shopwave-connect 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,88 +1,179 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useReducer, useState } from "react";
2
2
 
3
+ //#region src/core/errors.ts
4
+ /**
5
+ * Thrown by every `core` function when a request fails, either at the HTTP
6
+ * level (`status` is the HTTP status) or because the Shopwave response carried
7
+ * `api.message.errors` (then `status` is the HTTP status of that response,
8
+ * usually 200/201, and `errors` holds the API errors).
9
+ *
10
+ * Branch on `status` (e.g. `isUnauthorized` → send the user to log in) rather
11
+ * than parsing `message`.
12
+ */
13
+ var ShopwaveApiError = class extends Error {
14
+ /** HTTP status of the response (0 when the request never got a response). */
15
+ status;
16
+ /** Shopwave API errors, when the response carried any. */
17
+ errors;
18
+ /** Parsed response body, when there was one. */
19
+ body;
20
+ constructor(status, message, details = {}) {
21
+ super(message, details.cause !== void 0 ? { cause: details.cause } : void 0);
22
+ this.name = "ShopwaveApiError";
23
+ this.status = status;
24
+ this.errors = details.errors ?? null;
25
+ this.body = details.body;
26
+ }
27
+ /** 401, or the Shopwave "token expired / invalid" error 908. */
28
+ get isUnauthorized() {
29
+ if (this.status === 401) return true;
30
+ return !!this.errors && Object.entries(this.errors).some(([k, v]) => k === "908" || Number(v?.id) === 908);
31
+ }
32
+ get isNotFound() {
33
+ return this.status === 404;
34
+ }
35
+ };
36
+ /** The `api.message.errors` (or `api.message.error`) map of a Shopwave response, or `null` when there are none. */
37
+ function getApiErrorMap(body) {
38
+ const message = body?.api?.message;
39
+ const errors = message?.errors ?? message?.error;
40
+ if (errors && typeof errors === "object" && Object.keys(errors).length > 0) return errors;
41
+ return null;
42
+ }
43
+ function describeErrors(errors) {
44
+ return Object.entries(errors).map(([code, e]) => {
45
+ const text = [e?.title, e?.details].filter(Boolean).join(": ");
46
+ return text ? `${code} ${text}` : code;
47
+ }).join("; ");
48
+ }
49
+ /**
50
+ * Builds a {@link ShopwaveApiError} for a response. Understands the Shopwave
51
+ * envelope (`api.message.errors`) and the `{ error, message }` bodies returned
52
+ * by the `react-shopwave-connect/next` routes.
53
+ */
54
+ function toShopwaveApiError(status, body, fallback) {
55
+ const errors = getApiErrorMap(body);
56
+ let detail = errors ? describeErrors(errors) : "";
57
+ if (!detail && body && typeof body === "object") {
58
+ const b = body;
59
+ if (typeof b.message === "string") detail = b.message;
60
+ else if (typeof b.error === "string") detail = b.error;
61
+ }
62
+ if (!detail) detail = fallback ?? (status >= 400 ? "request failed" : "API returned errors");
63
+ return new ShopwaveApiError(status, `Shopwave API error (${status}): ${detail}`, {
64
+ errors,
65
+ body
66
+ });
67
+ }
68
+ /** Throws a {@link ShopwaveApiError} when the response body carries API errors. */
69
+ function assertNoApiErrors(body, status) {
70
+ if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
71
+ }
72
+
73
+ //#endregion
3
74
  //#region src/core/request.ts
4
75
  function resolveFetch(options) {
5
76
  const f = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
6
77
  if (!f) throw new Error("No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.");
7
78
  return f;
8
79
  }
9
- function buildExtras(extras, options) {
80
+ /** Options with `token` filled in from a legacy per-call `params.token`, unless `options.token` is set. */
81
+ function withToken(options, token) {
82
+ return token && !options.token ? {
83
+ ...options,
84
+ token
85
+ } : options;
86
+ }
87
+ /** `Authorization` header value for a token (`OAuth <token>` unless it already has a scheme). */
88
+ function authorizationFor(token) {
89
+ const t = token.trim();
90
+ return /^(oauth|bearer)\s+\S/i.test(t) ? t : `OAuth ${t}`;
91
+ }
92
+ /** Headers shared by every request: Accept + the token, when one is given. */
93
+ function baseHeaders(options) {
94
+ const headers = { Accept: "application/json" };
95
+ if (options.token) headers.Authorization = authorizationFor(options.token);
96
+ return headers;
97
+ }
98
+ function buildExtras(extras) {
10
99
  const merged = {
11
100
  "Content-Type": "application/json",
12
101
  ...extras
13
102
  };
14
- if (options.token != null && merged.token == null) merged.token = options.token;
103
+ delete merged.token;
15
104
  return merged;
16
105
  }
17
- /**
18
- * Returns a JSON string of API errors if the response carries any, otherwise
19
- * `null`. Mirrors the original `!responseJson.api.message.errors` checks.
20
- */
21
- function getApiErrors(api) {
22
- const errors = api?.message?.errors;
23
- if (errors && Object.keys(errors).length > 0) return JSON.stringify(errors);
24
- return null;
106
+ /** Parses a response body as JSON; `null` for an empty body, `{ raw }` for non-JSON. */
107
+ async function readBody(response) {
108
+ const text = await response.text();
109
+ if (!text) return null;
110
+ try {
111
+ return JSON.parse(text);
112
+ } catch {
113
+ return { raw: text };
114
+ }
115
+ }
116
+ async function send(path, init, options) {
117
+ const doFetch = resolveFetch(options);
118
+ const url = (options.baseUrl ?? "") + path;
119
+ let response;
120
+ try {
121
+ response = await doFetch(url, {
122
+ ...init,
123
+ signal: options.signal
124
+ });
125
+ } catch (error) {
126
+ if (error?.name === "AbortError") throw error;
127
+ throw new ShopwaveApiError(0, `Shopwave API request failed: ${error?.message ?? error}`, { cause: error });
128
+ }
129
+ const body = await readBody(response);
130
+ if (!response.ok) throw toShopwaveApiError(response.status, body, response.statusText || void 0);
131
+ return {
132
+ status: response.status,
133
+ body
134
+ };
25
135
  }
26
136
  /**
27
- * GET helper. Sends request metadata via the `extras` header exactly like the
28
- * original hooks did, parses JSON, and returns the typed payload.
137
+ * GET helper. Sends request metadata via the `extras` header, parses JSON and
138
+ * returns the payload. Throws {@link ShopwaveApiError} for non-2xx responses.
29
139
  */
30
140
  async function apiGet(path, extras, options = {}) {
31
- const doFetch = resolveFetch(options);
32
- const url = (options.baseUrl ?? "") + path;
33
- const headers = { extras: JSON.stringify(buildExtras(extras, options)) };
34
- console.log("[API GET]", {
35
- url,
36
- headers
37
- });
38
- const response = await doFetch(url, {
141
+ const { body } = await send(path, {
39
142
  method: "GET",
40
- headers,
41
- signal: options.signal
42
- });
43
- if (!response.ok) throw new Error(`Network response was not ok (${response.status})`);
44
- return await response.json();
143
+ headers: {
144
+ ...baseHeaders(options),
145
+ extras: JSON.stringify(buildExtras(extras))
146
+ }
147
+ }, options);
148
+ return body;
45
149
  }
46
150
  /**
47
- * JSON body helper for POST/PUT mutations.
151
+ * JSON body helper for POST/PUT mutations. Returns the parsed body (`null` for
152
+ * an empty one). Throws {@link ShopwaveApiError} for non-2xx responses.
48
153
  */
49
154
  async function apiSend(path, method, payload, options = {}) {
50
- const doFetch = resolveFetch(options);
51
- const url = (options.baseUrl ?? "") + path;
52
- const headers = { "Content-Type": "application/json" };
53
- if (options.token != null) headers.token = options.token;
54
- console.log(`[API ${method}]`, {
55
- url,
56
- headers,
57
- payload
58
- });
59
- const response = await doFetch(url, {
155
+ const { body } = await send(path, {
60
156
  method,
61
- headers,
62
- body: JSON.stringify(payload),
63
- signal: options.signal
64
- });
65
- if (!response.ok) throw new Error(`Failed to ${method.toLowerCase()}: ${response.statusText}`);
66
- return await response.json();
157
+ headers: {
158
+ ...baseHeaders(options),
159
+ "Content-Type": "application/json"
160
+ },
161
+ body: JSON.stringify(payload)
162
+ }, options);
163
+ return body;
67
164
  }
68
165
  /**
69
- * DELETE helper.
166
+ * DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body
167
+ * does come back and carries API errors, they are thrown. Returns the parsed
168
+ * body (`null` when empty).
70
169
  */
71
170
  async function apiDelete(path, options = {}) {
72
- const doFetch = resolveFetch(options);
73
- const url = (options.baseUrl ?? "") + path;
74
- const headers = {};
75
- if (options.token != null) headers.token = options.token;
76
- console.log("[API DELETE]", {
77
- url,
78
- headers
79
- });
80
- const response = await doFetch(url, {
171
+ const { status, body } = await send(path, {
81
172
  method: "DELETE",
82
- headers,
83
- signal: options.signal
84
- });
85
- if (!response.ok) throw new Error(`Failed to delete: ${response.statusText}`);
173
+ headers: baseHeaders(options)
174
+ }, options);
175
+ if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
176
+ return body;
86
177
  }
87
178
 
88
179
  //#endregion
@@ -94,11 +185,9 @@ async function apiDelete(path, options = {}) {
94
185
  async function fetchCategories(params = {}, options = {}) {
95
186
  const extras = { deleted: params.deleted ?? false };
96
187
  if (params.categoryIds) extras.categoryIds = params.categoryIds;
97
- if (params.token) extras.token = params.token;
98
- const json = await apiGet("/api/categories", extras, options);
99
- const error = getApiErrors(json.api);
100
- if (error) throw new Error(error);
101
- return Object.values(json.categories ?? {});
188
+ const json = await apiGet("/api/categories", extras, withToken(options, params.token));
189
+ assertNoApiErrors(json, 200);
190
+ return Object.values(json?.categories ?? {});
102
191
  }
103
192
 
104
193
  //#endregion
@@ -110,9 +199,8 @@ async function fetchCategories(params = {}, options = {}) {
110
199
  async function fetchConsumers(consumerIds, options = {}) {
111
200
  if (!consumerIds || consumerIds.length === 0) return [];
112
201
  const json = await apiGet("/api/consumer", { ids: consumerIds.join(",") }, options);
113
- const error = getApiErrors(json.api);
114
- if (error) throw new Error(error);
115
- return Object.values(json.consumers ?? {});
202
+ assertNoApiErrors(json, 200);
203
+ return Object.values(json?.consumers ?? {});
116
204
  }
117
205
 
118
206
  //#endregion
@@ -123,10 +211,9 @@ async function fetchConsumers(consumerIds, options = {}) {
123
211
  async function fetchEmployees(params = {}, options = {}) {
124
212
  const extras = { deleted: params.deleted ?? false };
125
213
  if (params.employeeIds) extras.employeeIds = params.employeeIds;
126
- const json = await apiGet("/api/employees", extras, options);
127
- const error = getApiErrors(json.api);
128
- if (error) throw new Error(error);
129
- return Object.values(json.employees ?? {});
214
+ const json = await apiGet("/api/employees", extras, withToken(options, params.token));
215
+ assertNoApiErrors(json, 200);
216
+ return Object.values(json?.employees ?? {});
130
217
  }
131
218
 
132
219
  //#endregion
@@ -138,11 +225,9 @@ async function fetchProducts(params = {}, options = {}) {
138
225
  const extras = { deleted: params.deleted ?? false };
139
226
  if (params.productIds) extras.productIds = params.productIds;
140
227
  if (params.storeId) extras.storeId = params.storeId;
141
- if (params.token) extras.token = params.token;
142
- const json = await apiGet("/api/products", extras, options);
143
- const error = getApiErrors(json.api);
144
- if (error) throw new Error(error);
145
- return Object.values(json.products ?? {});
228
+ const json = await apiGet("/api/products", extras, withToken(options, params.token));
229
+ assertNoApiErrors(json, 200);
230
+ return Object.values(json?.products ?? {});
146
231
  }
147
232
  /**
148
233
  * Fetches products by id and returns them keyed by id, transparently batching
@@ -171,11 +256,9 @@ async function fetchPromotions(params = {}, options = {}) {
171
256
  if (params.imminent !== void 0) extras.imminent = params.imminent;
172
257
  if (params.active !== void 0) extras.active = params.active;
173
258
  if (params.expired !== void 0) extras.expired = params.expired;
174
- if (params.token) extras.token = params.token;
175
- const json = await apiGet("/api/promotions", extras, options);
176
- const error = getApiErrors(json.api);
177
- if (error) throw new Error(error);
178
- return Object.values(json.promotions ?? {});
259
+ const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
260
+ assertNoApiErrors(json, 200);
261
+ return Object.values(json?.promotions ?? {});
179
262
  }
180
263
 
181
264
  //#endregion
@@ -184,10 +267,11 @@ async function fetchPromotions(params = {}, options = {}) {
184
267
  * Fetches stores. Returns the flattened `Store[]`.
185
268
  */
186
269
  async function fetchStores(params = {}, options = {}) {
187
- const json = await apiGet("/api/stores", { deleted: params.deleted ?? false }, options);
188
- const error = getApiErrors(json.api);
189
- if (error) throw new Error(error);
190
- return Object.values(json.stores ?? {});
270
+ const extras = { deleted: params.deleted ?? false };
271
+ if (params.storeIds) extras.storeIds = params.storeIds;
272
+ const json = await apiGet("/api/stores", extras, withToken(options, params.token));
273
+ assertNoApiErrors(json, 200);
274
+ return Object.values(json?.stores ?? {});
191
275
  }
192
276
 
193
277
  //#endregion
@@ -199,20 +283,43 @@ async function fetchStores(params = {}, options = {}) {
199
283
  */
200
284
  async function fetchReport(query, options = {}) {
201
285
  const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
202
- const error = getApiErrors(json.api);
203
- if (error) throw new Error(error);
204
- return json.reports ?? {};
286
+ assertNoApiErrors(json, 200);
287
+ return json?.reports ?? {};
205
288
  }
206
289
 
207
290
  //#endregion
208
291
  //#region src/core/session.ts
209
292
  /**
210
- * Ends the current server session.
293
+ * Reads the current login status from `GET /api/session`.
294
+ *
295
+ * Works with the new `react-shopwave-connect/next` session route
296
+ * (`{ loggedIn }`) and with older routes that returned the raw session
297
+ * (`{ token: {...} }`) — in that case only a boolean is surfaced.
298
+ */
299
+ async function fetchSession(options = {}) {
300
+ const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
301
+ if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
302
+ const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
303
+ method: "GET",
304
+ headers: { Accept: "application/json" },
305
+ cache: "no-store",
306
+ credentials: "same-origin",
307
+ signal: options.signal
308
+ });
309
+ if (response.status === 401) return { loggedIn: false };
310
+ if (!response.ok) throw new Error(`Session request failed (${response.status})`);
311
+ const json = await response.json();
312
+ if (json && typeof json.loggedIn === "boolean") return {
313
+ loggedIn: json.loggedIn,
314
+ expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
315
+ };
316
+ return { loggedIn: Boolean(json && json.token) };
317
+ }
318
+ /**
319
+ * Ends the current server session (no redirect).
211
320
  *
212
- * This is the framework-agnostic half of the original `useLogout` hook. The
213
- * Next.js-specific redirect logic (router.push to the auth server logout URL,
214
- * built from server actions) is intentionally NOT included here because it is
215
- * tied to `next/navigation` and `'use server'` actions — see the README.
321
+ * Prefer navigating to {@link logoutPath} when using
322
+ * `react-shopwave-connect/next`: it also signs the user out of the auth server.
216
323
  */
217
324
  async function logout(options = {}) {
218
325
  await apiDelete("/api/session?action=logout", options);
@@ -221,10 +328,12 @@ async function logout(options = {}) {
221
328
  //#endregion
222
329
  //#region src/core/entity.ts
223
330
  /**
224
- * Generic CRUD-style mutations extracted from `useHandleDelete` /
225
- * `useHandleSubmit`. The original hooks also surfaced notistack snackbars; that
226
- * UI concern is deliberately left to the hooks/host layer so core stays
227
- * dependency-free.
331
+ * Low-level, untyped mutations: the caller supplies the route and payload.
332
+ *
333
+ * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
334
+ * …): they know each route, build the request envelope, check the response and
335
+ * return the saved entity with its id. Keep these for routes that have no typed
336
+ * function yet (e.g. `/api/merchant`).
228
337
  */
229
338
  /**
230
339
  * Deletes `/api/{resourcePath}/{entityId}`.
@@ -234,11 +343,15 @@ async function deleteEntity(resourcePath, entityId, options = {}) {
234
343
  await apiDelete(`/api/${resourcePath}/${entityId}`, options);
235
344
  }
236
345
  /**
237
- * Sends a create/update mutation and returns the parsed `result` field
238
- * (matching the original `result.result` access).
346
+ * Sends a create/update mutation. Returns the response's `result` field when it
347
+ * has one (older app routes wrapped their answer that way), otherwise the whole
348
+ * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
239
349
  */
240
350
  async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
241
- return (await apiSend(endpoint, method, payload, options)).result;
351
+ const json = await apiSend(endpoint, method, payload, options);
352
+ assertNoApiErrors(json, 200);
353
+ if (json && typeof json === "object" && "result" in json) return json.result;
354
+ return json ?? void 0;
242
355
  }
243
356
 
244
357
  //#endregion
@@ -391,50 +504,127 @@ function toMessage(e) {
391
504
  if (e instanceof Error) return e.message;
392
505
  return String(e);
393
506
  }
507
+ function toStatus(e) {
508
+ return e instanceof ShopwaveApiError ? e.status : null;
509
+ }
510
+ function queryReducer(state, action) {
511
+ switch (action.type) {
512
+ case "start":
513
+ if (action.key !== state.key) return {
514
+ data: null,
515
+ key: action.key,
516
+ fetching: true,
517
+ error: null,
518
+ errorStatus: null
519
+ };
520
+ return {
521
+ ...state,
522
+ fetching: true,
523
+ error: null,
524
+ errorStatus: null
525
+ };
526
+ case "success":
527
+ if (action.key !== state.key) return state;
528
+ return {
529
+ data: action.data,
530
+ key: action.key,
531
+ fetching: false,
532
+ error: null,
533
+ errorStatus: null
534
+ };
535
+ case "failure":
536
+ if (action.key !== state.key) return state;
537
+ return {
538
+ ...state,
539
+ fetching: false,
540
+ error: action.error,
541
+ errorStatus: action.errorStatus
542
+ };
543
+ case "idle": return state.fetching ? {
544
+ ...state,
545
+ fetching: false
546
+ } : state;
547
+ default: return state;
548
+ }
549
+ }
550
+ /** Public view of the internal state for the params currently being rendered. */
551
+ function selectQueryView(state, key, enabled) {
552
+ if (state.key !== key) return {
553
+ data: null,
554
+ loading: enabled,
555
+ fetching: enabled,
556
+ error: null,
557
+ errorStatus: null
558
+ };
559
+ return {
560
+ data: state.data,
561
+ loading: state.fetching && state.data === null,
562
+ fetching: state.fetching,
563
+ error: state.error,
564
+ errorStatus: state.errorStatus
565
+ };
566
+ }
567
+ function depsKey(deps) {
568
+ try {
569
+ return JSON.stringify(deps);
570
+ } catch {
571
+ return String(deps);
572
+ }
573
+ }
394
574
  /**
395
575
  * Internal primitive powering the auto-fetch hooks. Runs `fetcher` on mount and
396
576
  * whenever `deps` change, tracks loading/error/data, cancels in-flight requests
397
- * on unmount or dep change, and exposes `refetch`.
577
+ * on unmount or dep change, and exposes `refetch` (which keeps the current data
578
+ * visible until the new data arrives).
398
579
  *
399
580
  * When `enabled` is false the fetch is skipped (used for hooks that need
400
581
  * arguments before they can run, e.g. consumers-by-id).
401
582
  */
402
583
  function useQuery(fetcher, deps, enabled = true) {
403
- const [data, setData] = useState(null);
404
- const [loading, setLoading] = useState(enabled);
405
- const [error, setError] = useState(null);
584
+ const key = depsKey(deps);
585
+ const [state, dispatch] = useReducer(queryReducer, {
586
+ data: null,
587
+ key: enabled ? key : null,
588
+ fetching: enabled,
589
+ error: null,
590
+ errorStatus: null
591
+ });
406
592
  const [tick, setTick] = useState(0);
407
593
  const refetch = useCallback(() => setTick((t) => t + 1), []);
408
594
  useEffect(() => {
409
595
  if (!enabled) {
410
- setLoading(false);
596
+ dispatch({ type: "idle" });
411
597
  return;
412
598
  }
413
599
  const controller = new AbortController();
414
- let active = true;
415
- setData(null);
416
- setError(null);
417
- setLoading(true);
600
+ dispatch({
601
+ type: "start",
602
+ key
603
+ });
418
604
  fetcher(controller.signal).then((result) => {
419
- if (active) setData(result);
605
+ if (!controller.signal.aborted) dispatch({
606
+ type: "success",
607
+ key,
608
+ data: result
609
+ });
420
610
  }).catch((e) => {
421
- if (active && e?.name !== "AbortError") setError(toMessage(e));
422
- }).finally(() => {
423
- if (active) setLoading(false);
611
+ if (!controller.signal.aborted && e?.name !== "AbortError") dispatch({
612
+ type: "failure",
613
+ key,
614
+ error: toMessage(e),
615
+ errorStatus: toStatus(e)
616
+ });
424
617
  });
425
618
  return () => {
426
- active = false;
427
619
  controller.abort();
428
620
  };
429
621
  }, [
430
- ...deps,
622
+ key,
431
623
  tick,
432
624
  enabled
433
625
  ]);
434
626
  return {
435
- data,
436
- loading,
437
- error,
627
+ ...selectQueryView(state, key, enabled),
438
628
  refetch
439
629
  };
440
630
  }
@@ -445,16 +635,19 @@ function useMutation(action, deps) {
445
635
  const [data, setData] = useState(null);
446
636
  const [loading, setLoading] = useState(false);
447
637
  const [error, setError] = useState(null);
638
+ const [errorStatus, setErrorStatus] = useState(null);
448
639
  return {
449
640
  mutate: useCallback(async (...args) => {
450
641
  setLoading(true);
451
642
  setError(null);
643
+ setErrorStatus(null);
452
644
  try {
453
645
  const result = await action(...args);
454
646
  setData(result);
455
647
  return result;
456
648
  } catch (e) {
457
649
  setError(toMessage(e));
650
+ setErrorStatus(toStatus(e));
458
651
  return null;
459
652
  } finally {
460
653
  setLoading(false);
@@ -462,7 +655,8 @@ function useMutation(action, deps) {
462
655
  }, deps),
463
656
  data,
464
657
  loading,
465
- error
658
+ error,
659
+ errorStatus
466
660
  };
467
661
  }
468
662
 
@@ -644,6 +838,23 @@ function useLogout(options) {
644
838
  return useMutation(() => logout(options), [options?.baseUrl, options?.token]);
645
839
  }
646
840
 
841
+ //#endregion
842
+ //#region src/hooks/useSession.ts
843
+ /**
844
+ * Current login status (`{ loggedIn, expiresAt }`) from `GET /api/session`.
845
+ * Tokens are never exposed to the browser.
846
+ *
847
+ * @example
848
+ * const { data: session, loading } = useSession();
849
+ * if (!loading && !session?.loggedIn) window.location.href = loginPath(location.pathname);
850
+ */
851
+ function useSession(options) {
852
+ return useQuery((signal) => fetchSession({
853
+ ...options,
854
+ signal
855
+ }), [options?.baseUrl]);
856
+ }
857
+
647
858
  //#endregion
648
859
  //#region src/hooks/useBasketReport.ts
649
860
  /**
@@ -748,5 +959,5 @@ function useBasketReport(options) {
748
959
  }
749
960
 
750
961
  //#endregion
751
- export { useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useStore, useSubmit };
962
+ export { useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useSession, useStore, useSubmit };
752
963
  //# sourceMappingURL=index.js.map