diff --git a/package.json b/package.json
index 5f5082d..fcf1e9e 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,6 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@aws-amplify/auth": "^4.4.4",
"@chakra-ui/icons": "^1.1.7",
"@chakra-ui/react": "^1.8.3",
"@emotion/react": "^11",
diff --git a/src/App.tsx b/src/App.tsx
index 8265622..bee5e5d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -13,6 +13,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import Routes from "./routes";
import chakraTheme from "./config/theme";
import { CartProvider } from "./context/cart";
+import { CheckoutProvider } from "./context/checkout";
const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false } } });
@@ -22,7 +23,9 @@ const App = () => {
-
+
+
+
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 0a9bb6d..81c636e 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -8,27 +8,9 @@ type FooterLinkProps = {
function Footer() {
return (
-
-
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index c2bcd81..a42b8db 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -10,35 +10,28 @@ import {
Show,
Hide,
Icon,
+ Text,
} from "@chakra-ui/react";
import { useState, useEffect } from "react";
import { Link as RouterLink } from "react-router-dom";
-import CognitoClient from "../utils/aws/cognito/cognitoClient";
import routes from "../utils/constants/routes";
import { useCartStore } from "../context/cart";
import { CartItemType } from "../typings/cart";
const Header = () => {
- const [isAuthenticated, setIsAuthenticated] = useState(false);
const cartContext = useCartStore();
const { state: cartState } = cartContext;
const cartLength = cartState.items.reduce((acc: number, cur: CartItemType) => acc + cur.quantity, 0);
- useEffect(() => {
- CognitoClient.isUserSignedIn().then((isSignedIn) => {
- setIsAuthenticated(isSignedIn);
- });
- }, []);
-
return (
-
+
-
-
- SCSE MERCH
-
+
+
+ SCSE MERCH
+
@@ -46,23 +39,27 @@ const Header = () => {
-
- {cartLength > 0 && {cartLength > 99 ? "99+" : cartLength}}
+
+ {cartLength > 0 && {cartLength > 99 ? "99+" : cartLength}}
- Home
+ _hover={{ bg: "#426899", transition: "0.5s", color: "white" }}>
+
+ Home
+
-
-
- {cartLength > 0 && {cartLength > 99 ? "99+" : cartLength}}
-
+
+
+
+ {cartLength > 0 && {cartLength > 99 ? "99+" : cartLength}}
+
+
diff --git a/src/components/OrderItem.tsx b/src/components/OrderItem.tsx
index 1f875d5..7ab9436 100644
--- a/src/components/OrderItem.tsx
+++ b/src/components/OrderItem.tsx
@@ -36,7 +36,7 @@ const OrderItem: React.FC = (props: OrderItemProps) => {
Color:
- {data.color}
+ {data.colorway}
diff --git a/src/context/checkout/index.tsx b/src/context/checkout/index.tsx
new file mode 100644
index 0000000..52d809e
--- /dev/null
+++ b/src/context/checkout/index.tsx
@@ -0,0 +1,28 @@
+import React, { useContext, useMemo, useState } from "react";
+import { CheckoutResponseDto } from "../../typings/cart";
+
+type ContextType = {
+ state: CheckoutResponseDto | null;
+ setState: React.Dispatch>
+} | null
+
+const CheckoutContext = React.createContext(null)
+
+export const useCheckoutStore = () => {
+ const context = useContext(CheckoutContext);
+ if (context === null){
+ throw new Error("useCheckoutStore must be used within a CheckoutProvider.")
+ }
+ return context
+}
+
+export const CheckoutProvider: React.FC = ({ children }) => {
+ const [checkoutState, setCheckoutState] = useState(null)
+
+ const value = useMemo(() => ({
+ state: checkoutState,
+ setState: setCheckoutState
+ }), [checkoutState])
+ return {children}
+
+}
diff --git a/src/data/mock/orderList/index.tsx b/src/data/mock/orderList/index.tsx
deleted file mode 100644
index c0f237f..0000000
--- a/src/data/mock/orderList/index.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { OrderStatusType, OrderType } from "../../../typings/order";
-
-export const orderList: OrderType[] = [
- {
- userId: "jacob",
- orderID: "1234567891023456",
- orderItems: [
- {
- id: "1",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/448391/item/sggoods_36_448391.jpg?width=1600&impolicy=quality_75",
- name: "SCSE Sweater",
- size: "m",
- color: "Brown",
- price: 25.0,
- quantity: 3,
- },
- {
- id: "2",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/444545/item/sggoods_00_444545.jpg?width=1008&impolicy=quality_75",
-
- name: "SCSE Shirt",
- size: "l",
- color: "Black",
- price: 30.0,
- quantity: 2,
- },
- ],
- status: OrderStatusType.DELAY,
- billing: {
- subtotal: 125.0,
- total: 110.0,
- },
- orderDateTime: new Date(1280981217 * 1000).toLocaleDateString("en-SG"),
- lastUpdate: new Date(1610981217 * 1000).toLocaleDateString("en-SG"),
- },
- {
- userId: "jacob",
-
- orderID: "1234567891023455",
- orderItems: [
- {
- id: "1",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/457111/item/sggoods_00_457111.jpg?width=1008&impolicy=quality_75",
- name: "SCSE Hoodie",
- size: "m",
- color: "White",
- price: 25.0,
- quantity: 3,
- },
- {
- id: "2",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/425974/item/sggoods_56_425974.jpg?width=1600&impolicy=quality_75",
- name: "SCSE Shirt",
- size: "l",
- color: "Blue",
- price: 30.0,
- quantity: 2,
- },
- ],
- status: OrderStatusType.RECEIVED,
- billing: {
- subtotal: 125.0,
- total: 151210.0,
- },
- orderDateTime: new Date(73636123123 * 1000).toLocaleDateString("en-SG"),
- lastUpdate: new Date(83636123123 * 1000).toLocaleDateString("en-SG"),
- },
-
- {
- userId: "jacob",
- orderID: "1234567891023456",
- orderItems: [
- {
- id: "1",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/448391/item/sggoods_36_448391.jpg?width=1600&impolicy=quality_75",
- name: "SCSE Sweater",
- size: "m",
- color: "Brown",
- price: 25.0,
- quantity: 3,
- },
- {
- id: "2",
- image:
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/452344/item/sggoods_09_452344.jpg?width=1008&impolicy=quality_75",
- name: "SCSE Shirt",
- size: "l",
- color: "Black",
- price: 30.0,
- quantity: 2,
- },
- ],
- status: OrderStatusType.PROCESSING,
- billing: {
- subtotal: 1525.0,
- total: 320.0,
- },
- orderDateTime: new Date(1210981217 * 1000).toLocaleDateString("en-SG"),
- lastUpdate: new Date(1210981217 * 1000).toLocaleDateString("en-SG"),
- },
-];
diff --git a/src/data/mock/product/index.ts b/src/data/mock/product/index.ts
deleted file mode 100644
index 8043bcc..0000000
--- a/src/data/mock/product/index.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { ProductType, ProductCategoryType } from "../../../typings/product";
-
-export const productCategoryList: ProductCategoryType[] = ["sweater", "t-shirt", "hoodie"];
-
-export const productList: ProductType[] = [
- {
- id: "1",
- images: [
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/453401/item/sggoods_69_453401.jpg?width=1008&impolicy=quality_75",
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/444593/item/sggoods_32_444593.jpg?width=1008&impolicy=quality_75"
- ],
- name: "SCSE Sweater",
- sizes: ["s", "m", "l", "xl"],
- price: 2500,
- colorways: ["Brown", "Beige"],
- stock: [[5, 0, 3, 1], [1, 0, 5, 7]],
- isAvailable: true,
- productCategory: productCategoryList[0],
- },
- {
- id: "2",
- name: "Sweater for Winter",
- price: 4990,
- sizes: ["xs", "s", "m", "l", "xl"],
- images: [
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/433037/item/sggoods_67_433037.jpg?width=1600&impolicy=quality_75",
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/433037/sub/sggoods_433037_sub1.jpg?width=1600&impolicy=quality_75",
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/433037/sub/sggoods_433037_sub2.jpg?width=1600&impolicy=quality_75",
- ],
- colorways: ["White", "Black", "Navy"],
- stock: [[0, 10, 0, 0, 2], [5, 0, 0, 1, 0], [0, 0, 0, 0, 0]],
- isAvailable: true,
- productCategory: productCategoryList[0],
- },
- {
- id: "3",
- images: [
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/448969/item/sggoods_65_448969.jpg?width=1008&impolicy=quality_75",
- ],
- name: "SCSE Graphic Tee - G",
- sizes: ["s", "m", "l", "xl"],
- colorways: ["Grey"],
- stock: [[0, 0, 3, 12]],
- price: 2500,
- isAvailable: true,
- productCategory: productCategoryList[0],
- },
- {
- id: "4",
- images: [
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/435805/item/sggoods_03_435805.jpg?width=1008&impolicy=quality_75",
- ],
- name: "There's Jam on his shirt",
- sizes: ["s", "m", "l", "xl"],
- colorways: ["Red"],
- stock: [[5, 0, 3, 1]],
- price: 8500,
- isAvailable: false,
- productCategory: productCategoryList[1],
- },
- {
- id: "5",
- images: [
- "https://image.uniqlo.com/UQ/ST3/sg/imagesgoods/446382/item/sggoods_15_446382.jpg?width=1008&impolicy=quality_75",
- ],
- name: "Blue note Records UT",
- sizes: ["s", "m", "l", "xl"],
- colorways: ["Blue", "Green"],
- stock: [[0, 0, 0, 0], [0, 0, 0, 0]],
- price: 1200,
- isAvailable: true,
- productCategory: productCategoryList[1],
- },
- {
- id: "6",
- images: [
- "https://image.uniqlo.com/UQ/ST3/AsianCommon/imagesgoods/447167/item/goods_09_447167.jpg?width=1008&impolicy=quality_75",
- ],
- name: "Mango Looking T-Shirt from Daiso",
- sizes: ["s", "m", "l", "xl"],
- colorways: ["Yellow"],
- stock: [[0, 0, 0, 0]],
- price: 8500,
- isAvailable: false,
- productCategory: productCategoryList[1],
- },
-];
diff --git a/src/pages/Cart/CartItem.tsx b/src/pages/Cart/CartItem.tsx
index 4611966..979b99d 100644
--- a/src/pages/Cart/CartItem.tsx
+++ b/src/pages/Cart/CartItem.tsx
@@ -89,7 +89,13 @@ const CartItem: React.FC = ({ isMobile, data, onRemove, onQuantit
-
+
diff --git a/src/pages/Checkout/Checkout.tsx b/src/pages/Checkout/Checkout.tsx
index 87369da..c31890e 100644
--- a/src/pages/Checkout/Checkout.tsx
+++ b/src/pages/Checkout/Checkout.tsx
@@ -12,13 +12,14 @@ import CheckoutSkeleton from "./Skeleton";
import StripeForm from "./StripeForm";
import { QueryKeys } from "../../utils/constants/queryKeys";
import { displayPrice } from "../../utils/functions/currency";
+import { useCheckoutStore } from "../../context/checkout";
export const Checkout: FC = () => {
// Cart Context Hook.
const cartContext = useCartStore();
const [isLoading, setIsLoading] = useState(true);
const { state: cartState, dispatch: cartDispatch } = cartContext;
- const [checkoutState, setCheckoutState] = useState(null);
+ const { state: checkoutState, setState: setCheckoutState } = useCheckoutStore()
// For mapping between cart item and info
// const [productInfo, setProductInfo] = useState({});
@@ -72,7 +73,13 @@ export const Checkout: FC = () => {
const subtotal = (product?.price ?? -1) * item.quantity;
return (
-
+
@@ -80,6 +87,9 @@ export const Checkout: FC = () => {
{displayPrice(subtotal)}
+
+ {`Color: ${item.colorway}`}
+
{`Qty x${item.quantity}`}
@@ -97,13 +107,13 @@ export const Checkout: FC = () => {
- {/*Subtotal:*/}
- {/*Discount:*/}
+ {/* Subtotal: */}
+ {/* Discount: */}
Grand total:
- {/*{displayPrice(checkoutState?.price?.subtotal ?? 0)}*/}
- {/*{displayPrice(checkoutState?.price?.discount ?? 0)}*/}
+ {/* {displayPrice(checkoutState?.price?.subtotal ?? 0)} */}
+ {/* {displayPrice(checkoutState?.price?.discount ?? 0)} */}
{displayPrice(checkoutState?.price?.grandTotal ?? 0)}
diff --git a/src/pages/Checkout/StripeForm.tsx b/src/pages/Checkout/StripeForm.tsx
index beb5a3c..f92c6d6 100644
--- a/src/pages/Checkout/StripeForm.tsx
+++ b/src/pages/Checkout/StripeForm.tsx
@@ -7,6 +7,7 @@ import { CartAction,CartActionType, useCartStore } from "../../context/cart";
import routes from "../../utils/constants/routes";
import { OrderStatusType } from "../../typings/order";
import { api } from "../../services/api";
+import { useCheckoutStore } from "../../context/checkout";
@@ -25,6 +26,9 @@ const PaymentForm = () => {
const { dispatch: cartDispatch, state: cartState } = cartContext;
const [isLoading, setIsLoading] = useState(false);
const toast = useToast();
+
+ const { state: checkoutState } = useCheckoutStore()
+
const handleSubmit = async (event: React.FormEvent) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
@@ -55,13 +59,13 @@ const PaymentForm = () => {
// TODO: remove userId as we do not have a login
// TODO: order ID to be generated iteratively with api call
setIsLoading(true);
- const checkoutCart = await api.postCheckoutCart(cartState.items, cartState.billingEmail, cartState.voucher)
+
const payload : CartAction = {
type: CartActionType.RESET_CART
}
cartDispatch(payload);
setIsLoading(false);
- navigate(`${routes.ORDER_SUMMARY}/${checkoutCart.orderId}`);
+ navigate(`${routes.ORDER_SUMMARY}/${checkoutState?.orderId}`);
}
};
return (
diff --git a/src/pages/ConfirmSignUp/ConfirmSignUp.tsx b/src/pages/ConfirmSignUp/ConfirmSignUp.tsx
deleted file mode 100644
index c897b9d..0000000
--- a/src/pages/ConfirmSignUp/ConfirmSignUp.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Flex } from "@chakra-ui/react";
-import Header from "../../components/Header";
-import Footer from "../../components/Footer";
-import ConfirmSignUpForm from "./ConfirmSignUpForm";
-
-const ConfirmSignUp = () => {
- return (
-
-
-
-
-
- );
-};
-
-export default ConfirmSignUp;
diff --git a/src/pages/ConfirmSignUp/ConfirmSignUpForm.tsx b/src/pages/ConfirmSignUp/ConfirmSignUpForm.tsx
deleted file mode 100644
index c9d7116..0000000
--- a/src/pages/ConfirmSignUp/ConfirmSignUpForm.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Box, Flex, Heading, Image, VStack, Input, Button, Text, FormControl, FormLabel } from "@chakra-ui/react";
-
-import { ArrowForwardIcon } from "@chakra-ui/icons";
-import React from "react";
-import { useNavigate } from "react-router-dom";
-import CognitoClient from "../../utils/aws/cognito/cognitoClient";
-import routes from "../../utils/constants/routes";
-import ErrorUI from "../../components/ErrorUI";
-
-const ConfirmSignUpForm = () => {
- const navigate = useNavigate();
- const [email, setEmail] = React.useState("");
- const [code, setCode] = React.useState("");
- const [error, setError] = React.useState({
- errorTitle: "",
- errorDescription: "",
- hasError: false,
- });
-
- const onSubmitConfirmSignUp = async (event: any) => {
- event.preventDefault();
- try {
- await CognitoClient.confirmSignUp(email, code);
- navigate(routes.SIGN_IN);
- } catch (err: any) {
- switch (err.code) {
- case "CodeMismatchException":
- setError({
- errorTitle: "Confirmation failed",
- errorDescription: "Invalid confirmation code",
- hasError: true,
- });
- break;
- default:
- setError({
- errorTitle: "Something went wrong",
- errorDescription: err.toString(),
- hasError: true,
- });
- }
- }
- };
-
- return (
-
-
-
- Registration
-
-
-
- );
-};
-
-export default ConfirmSignUpForm;
diff --git a/src/pages/ConfirmSignUp/index.tsx b/src/pages/ConfirmSignUp/index.tsx
deleted file mode 100644
index 3eac04f..0000000
--- a/src/pages/ConfirmSignUp/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import ConfirmSignUp from "./ConfirmSignUp";
-
-export default function index() {
- return ;
-}
diff --git a/src/pages/MerchDetail/MerchDetail.tsx b/src/pages/MerchDetail/MerchDetail.tsx
index 34d2511..a330841 100644
--- a/src/pages/MerchDetail/MerchDetail.tsx
+++ b/src/pages/MerchDetail/MerchDetail.tsx
@@ -141,9 +141,9 @@ export const MerchDetail: React.FC = () => {
Sizes
- }
{product?.sizes?.map((size, idx) => {
@@ -317,7 +317,7 @@ export const MerchDetail: React.FC = () => {
{/* */}
{/* {renderDescription} */}
-
+
);
};
diff --git a/src/pages/MerchDetail/SizeDialog.tsx b/src/pages/MerchDetail/SizeDialog.tsx
index 020cd59..c5c7bef 100644
--- a/src/pages/MerchDetail/SizeDialog.tsx
+++ b/src/pages/MerchDetail/SizeDialog.tsx
@@ -11,11 +11,12 @@ import {
} from "@chakra-ui/react";
type SizeDialogType = {
+ sizeChart?: string;
isOpen: boolean;
onClose: () => void;
};
-const SizeDialog: React.FC = ({ isOpen, onClose }) => {
+const SizeDialog: React.FC = ({ sizeChart, isOpen, onClose }) => {
return (
@@ -24,7 +25,7 @@ const SizeDialog: React.FC = ({ isOpen, onClose }) => {
-
+
diff --git a/src/pages/MerchDetail/SizeOption.tsx b/src/pages/MerchDetail/SizeOption.tsx
index 8c0da12..0f0f3bc 100644
--- a/src/pages/MerchDetail/SizeOption.tsx
+++ b/src/pages/MerchDetail/SizeOption.tsx
@@ -13,8 +13,11 @@ export const SizeOption: React.FC = (props) => {
-
+
+
+
{
- return (
-
-
- You have no past purchases
-
- Continue Shopping
-
-
-
- );
-};
-export default HistoryEmptyView;
diff --git a/src/pages/OrderHistory/OrderHistory.tsx b/src/pages/OrderHistory/OrderHistory.tsx
deleted file mode 100644
index 62cadaa..0000000
--- a/src/pages/OrderHistory/OrderHistory.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { FC, useState } from "react";
-import { Box, Flex, Heading, Text, useBreakpointValue, Divider, Badge } from "@chakra-ui/react";
-
-import { useQuery } from "@tanstack/react-query";
-import { Link } from "react-router-dom";
-import { OrderItemType, OrderType } from "../../typings/order";
-import { renderOrderStatus, getOrderStatusColor } from "../../utils/constants/order-status";
-import OrderItem from "../../components/OrderItem";
-import { QueryKeys } from "../../utils/constants/queryKeys";
-import { api } from "../../services/api";
-import Error404 from "../Error404";
-import LoadingScreen from "../../components/LoadingScreen";
-import Page from "../../components/Page";
-import routes from "../../utils/constants/routes";
-import HistoryEmptyView from "./EmptyView";
-import { displayPrice } from "../../utils/functions/currency";
-
-export const OrderHistory: FC = () => {
- // Check if break point hit.
- const [orderList, setOrderList] = useState([]);
- const isMobile: boolean = useBreakpointValue({ base: true, md: false }) || false;
-
- // Fetch and check if cart item is valid.
- const { isLoading, isRefetching } = useQuery([QueryKeys.ORDERS], () => api.getOrderHistory("jacob"), {
- onSuccess: (data: OrderType[]) => {
- setOrderList(data);
- },
- });
-
- const renderOrderHistory = () => {
- return (
-
-
- MY PURCHASE
-
-
- {orderList?.map((order: OrderType) => {
- const orderItems = order?.orderItems || [];
- const { orderID, orderDateTime, lastUpdate } = order ?? {};
- const orderHeader = (
-
-
- Order ID: {orderID}
- {!isMobile && |}
- Order Date: {orderDateTime}
- {!isMobile && |}
- Last Update: {lastUpdate}
-
-
- {renderOrderStatus(order?.status)}
-
-
- );
- return (
-
-
- {orderHeader}
-
- {orderItems.map((item: OrderItemType) => {
- return ;
- })}
-
-
- Order Total:
- {displayPrice(order?.billing?.total)}
-
-
- View detail
-
-
-
-
- );
- })}
-
- );
- };
-
- const renderOrderHistoryPage = () => {
- if (isLoading || isRefetching) return ;
- if (orderList === null || orderList === undefined) return ;
- if (orderList.length === 0) return ;
- return renderOrderHistory();
- };
-
- return {renderOrderHistoryPage()};
-};
diff --git a/src/pages/OrderHistory/index.tsx b/src/pages/OrderHistory/index.tsx
deleted file mode 100644
index ba65f1c..0000000
--- a/src/pages/OrderHistory/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { OrderHistory } from "./OrderHistory";
-
-export default function index() {
- return ;
-}
diff --git a/src/pages/OrderSummary/OrderSummary.tsx b/src/pages/OrderSummary/OrderSummary.tsx
index 79d04d8..59bb928 100644
--- a/src/pages/OrderSummary/OrderSummary.tsx
+++ b/src/pages/OrderSummary/OrderSummary.tsx
@@ -8,11 +8,13 @@ import {
Divider,
Image,
Badge,
+ Show,
+ Hide,
} from "@chakra-ui/react";
import { Link, useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import OrderItem from "../../components/OrderItem";
-import { renderOrderStatus } from "../../utils/constants/order-status";
+import { renderOrderStatus, getOrderStatusColor } from "../../utils/constants/order-status";
import { QueryKeys } from "../../utils/constants/queryKeys";
import { api } from "../../services/api";
import { OrderStatusType, OrderType } from "../../typings/order";
@@ -77,30 +79,71 @@ export const OrderSummary: FC = () => {
overflow="hidden"
flexDir="column"
>
-
-
-
- Order Number
-
+
+
+
+
{renderOrderStatus(orderState?.status ?? OrderStatusType.DELAY)}
+ Order Number
+
+ {orderState?.orderID.split("-")[0]}
+
+
+ {orderState?.orderID}
+
+
+ Order date:{" "}
+ {orderState
+ ? new Date(`${orderState.orderDateTime}Z`).toLocaleString(
+ "en-sg"
+ )
+ : ""}
+
+
+ Last update: {orderState?.lastUpdate}
+
-
- {orderState?.orderID}
-
-
-
- Order date:{" "}
- {orderState
- ? new Date(`${orderState.orderDateTime}Z`).toLocaleString(
- "en-sg"
- )
- : ""}
-
- Last update: {orderState?.lastUpdate}
+
+
+
+
+
+ Order Number
+
+ {renderOrderStatus(orderState?.status ?? OrderStatusType.DELAY)}
+
+
+
+ {orderState?.orderID.split("-")[0]}
+
+
+ {orderState?.orderID}
+
+
+
+
+ Order date:{" "}
+ {orderState
+ ? new Date(`${orderState.orderDateTime}Z`).toLocaleString(
+ "en-sg"
+ )
+ : ""}
+
+ Last update: {orderState?.lastUpdate}
+
-
+
{orderState?.orderItems.map((item) => (
diff --git a/src/pages/SignIn/SignIn.tsx b/src/pages/SignIn/SignIn.tsx
deleted file mode 100644
index b2a1f0f..0000000
--- a/src/pages/SignIn/SignIn.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Page from "../../components/Page";
-import SignInForm from "./SignInForm";
-
-const SignIn = () => {
- return (
-
-
-
- );
-};
-
-export default SignIn;
diff --git a/src/pages/SignIn/SignInForm.tsx b/src/pages/SignIn/SignInForm.tsx
deleted file mode 100644
index ae7c5c4..0000000
--- a/src/pages/SignIn/SignInForm.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { Box, Flex, Heading, Image, VStack, Input, Button, Text, FormControl } from "@chakra-ui/react";
-
-import { ArrowForwardIcon } from "@chakra-ui/icons";
-import { useNavigate } from "react-router-dom";
-import React from "react";
-import CognitoClient from "../../utils/aws/cognito/cognitoClient";
-import routes from "../../utils/constants/routes";
-import ErrorUI from "../../components/ErrorUI";
-
-const SignInForm = () => {
- const navigate = useNavigate();
- const [email, setEmail] = React.useState("");
- const [password, setPassword] = React.useState("");
- const [error, setError] = React.useState({
- errorTitle: "",
- errorDescription: "",
- hasError: false,
- });
-
- const onSubmitSignIn = async (event: any) => {
- try {
- event.preventDefault();
- await CognitoClient.signIn(email, password);
- navigate(routes.HOME);
- } catch (err: any) {
- switch (err.code) {
- case "NotAuthorizedException":
- setError({
- errorTitle: "Login failed",
- errorDescription: "Invalid email or password",
- hasError: true,
- });
- break;
- case "UserNotConfirmedException":
- setError({
- errorTitle: "Login failed",
- errorDescription: "User not confirmed",
- hasError: true,
- });
- break;
- default:
- setError({
- errorTitle: "Something went wrong",
- errorDescription: err.toString(),
- hasError: true,
- });
- }
- }
- };
-
- return (
-
-
-
- Sign in
-
-
-
- );
-};
-
-export default SignInForm;
diff --git a/src/pages/SignIn/index.tsx b/src/pages/SignIn/index.tsx
deleted file mode 100644
index b7002c5..0000000
--- a/src/pages/SignIn/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import SignIn from "./SignIn";
-
-export default function index() {
- return ;
-}
diff --git a/src/pages/Signup/SignUp.tsx b/src/pages/Signup/SignUp.tsx
deleted file mode 100644
index 45727df..0000000
--- a/src/pages/Signup/SignUp.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Page from "../../components/Page";
-import SignUpForm from "./SignUpForm";
-
-const SignUp = () => {
- return (
-
-
-
- );
-};
-
-export default SignUp;
diff --git a/src/pages/Signup/SignUpForm.tsx b/src/pages/Signup/SignUpForm.tsx
deleted file mode 100644
index 37f93fd..0000000
--- a/src/pages/Signup/SignUpForm.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import {
- Box,
- Flex,
- Heading,
- Image,
- VStack,
- Input,
- Button,
- Text,
- Divider,
- FormControl,
- FormLabel,
-} from "@chakra-ui/react";
-
-import { ArrowForwardIcon } from "@chakra-ui/icons";
-import Joi from "joi";
-import { joiPassword } from "joi-password";
-import React from "react";
-import { useNavigate } from "react-router-dom";
-import CognitoClient from "../../utils/aws/cognito/cognitoClient";
-import routes from "../../utils/constants/routes";
-import ErrorUI from "../../components/ErrorUI";
-
-const SignUpForm = () => {
- const navigate = useNavigate();
- const [email, setEmail] = React.useState("");
- const [phoneNumber, setPhoneNumber] = React.useState("");
- const [password, setPassword] = React.useState("");
- const [confirmPassword, setConfirmPassword] = React.useState("");
- const [givenName, setGivenName] = React.useState("");
- const [familyName, setFamilyName] = React.useState("");
- const [error, setError] = React.useState({
- errorTitle: "",
- errorDescription: "",
- hasError: false,
- });
- const [validationError, setValidationError] = React.useState({
- errorMessage: "",
- hasError: false,
- });
-
- const formValidation = async () => {
- const emailValidator = Joi.string()
- .email({ tlds: { allow: false } })
- .required()
- .label("Email");
- const passwordValidator = joiPassword
- .string()
- .min(8)
- .noWhiteSpaces()
- .minOfSpecialCharacters(1)
- .minOfNumeric(1)
- .minOfUppercase(1)
- .minOfLowercase(1)
- .label("Password");
- const phoneValidator = Joi.string()
- .regex(/^\+[1-9]\d{1,14}$/)
- .label("Phone Number")
- .messages({
- "string.pattern.base": "Phone number must be in the format +65xxxxxxxxxx",
- });
- const confirmPasswordValidator = () => {
- if (password === confirmPassword) {
- return Joi.valid(true);
- }
- throw new Error("Passwords do not match");
- };
- try {
- await emailValidator.validateAsync(email);
- await passwordValidator.validateAsync(password);
- await phoneValidator.validateAsync(phoneNumber);
- confirmPasswordValidator();
- } catch (err: any) {
- setValidationError({
- errorMessage: err.message,
- hasError: true,
- });
- return false;
- }
- return true;
- };
-
- const onSubmitSignUp = async (event: any) => {
- event.preventDefault();
- setValidationError({
- errorMessage: "",
- hasError: false,
- });
- setError({
- errorTitle: "",
- errorDescription: "",
- hasError: false,
- });
- const isValidated = await formValidation();
- if (!isValidated) {
- return;
- }
- try {
- await CognitoClient.signUp(email, password, familyName, givenName, phoneNumber);
- navigate(routes.CONFIRM_SIGN_UP);
- } catch (err: any) {
- switch (err.code) {
- case "UserLambdaValidationException":
- setError({
- errorTitle: "Sign up failed",
- errorDescription: "Invalid email domain",
- hasError: true,
- });
- break;
- case "UsernameExistsException":
- setError({
- errorTitle: "Signup failed",
- errorDescription: "User already exist",
- hasError: true,
- });
- break;
- default:
- setError({
- errorTitle: "Something went wrong",
- errorDescription: err.toString(),
- hasError: true,
- });
- }
- }
- };
-
- return (
-
-
-
- Registration
-
-
-
- );
-};
-
-export default SignUpForm;
diff --git a/src/pages/Signup/index.tsx b/src/pages/Signup/index.tsx
deleted file mode 100644
index 64bbc82..0000000
--- a/src/pages/Signup/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import SignUp from "./SignUp";
-
-export default function index() {
- return ;
-}
diff --git a/src/routes.tsx b/src/routes.tsx
index 64490e4..f232a89 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -3,7 +3,6 @@ import MerchandiseList from "./pages/MerchandiseList";
import Cart from "./pages/Cart";
import Checkout from "./pages/Checkout";
import OrderSummary from "./pages/OrderSummary";
-import OrderHistory from "./pages/OrderHistory";
import MerchDetail from "./pages/MerchDetail";
import routes from "./utils/constants/routes";
import Error404 from "./pages/Error404";
@@ -14,7 +13,6 @@ const Routes = () => (
} />
} />
} />
- } />
} />
} />
diff --git a/src/services/api.tsx b/src/services/api.tsx
index 4c76304..ab61c72 100644
--- a/src/services/api.tsx
+++ b/src/services/api.tsx
@@ -1,12 +1,8 @@
-import { orderList } from "../data/mock/orderList";
-import { productList } from "../data/mock/product";
import { CartItemType } from "../typings/cart";
import { fakeDelay } from "../utils/functions/random";
import { ProductType } from "../typings/product";
const QUERY_DELAY_TIME = 1000;
-const CUSTOM_MOCK_DATA = false;
-const CUSTOM_STRIPE_DATA = false;
export class Api {
private API_ORIGIN: string;
@@ -45,10 +41,6 @@ export class Api {
// eslint-disable-next-line class-methods-use-this
async getProducts(): Promise {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- return productList;
- }
const res = await this.get("/products");
console.log("product-list", res);
return res?.products ?? [];
@@ -60,10 +52,6 @@ export class Api {
// eslint-disable-next-line class-methods-use-this
async getProduct(productId: string) {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- return productList.find((product) => product.id === productId);
- }
const res = await this.get(`/products/${productId}`);
console.log("product res", res);
return res;
@@ -74,18 +62,6 @@ export class Api {
async getOrder(userId: string, orderId: string) {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- return orderList.find(
- (order) => order.userId === userId && order.orderID === orderId
- );
- }
- if (CUSTOM_STRIPE_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- return (
- JSON.parse(localStorage.getItem("order-history") as string)[0] ?? {}
- );
- }
const res = await this.get(`/orders/${orderId}`);
console.log("Order Summary response:", res);
return res;
@@ -96,10 +72,6 @@ export class Api {
async getOrderHistory(userId: string) {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- return orderList.filter((order) => order.userId === userId) ?? [];
- }
const res = await this.get(`/orders/${userId}`);
console.log("Order Summary response:", res);
return res.json();
@@ -114,11 +86,6 @@ export class Api {
promoCode: string | null
) {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- // return orderList.filter((order) => order.userId === userId) ?? [];
- }
-
const res = await this.post(`/cart/checkout`, {
items,
promoCode: promoCode ?? "",
@@ -132,11 +99,6 @@ export class Api {
async postQuotation(items: CartItemType[], promoCode: string | null) {
try {
- if (CUSTOM_MOCK_DATA) {
- await fakeDelay(QUERY_DELAY_TIME);
- // return orderList.filter((order) => order.userId === userId) ?? [];
- }
-
const res = await this.post(`/cart/quotation`, {
items,
promoCode: promoCode ?? "",
diff --git a/src/typings/cart.tsx b/src/typings/cart.tsx
index e18ef5d..da4c752 100644
--- a/src/typings/cart.tsx
+++ b/src/typings/cart.tsx
@@ -51,6 +51,7 @@ export type CartResponseDto = {
};
export type CheckoutResponseDto = {
+ orderId: string;
items: [
{
id: string;
@@ -73,6 +74,7 @@ export type CheckoutResponseDto = {
paymentGateway: string;
clientSecret: string;
};
+ email: string;
};
export type ProductInfoMapType = Record;
diff --git a/src/typings/order.tsx b/src/typings/order.tsx
index 4541163..7d5278e 100644
--- a/src/typings/order.tsx
+++ b/src/typings/order.tsx
@@ -11,7 +11,7 @@ export type OrderItemType = {
id: string;
image: string;
size: string;
- color: string;
+ colorway: string;
price: number;
quantity: number;
name: string;
diff --git a/src/typings/product.tsx b/src/typings/product.tsx
index 50c4484..c043ea1 100644
--- a/src/typings/product.tsx
+++ b/src/typings/product.tsx
@@ -8,8 +8,9 @@ export type ProductType = {
id: string;
name: string;
price: number;
- stock: number[][]; // stock[colorway][size] = qty
+ stock: { [colorway: string]: { [sizeIndex: string]: number } }; // stock[colorway][size] = qty
sizes: string[];
+ sizeChart: string;
colorways: string[];
images?: string[];
productCategory?: ProductCategoryType;
diff --git a/src/utils/aws/cognito/cognitoClient.ts b/src/utils/aws/cognito/cognitoClient.ts
deleted file mode 100644
index 27f2d0a..0000000
--- a/src/utils/aws/cognito/cognitoClient.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Auth, Amplify } from 'aws-amplify';
-import { CognitoUser } from '@aws-amplify/auth'
-
-
-Amplify.configure({
- aws_cognito_region: process.env.REACT_APP_AWS_COGNITO_REGION,
- aws_user_pools_id: process.env.REACT_APP_AWS_COGNITO_USER_POOLS_ID,
- aws_user_pools_web_client_id: process.env.REACT_APP_AWS_COGNITO_USER_POOLS_WEB_CLIENT_ID,
-})
-
-
-class CognitoClient {
- public static async signUp(email: string, password: string, familyName: string, givenName: string, phoneNumber: string): Promise {
- const userAttributes = {
- email,
- family_name: familyName,
- given_name: givenName,
- updated_at: Date.now().toString(),
- }
-
- if (phoneNumber !== "") {
- Object.assign(userAttributes, { phone_number: phoneNumber })
- }
-
- await Auth.signUp({
- username: email,
- password,
- attributes: userAttributes,
- });
- }
-
- public static async confirmSignUp(email: string, code: string): Promise {
- await Auth.confirmSignUp(email, code);
- }
-
- public static async signIn(email: string, password: string): Promise {
- return (await Auth.signIn(email, password)) as CognitoUser;
- }
-
- public static async signOut(): Promise {
- await Auth.signOut();
- }
-
- public static async forgotPassword(email: string): Promise {
- await Auth.forgotPassword(email);
- }
-
- public static async confirmForgotPassword(email: string, code: string, password: string): Promise {
- await Auth.forgotPasswordSubmit(email, code, password);
- }
-
- public static async fetchJwtToken(): Promise {
- const user = await Auth.currentAuthenticatedUser()
- return user.signInUserSession.accessToken.jwtToken;
- }
-
- public static async fetchUserEmail(): Promise {
- const user = await Auth.currentAuthenticatedUser()
- return user.attributes.email
- }
-
- public static async refreshJWTToken(): Promise {
- const session = await Auth.currentSession()
- return session.getAccessToken().getJwtToken()
- }
-
- public static async isUserSignedIn(): Promise {
- const user = await Auth.currentAuthenticatedUser()
- return user !== null
- }
-
-}
-
-export default CognitoClient;
diff --git a/src/utils/constants/routes.ts b/src/utils/constants/routes.ts
index 1c2be18..810995d 100644
--- a/src/utils/constants/routes.ts
+++ b/src/utils/constants/routes.ts
@@ -1,27 +1,17 @@
type Routes = {
HOME: string;
PRODUCT: string;
- SIGN_UP: string;
- CONFIRM_SIGN_UP: string;
- SIGN_IN: string;
- FORGOT_PASSWORD: string;
CART: string;
CHECKOUT: string;
ORDER_SUMMARY: string;
- ORDER_HISTORY: string;
};
export const routes: Routes = {
HOME: "/",
PRODUCT: "/product",
- SIGN_UP: "/sign-up",
- CONFIRM_SIGN_UP: "/confirm-sign-up",
- SIGN_IN: "/sign-in",
- FORGOT_PASSWORD: "/forgot-password",
CART: "/cart",
CHECKOUT: "/checkout",
ORDER_SUMMARY: "/order-summary",
- ORDER_HISTORY: "/order-history",
};
export default routes;
diff --git a/src/utils/functions/stock.ts b/src/utils/functions/stock.ts
index 6148f14..90edb8a 100644
--- a/src/utils/functions/stock.ts
+++ b/src/utils/functions/stock.ts
@@ -2,20 +2,16 @@ import { ProductType } from "../../typings/product"
export const getQtyInStock = (product: ProductType, colorway: string, size: string): number => {
// returns remaining stock for specified colorway and size
- const colorwayIndex = product.colorways.indexOf(colorway);
- const sizeIndex = product.sizes.indexOf(size);
- if (colorwayIndex !== -1 && sizeIndex !== -1) {
- return product.stock[colorwayIndex][sizeIndex];
+ if (product.stock[colorway] && product.stock[colorway][size]) {
+ return product.stock[colorway][size]
}
return 0;
}
export const displayStock = (product: ProductType, colorway: string, size: string): string => {
// returns string describing remaining stock
- const colorwayIndex = product.colorways.indexOf(colorway);
- const sizeIndex = product.sizes.indexOf(size);
- if (colorwayIndex !== -1 && sizeIndex !== -1) {
- const qty = product.stock[colorwayIndex][sizeIndex];
+ if (product.stock[colorway] && product.stock[colorway][size]) {
+ const qty = product.stock[colorway][size];
if (qty > 0) {
return `${qty} available`;
}
@@ -27,33 +23,27 @@ export const displayStock = (product: ProductType, colorway: string, size: strin
export const isOutOfStock = (product: ProductType): boolean => {
// returns true if product is out of stock in all colorways and sizes
- const totalQty = product.stock.reduce((a,b) => { return a.concat(b) }) // flatten array
- .reduce((a,b) => { return a + b }); // sum elements
- return (totalQty <= 0);
+ const totalQty = Object.values(product.stock).reduce((acc, stockByColorway)=>{
+ const colorQty = Object.values(stockByColorway).reduce((acc2, qty)=>acc2+qty, 0);
+ return acc+colorQty
+ }, 0);
+ return totalQty <= 0;
+
}
export const isColorwayAvailable = (product: ProductType, colorway: string): boolean => {
// returns true if colorway is available in any size
// returns false if colorway is out of stock in all sizes
- const index = product.colorways.indexOf(colorway);
- if (index === -1) { // no such colorway
- return false;
- }
- const colorwayStock = product.stock[index];
- const totalQty = colorwayStock.reduce((a, b) => {
- return a + b;
- }, 0);
- return (totalQty > 0);
-}
+ const colorwayStock = Object.values(product.stock[colorway]).reduce(
+ (acc: any, size: any)=>acc+size, 0
+ );
+ return (colorwayStock > 0);
+}
export const isSizeAvailable = (product: ProductType, size: string): boolean => {
// returns true if size is available in any colorway
// returns false if size is out of stock in all colorways
- const index = product.sizes.indexOf(size);
- if (index === -1) { // no such size
- return false;
- }
- const sizeStock = product.stock.map(d => d[index]);
+ const sizeStock = Object.values(product.stock).map(d => d[size]||0);
const totalQty = sizeStock.reduce((a, b) => {
return a + b;
}, 0);
@@ -73,7 +63,7 @@ export const getDefaultColorway = (product: ProductType, size: string): string =
if (sizeIndex === -1) { // no such size
return "";
}
- const colorwayStock = product.stock.map(d => d[sizeIndex]);
+ const colorwayStock = Object.values(product.stock).map(d => d[sizeIndex]);
const availColorwayIndex = colorwayStock.map((qty, idx) => qty > 0 ? idx : -1).filter(idx => idx !== -1);
if (availColorwayIndex.length > 0) {
return product.colorways[availColorwayIndex[0]];
@@ -85,4 +75,4 @@ export const getDefaults = (product: ProductType): [string, string] => {
const size = getDefaultSize(product);
const colorway = getDefaultColorway(product, size);
return [colorway, size];
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index 30993e7..9028bdb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -63,7 +63,7 @@
"@aws-amplify/api-graphql" "2.2.24"
"@aws-amplify/api-rest" "2.0.35"
-"@aws-amplify/auth@4.4.4", "@aws-amplify/auth@^4.4.4":
+"@aws-amplify/auth@4.4.4":
version "4.4.4"
resolved "https://registry.yarnpkg.com/@aws-amplify/auth/-/auth-4.4.4.tgz#b1c78a3bc0f80bd5303de91bf836eb2b543d2fa0"
integrity sha512-/iQB8teOXxb6XkOK2nPBxldU5YZjMLy6vTpihWMOPP96TWqldgnKSZykYtsrTb6uYK8iF1VWr7DI9OE7UpKONQ==