@doswiftly/storefront-sdk 4.0.0 → 4.2.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.
- package/README.md +51 -9
- package/dist/core/bot-protection/abstract-manager.d.ts +57 -0
- package/dist/core/bot-protection/abstract-manager.d.ts.map +1 -0
- package/dist/core/bot-protection/abstract-manager.js +144 -0
- package/dist/core/bot-protection/create-manager.d.ts +15 -0
- package/dist/core/bot-protection/create-manager.d.ts.map +1 -0
- package/dist/core/bot-protection/create-manager.js +33 -0
- package/dist/core/bot-protection/eucaptcha-manager.d.ts +15 -0
- package/dist/core/bot-protection/eucaptcha-manager.d.ts.map +1 -0
- package/dist/core/bot-protection/eucaptcha-manager.js +76 -0
- package/dist/core/bot-protection/fallback-manager.d.ts +18 -0
- package/dist/core/bot-protection/fallback-manager.d.ts.map +1 -0
- package/dist/core/bot-protection/fallback-manager.js +42 -0
- package/dist/core/bot-protection/turnstile-manager.d.ts +15 -0
- package/dist/core/bot-protection/turnstile-manager.d.ts.map +1 -0
- package/dist/core/bot-protection/turnstile-manager.js +78 -0
- package/dist/core/cart/cookie-config.d.ts +14 -0
- package/dist/core/cart/cookie-config.d.ts.map +1 -0
- package/dist/core/cart/cookie-config.js +13 -0
- package/dist/core/currency/cookie-config.d.ts +14 -0
- package/dist/core/currency/cookie-config.d.ts.map +1 -0
- package/dist/core/currency/cookie-config.js +13 -0
- package/dist/core/image.d.ts +55 -0
- package/dist/core/image.d.ts.map +1 -0
- package/dist/core/image.js +48 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +13 -0
- package/dist/core/language/cookie-config.d.ts +14 -0
- package/dist/core/language/cookie-config.d.ts.map +1 -0
- package/dist/core/language/cookie-config.js +13 -0
- package/dist/core/middleware/bot-protection.d.ts +71 -0
- package/dist/core/middleware/bot-protection.d.ts.map +1 -0
- package/dist/core/middleware/bot-protection.js +63 -0
- package/dist/core/middleware/currency.d.ts.map +1 -1
- package/dist/core/middleware/currency.js +2 -1
- package/dist/core/middleware/language.d.ts +18 -0
- package/dist/core/middleware/language.d.ts.map +1 -0
- package/dist/core/middleware/language.js +25 -0
- package/dist/react/bot-protection/bot-protection-context.d.ts +12 -0
- package/dist/react/bot-protection/bot-protection-context.d.ts.map +1 -0
- package/dist/react/bot-protection/bot-protection-context.js +9 -0
- package/dist/react/bot-protection/bot-protection-widget.d.ts +13 -0
- package/dist/react/bot-protection/bot-protection-widget.d.ts.map +1 -0
- package/dist/react/bot-protection/bot-protection-widget.js +34 -0
- package/dist/react/cookies.d.ts +17 -0
- package/dist/react/cookies.d.ts.map +1 -1
- package/dist/react/cookies.js +36 -3
- package/dist/react/hooks/use-bot-protection.d.ts +16 -0
- package/dist/react/hooks/use-bot-protection.d.ts.map +1 -0
- package/dist/react/hooks/use-bot-protection.js +24 -0
- package/dist/react/index.d.ts +10 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +9 -1
- package/dist/react/providers/language-provider.d.ts +18 -0
- package/dist/react/providers/language-provider.d.ts.map +1 -0
- package/dist/react/providers/language-provider.js +24 -0
- package/dist/react/providers/storefront-client-provider.d.ts +7 -2
- package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-client-provider.js +14 -3
- package/dist/react/providers/storefront-provider.d.ts +7 -1
- package/dist/react/providers/storefront-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-provider.js +11 -4
- package/dist/react/stores/cart.context.d.ts +12 -0
- package/dist/react/stores/cart.context.d.ts.map +1 -0
- package/dist/react/stores/cart.context.js +3 -0
- package/dist/react/stores/cart.store.d.ts +71 -0
- package/dist/react/stores/cart.store.d.ts.map +1 -0
- package/dist/react/stores/cart.store.js +166 -0
- package/dist/react/stores/currency.store.d.ts +6 -9
- package/dist/react/stores/currency.store.d.ts.map +1 -1
- package/dist/react/stores/currency.store.js +5 -22
- package/dist/react/stores/language.store.d.ts +33 -0
- package/dist/react/stores/language.store.d.ts.map +1 -0
- package/dist/react/stores/language.store.js +67 -0
- package/dist/react/stores/store-context.d.ts +5 -0
- package/dist/react/stores/store-context.d.ts.map +1 -1
- package/dist/react/stores/store-context.js +14 -0
- package/dist/react/types/shop-config.d.ts +19 -0
- package/dist/react/types/shop-config.d.ts.map +1 -0
- package/dist/react/types/shop-config.js +7 -0
- package/package.json +1 -1
- package/src/__tests__/unit/bot-protection.test.ts +461 -0
- package/src/__tests__/unit/cart-store.test.ts +349 -0
- package/src/core/bot-protection/abstract-manager.ts +185 -0
- package/src/core/bot-protection/create-manager.ts +37 -0
- package/src/core/bot-protection/eucaptcha-manager.ts +88 -0
- package/src/core/bot-protection/fallback-manager.ts +43 -0
- package/src/core/bot-protection/turnstile-manager.ts +92 -0
- package/src/core/bot-protection/types/eucaptcha.d.ts +28 -0
- package/src/core/bot-protection/types/turnstile.d.ts +33 -0
- package/src/core/cart/cookie-config.ts +13 -0
- package/src/core/currency/cookie-config.ts +13 -0
- package/src/core/image.ts +75 -0
- package/src/core/index.ts +30 -0
- package/src/core/language/cookie-config.ts +13 -0
- package/src/core/middleware/bot-protection.ts +140 -0
- package/src/core/middleware/currency.ts +2 -1
- package/src/core/middleware/language.ts +30 -0
- package/src/react/bot-protection/bot-protection-context.ts +17 -0
- package/src/react/bot-protection/bot-protection-widget.tsx +46 -0
- package/src/react/cookies.ts +39 -4
- package/src/react/hooks/use-bot-protection.ts +31 -0
- package/src/react/index.ts +27 -1
- package/src/react/providers/language-provider.tsx +34 -0
- package/src/react/providers/storefront-client-provider.tsx +20 -3
- package/src/react/providers/storefront-provider.tsx +34 -6
- package/src/react/stores/cart.context.ts +10 -0
- package/src/react/stores/cart.store.ts +254 -0
- package/src/react/stores/currency.store.ts +12 -32
- package/src/react/stores/language.store.ts +90 -0
- package/src/react/stores/store-context.tsx +21 -0
- package/src/react/types/shop-config.ts +22 -0
package/dist/react/cookies.js
CHANGED
|
@@ -15,12 +15,17 @@ export function getCookie(name) {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Set cookie (client-side only).
|
|
18
|
+
*
|
|
19
|
+
* `secure` defaults to auto-detect from `location.protocol` (true on HTTPS).
|
|
18
20
|
*/
|
|
19
21
|
export function setCookie(name, value, options = {}) {
|
|
20
22
|
if (typeof document === 'undefined')
|
|
21
23
|
return;
|
|
22
24
|
const { maxAge = 365 * 24 * 60 * 60, path = '/', sameSite = 'lax' } = options;
|
|
23
|
-
|
|
25
|
+
const secure = options.secure ??
|
|
26
|
+
(typeof location !== 'undefined' && location.protocol === 'https:');
|
|
27
|
+
const securePart = secure ? ';secure' : '';
|
|
28
|
+
document.cookie = `${name}=${encodeURIComponent(value)};max-age=${maxAge};path=${path};samesite=${sameSite}${securePart}`;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* Delete cookie (client-side only).
|
|
@@ -30,6 +35,8 @@ export function deleteCookie(name, path = '/') {
|
|
|
30
35
|
return;
|
|
31
36
|
document.cookie = `${name}=;max-age=0;path=${path}`;
|
|
32
37
|
}
|
|
38
|
+
import { CURRENCY_COOKIE_NAME } from '../core/currency/cookie-config';
|
|
39
|
+
import { CART_COOKIE_NAME } from '../core/cart/cookie-config';
|
|
33
40
|
/**
|
|
34
41
|
* Get preferred currency from cookie (async — works with Next.js cookies()).
|
|
35
42
|
* Falls back to document.cookie on client.
|
|
@@ -39,11 +46,37 @@ export async function getCurrencyFromCookieAsync() {
|
|
|
39
46
|
try {
|
|
40
47
|
const { cookies } = await import('next/headers');
|
|
41
48
|
const cookieStore = await cookies();
|
|
42
|
-
return cookieStore.get(
|
|
49
|
+
return cookieStore.get(CURRENCY_COOKIE_NAME)?.value ?? null;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Not in a server context or next not available
|
|
53
|
+
}
|
|
54
|
+
// Client-side fallback
|
|
55
|
+
return getCookie(CURRENCY_COOKIE_NAME);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get cart ID from cookie (async — works with Next.js cookies()).
|
|
59
|
+
* Falls back to document.cookie on client.
|
|
60
|
+
*
|
|
61
|
+
* Use in Server Components for SSR cart badge:
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const cartId = await getCartIdFromCookieAsync();
|
|
64
|
+
* if (cartId) {
|
|
65
|
+
* const cart = await fetchCart(cartId);
|
|
66
|
+
* // Render cart badge with real totalQuantity — no skeleton needed
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export async function getCartIdFromCookieAsync() {
|
|
71
|
+
// Server-side: try Next.js cookies()
|
|
72
|
+
try {
|
|
73
|
+
const { cookies } = await import('next/headers');
|
|
74
|
+
const cookieStore = await cookies();
|
|
75
|
+
return cookieStore.get(CART_COOKIE_NAME)?.value ?? null;
|
|
43
76
|
}
|
|
44
77
|
catch {
|
|
45
78
|
// Not in a server context or next not available
|
|
46
79
|
}
|
|
47
80
|
// Client-side fallback
|
|
48
|
-
return getCookie(
|
|
81
|
+
return getCookie(CART_COOKIE_NAME);
|
|
49
82
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useBotProtection — escape hatch hook for custom bot protection use cases.
|
|
3
|
+
*
|
|
4
|
+
* Normally the middleware handles token injection automatically.
|
|
5
|
+
* Use this hook only when you need manual control (e.g. custom forms).
|
|
6
|
+
*/
|
|
7
|
+
export declare function useBotProtection(): {
|
|
8
|
+
/** Execute a bot protection challenge and get a token */
|
|
9
|
+
execute: (options?: {
|
|
10
|
+
action?: string;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}) => Promise<string | null>;
|
|
13
|
+
/** Whether bot protection is configured and available */
|
|
14
|
+
isAvailable: boolean;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=use-bot-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-bot-protection.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-bot-protection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,wBAAgB,gBAAgB;IAa5B,yDAAyD;wBAR9C;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAUlD,yDAAyD;;EAG5D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useBotProtection — escape hatch hook for custom bot protection use cases.
|
|
3
|
+
*
|
|
4
|
+
* Normally the middleware handles token injection automatically.
|
|
5
|
+
* Use this hook only when you need manual control (e.g. custom forms).
|
|
6
|
+
*/
|
|
7
|
+
'use client';
|
|
8
|
+
import { useContext, useCallback } from 'react';
|
|
9
|
+
import { BotProtectionContext } from '../bot-protection/bot-protection-context';
|
|
10
|
+
export function useBotProtection() {
|
|
11
|
+
const ctx = useContext(BotProtectionContext);
|
|
12
|
+
const manager = ctx?.manager ?? null;
|
|
13
|
+
const execute = useCallback((options) => {
|
|
14
|
+
if (!manager)
|
|
15
|
+
return Promise.resolve(null);
|
|
16
|
+
return manager.execute(options);
|
|
17
|
+
}, [manager]);
|
|
18
|
+
return {
|
|
19
|
+
/** Execute a bot protection challenge and get a token */
|
|
20
|
+
execute,
|
|
21
|
+
/** Whether bot protection is configured and available */
|
|
22
|
+
isAvailable: !!manager,
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -14,17 +14,26 @@
|
|
|
14
14
|
export { StorefrontProvider, type StorefrontProviderProps } from './providers/storefront-provider';
|
|
15
15
|
export { StorefrontClientProvider, type StorefrontClientProviderProps } from './providers/storefront-client-provider';
|
|
16
16
|
export { CurrencyProvider, type CurrencyProviderProps } from './providers/currency-provider';
|
|
17
|
+
export { LanguageProvider, type LanguageProviderProps } from './providers/language-provider';
|
|
17
18
|
export { useAuth, type UseAuthOptions, type LoginResult, type LogoutResult, type TokenRenewResult } from './hooks/use-auth';
|
|
18
19
|
export { useCartManager } from './hooks/use-cart-manager';
|
|
19
20
|
export { useStorefrontClient } from './hooks/use-storefront-client';
|
|
20
21
|
export { useCurrency } from './hooks/use-currency';
|
|
21
22
|
export { useAuthStore, useAuthStoreApi, useAuthHydrated } from './stores/store-context';
|
|
22
23
|
export { useCurrencyStore, useCurrencyStoreApi } from './stores/store-context';
|
|
24
|
+
export { useLanguageStore, useLanguageStoreApi } from './stores/store-context';
|
|
23
25
|
export type { AuthStore, CustomerInfo } from './stores/auth.store';
|
|
24
26
|
export type { CurrencyStore, ShopCurrencyData } from './stores/currency.store';
|
|
27
|
+
export type { LanguageStore } from './stores/language.store';
|
|
28
|
+
export type { ShopConfig } from './types/shop-config';
|
|
25
29
|
export { selectCurrency, selectBaseCurrency, selectSupportedCurrencies, selectIsLoaded } from './stores/currency.store';
|
|
26
|
-
export {
|
|
30
|
+
export { selectLanguage, selectDefaultLanguage, selectSupportedLanguages, selectLanguageIsLoaded } from './stores/language.store';
|
|
31
|
+
export { getCookie, setCookie, deleteCookie, getCurrencyFromCookieAsync, getCartIdFromCookieAsync } from './cookies';
|
|
32
|
+
export { useBotProtection } from './hooks/use-bot-protection';
|
|
27
33
|
export { useHydrated } from './hooks/use-hydrated';
|
|
28
34
|
export { useDebouncedValue } from './hooks/use-debounced-value';
|
|
35
|
+
export { createCartStore, selectCartId, selectIsCartOpen, selectCartIsLoading, } from './stores/cart.store';
|
|
36
|
+
export type { CartState, CartStoreConfig, CartActions, CartData, CartMutationAction, CartLineInput, CartLineUpdateInput, } from './stores/cart.store';
|
|
37
|
+
export { CartProvider, useCartStore, useCartStoreApi } from './stores/cart.context';
|
|
29
38
|
export { createStoreContext } from './helpers/create-store-context';
|
|
30
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC5H,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG/E,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC5H,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG/E,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACxH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGlI,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAGrH,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC"}
|
package/dist/react/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
export { StorefrontProvider } from './providers/storefront-provider';
|
|
16
16
|
export { StorefrontClientProvider } from './providers/storefront-client-provider';
|
|
17
17
|
export { CurrencyProvider } from './providers/currency-provider';
|
|
18
|
+
export { LanguageProvider } from './providers/language-provider';
|
|
18
19
|
// Hooks
|
|
19
20
|
export { useAuth } from './hooks/use-auth';
|
|
20
21
|
export { useCartManager } from './hooks/use-cart-manager';
|
|
@@ -23,12 +24,19 @@ export { useCurrency } from './hooks/use-currency';
|
|
|
23
24
|
// Store hooks (Context-based)
|
|
24
25
|
export { useAuthStore, useAuthStoreApi, useAuthHydrated } from './stores/store-context';
|
|
25
26
|
export { useCurrencyStore, useCurrencyStoreApi } from './stores/store-context';
|
|
27
|
+
export { useLanguageStore, useLanguageStoreApi } from './stores/store-context';
|
|
26
28
|
// Selectors
|
|
27
29
|
export { selectCurrency, selectBaseCurrency, selectSupportedCurrencies, selectIsLoaded } from './stores/currency.store';
|
|
30
|
+
export { selectLanguage, selectDefaultLanguage, selectSupportedLanguages, selectLanguageIsLoaded } from './stores/language.store';
|
|
28
31
|
// Cookie utilities
|
|
29
|
-
export { getCookie, setCookie, deleteCookie, getCurrencyFromCookieAsync } from './cookies';
|
|
32
|
+
export { getCookie, setCookie, deleteCookie, getCurrencyFromCookieAsync, getCartIdFromCookieAsync } from './cookies';
|
|
33
|
+
// Bot protection
|
|
34
|
+
export { useBotProtection } from './hooks/use-bot-protection';
|
|
30
35
|
// Generic hooks
|
|
31
36
|
export { useHydrated } from './hooks/use-hydrated';
|
|
32
37
|
export { useDebouncedValue } from './hooks/use-debounced-value';
|
|
38
|
+
// Cart store (DI-based)
|
|
39
|
+
export { createCartStore, selectCartId, selectIsCartOpen, selectCartIsLoading, } from './stores/cart.store';
|
|
40
|
+
export { CartProvider, useCartStore, useCartStoreApi } from './stores/cart.context';
|
|
33
41
|
// Store context helper
|
|
34
42
|
export { createStoreContext } from './helpers/create-store-context';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LanguageProvider — initializes language store from Shop data.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper that calls useLanguageStore.initialize() on mount
|
|
5
|
+
* with the shop's language configuration from the server.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This only sets defaultLanguage and supportedLanguages.
|
|
8
|
+
* The active `language` is set by the template's LanguageSyncProvider
|
|
9
|
+
* which reads locale from the URL (via next-intl) and calls setLanguage().
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import type { ShopConfig } from '../types/shop-config';
|
|
13
|
+
export interface LanguageProviderProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
shopData: ShopConfig;
|
|
16
|
+
}
|
|
17
|
+
export declare function LanguageProvider({ children, shopData }: LanguageProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=language-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/language-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,qBAAqB,2CAW7E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LanguageProvider — initializes language store from Shop data.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper that calls useLanguageStore.initialize() on mount
|
|
5
|
+
* with the shop's language configuration from the server.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This only sets defaultLanguage and supportedLanguages.
|
|
8
|
+
* The active `language` is set by the template's LanguageSyncProvider
|
|
9
|
+
* which reads locale from the URL (via next-intl) and calls setLanguage().
|
|
10
|
+
*/
|
|
11
|
+
'use client';
|
|
12
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { useEffect } from 'react';
|
|
14
|
+
import { useLanguageStore } from '../stores/store-context';
|
|
15
|
+
export function LanguageProvider({ children, shopData }) {
|
|
16
|
+
const initialize = useLanguageStore((s) => s.initialize);
|
|
17
|
+
const isLoaded = useLanguageStore((s) => s.isLoaded);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!isLoaded) {
|
|
20
|
+
initialize(shopData);
|
|
21
|
+
}
|
|
22
|
+
}, [initialize, isLoaded, shopData]);
|
|
23
|
+
return _jsx(_Fragment, { children: children });
|
|
24
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { CartClient } from '../../core/cart/cart-client';
|
|
11
11
|
import { AuthClient } from '../../core/auth/auth-client';
|
|
12
|
+
import { type BotProtectionTokenProvider } from '../../core/middleware/bot-protection';
|
|
12
13
|
import type { StorefrontClient, StorefrontClientConfig, Middleware } from '../../core/client/types';
|
|
13
14
|
export interface StorefrontClientContextValue {
|
|
14
15
|
client: StorefrontClient;
|
|
@@ -20,11 +21,15 @@ export interface StorefrontClientProviderProps {
|
|
|
20
21
|
config: StorefrontClientConfig;
|
|
21
22
|
/**
|
|
22
23
|
* Additional middleware to prepend to the default pipeline.
|
|
23
|
-
* Default pipeline: auth → currency → [custom] → retry → timeout → errors
|
|
24
|
+
* Default pipeline: auth → currency → bot-protection → [custom] → retry → timeout → errors
|
|
24
25
|
*/
|
|
25
26
|
middleware?: Middleware[];
|
|
27
|
+
/** Bot protection token provider (created by StorefrontProvider) */
|
|
28
|
+
botProtection?: BotProtectionTokenProvider | null;
|
|
29
|
+
/** Operations that require bot protection (from shop query) */
|
|
30
|
+
botProtectionOperations?: string[];
|
|
26
31
|
}
|
|
27
|
-
export declare function StorefrontClientProvider({ children, config, middleware: customMiddleware, }: StorefrontClientProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export declare function StorefrontClientProvider({ children, config, middleware: customMiddleware, botProtection, botProtectionOperations, }: StorefrontClientProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
28
33
|
/**
|
|
29
34
|
* Get StorefrontClient context value.
|
|
30
35
|
* Must be used within StorefrontClientProvider.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storefront-client-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-client-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"storefront-client-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-client-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAIzD,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAKhH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;CACxB;AAID,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1B,oEAAoE;IACpE,aAAa,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAClD,+DAA+D;IAC/D,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC;AAED,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,MAAM,EACN,UAAU,EAAE,gBAAqB,EACjC,aAAa,EACb,uBAAuB,GACxB,EAAE,6BAA6B,2CAyC/B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,4BAA4B,CAMzE"}
|
|
@@ -14,14 +14,17 @@ import { CartClient } from '../../core/cart/cart-client';
|
|
|
14
14
|
import { AuthClient } from '../../core/auth/auth-client';
|
|
15
15
|
import { authMiddleware } from '../../core/middleware/auth';
|
|
16
16
|
import { currencyMiddleware } from '../../core/middleware/currency';
|
|
17
|
+
import { languageMiddleware } from '../../core/middleware/language';
|
|
18
|
+
import { botProtectionMiddleware } from '../../core/middleware/bot-protection';
|
|
17
19
|
import { retryMiddleware } from '../../core/middleware/retry';
|
|
18
20
|
import { timeoutMiddleware } from '../../core/middleware/timeout';
|
|
19
21
|
import { errorMiddleware } from '../../core/middleware/errors';
|
|
20
|
-
import { useAuthStoreApi, useCurrencyStoreApi } from '../stores/store-context';
|
|
22
|
+
import { useAuthStoreApi, useCurrencyStoreApi, useLanguageStoreApi } from '../stores/store-context';
|
|
21
23
|
const StorefrontClientContext = createContext(null);
|
|
22
|
-
export function StorefrontClientProvider({ children, config, middleware: customMiddleware = [], }) {
|
|
24
|
+
export function StorefrontClientProvider({ children, config, middleware: customMiddleware = [], botProtection, botProtectionOperations, }) {
|
|
23
25
|
const authStore = useAuthStoreApi();
|
|
24
26
|
const currencyStore = useCurrencyStoreApi();
|
|
27
|
+
const languageStore = useLanguageStoreApi();
|
|
25
28
|
const value = useMemo(() => {
|
|
26
29
|
const client = createStorefrontClient({
|
|
27
30
|
...config,
|
|
@@ -29,6 +32,14 @@ export function StorefrontClientProvider({ children, config, middleware: customM
|
|
|
29
32
|
// Header middleware (runs first)
|
|
30
33
|
authMiddleware(() => authStore.getState().accessToken),
|
|
31
34
|
currencyMiddleware(() => currencyStore.getState().currency),
|
|
35
|
+
languageMiddleware(() => languageStore.getState().language),
|
|
36
|
+
// Bot protection (if configured)
|
|
37
|
+
...(botProtection && botProtectionOperations?.length
|
|
38
|
+
? [botProtectionMiddleware({
|
|
39
|
+
tokenProvider: botProtection,
|
|
40
|
+
protectedOperations: botProtectionOperations,
|
|
41
|
+
})]
|
|
42
|
+
: []),
|
|
32
43
|
// Custom middleware from props
|
|
33
44
|
...customMiddleware,
|
|
34
45
|
// Infrastructure middleware (runs last)
|
|
@@ -41,7 +52,7 @@ export function StorefrontClientProvider({ children, config, middleware: customM
|
|
|
41
52
|
const authClient = new AuthClient(client);
|
|
42
53
|
return { client, cartClient, authClient };
|
|
43
54
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
-
}, [config.apiUrl, config.shopSlug, authStore, currencyStore]);
|
|
55
|
+
}, [config.apiUrl, config.shopSlug, authStore, currencyStore, languageStore]);
|
|
45
56
|
return (_jsx(StorefrontClientContext.Provider, { value: value, children: children }));
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
@@ -37,6 +37,12 @@ export interface StorefrontProviderProps extends StorefrontClientProviderProps {
|
|
|
37
37
|
* Read from cookies() in a Server Component (layout.tsx) and pass here.
|
|
38
38
|
*/
|
|
39
39
|
initialIsAuthenticated?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Server-side language hint — pass the URL locale from next-intl params.
|
|
42
|
+
* Eliminates flash of wrong language on first render by initializing the
|
|
43
|
+
* language store with the correct value from the server.
|
|
44
|
+
*/
|
|
45
|
+
initialLanguage?: string;
|
|
40
46
|
}
|
|
41
|
-
export declare function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
47
|
+
export declare function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialLanguage, }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
42
48
|
//# sourceMappingURL=storefront-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storefront-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"storefront-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH,OAAO,EAA4B,KAAK,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC5G,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAMnF,MAAM,WAAW,uBAAwB,SAAQ,6BAA6B;IAC5E,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,MAAM,EACN,UAAU,EACV,QAAQ,EACR,sBAAsB,EACtB,eAAe,GAChB,EAAE,uBAAuB,2CA+BzB"}
|
|
@@ -26,15 +26,22 @@
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
'use client';
|
|
29
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
29
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
30
30
|
import { useRef } from 'react';
|
|
31
|
-
import { AuthStoreContext, CurrencyStoreContext } from '../stores/store-context';
|
|
31
|
+
import { AuthStoreContext, CurrencyStoreContext, LanguageStoreContext } from '../stores/store-context';
|
|
32
32
|
import { createAuthStore } from '../stores/auth.store';
|
|
33
33
|
import { createCurrencyStore } from '../stores/currency.store';
|
|
34
|
+
import { createLanguageStore } from '../stores/language.store';
|
|
34
35
|
import { StorefrontClientProvider } from './storefront-client-provider';
|
|
35
36
|
import { CurrencyProvider } from './currency-provider';
|
|
36
|
-
|
|
37
|
+
import { LanguageProvider } from './language-provider';
|
|
38
|
+
import { createBotProtectionManager } from '../../core/bot-protection/create-manager';
|
|
39
|
+
import { BotProtectionContext } from '../bot-protection/bot-protection-context';
|
|
40
|
+
import { BotProtectionWidget } from '../bot-protection/bot-protection-widget';
|
|
41
|
+
export function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialLanguage, }) {
|
|
37
42
|
const authStoreRef = useRef(createAuthStore(initialIsAuthenticated));
|
|
38
43
|
const currencyStoreRef = useRef(createCurrencyStore());
|
|
39
|
-
|
|
44
|
+
const languageStoreRef = useRef(createLanguageStore(initialLanguage));
|
|
45
|
+
const botProtectionRef = useRef(shopData.botProtection ? createBotProtectionManager(shopData.botProtection) : null);
|
|
46
|
+
return (_jsx(AuthStoreContext.Provider, { value: authStoreRef.current, children: _jsx(CurrencyStoreContext.Provider, { value: currencyStoreRef.current, children: _jsx(LanguageStoreContext.Provider, { value: languageStoreRef.current, children: _jsx(StorefrontClientProvider, { config: config, middleware: middleware, botProtection: botProtectionRef.current, botProtectionOperations: shopData.botProtection?.protectedOperations, children: _jsx(BotProtectionContext.Provider, { value: { manager: botProtectionRef.current }, children: _jsx(CurrencyProvider, { shopData: shopData, children: _jsxs(LanguageProvider, { shopData: shopData, children: [_jsx(BotProtectionWidget, { manager: botProtectionRef.current }), children] }) }) }) }) }) }) }));
|
|
40
47
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CartState } from './cart.store';
|
|
2
|
+
export declare const CartProvider: {
|
|
3
|
+
({ store, children }: {
|
|
4
|
+
store: import("zustand").StoreApi<CartState>;
|
|
5
|
+
children: import("react").ReactNode;
|
|
6
|
+
}): import("react").FunctionComponentElement<import("react").ProviderProps<import("zustand").StoreApi<CartState> | null>>;
|
|
7
|
+
displayName: string;
|
|
8
|
+
}, useCartStore: {
|
|
9
|
+
(): CartState;
|
|
10
|
+
<U>(selector: (s: CartState) => U): U;
|
|
11
|
+
}, useCartStoreApi: () => import("zustand").StoreApi<CartState>;
|
|
12
|
+
//# sourceMappingURL=cart.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart.context.d.ts","sourceRoot":"","sources":["../../../src/react/stores/cart.context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,eAAO,MACK,YAAY;;;;;;GACZ,YAAY;;;GACd,eAAe,6CACqB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart Store — DI-based cart state management with cookie persistence.
|
|
3
|
+
*
|
|
4
|
+
* SDK orchestrates cart lifecycle (init, mutations, error handling).
|
|
5
|
+
* Template provides CartActions implementation via DI (getActions getter).
|
|
6
|
+
* Cart ID persisted in cookie (SSR/edge visible) — follows currency store pattern.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createCartStore, type CartActions } from '@doswiftly/storefront-sdk/react';
|
|
11
|
+
*
|
|
12
|
+
* const actions: CartActions = {
|
|
13
|
+
* fetchCart: async (cartId) => api.getCart(cartId),
|
|
14
|
+
* createCart: async () => api.createCart().then(c => c.id),
|
|
15
|
+
* addLines: async (cartId, lines) => api.addLines(cartId, lines),
|
|
16
|
+
* updateLines: async (cartId, lines) => api.updateLines(cartId, lines),
|
|
17
|
+
* removeLines: async (cartId, lineIds) => api.removeLines(cartId, lineIds),
|
|
18
|
+
* };
|
|
19
|
+
*
|
|
20
|
+
* const store = createCartStore({ getActions: () => actions });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { CartLineInput, CartLineUpdateInput } from '../../core/cart/types';
|
|
24
|
+
export type { CartLineInput, CartLineUpdateInput } from '../../core/cart/types';
|
|
25
|
+
/** Minimal cart data returned by DI actions. */
|
|
26
|
+
export interface CartData {
|
|
27
|
+
id: string;
|
|
28
|
+
totalQuantity: number;
|
|
29
|
+
}
|
|
30
|
+
/** DI contract — template implements these methods. */
|
|
31
|
+
export interface CartActions {
|
|
32
|
+
/** Fetch cart by ID. Return null if cart expired/not found. */
|
|
33
|
+
fetchCart: (cartId: string) => Promise<CartData | null>;
|
|
34
|
+
/** Create new empty cart. Return new cart ID. */
|
|
35
|
+
createCart: () => Promise<string>;
|
|
36
|
+
/** Add line items. Return updated cart or throw. */
|
|
37
|
+
addLines: (cartId: string, lines: CartLineInput[]) => Promise<CartData>;
|
|
38
|
+
/** Update line quantities. Return updated cart or throw. */
|
|
39
|
+
updateLines: (cartId: string, lines: CartLineUpdateInput[]) => Promise<CartData>;
|
|
40
|
+
/** Remove line items. Return updated cart or throw. */
|
|
41
|
+
removeLines: (cartId: string, lineIds: string[]) => Promise<CartData>;
|
|
42
|
+
}
|
|
43
|
+
/** Action names passed to mutation callbacks. */
|
|
44
|
+
export type CartMutationAction = 'initCart' | 'addToCart' | 'updateQuantity' | 'removeFromCart';
|
|
45
|
+
export interface CartStoreConfig {
|
|
46
|
+
/** Getter for DI actions — called on every operation (not captured). */
|
|
47
|
+
getActions: () => CartActions;
|
|
48
|
+
/** Called after successful mutation. */
|
|
49
|
+
onMutationSuccess?: (action: CartMutationAction, cart: CartData) => void;
|
|
50
|
+
/** Called on mutation error. */
|
|
51
|
+
onMutationError?: (action: CartMutationAction, error: unknown) => void;
|
|
52
|
+
}
|
|
53
|
+
export interface CartState {
|
|
54
|
+
cartId: string | null;
|
|
55
|
+
isOpen: boolean;
|
|
56
|
+
isLoading: boolean;
|
|
57
|
+
error: unknown | null;
|
|
58
|
+
openCart: () => void;
|
|
59
|
+
closeCart: () => void;
|
|
60
|
+
toggleCart: () => void;
|
|
61
|
+
initCart: () => Promise<void>;
|
|
62
|
+
addToCart: (lines: CartLineInput[]) => Promise<void>;
|
|
63
|
+
updateQuantity: (lines: CartLineUpdateInput[]) => Promise<void>;
|
|
64
|
+
removeFromCart: (lineIds: string[]) => Promise<void>;
|
|
65
|
+
clearCart: () => void;
|
|
66
|
+
}
|
|
67
|
+
export declare const selectCartId: (state: CartState) => string | null;
|
|
68
|
+
export declare const selectIsCartOpen: (state: CartState) => boolean;
|
|
69
|
+
export declare const selectCartIsLoading: (state: CartState) => boolean;
|
|
70
|
+
export declare function createCartStore(config: CartStoreConfig): import("zustand").StoreApi<CartState>;
|
|
71
|
+
//# sourceMappingURL=cart.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/cart.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAKhF,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAMhF,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACxD,iDAAiD;IACjD,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oDAAoD;IACpD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxE,4DAA4D;IAC5D,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,uDAAuD;IACvD,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACvE;AAMD,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEhG,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,wCAAwC;IACxC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzE,gCAAgC;IAChC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACxE;AAMD,MAAM,WAAW,SAAS;IAExB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAGtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IAGvB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAMD,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,kBAAiB,CAAC;AAC/D,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,YAAiB,CAAC;AACnE,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,YAAoB,CAAC;AAMzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,yCAkJtD"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart Store — DI-based cart state management with cookie persistence.
|
|
3
|
+
*
|
|
4
|
+
* SDK orchestrates cart lifecycle (init, mutations, error handling).
|
|
5
|
+
* Template provides CartActions implementation via DI (getActions getter).
|
|
6
|
+
* Cart ID persisted in cookie (SSR/edge visible) — follows currency store pattern.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createCartStore, type CartActions } from '@doswiftly/storefront-sdk/react';
|
|
11
|
+
*
|
|
12
|
+
* const actions: CartActions = {
|
|
13
|
+
* fetchCart: async (cartId) => api.getCart(cartId),
|
|
14
|
+
* createCart: async () => api.createCart().then(c => c.id),
|
|
15
|
+
* addLines: async (cartId, lines) => api.addLines(cartId, lines),
|
|
16
|
+
* updateLines: async (cartId, lines) => api.updateLines(cartId, lines),
|
|
17
|
+
* removeLines: async (cartId, lineIds) => api.removeLines(cartId, lineIds),
|
|
18
|
+
* };
|
|
19
|
+
*
|
|
20
|
+
* const store = createCartStore({ getActions: () => actions });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { createStore } from 'zustand/vanilla';
|
|
24
|
+
import { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from '../../core/cart/cookie-config';
|
|
25
|
+
import { getCookie, setCookie, deleteCookie } from '../cookies';
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Selectors
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
export const selectCartId = (state) => state.cartId;
|
|
30
|
+
export const selectIsCartOpen = (state) => state.isOpen;
|
|
31
|
+
export const selectCartIsLoading = (state) => state.isLoading;
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Factory
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export function createCartStore(config) {
|
|
36
|
+
// Deduplication: shared Promise in closure (not in state)
|
|
37
|
+
let initPromise = null;
|
|
38
|
+
// Read initial cartId from cookie (client-side only, returns null on server)
|
|
39
|
+
const initialCartId = getCookie(CART_COOKIE_NAME);
|
|
40
|
+
// Clean up old localStorage entry from pre-cookie era (v1 persist)
|
|
41
|
+
if (typeof localStorage !== 'undefined') {
|
|
42
|
+
try {
|
|
43
|
+
localStorage.removeItem('cart-storage');
|
|
44
|
+
}
|
|
45
|
+
catch { /* ignore */ }
|
|
46
|
+
}
|
|
47
|
+
async function performInit(set, get) {
|
|
48
|
+
const actions = config.getActions();
|
|
49
|
+
set({ isLoading: true, error: null });
|
|
50
|
+
try {
|
|
51
|
+
const currentCartId = get().cartId;
|
|
52
|
+
if (currentCartId) {
|
|
53
|
+
const cart = await actions.fetchCart(currentCartId);
|
|
54
|
+
if (cart) {
|
|
55
|
+
// Cart exists — keep current cartId
|
|
56
|
+
set({ isLoading: false });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Cart expired — fall through to create
|
|
60
|
+
}
|
|
61
|
+
// No cart or expired — create new
|
|
62
|
+
const newCartId = await actions.createCart();
|
|
63
|
+
set({ cartId: newCartId, isLoading: false, error: null });
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
set({ error, isLoading: false });
|
|
67
|
+
config.onMutationError?.('initCart', error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const store = createStore()((set, get) => ({
|
|
71
|
+
// State — initialCartId from cookie
|
|
72
|
+
cartId: initialCartId,
|
|
73
|
+
isOpen: false,
|
|
74
|
+
isLoading: false,
|
|
75
|
+
error: null,
|
|
76
|
+
// UI Actions
|
|
77
|
+
openCart: () => set({ isOpen: true }),
|
|
78
|
+
closeCart: () => set({ isOpen: false }),
|
|
79
|
+
toggleCart: () => set((state) => ({ isOpen: !state.isOpen })),
|
|
80
|
+
// Orchestrated: initCart (deduplicated)
|
|
81
|
+
initCart: async () => {
|
|
82
|
+
if (initPromise)
|
|
83
|
+
return initPromise;
|
|
84
|
+
initPromise = performInit(set, get).finally(() => {
|
|
85
|
+
initPromise = null;
|
|
86
|
+
});
|
|
87
|
+
return initPromise;
|
|
88
|
+
},
|
|
89
|
+
// Orchestrated: addToCart (auto-init if no cartId)
|
|
90
|
+
addToCart: async (lines) => {
|
|
91
|
+
const actions = config.getActions();
|
|
92
|
+
set({ isLoading: true, error: null });
|
|
93
|
+
try {
|
|
94
|
+
let cartId = get().cartId;
|
|
95
|
+
if (!cartId) {
|
|
96
|
+
await get().initCart();
|
|
97
|
+
cartId = get().cartId;
|
|
98
|
+
}
|
|
99
|
+
if (!cartId) {
|
|
100
|
+
throw new Error('Failed to initialize cart');
|
|
101
|
+
}
|
|
102
|
+
const cart = await actions.addLines(cartId, lines);
|
|
103
|
+
set({ isLoading: false, error: null });
|
|
104
|
+
config.onMutationSuccess?.('addToCart', cart);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
set({ error, isLoading: false });
|
|
108
|
+
config.onMutationError?.('addToCart', error);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
// Orchestrated: updateQuantity (error if no cartId)
|
|
112
|
+
updateQuantity: async (lines) => {
|
|
113
|
+
const actions = config.getActions();
|
|
114
|
+
const cartId = get().cartId;
|
|
115
|
+
if (!cartId) {
|
|
116
|
+
const err = new Error('Cannot update quantity: no cart exists');
|
|
117
|
+
set({ error: err });
|
|
118
|
+
config.onMutationError?.('updateQuantity', err);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
set({ isLoading: true, error: null });
|
|
122
|
+
try {
|
|
123
|
+
const cart = await actions.updateLines(cartId, lines);
|
|
124
|
+
set({ isLoading: false, error: null });
|
|
125
|
+
config.onMutationSuccess?.('updateQuantity', cart);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
set({ error, isLoading: false });
|
|
129
|
+
config.onMutationError?.('updateQuantity', error);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
// Orchestrated: removeFromCart (silent return if no cartId)
|
|
133
|
+
removeFromCart: async (lineIds) => {
|
|
134
|
+
const cartId = get().cartId;
|
|
135
|
+
if (!cartId)
|
|
136
|
+
return;
|
|
137
|
+
const actions = config.getActions();
|
|
138
|
+
set({ isLoading: true, error: null });
|
|
139
|
+
try {
|
|
140
|
+
const cart = await actions.removeLines(cartId, lineIds);
|
|
141
|
+
set({ isLoading: false, error: null });
|
|
142
|
+
config.onMutationSuccess?.('removeFromCart', cart);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
set({ error, isLoading: false });
|
|
146
|
+
config.onMutationError?.('removeFromCart', error);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
// clearCart — reset all state
|
|
150
|
+
clearCart: () => {
|
|
151
|
+
set({ cartId: null, isOpen: false, isLoading: false, error: null });
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
// Sync cartId changes to cookie (like currency store pattern)
|
|
155
|
+
store.subscribe((state, prevState) => {
|
|
156
|
+
if (state.cartId !== prevState.cartId) {
|
|
157
|
+
if (state.cartId) {
|
|
158
|
+
setCookie(CART_COOKIE_NAME, state.cartId, { maxAge: CART_COOKIE_MAX_AGE });
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
deleteCookie(CART_COOKIE_NAME);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return store;
|
|
166
|
+
}
|