-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
task: Port home page (product listing)
- Loading branch information
Showing
20 changed files
with
842 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import React, { useEffect, useReducer, useMemo, useContext } from "react"; | ||
import { CartStateType, CartItemType } from "../../typings/cart"; | ||
|
||
type ContextType = { | ||
state: CartStateType; | ||
dispatch: React.Dispatch<any>; | ||
} | null; | ||
|
||
export enum CartActionType { | ||
RESET_CART = "RESET_CART", | ||
INITALIZE = "initialize", | ||
ADD_ITEM = "add_item", | ||
UPDATE_QUANTITY = "update_quantity", | ||
REMOVE_ITEM = "remove_item", | ||
VALID_VOUCHER = "valid_voucher", | ||
REMOVE_VOUCHER = "remove_voucher", | ||
UPDATE_NAME = "update_name", | ||
UPDATE_BILLING_EMAIL = "update_billing_email", | ||
} | ||
|
||
export type CartAction = | ||
| { | ||
type: CartActionType.RESET_CART; | ||
} | ||
| { | ||
type: CartActionType.INITALIZE; | ||
payload: CartStateType; | ||
} | ||
| { type: CartActionType.ADD_ITEM; payload: CartItemType } | ||
| { | ||
type: CartActionType.UPDATE_QUANTITY; | ||
payload: { productId: string; size: string; quantity: number }; | ||
} | ||
| { type: CartActionType.REMOVE_ITEM; payload: { productId: string; size: string } } | ||
| { type: CartActionType.VALID_VOUCHER; payload: string } | ||
| { type: CartActionType.REMOVE_VOUCHER; payload: null } | ||
| { type: CartActionType.UPDATE_NAME; payload: string } | ||
| { type: CartActionType.UPDATE_BILLING_EMAIL; payload: string }; | ||
|
||
const CartContext = React.createContext<ContextType>(null); | ||
|
||
const initState: CartStateType = { | ||
items: [], | ||
voucher: "", | ||
name: "", | ||
billingEmail: "", | ||
}; | ||
|
||
export const cartReducer = (state: CartStateType, action: CartAction) => { | ||
switch (action.type) { | ||
case CartActionType.RESET_CART: { | ||
return JSON.parse(JSON.stringify(initState)); | ||
} | ||
case CartActionType.INITALIZE: { | ||
return { ...state, ...action.payload }; | ||
} | ||
case CartActionType.ADD_ITEM: { | ||
// Find if there's an existing item already: | ||
const { productId, size, quantity } = action.payload; | ||
const idx = state.items.findIndex((x) => x.productId === productId && x.size === size); | ||
const newQuantity = Math.min((state?.items[idx]?.quantity ?? 0) + quantity, 99); | ||
return { | ||
...state, | ||
items: | ||
idx === -1 | ||
? [...state.items, action.payload] | ||
: [ | ||
...state.items.slice(0, idx), | ||
{ ...state.items[idx], quantity: newQuantity }, | ||
...state.items.slice(idx + 1), | ||
], | ||
}; | ||
} | ||
|
||
case CartActionType.UPDATE_QUANTITY: { | ||
const { productId, size, quantity } = action.payload; | ||
const idx = state.items.findIndex((x) => x.productId === productId && x.size === size); | ||
return { | ||
...state, | ||
items: | ||
idx === -1 | ||
? [...state.items] | ||
: [...state.items.slice(0, idx), { ...state.items[idx], quantity }, ...state.items.slice(idx + 1)], | ||
}; | ||
} | ||
case CartActionType.REMOVE_ITEM: { | ||
const { productId, size } = action.payload; | ||
return { | ||
...state, | ||
items: [...state.items.filter((x) => !(x.productId === productId && x.size === size))], | ||
}; | ||
} | ||
|
||
case CartActionType.VALID_VOUCHER: { | ||
return { ...state, voucher: action.payload }; | ||
} | ||
|
||
case CartActionType.REMOVE_VOUCHER: { | ||
return { ...state, voucher: "" }; | ||
} | ||
|
||
case CartActionType.UPDATE_NAME: { | ||
return { ...state, name: action.payload }; | ||
} | ||
|
||
case CartActionType.UPDATE_BILLING_EMAIL: { | ||
return { ...state, billingEmail: action.payload }; | ||
} | ||
|
||
default: { | ||
throw new Error(`Unhandled action type - ${JSON.stringify(action)}`); | ||
} | ||
} | ||
}; | ||
|
||
export const useCartStore = () => { | ||
const context = useContext(CartContext); | ||
if (context === null) { | ||
throw new Error("useCardStore must be used within a CartProvider."); | ||
} | ||
return context; | ||
}; | ||
|
||
const initStorageCart: CartStateType = { voucher: "", name: "", billingEmail: "", items: [] }; | ||
|
||
export const CartProvider: React.FC = ({ children }) => { | ||
const [state, dispatch] = useReducer(cartReducer, initState); | ||
const value = useMemo(() => ({ state, dispatch }), [state]); | ||
|
||
useEffect(() => { | ||
const cartState: CartStateType = JSON.parse(JSON.stringify(initState)); | ||
const storedCartData: CartStateType = JSON.parse(localStorage.getItem("cart") as string) ?? initStorageCart; | ||
cartState.items = storedCartData.items; | ||
cartState.name = storedCartData.name; | ||
cartState.billingEmail = storedCartData.billingEmail; | ||
dispatch({ type: CartActionType.INITALIZE, payload: cartState }); | ||
}, []); | ||
|
||
useEffect(() => { | ||
localStorage.setItem("cart", JSON.stringify(state)); | ||
}, [state]); | ||
|
||
return <CartContext.Provider value={value}>{children}</CartContext.Provider>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { CartItemType } from "../typings/cart"; | ||
import { fakeDelay } from "../utils/functions/random"; | ||
import { ProductType } from "../typings/product"; | ||
|
||
const QUERY_DELAY_TIME = 1000; | ||
|
||
export class Api { | ||
private API_ORIGIN: string; | ||
|
||
constructor() { | ||
if (!process.env.REACT_APP_API_ORIGIN) { | ||
throw new Error("API_ORIGIN environment variable is not set") | ||
} | ||
this.API_ORIGIN = process.env.REACT_APP_API_ORIGIN || ""; | ||
} | ||
|
||
// http methods | ||
async get(urlPath: string): Promise<Record<string, any>> { | ||
const response = await fetch(`${this.API_ORIGIN}${urlPath}`); | ||
return response.json(); | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
async post(urlPath: string, data: any): Promise<any> { | ||
const response = await fetch(`${this.API_ORIGIN}${urlPath}`, { | ||
method: "POST", // *GET, POST, PUT, DELETE, etc. | ||
mode: "cors", // no-cors, *cors, same-origin | ||
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached | ||
credentials: "same-origin", // include, *same-origin, omit | ||
headers: { | ||
"Content-Type": "application/json", | ||
// 'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
redirect: "follow", // manual, *follow, error | ||
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url | ||
body: JSON.stringify(data), // body data type must match | ||
}); | ||
return response.json(); | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
async getProducts(): Promise<ProductType[]> { | ||
try { | ||
const res = await this.get("/products"); | ||
console.log("product-list", res); | ||
return res?.products ?? []; | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
async getProduct(productId: string) { | ||
try { | ||
const res = await this.get(`/products/${productId}`); | ||
console.log("product res", res); | ||
return res; | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
async getOrder(userId: string, orderId: string) { | ||
try { | ||
const res = await this.get(`/orders/${orderId}`); | ||
console.log("Order Summary response:", res); | ||
return res; | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
async getOrderHistory(userId: string) { | ||
try { | ||
const res = await this.get(`/orders/${userId}`); | ||
console.log("Order Summary response:", res); | ||
return res.json(); | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
async postCheckoutCart( | ||
items: CartItemType[], | ||
email: string, | ||
promoCode: string | null | ||
) { | ||
try { | ||
const res = await this.post(`/cart/checkout`, { | ||
items, | ||
promoCode: promoCode ?? "", | ||
email, | ||
}); | ||
return res; | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
async postQuotation(items: CartItemType[], promoCode: string | null) { | ||
try { | ||
const res = await this.post(`/cart/quotation`, { | ||
items, | ||
promoCode: promoCode ?? "", | ||
}); | ||
return res; | ||
} catch (e: any) { | ||
throw new Error(e); | ||
} | ||
} | ||
} | ||
|
||
export const api = new Api(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
export type CartItemType = { | ||
productId: string; | ||
size: string; | ||
colorway: string; | ||
quantity: number; | ||
}; | ||
|
||
/** | ||
* @Title CartItemType | ||
* @description Displayed on FE in shopping-cart | ||
*/ | ||
|
||
export type CartStateType = { | ||
voucher: string | null; | ||
items: CartItemType[]; | ||
name: string; | ||
billingEmail: string; | ||
}; | ||
export type ProductInfoType = { | ||
name: string; | ||
image: string; | ||
price: number; | ||
}; | ||
|
||
export type CartPriceType = { | ||
currency: string; | ||
subtotal: number; | ||
discount: number; | ||
grandTotal: number; | ||
}; | ||
|
||
export type CartResponseDto = { | ||
items: [ | ||
{ | ||
id: string; | ||
name: string; | ||
price: number; | ||
images: string[]; | ||
sizes: string; | ||
productCategory: string; | ||
isAvailable: boolean; | ||
quantity: number; | ||
} | ||
]; | ||
price: { | ||
currency: string; | ||
subtotal: number; | ||
discount: number; | ||
grandTotal: number; | ||
}; | ||
}; | ||
|
||
export type CheckoutResponseDto = { | ||
orderId: string; | ||
items: [ | ||
{ | ||
id: string; | ||
name: string; | ||
price: number; | ||
images: string[]; | ||
sizes: string[]; | ||
productCategory: string; | ||
isAvailable: boolean; | ||
quantity: number; | ||
} | ||
]; | ||
price: { | ||
currency: string; | ||
subtotal: number; | ||
discount: number; | ||
grandTotal: number; | ||
}; | ||
payment: { | ||
paymentGateway: string; | ||
clientSecret: string; | ||
}; | ||
email: string; | ||
}; | ||
|
||
export type ProductInfoMapType = Record<string, ProductInfoType>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// eslint-disable-next-line no-shadow | ||
export enum OrderStatusType { | ||
RECEIVED, | ||
PROCESSING, | ||
READY_TO_COLLECT, | ||
DELAY, | ||
COLLECTED, | ||
} | ||
|
||
export type OrderItemType = { | ||
id: string; | ||
image: string; | ||
size: string; | ||
colorway: string; | ||
price: number; | ||
quantity: number; | ||
name: string; | ||
}; | ||
|
||
export type OrderBillingType = { | ||
total: number; | ||
subtotal: number; | ||
appliedVoucher?: null; | ||
}; | ||
|
||
export type OrderType = { | ||
userId: string; | ||
orderID: string; | ||
orderItems: OrderItemType[]; | ||
status: OrderStatusType; | ||
billing: OrderBillingType; | ||
orderDateTime: string | Date; | ||
lastUpdate: string | Date | undefined; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export type ProductCategoryType = string; | ||
|
||
export type ProductType = { | ||
id: string; | ||
name: string; | ||
price: number; | ||
stock: { [colorway: string]: { [sizeIndex: string]: number } }; // stock[colorway][size] = qty | ||
sizes: string[]; | ||
sizeChart: string; | ||
colorways: string[]; | ||
images?: string[]; | ||
productCategory?: ProductCategoryType; | ||
isAvailable: boolean; | ||
}; |
Oops, something went wrong.