-
Notifications
You must be signed in to change notification settings - Fork 9
/
CartList.js
260 lines (224 loc) · 10.3 KB
/
CartList.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import styled from 'styled-components'
import { useEffect, useContext, useState } from 'react'
import CartContext from '../../context/cartContext'
import CurrencyContext from '../../context/currencyContext'
import { useRouter } from 'next/router'
import CartListItem from './cartList/CartListItem'
export default function CartList({ assignProductAmountInCart }) {
const { isCurrencySet, currency, currencyRate } = useContext(CurrencyContext)
const {
cartList,
estimateTotalPriceOfAllItems,
cartBadgeToggle,
setCartBadgeToggle,
totalPriceInCart,
totalDiscountedPriceInCart,
areThereAnyDiscountsInCart
} = useContext(CartContext)
const router = useRouter()
const [ outOfStockMessage, setOutOfStockMessage ] = useState(false)
const [ isCheckoutButtonDisabled, setIsCheckoutButtonDisabled ] = useState(null)
const [ itemsWithExceededAmount, setItemsWithExceededAmount ] = useState([])
const amountSaved = ((totalPriceInCart - totalDiscountedPriceInCart) * currencyRate).toFixed(2)
// this is DRY helper function and it is inside of checkIfItemsAreAvailable() and goToCheckout() functions below
const checkItemsAmount = (cartListFromCms, cartListFromLocalStorage) => {
// here we use .some(), that returns boolean value, to find at least one item being out of stock to trigger the error message and toggle goToCheckout button
const areAnyOfItemsOutOfStock = cartListFromCms.some(i => i.attributes.available <= 0)
// we need to check cartList from CMS against cartList from localStorage, because the former doesn't contain <selectedAmount> value unlike the latter (we got nowhere to put <selectedAmount> value into CMS, because it needs to be tied with user, which requires authentication feature that is not implemented in this project)
// here we use .filter() to create array of items' ids. We need them, because we toggle border colour of select element of each item individually
const listOfItemsExceededAvailable = cartListFromCms
.filter(cartListItemFromCms => {
for (let i = 0; i < cartListFromLocalStorage.length; i++) {
// if ids are coincide (meaning it's the same item) and the item is not out of stock and selected amount is greater than available, we return this item
if (
cartListItemFromCms.id === cartListFromLocalStorage[i].id
&& cartListItemFromCms.attributes.available > 0
&& cartListFromLocalStorage[i].selectedAmount > cartListItemFromCms.attributes.available
) {
return cartListFromLocalStorage[i].id
}
}
})
// return id of each item
.map(i => i.id)
return { areAnyOfItemsOutOfStock, listOfItemsExceededAvailable }
}
const checkIfItemsAreAvailable = () => {
const {
areAnyOfItemsOutOfStock,
listOfItemsExceededAvailable
} = checkItemsAmount(cartList, JSON.parse(localStorage.cartList))
// this state is dependency of useEffect in components/cart/cartList/CartListItem.js. We need to set this state to trigger function inside that useEffect, that toggles border colour
setItemsWithExceededAmount(listOfItemsExceededAvailable)
if (areAnyOfItemsOutOfStock || listOfItemsExceededAvailable.length > 0) {
setOutOfStockMessage(true)
setIsCheckoutButtonDisabled(true)
} else {
setOutOfStockMessage(false)
setIsCheckoutButtonDisabled(false)
}
}
const clearCart = () => {
localStorage.removeItem('cartList')
localStorage.removeItem('order')
localStorage.removeItem('isFormSubmitted')
assignProductAmountInCart()
// toggle cart badge state to make the badge dissappear (in components/layout/header/CartButton.js)
setCartBadgeToggle(!cartBadgeToggle)
}
const goToCheckout = async () => {
setIsCheckoutButtonDisabled(true)
const cartList = JSON.parse(localStorage.cartList)
const ids = cartList.map(i => i.id)
// fetch values of available amounts of items in cart
const data = await fetch(`/api/available?ids=${ids}`)
.then(r => {
if (r.status >= 400) {
const err = new Error('Error in src/components/cart/cartList/CartList.js component, goToCheckout() function, if (r.status >= 400) condition')
err.data = r
throw err
}
return r.json()
})
.catch(err => console.error('Error in src/components/cart/cartList/CartList.js component, goToCheckout() function, .catch statement, err object:', err))
const {
areThereAnyOutOfStockItems,
listOfItemsExceededAvailable
} = checkItemsAmount(data, cartList)
// this state is dependency of useEffect in components/cart/cartList/CartListItem.js. We need to set this state to trigger function inside that useEffect, that toggles border colour
setItemsWithExceededAmount(listOfItemsExceededAvailable)
// if there are no out of stock items and no items that exceeded available, we go to checkout page, if not, we reassign product amount in cart to trigger errors
if (areThereAnyOutOfStockItems || listOfItemsExceededAvailable.length > 0) {
assignProductAmountInCart()
} else {
router.push('/checkout')
}
}
useEffect(() => {
// we need to check if both cart lists (from localStorage with selected amount and fetched one with prices) are in sync. We need to check 2 conditions: 1. if lengths of both cart lists are the same, and at the same time 2. if fetched cart list contains the same ids as in localStorage cart list. If both conditions are true - cart lists are in sync, and we launch functions, the correct execution of whose depends on up-to-date <cartList> values
const localStorageCartList = JSON.parse(localStorage.cartList)
// for each item in localStorage cart list we check if its id coincides with at least one of ids in fetched cart list
const checkResults = localStorageCartList.map(localStorageCartListItem => {
return cartList.some(cartListItem => {
return localStorageCartListItem.id === cartListItem.id
})
})
// this method is not perfect, because it doesn't check the case where stale cart list would've had more items than cart list from localStorage (but all other item ids are coincide just fine), but it doesn't matter, because we check the lengths of both as a separate condition below (and if it has less items, it won't pass this check)
// if at least one element is false - carts are out of sync, and the check'll return true
const areCartsOutOfSync = checkResults.some(result => result !== true)
if (localStorageCartList.length === cartList.length && !areCartsOutOfSync) {
estimateTotalPriceOfAllItems()
checkIfItemsAreAvailable()
}
},[cartList]) // triggers in pages/_app.js in assignProductAmountInCart()
return (
<DivCartList>
<div className="cart-items-wrapper">
{
cartList.map(cartListItem => {
return (
<CartListItem
key={cartListItem.id}
cartListItem={cartListItem}
assignProductAmountInCart={assignProductAmountInCart}
estimateTotalPriceOfAllItems={estimateTotalPriceOfAllItems}
currency={currency}
currencyRate={currencyRate}
isCurrencySet={isCurrencySet}
cartBadgeToggle={cartBadgeToggle}
setCartBadgeToggle={setCartBadgeToggle}
itemsWithExceededAmount={itemsWithExceededAmount}
checkIfItemsAreAvailable={checkIfItemsAreAvailable}
/>
)
})
}
</div>
<button
type="button"
className="btn btn-danger clear-cart-button"
onClick={() => clearCart()}
>
Clear cart
</button>
{
isCurrencySet
? (
<>
<div className="total-price">
<h1>
<span>Total price:</span>
{
!areThereAnyDiscountsInCart
? (
<b>{currency} {(totalPriceInCart * currencyRate).toFixed(2)}</b>
) : (
<span className="d-flex flex-column">
<s>{currency} {(totalPriceInCart * currencyRate).toFixed(2)}</s>
<b className="text-danger">{currency} {(totalDiscountedPriceInCart * currencyRate).toFixed(2)}</b>
</span>
)
}
</h1>
</div>
{
areThereAnyDiscountsInCart
? (
<h4 className="text-success">
You will save <b>{currency} {amountSaved}</b> on this purchase
</h4>
) : null
}
<button
type="button"
id="proceed-to-checkout-button"
className="btn btn-primary mb-3 btn-lg proceed-to-checkout"
disabled={isCheckoutButtonDisabled}
onClick={e => goToCheckout(e)}
>
Proceed to checkout
</button>
{
outOfStockMessage
? <h5>
<b className="text-danger">
<div>Sorry, some of the cart items became out of stock or the amount in stock is lesser than selected! 🙁</div>
<div>Please, delete or choose the lesser amount of items in your cart to proceed to ckeckout!</div>
</b>
</h5>
: null
}
</>
) : (
<div className="loader"></div>
)
}
</DivCartList>
)
}
const DivCartList = styled.div`
display: flex;
flex-direction: column;
width: 100%;
> .cart-items-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 1.5em;
}
> .clear-cart-button {
align-self: flex-start;
margin-bottom: 2.5em;
}
> .total-price {
display: flex;
flex-direction: column;
> h1 {
display: flex;
flex-wrap: wrap;
}
}
> .proceed-to-checkout {
width: 290px;
}
`