@tagadapay/plugin-sdk 2.8.7 → 2.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/react/config/environment.d.ts +1 -22
  2. package/dist/react/config/environment.js +1 -132
  3. package/dist/react/utils/deviceInfo.d.ts +1 -39
  4. package/dist/react/utils/deviceInfo.js +1 -163
  5. package/dist/react/utils/jwtDecoder.d.ts +1 -14
  6. package/dist/react/utils/jwtDecoder.js +1 -86
  7. package/dist/react/utils/tokenStorage.d.ts +1 -16
  8. package/dist/react/utils/tokenStorage.js +1 -53
  9. package/dist/v2/core/client.d.ts +92 -0
  10. package/dist/v2/core/client.js +386 -0
  11. package/dist/v2/core/config/environment.d.ts +22 -0
  12. package/dist/v2/core/config/environment.js +140 -0
  13. package/dist/v2/core/pathRemapping.js +61 -3
  14. package/dist/v2/core/resources/apiClient.d.ts +8 -0
  15. package/dist/v2/core/resources/apiClient.js +30 -9
  16. package/dist/v2/core/resources/funnel.d.ts +253 -16
  17. package/dist/v2/core/resources/payments.d.ts +23 -0
  18. package/dist/v2/core/types.d.ts +271 -0
  19. package/dist/v2/core/types.js +4 -0
  20. package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
  21. package/dist/v2/core/utils/deviceInfo.js +162 -0
  22. package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
  23. package/dist/v2/core/utils/eventDispatcher.js +24 -0
  24. package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
  25. package/dist/v2/core/utils/jwtDecoder.js +85 -0
  26. package/dist/v2/core/utils/pluginConfig.js +6 -0
  27. package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
  28. package/dist/v2/core/utils/tokenStorage.js +52 -0
  29. package/dist/v2/index.d.ts +2 -1
  30. package/dist/v2/index.js +1 -1
  31. package/dist/v2/react/components/DebugDrawer.js +90 -1
  32. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
  34. package/dist/v2/react/hooks/useFunnel.d.ts +2 -2
  35. package/dist/v2/react/hooks/useFunnel.js +209 -32
  36. package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
  37. package/dist/v2/react/hooks/useISOData.js +4 -2
  38. package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
  39. package/dist/v2/react/hooks/useOffersQuery.js +164 -204
  40. package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
  41. package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
  42. package/dist/v2/react/providers/TagadaProvider.js +79 -673
  43. package/package.json +1 -1
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Types for the Tagada Pay SDK Core
3
+ */
4
+ export type Environment = 'production' | 'development' | 'local';
5
+ export interface ApiConfig {
6
+ baseUrl: string;
7
+ endpoints: {
8
+ checkout: {
9
+ sessionInit: string;
10
+ sessionStatus: string;
11
+ };
12
+ customer: {
13
+ profile: string;
14
+ session: string;
15
+ };
16
+ store: {
17
+ config: string;
18
+ };
19
+ };
20
+ }
21
+ export interface EnvironmentConfig {
22
+ environment: Environment;
23
+ apiConfig: ApiConfig;
24
+ }
25
+ export interface Customer {
26
+ id: string;
27
+ email?: string;
28
+ firstName?: string;
29
+ lastName?: string;
30
+ phone?: string;
31
+ isAuthenticated: boolean;
32
+ role: 'authenticated' | 'anonymous';
33
+ }
34
+ export type SessionRole = 'authenticated' | 'anonymous';
35
+ export interface Session {
36
+ sessionId: string;
37
+ storeId: string;
38
+ accountId: string;
39
+ customerId: string;
40
+ role: SessionRole;
41
+ isValid: boolean;
42
+ isLoading: boolean;
43
+ }
44
+ export interface AuthState {
45
+ isAuthenticated: boolean;
46
+ isLoading: boolean;
47
+ customer: Customer | null;
48
+ session: Session | null;
49
+ }
50
+ export interface Locale {
51
+ locale: string;
52
+ language: string;
53
+ region: string;
54
+ messages?: Record<string, string>;
55
+ }
56
+ export interface Currency {
57
+ code: string;
58
+ symbol: string;
59
+ name: string;
60
+ }
61
+ export interface Store {
62
+ id: string;
63
+ name: string;
64
+ domain: string;
65
+ currency: string;
66
+ locale: string;
67
+ presentmentCurrencies: string[];
68
+ chargeCurrencies: string[];
69
+ }
70
+ export interface PickupPoint {
71
+ id: string;
72
+ name: string;
73
+ country: string;
74
+ postal_code: string;
75
+ city: string;
76
+ address: string;
77
+ address2?: string;
78
+ house_number?: string;
79
+ phone?: string;
80
+ email?: string;
81
+ latitude?: number;
82
+ longitude?: number;
83
+ opening_hours?: string;
84
+ extra_info?: string;
85
+ }
86
+ export interface OrderItem {
87
+ id: string;
88
+ productId: string;
89
+ variantId: string;
90
+ quantity: number;
91
+ unitAmount: number;
92
+ amount: number;
93
+ adjustedAmount: number;
94
+ currency: string;
95
+ recurring?: boolean;
96
+ interval?: 'day' | 'week' | 'month' | 'year' | null;
97
+ intervalCount?: number | null;
98
+ orderLineItemProduct?: {
99
+ name: string;
100
+ };
101
+ orderLineItemVariant?: {
102
+ name: string;
103
+ imageUrl: string | null;
104
+ };
105
+ subscriptionSettings?: {
106
+ trial?: boolean;
107
+ };
108
+ }
109
+ export interface OrderSummary {
110
+ currency: string;
111
+ totalPromotionAmount: number;
112
+ totalTaxAmount: number;
113
+ shippingCost: number;
114
+ shippingCostIsFree: boolean;
115
+ subtotalAmount: number;
116
+ subtotalAdjustedAmount: number;
117
+ totalAdjustedAmount: number;
118
+ adjustments?: {
119
+ type: string;
120
+ amount: number;
121
+ description: string;
122
+ }[];
123
+ }
124
+ export interface OrderAddress {
125
+ firstName: string;
126
+ lastName: string;
127
+ address1: string;
128
+ address2?: string;
129
+ city: string;
130
+ state: string;
131
+ postal: string;
132
+ country: string;
133
+ phone?: string;
134
+ }
135
+ export interface Order {
136
+ id: string;
137
+ currency: string;
138
+ paidAmount: number;
139
+ status: string;
140
+ createdAt: string;
141
+ metadata?: Record<string, any>;
142
+ items: OrderItem[];
143
+ summaries?: OrderSummary[];
144
+ shippingAddress?: OrderAddress;
145
+ billingAddress?: OrderAddress;
146
+ pickupAddress?: PickupPoint;
147
+ checkoutSession?: {
148
+ returnUrl?: string;
149
+ [key: string]: any;
150
+ };
151
+ relatedOrders?: Order[];
152
+ }
153
+ export interface PaymentSummary {
154
+ id: string;
155
+ status: string;
156
+ amount: number;
157
+ currency: string;
158
+ createdAt: string;
159
+ updatedAt?: string;
160
+ provider?: string;
161
+ metadata?: Record<string, any>;
162
+ }
163
+ export interface PromotionSummary {
164
+ id: string;
165
+ code?: string | null;
166
+ type?: string;
167
+ amount?: number;
168
+ description?: string | null;
169
+ }
170
+ export interface OrderAdjustmentSummary {
171
+ type: string;
172
+ amount: number;
173
+ description: string;
174
+ }
175
+ export interface OrderWithRelations extends Order {
176
+ customer?: Customer;
177
+ store?: Store;
178
+ account?: {
179
+ id: string;
180
+ name?: string;
181
+ } | undefined;
182
+ items: OrderItem[];
183
+ payments?: PaymentSummary[];
184
+ summaries: OrderSummary[];
185
+ checkoutSession?: {
186
+ id?: string;
187
+ returnUrl?: string;
188
+ [key: string]: any;
189
+ };
190
+ promotions?: PromotionSummary[];
191
+ subscriptions?: any[];
192
+ adjustments: OrderAdjustmentSummary[];
193
+ }
194
+ export interface CustomerAddress {
195
+ company?: string;
196
+ firstName: string;
197
+ lastName: string;
198
+ address1: string;
199
+ city: string;
200
+ country: string;
201
+ state: string;
202
+ postal: string;
203
+ phone?: string;
204
+ email?: string;
205
+ }
206
+ export interface CustomerOrderSummary {
207
+ id: string;
208
+ storeId: string;
209
+ accountId: string;
210
+ createdAt: string;
211
+ updatedAt: string;
212
+ status: string;
213
+ cancelledAt: string | null;
214
+ cancelledReason: string | null;
215
+ paidAt: string | null;
216
+ paidAmount: number | null;
217
+ openAt: string | null;
218
+ abandonedAt: string | null;
219
+ currency: string;
220
+ externalCustomerType: string | null;
221
+ externalCustomerId: string | null;
222
+ externalOrderId: string | null;
223
+ billingAddress: CustomerAddress;
224
+ shippingAddress: Omit<CustomerAddress, 'email'>;
225
+ pickupAddress: any | null;
226
+ taxesIncluded: boolean;
227
+ draft: boolean;
228
+ checkoutSessionId: string | null;
229
+ sessionHash: string | null;
230
+ customerId: string;
231
+ createdFrom: string | null;
232
+ paymentInstrumentId: string | null;
233
+ refundedAt: string | null;
234
+ refundedAmount: number | null;
235
+ metadata?: Record<string, any>;
236
+ }
237
+ export interface CustomerInfos {
238
+ customer: {
239
+ id: string;
240
+ email: string | null;
241
+ firstName: string | null;
242
+ lastName: string | null;
243
+ externalCustomerId: string | null;
244
+ lastOrderId: string | null;
245
+ accountId: string;
246
+ storeId: string;
247
+ billingAddress: CustomerAddress | null;
248
+ shippingAddress: Omit<CustomerAddress, 'email'> | null;
249
+ currency: string | null;
250
+ locale: string | null;
251
+ draft: boolean;
252
+ acceptsMarketing: boolean;
253
+ createdAt: string;
254
+ updatedAt: string;
255
+ metadata: Record<string, any>;
256
+ device: any | null;
257
+ orders: CustomerOrderSummary[];
258
+ subscriptions: any[];
259
+ };
260
+ promotionCodes: any[];
261
+ }
262
+ export interface SessionInitResponse {
263
+ store: Store;
264
+ locale: string;
265
+ messages?: Record<string, string>;
266
+ customer?: Customer;
267
+ session?: Session;
268
+ }
269
+ export interface AnonymousTokenResponse {
270
+ token: string;
271
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Types for the Tagada Pay SDK Core
3
+ */
4
+ export {};
@@ -0,0 +1,39 @@
1
+ export interface DeviceInfo {
2
+ userAgent: {
3
+ browser: {
4
+ name: string;
5
+ version: string;
6
+ };
7
+ os: {
8
+ name: string;
9
+ version: string;
10
+ };
11
+ device?: {
12
+ type: string;
13
+ model: string;
14
+ };
15
+ };
16
+ screenResolution: {
17
+ width: number;
18
+ height: number;
19
+ };
20
+ timeZone: string;
21
+ }
22
+ /**
23
+ * Get browser locale
24
+ */
25
+ export declare function getBrowserLocale(): string;
26
+ /**
27
+ * Collect all device information
28
+ */
29
+ export declare function collectDeviceInfo(): DeviceInfo;
30
+ /**
31
+ * Get URL parameters for session initialization
32
+ */
33
+ export declare function getUrlParams(): {
34
+ locale?: string;
35
+ currency?: string;
36
+ utmSource?: string;
37
+ utmMedium?: string;
38
+ utmCampaign?: string;
39
+ };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Get basic browser information from user agent
3
+ */
4
+ function getBrowserInfo() {
5
+ const userAgent = navigator.userAgent;
6
+ // Chrome
7
+ if (userAgent.includes('Chrome')) {
8
+ const match = /Chrome\/(\d+)/.exec(userAgent);
9
+ return { name: 'Chrome', version: match ? match[1] : 'unknown' };
10
+ }
11
+ // Firefox
12
+ if (userAgent.includes('Firefox')) {
13
+ const match = /Firefox\/(\d+)/.exec(userAgent);
14
+ return { name: 'Firefox', version: match ? match[1] : 'unknown' };
15
+ }
16
+ // Safari
17
+ if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
18
+ const match = /Version\/(\d+)/.exec(userAgent);
19
+ return { name: 'Safari', version: match ? match[1] : 'unknown' };
20
+ }
21
+ // Edge
22
+ if (userAgent.includes('Edge')) {
23
+ const match = /Edge\/(\d+)/.exec(userAgent);
24
+ return { name: 'Edge', version: match ? match[1] : 'unknown' };
25
+ }
26
+ return { name: 'unknown', version: 'unknown' };
27
+ }
28
+ /**
29
+ * Get basic OS information from user agent
30
+ */
31
+ function getOSInfo() {
32
+ const userAgent = navigator.userAgent;
33
+ // Windows
34
+ if (userAgent.includes('Windows')) {
35
+ if (userAgent.includes('Windows NT 10.0'))
36
+ return { name: 'Windows', version: '10' };
37
+ if (userAgent.includes('Windows NT 6.3'))
38
+ return { name: 'Windows', version: '8.1' };
39
+ if (userAgent.includes('Windows NT 6.2'))
40
+ return { name: 'Windows', version: '8' };
41
+ if (userAgent.includes('Windows NT 6.1'))
42
+ return { name: 'Windows', version: '7' };
43
+ return { name: 'Windows', version: 'unknown' };
44
+ }
45
+ // macOS
46
+ if (userAgent.includes('Mac OS X')) {
47
+ const match = /Mac OS X (\d+[._]\d+)/.exec(userAgent);
48
+ return { name: 'macOS', version: match ? match[1].replace('_', '.') : 'unknown' };
49
+ }
50
+ // iOS
51
+ if (userAgent.includes('iPhone') || userAgent.includes('iPad')) {
52
+ const match = /OS (\d+[._]\d+)/.exec(userAgent);
53
+ return { name: 'iOS', version: match ? match[1].replace('_', '.') : 'unknown' };
54
+ }
55
+ // Android
56
+ if (userAgent.includes('Android')) {
57
+ const match = /Android (\d+[.\d]*)/.exec(userAgent);
58
+ return { name: 'Android', version: match ? match[1] : 'unknown' };
59
+ }
60
+ // Linux
61
+ if (userAgent.includes('Linux')) {
62
+ return { name: 'Linux', version: 'unknown' };
63
+ }
64
+ return { name: 'unknown', version: 'unknown' };
65
+ }
66
+ /**
67
+ * Get device information
68
+ */
69
+ function getDeviceInfo() {
70
+ const userAgent = navigator.userAgent;
71
+ // Mobile devices
72
+ if (userAgent.includes('iPhone')) {
73
+ return { type: 'mobile', model: 'iPhone' };
74
+ }
75
+ if (userAgent.includes('iPad')) {
76
+ return { type: 'tablet', model: 'iPad' };
77
+ }
78
+ if (userAgent.includes('Android')) {
79
+ if (userAgent.includes('Mobile')) {
80
+ return { type: 'mobile', model: 'Android' };
81
+ }
82
+ else {
83
+ return { type: 'tablet', model: 'Android' };
84
+ }
85
+ }
86
+ // Desktop (no specific device info)
87
+ return undefined;
88
+ }
89
+ /**
90
+ * Get screen resolution
91
+ */
92
+ function getScreenResolution() {
93
+ return {
94
+ width: window.screen.width,
95
+ height: window.screen.height,
96
+ };
97
+ }
98
+ /**
99
+ * Get timezone
100
+ */
101
+ function getTimeZone() {
102
+ try {
103
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
104
+ }
105
+ catch (error) {
106
+ console.error('Failed to get timezone:', error);
107
+ return 'UTC';
108
+ }
109
+ }
110
+ /**
111
+ * Get browser locale
112
+ */
113
+ export function getBrowserLocale() {
114
+ try {
115
+ return navigator.language || 'en-US';
116
+ }
117
+ catch (error) {
118
+ console.error('Failed to get browser locale:', error);
119
+ return 'en-US';
120
+ }
121
+ }
122
+ /**
123
+ * Collect all device information
124
+ */
125
+ export function collectDeviceInfo() {
126
+ if (typeof window === 'undefined') {
127
+ // Server-side fallback
128
+ return {
129
+ userAgent: {
130
+ browser: { name: 'unknown', version: 'unknown' },
131
+ os: { name: 'unknown', version: 'unknown' },
132
+ },
133
+ screenResolution: { width: 0, height: 0 },
134
+ timeZone: 'UTC',
135
+ };
136
+ }
137
+ return {
138
+ userAgent: {
139
+ browser: getBrowserInfo(),
140
+ os: getOSInfo(),
141
+ device: getDeviceInfo(),
142
+ },
143
+ screenResolution: getScreenResolution(),
144
+ timeZone: getTimeZone(),
145
+ };
146
+ }
147
+ /**
148
+ * Get URL parameters for session initialization
149
+ */
150
+ export function getUrlParams() {
151
+ if (typeof window === 'undefined') {
152
+ return {};
153
+ }
154
+ const params = new URLSearchParams(window.location.search);
155
+ return {
156
+ locale: params.get('locale') || undefined,
157
+ currency: params.get('currency') || undefined,
158
+ utmSource: params.get('utm_source') || undefined,
159
+ utmMedium: params.get('utm_medium') || undefined,
160
+ utmCampaign: params.get('utm_campaign') || undefined,
161
+ };
162
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Simple event dispatcher for handling state changes in the SDK
3
+ */
4
+ export type Listener<T> = (data: T) => void;
5
+ export declare class EventDispatcher<T> {
6
+ private listeners;
7
+ subscribe(listener: Listener<T>): () => void;
8
+ notify(data: T): void;
9
+ clear(): void;
10
+ }
@@ -0,0 +1,24 @@
1
+ export class EventDispatcher {
2
+ constructor() {
3
+ this.listeners = new Set();
4
+ }
5
+ subscribe(listener) {
6
+ this.listeners.add(listener);
7
+ return () => {
8
+ this.listeners.delete(listener);
9
+ };
10
+ }
11
+ notify(data) {
12
+ this.listeners.forEach((listener) => {
13
+ try {
14
+ listener(data);
15
+ }
16
+ catch (error) {
17
+ console.error('Error in event listener:', error);
18
+ }
19
+ });
20
+ }
21
+ clear() {
22
+ this.listeners.clear();
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ import { Session } from '../types';
2
+ /**
3
+ * Decode a JWT token to extract session information
4
+ * This is a simple client-side decoder - do not use for security validation
5
+ */
6
+ export declare function decodeJWTClient(token: string): Session | null;
7
+ /**
8
+ * Check if a JWT token is expired
9
+ */
10
+ export declare function isTokenExpired(token: string): boolean;
11
+ /**
12
+ * Get token expiration time
13
+ */
14
+ export declare function getTokenExpiration(token: string): Date | null;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Decode a JWT token to extract session information
3
+ * This is a simple client-side decoder - do not use for security validation
4
+ */
5
+ export function decodeJWTClient(token) {
6
+ try {
7
+ // Split the token into parts
8
+ const parts = token.split('.');
9
+ if (parts.length !== 3) {
10
+ console.error('Invalid JWT token format');
11
+ return null;
12
+ }
13
+ // Decode the payload (second part)
14
+ const payload = parts[1];
15
+ // Add padding if needed
16
+ const paddedPayload = payload + '='.repeat((4 - (payload.length % 4)) % 4);
17
+ // Decode base64
18
+ const decoded = atob(paddedPayload);
19
+ // Parse JSON
20
+ const parsedPayload = JSON.parse(decoded);
21
+ // Check if token is expired
22
+ if (parsedPayload.exp && Date.now() >= parsedPayload.exp * 1000) {
23
+ console.warn('JWT token is expired');
24
+ return null;
25
+ }
26
+ // Return session data
27
+ return {
28
+ sessionId: parsedPayload.sessionId,
29
+ storeId: parsedPayload.storeId,
30
+ accountId: parsedPayload.accountId,
31
+ customerId: parsedPayload.customerId,
32
+ role: parsedPayload.role,
33
+ isValid: true,
34
+ isLoading: false,
35
+ };
36
+ }
37
+ catch (error) {
38
+ console.error('Failed to decode JWT token:', error);
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Check if a JWT token is expired
44
+ */
45
+ export function isTokenExpired(token) {
46
+ try {
47
+ const parts = token.split('.');
48
+ if (parts.length !== 3)
49
+ return true;
50
+ const payload = parts[1];
51
+ const paddedPayload = payload + '='.repeat((4 - (payload.length % 4)) % 4);
52
+ const decoded = atob(paddedPayload);
53
+ const parsedPayload = JSON.parse(decoded);
54
+ if (parsedPayload.exp) {
55
+ return Date.now() >= parsedPayload.exp * 1000;
56
+ }
57
+ return false;
58
+ }
59
+ catch (error) {
60
+ console.error('Failed to check token expiration:', error);
61
+ return true;
62
+ }
63
+ }
64
+ /**
65
+ * Get token expiration time
66
+ */
67
+ export function getTokenExpiration(token) {
68
+ try {
69
+ const parts = token.split('.');
70
+ if (parts.length !== 3)
71
+ return null;
72
+ const payload = parts[1];
73
+ const paddedPayload = payload + '='.repeat((4 - (payload.length % 4)) % 4);
74
+ const decoded = atob(paddedPayload);
75
+ const parsedPayload = JSON.parse(decoded);
76
+ if (parsedPayload.exp) {
77
+ return new Date(parsedPayload.exp * 1000);
78
+ }
79
+ return null;
80
+ }
81
+ catch (error) {
82
+ console.error('Failed to get token expiration:', error);
83
+ return null;
84
+ }
85
+ }
@@ -8,6 +8,12 @@ import { resolveEnvValue } from './env';
8
8
  */
9
9
  const loadLocalDevConfig = async (configVariant = 'default') => {
10
10
  try {
11
+ // Check for explicit environment override
12
+ const env = resolveEnvValue('TAGADA_ENV') || resolveEnvValue('TAGADA_ENVIRONMENT');
13
+ if (env === 'production') {
14
+ // Skip local config loading if explicitly in production mode
15
+ return null;
16
+ }
11
17
  // Only try to load local config in development
12
18
  // Use hostname-based detection for better Vite compatibility
13
19
  const isLocalDev = typeof window !== 'undefined' &&
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Client-side token storage utilities
3
+ */
4
+ /**
5
+ * Set the CMS token in localStorage
6
+ */
7
+ export declare function setClientToken(token: string): void;
8
+ /**
9
+ * Get the CMS token from localStorage
10
+ */
11
+ export declare function getClientToken(): string | null;
12
+ /**
13
+ * Clear the CMS token from localStorage
14
+ */
15
+ export declare function clearClientToken(): void;
16
+ /**
17
+ * Check if a token exists in localStorage
18
+ */
19
+ export declare function hasClientToken(): boolean;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Client-side token storage utilities
3
+ */
4
+ const TOKEN_KEY = 'cms_token';
5
+ /**
6
+ * Set the CMS token in localStorage
7
+ */
8
+ export function setClientToken(token) {
9
+ if (typeof window !== 'undefined') {
10
+ try {
11
+ localStorage.setItem(TOKEN_KEY, token);
12
+ window.dispatchEvent(new Event('storage'));
13
+ }
14
+ catch (error) {
15
+ console.error('Failed to save token to localStorage:', error);
16
+ }
17
+ }
18
+ }
19
+ /**
20
+ * Get the CMS token from localStorage
21
+ */
22
+ export function getClientToken() {
23
+ if (typeof window !== 'undefined') {
24
+ try {
25
+ return localStorage.getItem(TOKEN_KEY);
26
+ }
27
+ catch (error) {
28
+ console.error('Failed to get token from localStorage:', error);
29
+ return null;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Clear the CMS token from localStorage
36
+ */
37
+ export function clearClientToken() {
38
+ if (typeof window !== 'undefined') {
39
+ try {
40
+ localStorage.removeItem(TOKEN_KEY);
41
+ }
42
+ catch (error) {
43
+ console.error('Failed to clear token from localStorage:', error);
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * Check if a token exists in localStorage
49
+ */
50
+ export function hasClientToken() {
51
+ return !!getClientToken();
52
+ }