@meeovi/commerce 1.0.0 → 1.0.2
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 +66 -0
- package/package.json +4 -2
- package/src/stores/cart.ts +218 -0
- package/src/stores/cartStore.ts +300 -0
- package/src/stores/checkout.ts +19 -0
- package/src/stores/compare.ts +65 -0
- package/src/stores/currency.js +29 -0
- package/src/stores/digital-products.js +11 -0
- package/src/stores/index.js +0 -0
- package/src/stores/orders.ts +161 -0
- package/src/stores/product.ts +26 -0
- package/src/stores/productList.ts +0 -0
- package/src/stores/productListInfo.ts +0 -0
- package/src/stores/products.ts +112 -0
- package/src/stores/recentlyViewedProducts.ts +0 -0
- package/src/stores/review.ts +25 -0
- package/src/stores/storeInPickUp.ts +22 -0
- package/src/stores/user.ts +20 -0
- package/src/stores/wishlist.ts +19 -0
- package/src/types/Order.type.ts +181 -0
- package/src/types/index.ts +285 -0
- package/src/types/product.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @meeovi/commerce
|
|
2
|
+
|
|
3
|
+
A backend‑agnostic commerce domain module for the Meeovi ecosystem.
|
|
4
|
+
This package provides a unified interface for products, categories, carts, and other commerce operations — regardless of the underlying backend (Directus, Medusa, Shopify, custom APIs, etc.).
|
|
5
|
+
|
|
6
|
+
## ✨ Features
|
|
7
|
+
|
|
8
|
+
- Pluggable provider architecture
|
|
9
|
+
- Unified composables (`useProducts`, `useCart`, `useCategories`)
|
|
10
|
+
- Backend‑agnostic types
|
|
11
|
+
- Runtime configuration
|
|
12
|
+
- Zero Nuxt dependency
|
|
13
|
+
- Works in any JS/TS environment
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @meeovi/commerce
|
|
19
|
+
|
|
20
|
+
⚙️ Configuration
|
|
21
|
+
Configure the active providers at runtime:
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
import { setCommerceConfig } from '@meeovi/commerce'
|
|
25
|
+
|
|
26
|
+
setCommerceConfig({
|
|
27
|
+
productProvider: 'directus',
|
|
28
|
+
cartProvider: 'directus',
|
|
29
|
+
categoryProvider: 'directus'
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
🧩 Usage
|
|
33
|
+
|
|
34
|
+
import { useProducts } from '@meeovi/commerce'
|
|
35
|
+
|
|
36
|
+
const { listProducts, getProduct } = useProducts()
|
|
37
|
+
|
|
38
|
+
const products = await listProducts()
|
|
39
|
+
const product = await getProduct('123')
|
|
40
|
+
🔌 Providers
|
|
41
|
+
A provider implements one or more domain interfaces:
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export interface ProductProvider {
|
|
45
|
+
getProduct(id: string): Promise<Product>
|
|
46
|
+
listProducts(params?: any): Promise<Product[]>
|
|
47
|
+
}
|
|
48
|
+
Register a provider:
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
import { registerProductProvider } from '@meeovi/commerce'
|
|
52
|
+
|
|
53
|
+
registerProductProvider('directus', {
|
|
54
|
+
getProduct: async (id) => { ... },
|
|
55
|
+
listProducts: async () => { ... }
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
🧱 Folder Structure
|
|
59
|
+
Code
|
|
60
|
+
src/
|
|
61
|
+
products/
|
|
62
|
+
cart/
|
|
63
|
+
categories/
|
|
64
|
+
config.
|
|
65
|
+
registry.
|
|
66
|
+
useCommerce.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meeovi/commerce",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Commerce module for the M Framework.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Meeovi",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@better-auth/stripe": "^1.4.15",
|
|
14
14
|
"@polar-sh/better-auth": "^1.6.4",
|
|
15
|
-
"@polar-sh/sdk": "^0.42.2"
|
|
15
|
+
"@polar-sh/sdk": "^0.42.2",
|
|
16
|
+
"@storefront-ui/nuxt": "^3.1.1",
|
|
17
|
+
"@vue-storefront/unified-data-model": "^3.0.0"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { ref, computed, readonly } from 'vue'
|
|
2
|
+
import type { Cart, CartItem, Product } from '../types'
|
|
3
|
+
import { useCart } from '../composables/cart/useCart'
|
|
4
|
+
import { defineStore } from 'pinia'
|
|
5
|
+
|
|
6
|
+
export const useCartStore = defineStore('cart', () => {
|
|
7
|
+
const cart = ref<Cart | null>(null)
|
|
8
|
+
const loading = ref(false)
|
|
9
|
+
const error = ref<string | null>(null)
|
|
10
|
+
|
|
11
|
+
// Import cart composable
|
|
12
|
+
const {
|
|
13
|
+
fetchCart,
|
|
14
|
+
addToCart,
|
|
15
|
+
removeFromCart,
|
|
16
|
+
updateCartItem,
|
|
17
|
+
applyCoupon,
|
|
18
|
+
removeCoupon,
|
|
19
|
+
clearCart,
|
|
20
|
+
setShippingOption: setShippingOptionFn
|
|
21
|
+
,createCheckoutSession: createCheckoutSessionFn
|
|
22
|
+
} = useCart()
|
|
23
|
+
|
|
24
|
+
const initializeCart = async () => {
|
|
25
|
+
loading.value = true
|
|
26
|
+
error.value = null
|
|
27
|
+
try {
|
|
28
|
+
await fetchCart()
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
error.value = err.message
|
|
31
|
+
console.error('Error initializing cart:', err)
|
|
32
|
+
} finally {
|
|
33
|
+
loading.value = false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const addProductToCart = async (productId: string, quantity: number = 1, variantId?: string) => {
|
|
38
|
+
loading.value = true
|
|
39
|
+
error.value = null
|
|
40
|
+
try {
|
|
41
|
+
await addToCart(productId, quantity, variantId)
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
error.value = err.message
|
|
44
|
+
console.error('Error adding product to cart:', err)
|
|
45
|
+
throw err
|
|
46
|
+
} finally {
|
|
47
|
+
loading.value = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const removeProductFromCart = async (itemId: string) => {
|
|
52
|
+
loading.value = true
|
|
53
|
+
error.value = null
|
|
54
|
+
try {
|
|
55
|
+
await removeFromCart(itemId)
|
|
56
|
+
} catch (err: any) {
|
|
57
|
+
error.value = err.message
|
|
58
|
+
console.error('Error removing product from cart:', err)
|
|
59
|
+
throw err
|
|
60
|
+
} finally {
|
|
61
|
+
loading.value = false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const updateProductQuantity = async (itemId: string, quantity: number) => {
|
|
66
|
+
loading.value = true
|
|
67
|
+
error.value = null
|
|
68
|
+
try {
|
|
69
|
+
await updateCartItem(itemId, quantity)
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
error.value = err.message
|
|
72
|
+
console.error('Error updating cart item:', err)
|
|
73
|
+
throw err
|
|
74
|
+
} finally {
|
|
75
|
+
loading.value = false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const applyCartCoupon = async (couponCode: string) => {
|
|
80
|
+
loading.value = true
|
|
81
|
+
error.value = null
|
|
82
|
+
try {
|
|
83
|
+
const success = await applyCoupon(couponCode)
|
|
84
|
+
if (!success) {
|
|
85
|
+
throw new Error('Failed to apply coupon')
|
|
86
|
+
}
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
error.value = err.message
|
|
89
|
+
console.error('Error applying coupon:', err)
|
|
90
|
+
throw err
|
|
91
|
+
} finally {
|
|
92
|
+
loading.value = false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const removeCartCoupon = async () => {
|
|
97
|
+
loading.value = true
|
|
98
|
+
error.value = null
|
|
99
|
+
try {
|
|
100
|
+
await removeCoupon()
|
|
101
|
+
} catch (err: any) {
|
|
102
|
+
error.value = err.message
|
|
103
|
+
console.error('Error removing coupon:', err)
|
|
104
|
+
throw err
|
|
105
|
+
} finally {
|
|
106
|
+
loading.value = false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const emptyCart = async () => {
|
|
111
|
+
loading.value = true
|
|
112
|
+
error.value = null
|
|
113
|
+
try {
|
|
114
|
+
await clearCart()
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
error.value = err.message
|
|
117
|
+
console.error('Error clearing cart:', err)
|
|
118
|
+
throw err
|
|
119
|
+
} finally {
|
|
120
|
+
loading.value = false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const setShippingOption = async (option: any) => {
|
|
125
|
+
loading.value = true
|
|
126
|
+
error.value = null
|
|
127
|
+
try {
|
|
128
|
+
if (!setShippingOptionFn) throw new Error('Shipping setter unavailable')
|
|
129
|
+
await setShippingOptionFn(option)
|
|
130
|
+
} catch (err: any) {
|
|
131
|
+
error.value = err.message
|
|
132
|
+
console.error('Error setting shipping option:', err)
|
|
133
|
+
throw err
|
|
134
|
+
} finally {
|
|
135
|
+
loading.value = false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const createCheckoutSession = async (cartId?: string) => {
|
|
140
|
+
loading.value = true
|
|
141
|
+
error.value = null
|
|
142
|
+
try {
|
|
143
|
+
const data = await createCheckoutSessionFn(cartId)
|
|
144
|
+
return data
|
|
145
|
+
} catch (err: any) {
|
|
146
|
+
error.value = err.message
|
|
147
|
+
throw err
|
|
148
|
+
} finally {
|
|
149
|
+
loading.value = false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Computed properties
|
|
154
|
+
const itemCount = computed(() => {
|
|
155
|
+
return cart.value?.items?.reduce((count: any, item: { quantity: any }) => count + item.quantity, 0) || 0
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const isEmpty = computed(() => {
|
|
159
|
+
return !cart.value?.items?.length
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const hasItems = computed(() => {
|
|
163
|
+
return !isEmpty.value
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const subtotal = computed(() => {
|
|
167
|
+
return cart.value?.subtotal || 0
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const total = computed(() => {
|
|
171
|
+
return cart.value?.total || 0
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const taxAmount = computed(() => {
|
|
175
|
+
return cart.value?.tax_amount || 0
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const shippingAmount = computed(() => {
|
|
179
|
+
return cart.value?.shipping_amount || 0
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const discountAmount = computed(() => {
|
|
183
|
+
return cart.value?.discount_amount || 0
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const hasCoupon = computed(() => {
|
|
187
|
+
return !!cart.value?.coupon_code
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
// State
|
|
192
|
+
cart: readonly(cart),
|
|
193
|
+
loading: readonly(loading),
|
|
194
|
+
error: readonly(error),
|
|
195
|
+
|
|
196
|
+
// Computed
|
|
197
|
+
itemCount,
|
|
198
|
+
isEmpty,
|
|
199
|
+
hasItems,
|
|
200
|
+
subtotal,
|
|
201
|
+
total,
|
|
202
|
+
taxAmount,
|
|
203
|
+
shippingAmount,
|
|
204
|
+
discountAmount,
|
|
205
|
+
hasCoupon,
|
|
206
|
+
|
|
207
|
+
// Actions
|
|
208
|
+
initializeCart,
|
|
209
|
+
addProductToCart,
|
|
210
|
+
removeProductFromCart,
|
|
211
|
+
updateProductQuantity,
|
|
212
|
+
applyCartCoupon,
|
|
213
|
+
removeCartCoupon,
|
|
214
|
+
emptyCart
|
|
215
|
+
,setShippingOption
|
|
216
|
+
,createCheckoutSession
|
|
217
|
+
}
|
|
218
|
+
})
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// stores/cartStore.ts
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { MagentoService } from '~/services/magento'
|
|
4
|
+
|
|
5
|
+
interface MagentoProduct {
|
|
6
|
+
sku: string
|
|
7
|
+
name: string
|
|
8
|
+
price: number
|
|
9
|
+
qty: number
|
|
10
|
+
quote_id?: string
|
|
11
|
+
item_id?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CartState {
|
|
15
|
+
isGuest: boolean
|
|
16
|
+
items: MagentoProduct[]
|
|
17
|
+
total: number
|
|
18
|
+
quoteId: string | null
|
|
19
|
+
magentoService: MagentoService
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export const useCartStore = defineStore('cart', {
|
|
24
|
+
state: () => ({
|
|
25
|
+
isGuest: true,
|
|
26
|
+
items: [] as MagentoProduct[],
|
|
27
|
+
total: 0,
|
|
28
|
+
quoteId: null as string | null,
|
|
29
|
+
magentoService: new MagentoService()
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
actions: {
|
|
33
|
+
async initializeCart() {
|
|
34
|
+
const auth = useAuth()
|
|
35
|
+
this.isGuest = !auth.token.value
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (this.isGuest) {
|
|
39
|
+
await this.createGuestCart()
|
|
40
|
+
} else {
|
|
41
|
+
await this.createCustomerCart()
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
errorHandler.handle(error)
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async createGuestCart() {
|
|
49
|
+
const response = await fetch(`${process.env.MAGENTO_API_URL}/guest-carts`, {
|
|
50
|
+
method: 'POST'
|
|
51
|
+
})
|
|
52
|
+
const quoteId = await response.json()
|
|
53
|
+
this.quoteId = quoteId as string
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async createCustomerCart() {
|
|
57
|
+
const auth = useAuth()
|
|
58
|
+
const response = await fetch(`${process.env.MAGENTO_API_URL}/carts/mine`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Authorization': `Bearer ${auth.token.value}`
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
this.quoteId = await response.json()
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async addItem(product: MagentoProduct) {
|
|
68
|
+
const loading = useLoading()
|
|
69
|
+
loading.startLoading('Adding to cart...')
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Check authentication
|
|
73
|
+
const auth = useAuth()
|
|
74
|
+
if (auth.isTokenExpired()) {
|
|
75
|
+
await auth.refreshAccessToken()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check inventory
|
|
79
|
+
const inventory = useInventory()
|
|
80
|
+
await inventory.checkInventory(product.sku, product.qty)
|
|
81
|
+
|
|
82
|
+
// If no quote ID, create cart
|
|
83
|
+
if (!this.quoteId) {
|
|
84
|
+
await this.initializeCart()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Ensure quoteId exists before proceeding
|
|
88
|
+
if (!this.quoteId) {
|
|
89
|
+
throw new CartError('Failed to create cart', 'CART_ERROR')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add to Magento cart
|
|
93
|
+
const response = await fetch(`${process.env.MAGENTO_API_URL}/carts/mine/items`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Bearer ${auth.token.value}`,
|
|
97
|
+
'Content-Type': 'application/json'
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
cartItem: {
|
|
101
|
+
sku: product.sku,
|
|
102
|
+
qty: product.qty,
|
|
103
|
+
quote_id: this.quoteId
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new CartError('Failed to add item to cart', 'CART_ERROR')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const cartItem = await response.json()
|
|
113
|
+
|
|
114
|
+
// Update local cart state
|
|
115
|
+
const existingItem = this.items.find(item => item.sku === product.sku)
|
|
116
|
+
if (existingItem) {
|
|
117
|
+
existingItem.qty += product.qty
|
|
118
|
+
} else {
|
|
119
|
+
this.items.push({
|
|
120
|
+
...product,
|
|
121
|
+
item_id: cartItem.item_id
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Calculate taxes
|
|
126
|
+
const tax = useTax()
|
|
127
|
+
if (this.quoteId) {
|
|
128
|
+
await tax.calculateTax(this.quoteId)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Cache product data
|
|
132
|
+
const cache = useCache()
|
|
133
|
+
cache.setCacheItem(`cart_product_${product.sku}`, product)
|
|
134
|
+
|
|
135
|
+
await this.calculateTotal()
|
|
136
|
+
|
|
137
|
+
loading.stopLoading()
|
|
138
|
+
const { show } = useNotification()
|
|
139
|
+
show({
|
|
140
|
+
type: 'success',
|
|
141
|
+
message: 'Product added to cart'
|
|
142
|
+
})
|
|
143
|
+
} catch (error) {
|
|
144
|
+
loading.stopLoading()
|
|
145
|
+
errorHandler.handle(error)
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async removeItem(itemId: string) {
|
|
150
|
+
const loading = useLoading()
|
|
151
|
+
loading.startLoading('Removing item...')
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const auth = useAuth()
|
|
155
|
+
if (auth.isTokenExpired()) {
|
|
156
|
+
await auth.refreshAccessToken()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Remove from Magento cart
|
|
160
|
+
const response = await fetch(
|
|
161
|
+
`${process.env.MAGENTO_API_URL}/carts/mine/items/${itemId}`,
|
|
162
|
+
{
|
|
163
|
+
method: 'DELETE',
|
|
164
|
+
headers: {
|
|
165
|
+
'Authorization': `Bearer ${auth.token.value}`
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw new CartError('Failed to remove item from cart', 'CART_ERROR')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Update local cart state
|
|
175
|
+
this.items = this.items.filter(item => item.item_id !== itemId)
|
|
176
|
+
await this.calculateTotal()
|
|
177
|
+
|
|
178
|
+
loading.stopLoading()
|
|
179
|
+
useNotification().show({
|
|
180
|
+
type: 'success',
|
|
181
|
+
message: 'Item removed from cart'
|
|
182
|
+
})
|
|
183
|
+
} catch (error) {
|
|
184
|
+
loading.stopLoading()
|
|
185
|
+
errorHandler.handle(error)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async calculateTotal() {
|
|
190
|
+
try {
|
|
191
|
+
const auth = useAuth()
|
|
192
|
+
if (!this.quoteId) return
|
|
193
|
+
|
|
194
|
+
// Get cart totals from Magento
|
|
195
|
+
const response = await fetch(
|
|
196
|
+
`${process.env.MAGENTO_API_URL}/carts/mine/totals`,
|
|
197
|
+
{
|
|
198
|
+
headers: {
|
|
199
|
+
'Authorization': `Bearer ${auth.token.value}`
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new CartError('Failed to get cart totals', 'CART_ERROR')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const totals = await response.json()
|
|
209
|
+
this.total = totals.grand_total
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Error calculating totals:', error)
|
|
212
|
+
errorHandler.handle(error)
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// Update other methods to use the notification composable
|
|
217
|
+
async clearCart() {
|
|
218
|
+
const loading = useLoading()
|
|
219
|
+
loading.startLoading('Clearing cart...')
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const auth = useAuth()
|
|
223
|
+
if (this.quoteId) {
|
|
224
|
+
const response = await fetch(`${process.env.MAGENTO_API_URL}/carts/mine/clear`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Authorization': `Bearer ${auth.token.value}`
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new CartError('Failed to clear cart', 'CART_ERROR')
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.items = []
|
|
237
|
+
this.total = 0
|
|
238
|
+
this.quoteId = null
|
|
239
|
+
|
|
240
|
+
// Clear cart-related cache
|
|
241
|
+
const cache = useCache()
|
|
242
|
+
this.items.forEach(item => {
|
|
243
|
+
cache.setCacheItem(`cart_product_${item.sku}`, null)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
loading.stopLoading()
|
|
247
|
+
const { show } = useNotification()
|
|
248
|
+
show({
|
|
249
|
+
type: 'success',
|
|
250
|
+
message: 'Cart cleared successfully'
|
|
251
|
+
})
|
|
252
|
+
} catch (error) {
|
|
253
|
+
loading.stopLoading()
|
|
254
|
+
errorHandler.handle(error)
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
async syncCartWithMagento() {
|
|
259
|
+
try {
|
|
260
|
+
const auth = useAuth()
|
|
261
|
+
const response = await fetch(`${process.env.MAGENTO_API_URL}/carts/mine/items`, {
|
|
262
|
+
headers: {
|
|
263
|
+
'Authorization': `Bearer ${auth.token.value}`
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
throw new CartError('Failed to sync cart', 'CART_ERROR')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const magentoItems = await response.json()
|
|
272
|
+
this.items = magentoItems
|
|
273
|
+
await this.calculateTotal()
|
|
274
|
+
} catch (error) {
|
|
275
|
+
errorHandler.handle(error)
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async validateCart() {
|
|
280
|
+
try {
|
|
281
|
+
// Check all items inventory
|
|
282
|
+
for (const item of this.items) {
|
|
283
|
+
const inventory = useInventory()
|
|
284
|
+
const isAvailable = await inventory.checkInventory(item.sku, item.qty)
|
|
285
|
+
if (!isAvailable) {
|
|
286
|
+
throw new CartError(`${item.name} is out of stock`, 'INVENTORY_ERROR')
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate prices
|
|
291
|
+
await this.syncCartWithMagento()
|
|
292
|
+
|
|
293
|
+
return true
|
|
294
|
+
} catch (error) {
|
|
295
|
+
errorHandler.handle(error)
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import type { CheckoutState } from '@/types/checkout';
|
|
3
|
+
|
|
4
|
+
export const useCheckoutStore = defineStore('checkout', {
|
|
5
|
+
state: (): CheckoutState => ({
|
|
6
|
+
shippingAddress: null,
|
|
7
|
+
paymentMethod: null,
|
|
8
|
+
orderId: '',
|
|
9
|
+
isLoading: false
|
|
10
|
+
}),
|
|
11
|
+
actions: {
|
|
12
|
+
setShippingAddress(address: string) {
|
|
13
|
+
this.shippingAddress = address;
|
|
14
|
+
},
|
|
15
|
+
setPaymentMethod(method: string) {
|
|
16
|
+
this.paymentMethod = method;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// stores/compare.ts
|
|
2
|
+
import { defineStore } from 'pinia';
|
|
3
|
+
import type {
|
|
4
|
+
ComparableAttribute,
|
|
5
|
+
ComparableItem,
|
|
6
|
+
ComparableProduct,
|
|
7
|
+
CompareList,
|
|
8
|
+
} from '~/graphql/queries-mutations_subscriptions/types/ProductCompare.type';
|
|
9
|
+
|
|
10
|
+
export const useCompareStore = defineStore('compare', {
|
|
11
|
+
state: () => ({
|
|
12
|
+
isLoading: false,
|
|
13
|
+
count: 0,
|
|
14
|
+
attributes: [] as ComparableAttribute[],
|
|
15
|
+
products: [] as ComparableProduct[],
|
|
16
|
+
productSkus: [] as string[],
|
|
17
|
+
items: [] as ComparableItem[]
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
actions: {
|
|
21
|
+
toggleLoader(isLoading: boolean) {
|
|
22
|
+
this.isLoading = isLoading;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
setCompareList(payload: CompareList) {
|
|
26
|
+
this.attributes = payload.attributes || [];
|
|
27
|
+
this.products = payload.products || [];
|
|
28
|
+
this.items = payload.items || [];
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
removeComparedProduct(productSku: string) {
|
|
32
|
+
this.products = this.products.filter(product => product.sku !== productSku);
|
|
33
|
+
this.productSkus = this.productSkus.filter(sku => sku !== productSku);
|
|
34
|
+
this.items = this.items.filter(item => item.product.sku !== productSku);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
clearComparedProducts() {
|
|
38
|
+
this.products = [];
|
|
39
|
+
this.productSkus = [];
|
|
40
|
+
this.items = [];
|
|
41
|
+
this.count = 0;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
setCompareListSkus(productSkus: string[]) {
|
|
45
|
+
this.productSkus = productSkus;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
addComparedProductSku(productSku: string) {
|
|
49
|
+
if (!this.productSkus.includes(productSku)) {
|
|
50
|
+
this.productSkus.push(productSku);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
updateCompareTotals(compareTotals: string) {
|
|
55
|
+
this.count = parseInt(compareTotals, 10); // Convert string to number
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
getters: {
|
|
60
|
+
getCompareCount: (state) => state.count,
|
|
61
|
+
getComparedProducts: (state) => state.products,
|
|
62
|
+
getComparedProductSkus: (state) => state.productSkus,
|
|
63
|
+
getIsLoading: (state) => state.isLoading
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
export const useCurrencyStore = defineStore('currency', {
|
|
4
|
+
state: () => ({
|
|
5
|
+
currentCurrency: null,
|
|
6
|
+
exchangeRates: {}
|
|
7
|
+
}),
|
|
8
|
+
|
|
9
|
+
actions: {
|
|
10
|
+
setCurrency(currency) {
|
|
11
|
+
this.currentCurrency = currency
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
setExchangeRates(rates) {
|
|
15
|
+
this.exchangeRates = rates
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async updateCurrency(currency) {
|
|
19
|
+
this.setCurrency(currency)
|
|
20
|
+
// Here you would typically make an API call to update the user's preference
|
|
21
|
+
// if they are logged in
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
getters: {
|
|
26
|
+
getCurrentCurrency: (state) => state.currentCurrency,
|
|
27
|
+
getExchangeRates: (state) => state.exchangeRates
|
|
28
|
+
}
|
|
29
|
+
})
|