arky-sdk 0.1.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/dist/api/cms.d.ts +19 -0
- package/dist/api/cms.d.ts.map +1 -0
- package/dist/api/cms.js +41 -0
- package/dist/api/eshop.d.ts +89 -0
- package/dist/api/eshop.d.ts.map +1 -0
- package/dist/api/eshop.js +183 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +5 -0
- package/dist/api/newsletter.d.ts +32 -0
- package/dist/api/newsletter.d.ts.map +1 -0
- package/dist/api/newsletter.js +70 -0
- package/dist/api/reservation.d.ts +84 -0
- package/dist/api/reservation.d.ts.map +1 -0
- package/dist/api/reservation.js +239 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/services/auth.d.ts +17 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +62 -0
- package/dist/services/http.d.ts +20 -0
- package/dist/services/http.d.ts.map +1 -0
- package/dist/services/http.js +73 -0
- package/dist/stores/business.d.ts +28 -0
- package/dist/stores/business.d.ts.map +1 -0
- package/dist/stores/business.js +122 -0
- package/dist/stores/cart.d.ts +8 -0
- package/dist/stores/cart.d.ts.map +1 -0
- package/dist/stores/cart.js +20 -0
- package/dist/stores/eshop.d.ts +121 -0
- package/dist/stores/eshop.d.ts.map +1 -0
- package/dist/stores/eshop.js +377 -0
- package/dist/stores/index.d.ts +7 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +19 -0
- package/dist/stores/reservation.d.ts +237 -0
- package/dist/stores/reservation.d.ts.map +1 -0
- package/dist/stores/reservation.js +853 -0
- package/dist/types/index.d.ts +244 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/utils/blocks.d.ts +30 -0
- package/dist/utils/blocks.d.ts.map +1 -0
- package/dist/utils/blocks.js +237 -0
- package/dist/utils/currency.d.ts +9 -0
- package/dist/utils/currency.d.ts.map +1 -0
- package/dist/utils/currency.js +99 -0
- package/dist/utils/errors.d.ts +121 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +114 -0
- package/dist/utils/i18n.d.ts +5 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +37 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/price.d.ts +33 -0
- package/dist/utils/price.d.ts.map +1 -0
- package/dist/utils/price.js +141 -0
- package/dist/utils/queryParams.d.ts +21 -0
- package/dist/utils/queryParams.d.ts.map +1 -0
- package/dist/utils/queryParams.js +47 -0
- package/dist/utils/svg.d.ts +17 -0
- package/dist/utils/svg.d.ts.map +1 -0
- package/dist/utils/svg.js +62 -0
- package/dist/utils/text.d.ts +26 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +64 -0
- package/dist/utils/timezone.d.ts +9 -0
- package/dist/utils/timezone.d.ts.map +1 -0
- package/dist/utils/timezone.js +49 -0
- package/dist/utils/validation.d.ts +9 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +44 -0
- package/package.json +58 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Unified Business Store - Single Source of Truth
|
|
2
|
+
import { computed, deepMap } from "nanostores";
|
|
3
|
+
import { BUSINESS_ID } from "../config";
|
|
4
|
+
import * as authService from "../services/auth";
|
|
5
|
+
import { getCurrencySymbol } from "../utils/currency";
|
|
6
|
+
// Core business state
|
|
7
|
+
export const businessStore = deepMap({
|
|
8
|
+
data: null,
|
|
9
|
+
loading: false,
|
|
10
|
+
error: null,
|
|
11
|
+
initialized: false,
|
|
12
|
+
});
|
|
13
|
+
// Computed values derived from business data
|
|
14
|
+
export const selectedMarket = computed(businessStore, (state) => {
|
|
15
|
+
if (!state.data?.configs?.markets)
|
|
16
|
+
return null;
|
|
17
|
+
const markets = state.data.configs.markets;
|
|
18
|
+
// For arky website, always use 'us' market (hardcoded for headless)
|
|
19
|
+
return markets.find(m => m.id === 'us') ||
|
|
20
|
+
markets[0] ||
|
|
21
|
+
null;
|
|
22
|
+
});
|
|
23
|
+
export const currency = computed(selectedMarket, (market) => {
|
|
24
|
+
return market?.currency || 'USD';
|
|
25
|
+
});
|
|
26
|
+
export const currencySymbol = computed(selectedMarket, (market) => {
|
|
27
|
+
return getCurrencySymbol(market?.currency || 'USD');
|
|
28
|
+
});
|
|
29
|
+
export const markets = computed(businessStore, (state) => {
|
|
30
|
+
if (!state.data?.configs?.markets)
|
|
31
|
+
return [];
|
|
32
|
+
return state.data.configs.markets;
|
|
33
|
+
});
|
|
34
|
+
export const zones = computed(businessStore, (state) => {
|
|
35
|
+
if (!state.data?.configs?.zones)
|
|
36
|
+
return [];
|
|
37
|
+
return state.data.configs.zones;
|
|
38
|
+
});
|
|
39
|
+
// Get zone by country code
|
|
40
|
+
export const getZoneByCountry = (countryCode) => {
|
|
41
|
+
const allZones = zones.get();
|
|
42
|
+
return allZones.find(zone => zone.countries.length === 0 || // Empty = all countries
|
|
43
|
+
zone.countries.includes(countryCode.toUpperCase())) || null;
|
|
44
|
+
};
|
|
45
|
+
// Get shipping methods for a specific country
|
|
46
|
+
export const getShippingMethodsForCountry = (countryCode) => {
|
|
47
|
+
const zone = getZoneByCountry(countryCode);
|
|
48
|
+
return zone?.shippingMethods || [];
|
|
49
|
+
};
|
|
50
|
+
export const paymentMethods = computed(selectedMarket, (market) => {
|
|
51
|
+
if (!market)
|
|
52
|
+
return ['CASH'];
|
|
53
|
+
const methods = market.paymentMethods || [];
|
|
54
|
+
return methods.map((pm) => pm.method || pm);
|
|
55
|
+
});
|
|
56
|
+
export const paymentConfig = computed(businessStore, (state) => {
|
|
57
|
+
if (!state.data?.configs)
|
|
58
|
+
return { provider: null, enabled: false };
|
|
59
|
+
const provider = state.data.configs.paymentProvider || null;
|
|
60
|
+
const hasCreditCard = paymentMethods.get().includes('CREDIT_CARD');
|
|
61
|
+
return {
|
|
62
|
+
provider,
|
|
63
|
+
enabled: hasCreditCard && !!provider
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
export const orderBlocks = computed(businessStore, (state) => {
|
|
67
|
+
return state.data?.configs?.orderBlocks || [];
|
|
68
|
+
});
|
|
69
|
+
export const reservationBlocks = computed(businessStore, (state) => {
|
|
70
|
+
return state.data?.configs?.reservationBlocks || [];
|
|
71
|
+
});
|
|
72
|
+
// Actions
|
|
73
|
+
export const businessActions = {
|
|
74
|
+
// Initialize business data - SINGLE API CALL for entire app
|
|
75
|
+
async init() {
|
|
76
|
+
const state = businessStore.get();
|
|
77
|
+
if (state.initialized && state.data) {
|
|
78
|
+
// Already loaded, skip
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
businessStore.setKey('loading', true);
|
|
83
|
+
businessStore.setKey('error', null);
|
|
84
|
+
const result = await authService.getBusinessConfig(BUSINESS_ID);
|
|
85
|
+
if (result.success) {
|
|
86
|
+
businessStore.setKey('data', result.data);
|
|
87
|
+
businessStore.setKey('initialized', true);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error(result.error || 'Failed to load business configuration');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
businessStore.setKey('error', error.message);
|
|
95
|
+
console.error('Business store initialization failed:', error);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
businessStore.setKey('loading', false);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// Reset store (useful for testing)
|
|
102
|
+
reset() {
|
|
103
|
+
businessStore.setKey('data', null);
|
|
104
|
+
businessStore.setKey('loading', false);
|
|
105
|
+
businessStore.setKey('error', null);
|
|
106
|
+
businessStore.setKey('initialized', false);
|
|
107
|
+
},
|
|
108
|
+
// Get business data (with auto-init)
|
|
109
|
+
async getBusiness() {
|
|
110
|
+
const state = businessStore.get();
|
|
111
|
+
if (!state.initialized || !state.data) {
|
|
112
|
+
await this.init();
|
|
113
|
+
}
|
|
114
|
+
return businessStore.get().data;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// Export everything for easy access
|
|
118
|
+
export { businessStore as store, businessActions as actions };
|
|
119
|
+
// Auto-initialize on first import (ensures data is always available)
|
|
120
|
+
if (typeof window !== 'undefined') {
|
|
121
|
+
businessActions.init().catch(console.error);
|
|
122
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const totalCartItems: import("nanostores").ReadableAtom<number>;
|
|
2
|
+
export declare const hasEshopItems: import("nanostores").ReadableAtom<boolean>;
|
|
3
|
+
export declare const hasReservationItems: import("nanostores").ReadableAtom<boolean>;
|
|
4
|
+
export declare const isCartEmpty: import("nanostores").ReadableAtom<boolean>;
|
|
5
|
+
export declare const showEshopSection: import("nanostores").ReadableAtom<boolean>;
|
|
6
|
+
export declare const showReservationSection: import("nanostores").ReadableAtom<boolean>;
|
|
7
|
+
export declare const showBothSections: import("nanostores").ReadableAtom<boolean>;
|
|
8
|
+
//# sourceMappingURL=cart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../src/stores/cart.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,2CAIzB,CAAC;AAGH,eAAO,MAAM,aAAa,4CAAqD,CAAC;AAChF,eAAO,MAAM,mBAAmB,4CAA2D,CAAC;AAC5F,eAAO,MAAM,WAAW,4CAEtB,CAAC;AAGH,eAAO,MAAM,gBAAgB,4CAAqF,CAAC;AACnH,eAAO,MAAM,sBAAsB,4CAAuG,CAAC;AAC3I,eAAO,MAAM,gBAAgB,4CAA2G,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Unified cart store that manages both e-shop and reservation carts
|
|
2
|
+
import { computed } from "nanostores";
|
|
3
|
+
import { cartItems as eshopItems } from "./eshop";
|
|
4
|
+
import { cartParts as reservationItems } from "./reservation";
|
|
5
|
+
// Combined cart count
|
|
6
|
+
export const totalCartItems = computed([eshopItems, reservationItems], (eshop, reservation) => {
|
|
7
|
+
const eshopCount = eshop?.reduce((sum, item) => sum + item.quantity, 0) || 0;
|
|
8
|
+
const reservationCount = reservation?.length || 0;
|
|
9
|
+
return eshopCount + reservationCount;
|
|
10
|
+
});
|
|
11
|
+
// Cart state helpers
|
|
12
|
+
export const hasEshopItems = computed(eshopItems, (items) => items?.length > 0);
|
|
13
|
+
export const hasReservationItems = computed(reservationItems, (items) => items?.length > 0);
|
|
14
|
+
export const isCartEmpty = computed([eshopItems, reservationItems], (eshop, reservation) => {
|
|
15
|
+
return (!eshop || eshop.length === 0) && (!reservation || reservation.length === 0);
|
|
16
|
+
});
|
|
17
|
+
// Cart section visibility logic
|
|
18
|
+
export const showEshopSection = computed([hasEshopItems, isCartEmpty], (hasEshop, isEmpty) => hasEshop || isEmpty);
|
|
19
|
+
export const showReservationSection = computed([hasReservationItems, isCartEmpty], (hasReservation, isEmpty) => hasReservation || isEmpty);
|
|
20
|
+
export const showBothSections = computed([hasEshopItems, hasReservationItems], (hasEshop, hasReservation) => hasEshop && hasReservation);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { currency, paymentConfig, orderBlocks } from "./business";
|
|
2
|
+
import type { EshopCartItem, Block, Price, Payment, Quote } from "../types";
|
|
3
|
+
import { PaymentMethod } from "../types";
|
|
4
|
+
export declare const cartItems: import("nanostores").WritableAtom<EshopCartItem[]>;
|
|
5
|
+
export declare const promoCodeAtom: import("nanostores").PreinitializedWritableAtom<string> & object;
|
|
6
|
+
export declare const quoteAtom: import("nanostores").PreinitializedWritableAtom<Quote> & object;
|
|
7
|
+
export declare const store: import("nanostores").DeepMapStore<{
|
|
8
|
+
businessId: string;
|
|
9
|
+
selectedShippingMethodId: any;
|
|
10
|
+
shippingLocation: any;
|
|
11
|
+
userToken: any;
|
|
12
|
+
processingCheckout: boolean;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: any;
|
|
15
|
+
phoneNumber: string;
|
|
16
|
+
phoneError: any;
|
|
17
|
+
verificationCode: string;
|
|
18
|
+
verifyError: any;
|
|
19
|
+
fetchingQuote: boolean;
|
|
20
|
+
quoteError: any;
|
|
21
|
+
}>;
|
|
22
|
+
export declare const cartTotal: import("nanostores").ReadableAtom<Payment>;
|
|
23
|
+
export declare const cartItemCount: import("nanostores").ReadableAtom<number>;
|
|
24
|
+
export { currency, paymentConfig, orderBlocks };
|
|
25
|
+
export declare const allowedPaymentMethods: import("nanostores").ReadableAtom<any[]>;
|
|
26
|
+
export declare const actions: {
|
|
27
|
+
addItem(product: any, variant: any, quantity?: number): void;
|
|
28
|
+
updateQuantity(itemId: string, newQuantity: number): void;
|
|
29
|
+
removeItem(itemId: string): void;
|
|
30
|
+
clearCart(): void;
|
|
31
|
+
getGuestToken(): Promise<string>;
|
|
32
|
+
prepareOrderItems(): {
|
|
33
|
+
productId: string;
|
|
34
|
+
variantId: string;
|
|
35
|
+
quantity: number;
|
|
36
|
+
}[];
|
|
37
|
+
getOrderInfoBlocks(): Block[];
|
|
38
|
+
checkout(paymentMethod?: PaymentMethod, orderInfoBlocks?: Block[], promoCode?: string | null): Promise<{
|
|
39
|
+
success: boolean;
|
|
40
|
+
error: string;
|
|
41
|
+
data?: undefined;
|
|
42
|
+
} | {
|
|
43
|
+
success: boolean;
|
|
44
|
+
data: {
|
|
45
|
+
orderId: any;
|
|
46
|
+
orderNumber: any;
|
|
47
|
+
clientSecret: any;
|
|
48
|
+
};
|
|
49
|
+
error?: undefined;
|
|
50
|
+
}>;
|
|
51
|
+
updateProfilePhone(): Promise<boolean>;
|
|
52
|
+
verifyPhoneCode(): Promise<boolean>;
|
|
53
|
+
formatPrice(priceOrPayment: Price | Payment): string;
|
|
54
|
+
getCartPayment(): Payment;
|
|
55
|
+
getAvailablePaymentMethods(): PaymentMethod[];
|
|
56
|
+
getShippingMethodsForCountry(countryCode: string): import("..").ShippingMethod[];
|
|
57
|
+
fetchQuote(promoCode?: string | null): Promise<void>;
|
|
58
|
+
applyPromoCode(code: string): Promise<void>;
|
|
59
|
+
removePromoCode(): Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
export declare function initEshopStore(): void;
|
|
62
|
+
declare const _default: {
|
|
63
|
+
store: import("nanostores").DeepMapStore<{
|
|
64
|
+
businessId: string;
|
|
65
|
+
selectedShippingMethodId: any;
|
|
66
|
+
shippingLocation: any;
|
|
67
|
+
userToken: any;
|
|
68
|
+
processingCheckout: boolean;
|
|
69
|
+
loading: boolean;
|
|
70
|
+
error: any;
|
|
71
|
+
phoneNumber: string;
|
|
72
|
+
phoneError: any;
|
|
73
|
+
verificationCode: string;
|
|
74
|
+
verifyError: any;
|
|
75
|
+
fetchingQuote: boolean;
|
|
76
|
+
quoteError: any;
|
|
77
|
+
}>;
|
|
78
|
+
actions: {
|
|
79
|
+
addItem(product: any, variant: any, quantity?: number): void;
|
|
80
|
+
updateQuantity(itemId: string, newQuantity: number): void;
|
|
81
|
+
removeItem(itemId: string): void;
|
|
82
|
+
clearCart(): void;
|
|
83
|
+
getGuestToken(): Promise<string>;
|
|
84
|
+
prepareOrderItems(): {
|
|
85
|
+
productId: string;
|
|
86
|
+
variantId: string;
|
|
87
|
+
quantity: number;
|
|
88
|
+
}[];
|
|
89
|
+
getOrderInfoBlocks(): Block[];
|
|
90
|
+
checkout(paymentMethod?: PaymentMethod, orderInfoBlocks?: Block[], promoCode?: string | null): Promise<{
|
|
91
|
+
success: boolean;
|
|
92
|
+
error: string;
|
|
93
|
+
data?: undefined;
|
|
94
|
+
} | {
|
|
95
|
+
success: boolean;
|
|
96
|
+
data: {
|
|
97
|
+
orderId: any;
|
|
98
|
+
orderNumber: any;
|
|
99
|
+
clientSecret: any;
|
|
100
|
+
};
|
|
101
|
+
error?: undefined;
|
|
102
|
+
}>;
|
|
103
|
+
updateProfilePhone(): Promise<boolean>;
|
|
104
|
+
verifyPhoneCode(): Promise<boolean>;
|
|
105
|
+
formatPrice(priceOrPayment: Price | Payment): string;
|
|
106
|
+
getCartPayment(): Payment;
|
|
107
|
+
getAvailablePaymentMethods(): PaymentMethod[];
|
|
108
|
+
getShippingMethodsForCountry(countryCode: string): import("..").ShippingMethod[];
|
|
109
|
+
fetchQuote(promoCode?: string | null): Promise<void>;
|
|
110
|
+
applyPromoCode(code: string): Promise<void>;
|
|
111
|
+
removePromoCode(): Promise<void>;
|
|
112
|
+
};
|
|
113
|
+
cartItems: import("nanostores").WritableAtom<EshopCartItem[]>;
|
|
114
|
+
cartTotal: import("nanostores").ReadableAtom<Payment>;
|
|
115
|
+
cartItemCount: import("nanostores").ReadableAtom<number>;
|
|
116
|
+
currency: import("nanostores").ReadableAtom<string>;
|
|
117
|
+
allowedPaymentMethods: import("nanostores").ReadableAtom<any[]>;
|
|
118
|
+
initEshopStore: typeof initEshopStore;
|
|
119
|
+
};
|
|
120
|
+
export default _default;
|
|
121
|
+
//# sourceMappingURL=eshop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eshop.d.ts","sourceRoot":"","sources":["../../src/stores/eshop.ts"],"names":[],"mappings":"AAOA,OAAO,EAEH,QAAQ,EAGR,aAAa,EACb,WAAW,EAEd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAmB,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,eAAO,MAAM,SAAS,oDAGpB,CAAC;AAGH,eAAO,MAAM,aAAa,kEAA4B,CAAC;AAGvD,eAAO,MAAM,SAAS,iEAA2B,CAAC;AAGlD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;EAgBhB,CAAC;AAGH,eAAO,MAAM,SAAS,4CAcpB,CAAC;AAEH,eAAO,MAAM,aAAa,2CAExB,CAAC;AAGH,OAAO,EACH,QAAQ,EACR,aAAa,EACb,WAAW,EACd,CAAC;AAGF,eAAO,MAAM,qBAAqB,0CAAiB,CAAC;AAGpD,eAAO,MAAM,OAAO;qBAEC,GAAG,WAAW,GAAG,aAAY,MAAM;2BAmD7B,MAAM,eAAe,MAAM;uBAS/B,MAAM;;qBAeF,OAAO,CAAC,MAAM,CAAC;;;;;;0BAoBhB,KAAK,EAAE;6BAKC,aAAa,oBAAyC,KAAK,EAAE,cAAc,MAAM,GAAG,IAAI;;;;;;;;;;;;;0BA+E1F,OAAO,CAAC,OAAO,CAAC;uBAenB,OAAO,CAAC,OAAO,CAAC;gCAgBb,KAAK,GAAG,OAAO,GAAG,MAAM;sBASlC,OAAO;kCAuBK,aAAa,EAAE;8CAKH,MAAM;2BAKnB,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;yBAyD/B,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;uBAMxB,OAAO,CAAC,IAAI,CAAC;CAIzC,CAAC;AAwBF,wBAAgB,cAAc,SAM7B;;;;;;;;;;;;;;;;;;yBA7VoB,GAAG,WAAW,GAAG,aAAY,MAAM;+BAmD7B,MAAM,eAAe,MAAM;2BAS/B,MAAM;;yBAeF,OAAO,CAAC,MAAM,CAAC;;;;;;8BAoBhB,KAAK,EAAE;iCAKC,aAAa,oBAAyC,KAAK,EAAE,cAAc,MAAM,GAAG,IAAI;;;;;;;;;;;;;8BA+E1F,OAAO,CAAC,OAAO,CAAC;2BAenB,OAAO,CAAC,OAAO,CAAC;oCAgBb,KAAK,GAAG,OAAO,GAAG,MAAM;0BASlC,OAAO;sCAuBK,aAAa,EAAE;kDAKH,MAAM;+BAKnB,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;6BAyD/B,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;2BAMxB,OAAO,CAAC,IAAI,CAAC;;;;;;;;;AAoC1C,wBASE"}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// E-shop store with TypeScript - Simplified with Business Store
|
|
2
|
+
import { atom, computed, deepMap } from "nanostores";
|
|
3
|
+
import { persistentAtom } from "@nanostores/persistent";
|
|
4
|
+
import { BUSINESS_ID } from "../config";
|
|
5
|
+
import { eshopApi } from "../api/eshop";
|
|
6
|
+
import { createPaymentForCheckout, getPriceAmount, formatPayment, formatMinor } from "../utils/price";
|
|
7
|
+
import * as authService from "../services/auth";
|
|
8
|
+
import { selectedMarket, currency, getShippingMethodsForCountry, paymentMethods, paymentConfig, orderBlocks, businessActions } from "./business";
|
|
9
|
+
import { PaymentMethod } from "../types";
|
|
10
|
+
// Toast notifications should be handled by UI layer
|
|
11
|
+
// Frontend cart items
|
|
12
|
+
export const cartItems = persistentAtom("eshopCart", [], {
|
|
13
|
+
encode: JSON.stringify,
|
|
14
|
+
decode: JSON.parse,
|
|
15
|
+
});
|
|
16
|
+
// Promo code state (not persisted - cleared on page reload)
|
|
17
|
+
export const promoCodeAtom = atom(null);
|
|
18
|
+
// Quote atom (fetched from backend)
|
|
19
|
+
export const quoteAtom = atom(null);
|
|
20
|
+
// Simplified store for cart-specific state only
|
|
21
|
+
export const store = deepMap({
|
|
22
|
+
businessId: BUSINESS_ID,
|
|
23
|
+
selectedShippingMethodId: null, // Selected shipping method ID
|
|
24
|
+
shippingLocation: null, // Deprecated; kept for backward compat
|
|
25
|
+
userToken: null,
|
|
26
|
+
processingCheckout: false,
|
|
27
|
+
loading: false,
|
|
28
|
+
error: null,
|
|
29
|
+
// Phone verification
|
|
30
|
+
phoneNumber: "",
|
|
31
|
+
phoneError: null,
|
|
32
|
+
verificationCode: "",
|
|
33
|
+
verifyError: null,
|
|
34
|
+
// Quote fetching
|
|
35
|
+
fetchingQuote: false,
|
|
36
|
+
quoteError: null,
|
|
37
|
+
});
|
|
38
|
+
// Computed values using business store
|
|
39
|
+
export const cartTotal = computed([cartItems, selectedMarket, currency], (items, market, curr) => {
|
|
40
|
+
// Return a Payment object with amounts in minor units
|
|
41
|
+
const subtotalMinor = (items || []).reduce((sum, item) => {
|
|
42
|
+
let amountMinor = 0;
|
|
43
|
+
if ('amount' in item.price) {
|
|
44
|
+
amountMinor = item.price.amount || 0;
|
|
45
|
+
}
|
|
46
|
+
return sum + (amountMinor * item.quantity);
|
|
47
|
+
}, 0);
|
|
48
|
+
const marketId = market?.id || 'us';
|
|
49
|
+
const currencyCode = curr || 'USD';
|
|
50
|
+
return createPaymentForCheckout(subtotalMinor, marketId, currencyCode, PaymentMethod.Cash);
|
|
51
|
+
});
|
|
52
|
+
export const cartItemCount = computed(cartItems, (items) => {
|
|
53
|
+
return items.reduce((sum, item) => sum + item.quantity, 0);
|
|
54
|
+
});
|
|
55
|
+
// Re-export business store computed values for convenience
|
|
56
|
+
export { currency, paymentConfig, orderBlocks };
|
|
57
|
+
// Create alias for backward compatibility
|
|
58
|
+
export const allowedPaymentMethods = paymentMethods;
|
|
59
|
+
// Actions
|
|
60
|
+
export const actions = {
|
|
61
|
+
// Add item to cart
|
|
62
|
+
addItem(product, variant, quantity = 1) {
|
|
63
|
+
const items = cartItems.get();
|
|
64
|
+
const market = selectedMarket.get();
|
|
65
|
+
// Check if item already exists in cart
|
|
66
|
+
const existingItemIndex = items.findIndex((item) => item.productId === product.id && item.variantId === variant.id);
|
|
67
|
+
if (existingItemIndex !== -1) {
|
|
68
|
+
// Update existing item quantity
|
|
69
|
+
const updatedItems = [...items];
|
|
70
|
+
updatedItems[existingItemIndex].quantity += quantity;
|
|
71
|
+
cartItems.set(updatedItems);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Add new item with market-based pricing
|
|
75
|
+
let cartPrice;
|
|
76
|
+
if (variant.prices && Array.isArray(variant.prices)) {
|
|
77
|
+
// Market-based pricing from backend (amounts are minor units)
|
|
78
|
+
const marketCode = market?.id || 'us';
|
|
79
|
+
const marketAmount = getPriceAmount(variant.prices, marketCode);
|
|
80
|
+
cartPrice = {
|
|
81
|
+
amount: marketAmount ?? 0,
|
|
82
|
+
market: marketCode
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Fallback
|
|
87
|
+
cartPrice = { amount: 0, market: market?.id || 'us' };
|
|
88
|
+
}
|
|
89
|
+
const newItem = {
|
|
90
|
+
id: crypto.randomUUID(),
|
|
91
|
+
productId: product.id,
|
|
92
|
+
variantId: variant.id,
|
|
93
|
+
productName: product.name,
|
|
94
|
+
productSlug: product.slug,
|
|
95
|
+
variantAttributes: variant.attributes || {},
|
|
96
|
+
price: cartPrice,
|
|
97
|
+
quantity,
|
|
98
|
+
addedAt: Date.now(),
|
|
99
|
+
};
|
|
100
|
+
cartItems.set([...items, newItem]);
|
|
101
|
+
}
|
|
102
|
+
// Toast notification should be handled by UI layer
|
|
103
|
+
// showToast(`${product.name} added to cart!`, "success", 3000);
|
|
104
|
+
},
|
|
105
|
+
// Update item quantity
|
|
106
|
+
updateQuantity(itemId, newQuantity) {
|
|
107
|
+
const items = cartItems.get();
|
|
108
|
+
const updatedItems = items.map((item) => item.id === itemId ? { ...item, quantity: Math.max(1, newQuantity) } : item);
|
|
109
|
+
cartItems.set(updatedItems);
|
|
110
|
+
},
|
|
111
|
+
// Remove item from cart
|
|
112
|
+
removeItem(itemId) {
|
|
113
|
+
const items = cartItems.get();
|
|
114
|
+
const updatedItems = items.filter((item) => item.id !== itemId);
|
|
115
|
+
cartItems.set(updatedItems);
|
|
116
|
+
// Toast notification should be handled by UI layer
|
|
117
|
+
// showToast("Item removed from cart!", "success", 2000);
|
|
118
|
+
},
|
|
119
|
+
// Clear entire cart
|
|
120
|
+
clearCart() {
|
|
121
|
+
cartItems.set([]);
|
|
122
|
+
},
|
|
123
|
+
// Get guest token
|
|
124
|
+
async getGuestToken() {
|
|
125
|
+
const state = store.get();
|
|
126
|
+
const token = await authService.getGuestToken(state.userToken);
|
|
127
|
+
if (token !== state.userToken) {
|
|
128
|
+
store.setKey("userToken", token);
|
|
129
|
+
}
|
|
130
|
+
return token;
|
|
131
|
+
},
|
|
132
|
+
// Prepare order items for checkout API
|
|
133
|
+
prepareOrderItems() {
|
|
134
|
+
const items = cartItems.get();
|
|
135
|
+
return items.map((item) => ({
|
|
136
|
+
productId: item.productId,
|
|
137
|
+
variantId: item.variantId,
|
|
138
|
+
quantity: item.quantity,
|
|
139
|
+
}));
|
|
140
|
+
},
|
|
141
|
+
// Get order info blocks (they already have values from DynamicForm)
|
|
142
|
+
getOrderInfoBlocks() {
|
|
143
|
+
return orderBlocks.get() || [];
|
|
144
|
+
},
|
|
145
|
+
// Process checkout - Updated to use Payment structure
|
|
146
|
+
async checkout(paymentMethod = PaymentMethod.Cash, orderInfoBlocks, promoCode) {
|
|
147
|
+
const items = cartItems.get();
|
|
148
|
+
if (!items.length) {
|
|
149
|
+
return { success: false, error: "Cart is empty" };
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
store.setKey("processingCheckout", true);
|
|
153
|
+
store.setKey("error", null);
|
|
154
|
+
const token = await this.getGuestToken();
|
|
155
|
+
const orderItems = this.prepareOrderItems();
|
|
156
|
+
const blocks = orderInfoBlocks || this.getOrderInfoBlocks();
|
|
157
|
+
const state = store.get();
|
|
158
|
+
const market = selectedMarket.get();
|
|
159
|
+
if (!market) {
|
|
160
|
+
throw new Error("No market selected");
|
|
161
|
+
}
|
|
162
|
+
// Extract country code from location block
|
|
163
|
+
const locationBlock = blocks.find(b => b.key === 'location' && b.type === 'GEO_LOCATION');
|
|
164
|
+
const countryCode = locationBlock?.value?.[0]?.countryCode;
|
|
165
|
+
if (!countryCode) {
|
|
166
|
+
throw new Error("Country is required for checkout");
|
|
167
|
+
}
|
|
168
|
+
// Get available shipping methods for the country
|
|
169
|
+
const availableShippingMethods = getShippingMethodsForCountry(countryCode) || [];
|
|
170
|
+
if (!availableShippingMethods || availableShippingMethods.length === 0) {
|
|
171
|
+
throw new Error(`No shipping methods available for country: ${countryCode}`);
|
|
172
|
+
}
|
|
173
|
+
// Get selected shipping method or first available
|
|
174
|
+
const shippingMethodId = state.selectedShippingMethodId;
|
|
175
|
+
const shippingMethod = availableShippingMethods.find(sm => sm.id === shippingMethodId) ||
|
|
176
|
+
availableShippingMethods[0];
|
|
177
|
+
if (!shippingMethod) {
|
|
178
|
+
throw new Error("No shipping method available");
|
|
179
|
+
}
|
|
180
|
+
const promo = promoCode !== undefined ? promoCode : promoCodeAtom.get();
|
|
181
|
+
const response = await eshopApi.checkout({
|
|
182
|
+
token,
|
|
183
|
+
businessId: BUSINESS_ID,
|
|
184
|
+
items: orderItems,
|
|
185
|
+
paymentMethod: paymentMethod,
|
|
186
|
+
blocks,
|
|
187
|
+
market: market.id,
|
|
188
|
+
shippingMethodId: shippingMethod.id,
|
|
189
|
+
promoCode: promo || undefined,
|
|
190
|
+
});
|
|
191
|
+
if (response.success) {
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
data: {
|
|
195
|
+
orderId: response.data.orderId,
|
|
196
|
+
orderNumber: response.data.orderNumber,
|
|
197
|
+
clientSecret: response.data.clientSecret,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
throw new Error(response.error || "Failed to place order");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const errorMessage = `Checkout failed: ${err.message}`;
|
|
207
|
+
store.setKey("error", errorMessage);
|
|
208
|
+
console.error("Checkout error:", err);
|
|
209
|
+
return { success: false, error: errorMessage };
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
store.setKey("processingCheckout", false);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
// Phone verification for eshop
|
|
216
|
+
async updateProfilePhone() {
|
|
217
|
+
try {
|
|
218
|
+
const token = await this.getGuestToken();
|
|
219
|
+
const phoneNumber = store.get().phoneNumber;
|
|
220
|
+
await authService.updateProfilePhone(token, phoneNumber);
|
|
221
|
+
store.setKey("phoneError", null);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error("Phone update error:", error);
|
|
226
|
+
store.setKey("phoneError", error.message);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
async verifyPhoneCode() {
|
|
231
|
+
try {
|
|
232
|
+
const token = await this.getGuestToken();
|
|
233
|
+
const phoneNumber = store.get().phoneNumber;
|
|
234
|
+
const verificationCode = store.get().verificationCode;
|
|
235
|
+
await authService.verifyPhoneCode(token, phoneNumber, verificationCode);
|
|
236
|
+
store.setKey("verifyError", null);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error("Phone verification error:", error);
|
|
241
|
+
store.setKey("verifyError", error.message);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
formatPrice(priceOrPayment) {
|
|
246
|
+
const currencyCode = currency.get();
|
|
247
|
+
if ('total' in priceOrPayment) {
|
|
248
|
+
return formatPayment(priceOrPayment, { showSymbols: true, decimalPlaces: 2 });
|
|
249
|
+
}
|
|
250
|
+
return formatMinor(priceOrPayment.amount || 0, currencyCode);
|
|
251
|
+
},
|
|
252
|
+
getCartPayment() {
|
|
253
|
+
const items = cartItems.get();
|
|
254
|
+
const market = selectedMarket.get();
|
|
255
|
+
const currencyCode = currency.get();
|
|
256
|
+
const marketId = market?.id || 'us';
|
|
257
|
+
if (!items || items.length === 0) {
|
|
258
|
+
return createPaymentForCheckout(0, marketId, currencyCode, PaymentMethod.Cash);
|
|
259
|
+
}
|
|
260
|
+
const subtotalMinor = items.reduce((sum, item) => {
|
|
261
|
+
let amountMinor = 0;
|
|
262
|
+
if ('amount' in item.price) {
|
|
263
|
+
amountMinor = item.price.amount || 0;
|
|
264
|
+
}
|
|
265
|
+
return sum + (amountMinor * item.quantity);
|
|
266
|
+
}, 0);
|
|
267
|
+
return createPaymentForCheckout(subtotalMinor, marketId, currencyCode, PaymentMethod.Cash);
|
|
268
|
+
},
|
|
269
|
+
// Get available payment methods for selected market
|
|
270
|
+
getAvailablePaymentMethods() {
|
|
271
|
+
return paymentMethods.get() || [PaymentMethod.Cash];
|
|
272
|
+
},
|
|
273
|
+
// Get shipping methods for a country code
|
|
274
|
+
getShippingMethodsForCountry(countryCode) {
|
|
275
|
+
return getShippingMethodsForCountry(countryCode);
|
|
276
|
+
},
|
|
277
|
+
// Fetch quote from backend
|
|
278
|
+
async fetchQuote(promoCode) {
|
|
279
|
+
const items = cartItems.get();
|
|
280
|
+
const market = selectedMarket.get();
|
|
281
|
+
const currencyCode = currency.get();
|
|
282
|
+
const state = store.get();
|
|
283
|
+
const promo = promoCode !== undefined ? promoCode : promoCodeAtom.get();
|
|
284
|
+
if (!items || items.length === 0) {
|
|
285
|
+
quoteAtom.set(null);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!market) {
|
|
289
|
+
console.error('No market selected for quote');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
store.setKey('fetchingQuote', true);
|
|
294
|
+
store.setKey('quoteError', null);
|
|
295
|
+
const token = await this.getGuestToken();
|
|
296
|
+
const shippingMethodId = state.selectedShippingMethodId || undefined;
|
|
297
|
+
const response = await eshopApi.getQuote({
|
|
298
|
+
token,
|
|
299
|
+
businessId: BUSINESS_ID,
|
|
300
|
+
items: items.map(item => ({
|
|
301
|
+
productId: item.productId,
|
|
302
|
+
variantId: item.variantId,
|
|
303
|
+
quantity: item.quantity,
|
|
304
|
+
})),
|
|
305
|
+
market: market.id,
|
|
306
|
+
currency: currencyCode,
|
|
307
|
+
userId: token,
|
|
308
|
+
paymentMethod: PaymentMethod.Cash,
|
|
309
|
+
shippingMethodId,
|
|
310
|
+
promoCode: promo || undefined,
|
|
311
|
+
});
|
|
312
|
+
if (response.success && response.data) {
|
|
313
|
+
quoteAtom.set(response.data);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
const friendly = mapQuoteError(response.code, response.error);
|
|
317
|
+
store.setKey('quoteError', friendly);
|
|
318
|
+
quoteAtom.set(null);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error('Quote fetch error:', error);
|
|
323
|
+
store.setKey('quoteError', error.message);
|
|
324
|
+
quoteAtom.set(null);
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
store.setKey('fetchingQuote', false);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
// Apply promo code
|
|
331
|
+
async applyPromoCode(code) {
|
|
332
|
+
promoCodeAtom.set(code);
|
|
333
|
+
await this.fetchQuote();
|
|
334
|
+
},
|
|
335
|
+
// Remove promo code
|
|
336
|
+
async removePromoCode() {
|
|
337
|
+
promoCodeAtom.set(null);
|
|
338
|
+
await this.fetchQuote();
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
function mapQuoteError(code, fallback) {
|
|
342
|
+
switch (code) {
|
|
343
|
+
case 'PROMO.MIN_ORDER':
|
|
344
|
+
return fallback || 'Promo requires a higher minimum order.';
|
|
345
|
+
case 'PROMO.NOT_ACTIVE':
|
|
346
|
+
return 'Promo code is not active.';
|
|
347
|
+
case 'PROMO.NOT_YET_VALID':
|
|
348
|
+
return 'Promo code is not yet valid.';
|
|
349
|
+
case 'PROMO.EXPIRED':
|
|
350
|
+
return 'Promo code has expired.';
|
|
351
|
+
case 'PROMO.MAX_USES':
|
|
352
|
+
return 'Promo code usage limit exceeded.';
|
|
353
|
+
case 'PROMO.MAX_USES_PER_USER':
|
|
354
|
+
return 'You have already used this promo code.';
|
|
355
|
+
case 'PROMO.NOT_FOUND':
|
|
356
|
+
return 'Promo code not found.';
|
|
357
|
+
default:
|
|
358
|
+
return fallback || 'Failed to fetch quote.';
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Initialize the store
|
|
362
|
+
export function initEshopStore() {
|
|
363
|
+
// Initialize business data (if not already initialized)
|
|
364
|
+
businessActions.init();
|
|
365
|
+
// Note: Shipping method selection now happens after user enters shipping address
|
|
366
|
+
// and we determine their country → zone → available shipping methods
|
|
367
|
+
}
|
|
368
|
+
export default {
|
|
369
|
+
store,
|
|
370
|
+
actions,
|
|
371
|
+
cartItems,
|
|
372
|
+
cartTotal,
|
|
373
|
+
cartItemCount,
|
|
374
|
+
currency,
|
|
375
|
+
allowedPaymentMethods,
|
|
376
|
+
initEshopStore
|
|
377
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ArkyConfig } from '../config';
|
|
2
|
+
export { businessStore, businessActions, selectedMarket, currency, currencySymbol, markets, zones, getZoneByCountry, getShippingMethodsForCountry, paymentMethods, paymentConfig, orderBlocks, reservationBlocks } from './business';
|
|
3
|
+
export { cartItems, cartTotal, cartItemCount, promoCodeAtom, quoteAtom, store as eshopStore, actions as eshopActions, initEshopStore, allowedPaymentMethods } from './eshop';
|
|
4
|
+
export { cartParts, store as reservationStore, actions as reservationActions, initReservationStore, canProceed, currentStepName } from './reservation';
|
|
5
|
+
export * from './cart';
|
|
6
|
+
export declare function initArky(config: ArkyConfig): ArkyConfig;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stores/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrO,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,IAAI,UAAU,EAAE,OAAO,IAAI,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAC7K,OAAO,EAAE,SAAS,EAAE,KAAK,IAAI,gBAAgB,EAAE,OAAO,IAAI,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGvJ,cAAc,QAAQ,CAAC;AAGvB,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAUvD"}
|