| @@ -1,176 +1,189 @@ | |||
| import { Box, Button, ButtonGroup, Card, Typography } from '@mui/material'; | |||
| import { useTranslation } from 'next-i18next'; | |||
| import Image from 'next/image'; | |||
| import { useState } from 'react'; | |||
| import { useState, FC } from 'react'; | |||
| import { ProductData } from '../../../utils/interface/productInterface'; | |||
| const CartCard = ({ product, initialQuantity, remove, updateQuantity }) => { | |||
| const [quantity, setQuantity] = useState(initialQuantity); | |||
| const { t } = useTranslation('cart'); | |||
| return ( | |||
| <Card | |||
| interface Props { | |||
| product: ProductData; | |||
| initialQuantity: number; | |||
| remove: (x: string) => void; | |||
| updateQuantity: (x: string, y: number) => void; | |||
| } | |||
| const CartCard: FC<Props> = ({ | |||
| product, | |||
| initialQuantity, | |||
| remove, | |||
| updateQuantity, | |||
| }) => { | |||
| const [quantity, setQuantity] = useState(initialQuantity); | |||
| const { t } = useTranslation('cart'); | |||
| return ( | |||
| <Card | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| p: 2, | |||
| mb: 2, | |||
| }} | |||
| > | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'column', md: 'row' }, | |||
| justifyContent: { xs: 'center' }, | |||
| }} | |||
| > | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| mb: { xs: 2, md: 0 }, | |||
| }} | |||
| > | |||
| <Image src={product.image} alt="profile" width={200} height={200} /> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyItems: 'center', | |||
| width: { md: '40%' }, | |||
| }} | |||
| > | |||
| <Typography | |||
| align="center" | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| p: 2, | |||
| mb: 2, | |||
| mb: { xs: 5, sm: 5, md: 0 }, | |||
| mr: { md: 5 }, | |||
| width: '100%', | |||
| fontWeight: 600, | |||
| fontSize: { xs: 20, sm: 20 }, | |||
| }} | |||
| > | |||
| {product?.name} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'row', md: 'column' }, | |||
| justifyContent: 'center', | |||
| alignItems: { xs: 'flex-end', md: 'center' }, | |||
| mb: { xs: 5, sm: 5, md: 0 }, | |||
| mr: { md: 5 }, | |||
| }} | |||
| > | |||
| <Box | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| alignItems: 'flex-end', | |||
| mr: { xs: 2, md: 0 }, | |||
| }} | |||
| > | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| height: 16, | |||
| fontSize: 14, | |||
| }} | |||
| > | |||
| {t('cart:quantity')} | |||
| </Typography> | |||
| <ButtonGroup | |||
| size="small" | |||
| aria-label="small outlined button group" | |||
| sx={{ | |||
| height: 35, | |||
| mt: 1, | |||
| backgroundColor: 'primary.main', | |||
| color: 'white', | |||
| border: 0, | |||
| }} | |||
| > | |||
| <Button | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'column', md: 'row' }, | |||
| justifyContent: { xs: 'center' }, | |||
| color: 'white', | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| > | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| mb: { xs: 2, md: 0 }, | |||
| }} | |||
| > | |||
| <Image src={product.image} alt="profile" width={200} height={200} /> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyItems: 'center', | |||
| width: { md: '40%' }, | |||
| }} | |||
| > | |||
| <Typography | |||
| align="center" | |||
| sx={{ | |||
| mb: { xs: 5, sm: 5, md: 0 }, | |||
| mr: { md: 5 }, | |||
| width: '100%', | |||
| fontWeight: 600, | |||
| fontSize: { xs: 20, sm: 20 }, | |||
| }} | |||
| > | |||
| {product?.name} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'row', md: 'column' }, | |||
| justifyContent: 'center', | |||
| alignItems: { xs: 'flex-end', md: 'center' }, | |||
| mb: { xs: 5, sm: 5, md: 0 }, | |||
| mr: { md: 5 }, | |||
| }} | |||
| > | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| alignItems: 'flex-end', | |||
| mr: { xs: 2, md: 0 }, | |||
| }} | |||
| > | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| height: 16, | |||
| fontSize: 14, | |||
| }} | |||
| > | |||
| {t('cart:quantity')} | |||
| </Typography> | |||
| <ButtonGroup | |||
| size="small" | |||
| aria-label="small outlined button group" | |||
| sx={{ | |||
| height: 35, | |||
| mt: 1, | |||
| backgroundColor: 'primary.main', | |||
| color: 'white', | |||
| border: 0, | |||
| }} | |||
| > | |||
| <Button | |||
| sx={{ | |||
| color: 'white', | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| onClick={() => { | |||
| if (quantity > 1) { | |||
| updateQuantity(product?.customID, quantity - 1); | |||
| setQuantity((prevState) => prevState - 1); | |||
| } | |||
| }} | |||
| > | |||
| - | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| color: 'white', | |||
| fontSize: 15, | |||
| width: 25, | |||
| }} | |||
| > | |||
| {quantity} | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| color: 'white', | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| onClick={() => { | |||
| updateQuantity(product?.customID, quantity + 1); | |||
| setQuantity((prevState) => prevState + 1); | |||
| }} | |||
| > | |||
| + | |||
| </Button> | |||
| </ButtonGroup> | |||
| </Box> | |||
| <Button | |||
| disableRipple | |||
| sx={{ | |||
| height: 35, | |||
| mt: 1, | |||
| width: 118, | |||
| fontSize: 15, | |||
| textTransform: 'none', | |||
| backgroundColor: '#C6453E', | |||
| color: 'white', | |||
| }} | |||
| startIcon={ | |||
| <Image src="/images/x.svg" alt="remove" width={15} height={15} /> | |||
| } | |||
| onClick={() => remove(product.customID)} | |||
| > | |||
| {t('cart:remove')} | |||
| </Button> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| height: 25, | |||
| fontSize: { xs: 15, md: 18 }, | |||
| }} | |||
| > | |||
| {t('cart:priceTag')} | |||
| {product?.price} | |||
| </Typography> | |||
| </Box> | |||
| </Box> | |||
| </Card> | |||
| ); | |||
| onClick={() => { | |||
| if (quantity > 1) { | |||
| updateQuantity(product?.customID, quantity - 1); | |||
| setQuantity((prevState) => prevState - 1); | |||
| } | |||
| }} | |||
| > | |||
| - | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| color: 'white', | |||
| fontSize: 15, | |||
| width: 25, | |||
| }} | |||
| > | |||
| {quantity} | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| color: 'white', | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| onClick={() => { | |||
| updateQuantity(product?.customID, quantity + 1); | |||
| setQuantity((prevState) => prevState + 1); | |||
| }} | |||
| > | |||
| + | |||
| </Button> | |||
| </ButtonGroup> | |||
| </Box> | |||
| <Button | |||
| disableRipple | |||
| sx={{ | |||
| height: 35, | |||
| mt: 1, | |||
| width: 118, | |||
| fontSize: 15, | |||
| textTransform: 'none', | |||
| backgroundColor: '#C6453E', | |||
| color: 'white', | |||
| }} | |||
| startIcon={ | |||
| <Image src="/images/x.svg" alt="remove" width={15} height={15} /> | |||
| } | |||
| onClick={() => remove(product.customID)} | |||
| > | |||
| {t('cart:remove')} | |||
| </Button> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| height: 25, | |||
| fontSize: { xs: 15, md: 18 }, | |||
| }} | |||
| > | |||
| {t('cart:priceTag')} | |||
| {product?.price} | |||
| </Typography> | |||
| </Box> | |||
| </Box> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default CartCard; | |||
| @@ -4,70 +4,78 @@ import { useTranslation } from 'next-i18next'; | |||
| import Image from 'next/image'; | |||
| import { useRouter } from 'next/router'; | |||
| import { setCookie } from 'nookies'; | |||
| import { FC } from 'react'; | |||
| const OrderSummaryCard = ({ data }) => { | |||
| const { t } = useTranslation('cart'); | |||
| const router = useRouter(); | |||
| return ( | |||
| <Card sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }}> | |||
| <Typography | |||
| sx={{ | |||
| fontSize: 26, | |||
| color: 'primary.main', | |||
| textAlign: 'center', | |||
| width: '100%', | |||
| }} | |||
| > | |||
| {t('cart:orderTitle')} | |||
| </Typography> | |||
| <Typography sx={{ mt: 4 }}> | |||
| {t('cart:itemsTotal')} | |||
| {data.totalPrice.toFixed(2)} | |||
| </Typography> | |||
| <Typography sx={{ mt: 1.5 }}>{t('cart:shipping')}</Typography> | |||
| <Typography sx={{ mt: 1.5, mb: 1.5 }}> | |||
| {t('cart:total')} | |||
| {data.totalPrice.toFixed(2)} | |||
| </Typography> | |||
| <Divider /> | |||
| <Box sx={{ textAlign: 'center', mt: 4, width: '100%' }}> | |||
| <Button | |||
| disableRipple | |||
| sx={{ | |||
| '&.Mui-disabled': { | |||
| backgroundColor: '#0066ff', | |||
| color: '#fff', | |||
| opacity: '0.6', | |||
| }, | |||
| '&:hover': { | |||
| backgroundColor: '#0066ff', | |||
| color: 'white', | |||
| boxShadow: 'none', | |||
| }, | |||
| backgroundColor: '#0066ff', | |||
| color: 'white', | |||
| textTransform: 'none', | |||
| px: 2, | |||
| }} | |||
| startIcon={ | |||
| <Image src="/images/lock.svg" alt="lock" width={18} height={18} /> | |||
| } | |||
| disabled={data.totalQuantity > 0 ? false : true} | |||
| onClick={() => { | |||
| router.push('/checkout'); | |||
| setCookie(null, 'checkout-session', 'active', { | |||
| maxAge: 3600, | |||
| expires: new Date(Date.now() + 3600), | |||
| path: '/', | |||
| }); | |||
| }} | |||
| > | |||
| {t('cart:proceed')} | |||
| </Button> | |||
| </Box> | |||
| <Typography sx={{ mt: 3, fontSize: 13 }}>{t('cart:infoMsg')}</Typography> | |||
| </Card> | |||
| ); | |||
| interface Props { | |||
| data: { | |||
| totalPrice: number; | |||
| totalQuantity: number; | |||
| }; | |||
| } | |||
| const OrderSummaryCard: FC<Props> = ({ data }) => { | |||
| const { t } = useTranslation('cart'); | |||
| const router = useRouter(); | |||
| return ( | |||
| <Card sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }}> | |||
| <Typography | |||
| sx={{ | |||
| fontSize: 26, | |||
| color: 'primary.main', | |||
| textAlign: 'center', | |||
| width: '100%', | |||
| }} | |||
| > | |||
| {t('cart:orderTitle')} | |||
| </Typography> | |||
| <Typography sx={{ mt: 4 }}> | |||
| {t('cart:itemsTotal')} | |||
| {data.totalPrice.toFixed(2)} | |||
| </Typography> | |||
| <Typography sx={{ mt: 1.5 }}>{t('cart:shipping')}</Typography> | |||
| <Typography sx={{ mt: 1.5, mb: 1.5 }}> | |||
| {t('cart:total')} | |||
| {data.totalPrice.toFixed(2)} | |||
| </Typography> | |||
| <Divider /> | |||
| <Box sx={{ textAlign: 'center', mt: 4, width: '100%' }}> | |||
| <Button | |||
| disableRipple | |||
| sx={{ | |||
| '&.Mui-disabled': { | |||
| backgroundColor: '#0066ff', | |||
| color: '#fff', | |||
| opacity: '0.6', | |||
| }, | |||
| '&:hover': { | |||
| backgroundColor: '#0066ff', | |||
| color: 'white', | |||
| boxShadow: 'none', | |||
| }, | |||
| backgroundColor: '#0066ff', | |||
| color: 'white', | |||
| textTransform: 'none', | |||
| px: 2, | |||
| }} | |||
| startIcon={ | |||
| <Image src="/images/lock.svg" alt="lock" width={18} height={18} /> | |||
| } | |||
| disabled={data.totalQuantity > 0 ? false : true} | |||
| onClick={() => { | |||
| router.push('/checkout'); | |||
| setCookie(null, 'checkout-session', 'active', { | |||
| maxAge: 3600, | |||
| expires: new Date(Date.now() + 3600), | |||
| path: '/', | |||
| }); | |||
| }} | |||
| > | |||
| {t('cart:proceed')} | |||
| </Button> | |||
| </Box> | |||
| <Typography sx={{ mt: 3, fontSize: 13 }}>{t('cart:infoMsg')}</Typography> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default OrderSummaryCard; | |||
| @@ -3,6 +3,7 @@ import { useTranslation } from 'next-i18next'; | |||
| import { destroyCookie } from 'nookies'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useStore, useStoreUpdate } from '../../store/cart-context'; | |||
| import { ProductData } from '../../utils/interface/productInterface'; | |||
| import CartCard from '../cards/cart-card/CartCard'; | |||
| import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard'; | |||
| import EmptyCart from '../empty-cart/EmptyCart'; | |||
| @@ -10,11 +11,20 @@ import ContentContainer from '../layout/content-wrapper/ContentContainer'; | |||
| import PageWrapper from '../layout/page-wrapper/PageWrapper'; | |||
| import StepTitle from '../layout/steps-title/StepTitle'; | |||
| const CartContent: React.FC = () => { | |||
| interface ICartInfo { | |||
| cartStorage: { | |||
| product: ProductData; | |||
| quantity: number; | |||
| }[]; | |||
| totalPrice: number; | |||
| totalQuantity: number; | |||
| } | |||
| const CartContent = () => { | |||
| const { t } = useTranslation('cart'); | |||
| const { cartStorage, totalPrice, totalQuantity } = useStore(); | |||
| const { removeCartValue, updateItemQuantity } = useStoreUpdate(); | |||
| const [cartInfo, setCartInfo] = useState({ | |||
| const [cartInfo, setCartInfo] = useState<ICartInfo>({ | |||
| cartStorage: [], | |||
| totalPrice: 0, | |||
| totalQuantity: 0, | |||
| @@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next'; | |||
| import { useRouter } from 'next/router'; | |||
| import { setCookie } from 'nookies'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useStore } from '../../store/cart-context'; | |||
| import { ICart, useStore } from '../../store/cart-context'; | |||
| import { useCheckoutDataUpdate } from '../../store/checkout-context'; | |||
| import CardContainer from '../cards/card-container/CardContainer'; | |||
| import DataCard from '../cards/data-card/DataCard'; | |||
| @@ -14,12 +14,21 @@ import PageWrapper from '../layout/page-wrapper/PageWrapper'; | |||
| import StepTitle from '../layout/steps-title/StepTitle'; | |||
| import PageDescription from '../page-description/PageDescription'; | |||
| interface FormValues { | |||
| fullName: string; | |||
| address: string; | |||
| address2: string; | |||
| city: string; | |||
| country: string; | |||
| postcode: string; | |||
| } | |||
| const CheckoutContent = () => { | |||
| const { t } = useTranslation('cart'); | |||
| const { cartStorage } = useStore(); | |||
| const { addCheckoutValue } = useCheckoutDataUpdate(); | |||
| const [cartData, setCartData] = useState([]); | |||
| const [cartData, setCartData] = useState<ICart[]>([]); | |||
| const { data: session } = useSession(); | |||
| const router = useRouter(); | |||
| @@ -28,18 +37,20 @@ const CheckoutContent = () => { | |||
| setCartData(cartStorage); | |||
| }, [cartStorage]); | |||
| const submitHandler = (formValues) => { | |||
| addCheckoutValue( | |||
| cartData, | |||
| { ...formValues, email: session.user.email }, | |||
| session.user._id | |||
| ); | |||
| setCookie(null, 'shipping-session', 'active', { | |||
| maxAge: 3600, | |||
| expires: new Date(Date.now() + 3600), | |||
| path: '/', | |||
| }); | |||
| router.push('/shipping'); | |||
| const submitHandler = (formValues: FormValues) => { | |||
| if (session?.user) { | |||
| addCheckoutValue( | |||
| cartData, | |||
| { ...formValues, email: session?.user?.email }, | |||
| session?.user?._id | |||
| ); | |||
| setCookie(null, 'shipping-session', 'active', { | |||
| maxAge: 3600, | |||
| expires: new Date(Date.now() + 3600), | |||
| path: '/', | |||
| }); | |||
| router.push('/shipping'); | |||
| } | |||
| }; | |||
| const mapProductsToDom = () => { | |||
| @@ -62,6 +73,7 @@ const CheckoutContent = () => { | |||
| <ContentContainer> | |||
| <Box flexGrow={1} sx={{ minWidth: '65%' }}> | |||
| <ShippingDetailsForm | |||
| enableBtn={false} | |||
| backBtn={true} | |||
| isCheckout={true} | |||
| submitHandler={submitHandler} | |||
| @@ -2,15 +2,31 @@ import { Box, Button, Card, TextField } from '@mui/material'; | |||
| import { useFormik } from 'formik'; | |||
| import { useTranslation } from 'next-i18next'; | |||
| import { useRouter } from 'next/router'; | |||
| import { useState } from 'react'; | |||
| import { useState, FC } from 'react'; | |||
| import { registerSchema } from '../../../schemas/shippingDetailsSchema'; | |||
| import { useUserData } from '../../../store/user-context'; | |||
| import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | |||
| const ShippingDetailsForm = ({ | |||
| interface FormValues { | |||
| fullName: string; | |||
| address: string; | |||
| address2: string; | |||
| city: string; | |||
| country: string; | |||
| postcode: string; | |||
| } | |||
| interface Props { | |||
| submitHandler: (x: FormValues) => void; | |||
| backBtn: boolean; | |||
| isCheckout: boolean; | |||
| enableBtn: boolean; | |||
| } | |||
| const ShippingDetailsForm: FC<Props> = ({ | |||
| submitHandler, | |||
| backBtn = false, | |||
| isCheckout = false, | |||
| submitHandler, | |||
| enableBtn = true, | |||
| }) => { | |||
| const { t } = useTranslation('addressForm'); | |||
| @@ -18,7 +34,7 @@ const ShippingDetailsForm = ({ | |||
| const { userStorage } = useUserData(); | |||
| const router = useRouter(); | |||
| const formikSubmitHandler = async (values) => { | |||
| const formikSubmitHandler = async (values: FormValues) => { | |||
| submitHandler(values); | |||
| }; | |||
| @@ -1,18 +1,22 @@ | |||
| import { Box } from '@mui/system'; | |||
| import { FC, ReactNode } from 'react'; | |||
| const ContentContainer = ({ children }) => { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'column', md: 'row' }, | |||
| mr: { xs: 2, md: 12 }, | |||
| ml: { xs: 2, md: 12 }, | |||
| }} | |||
| > | |||
| {children} | |||
| </Box> | |||
| ); | |||
| interface Props { | |||
| children: ReactNode; | |||
| } | |||
| const ContentContainer: FC<Props> = ({ children }) => { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'column', md: 'row' }, | |||
| mr: { xs: 2, md: 12 }, | |||
| ml: { xs: 2, md: 12 }, | |||
| }} | |||
| > | |||
| {children} | |||
| </Box> | |||
| ); | |||
| }; | |||
| export default ContentContainer; | |||
| @@ -1,7 +1,12 @@ | |||
| import { Box } from '@mui/system'; | |||
| import { FC, ReactNode } from 'react'; | |||
| const PageWrapper = ({ children }) => { | |||
| return <Box sx={{ py: 10, height: '100%', width: '100%' }}>{children}</Box>; | |||
| interface Props { | |||
| children: ReactNode; | |||
| } | |||
| const PageWrapper: FC<Props> = ({ children }) => { | |||
| return <Box sx={{ py: 10, height: '100%', width: '100%' }}>{children}</Box>; | |||
| }; | |||
| export default PageWrapper; | |||
| @@ -1,55 +1,61 @@ | |||
| import NavigateNextIcon from '@mui/icons-material/NavigateNext'; | |||
| import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material'; | |||
| import { FC } from 'react'; | |||
| const StepTitle = ({ title, breadcrumbsArray }) => { | |||
| return ( | |||
| <> | |||
| <Grid item xs={12}> | |||
| interface Props { | |||
| title: string; | |||
| breadcrumbsArray: string[]; | |||
| } | |||
| const StepTitle: FC<Props> = ({ title, breadcrumbsArray }) => { | |||
| return ( | |||
| <> | |||
| <Grid item xs={12}> | |||
| <Typography | |||
| variant="h4" | |||
| sx={{ | |||
| ml: { xs: 2, md: 12 }, | |||
| mt: 12, | |||
| height: '100%', | |||
| color: 'primary.main', | |||
| }} | |||
| > | |||
| {title} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <Divider | |||
| sx={{ | |||
| backgroundColor: 'primary.main', | |||
| ml: { xs: 2, md: 12 }, | |||
| mr: { xs: 2, md: 12 }, | |||
| }} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12} sx={{ mt: 4 }}> | |||
| <Breadcrumbs | |||
| aria-label="breadcrumb" | |||
| separator={<NavigateNextIcon fontSize="small" />} | |||
| sx={{ ml: { xs: 2, md: 12 }, fontSize: 20 }} | |||
| > | |||
| {breadcrumbsArray && | |||
| breadcrumbsArray.map((entry, index) => { | |||
| return ( | |||
| <Typography | |||
| variant="h4" | |||
| sx={{ | |||
| ml: { xs: 2, md: 12 }, | |||
| mt: 12, | |||
| height: '100%', | |||
| color: 'primary.main', | |||
| }} | |||
| sx={{ fontSize: { xs: '16px', md: '22px' } }} | |||
| key={index} | |||
| color={ | |||
| index === breadcrumbsArray.length - 1 ? 'red' : 'black' | |||
| } | |||
| > | |||
| {title} | |||
| {entry} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <Divider | |||
| sx={{ | |||
| backgroundColor: 'primary.main', | |||
| ml: { xs: 2, md: 12 }, | |||
| mr: { xs: 2, md: 12 }, | |||
| }} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12} sx={{ mt: 4 }}> | |||
| <Breadcrumbs | |||
| aria-label="breadcrumb" | |||
| separator={<NavigateNextIcon fontSize="small" />} | |||
| sx={{ ml: { xs: 2, md: 12 }, fontSize: 20 }} | |||
| > | |||
| {breadcrumbsArray && | |||
| breadcrumbsArray.map((entry, index) => { | |||
| return ( | |||
| <Typography | |||
| sx={{ fontSize: { xs: '16px', md: '22px' } }} | |||
| key={index} | |||
| color={ | |||
| index === breadcrumbsArray.length - 1 ? 'red' : 'black' | |||
| } | |||
| > | |||
| {entry} | |||
| </Typography> | |||
| ); | |||
| })} | |||
| </Breadcrumbs> | |||
| </Grid> | |||
| </> | |||
| ); | |||
| ); | |||
| })} | |||
| </Breadcrumbs> | |||
| </Grid> | |||
| </> | |||
| ); | |||
| }; | |||
| export default StepTitle; | |||
| @@ -5,11 +5,11 @@ import { useRouter } from 'next/router'; | |||
| import { destroyCookie } from 'nookies'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { postOrder } from '../../requests/products/postOrderRequest'; | |||
| import { postOrder } from '../../requests/orders/postOrderRequest'; | |||
| import { useStoreUpdate } from '../../store/cart-context'; | |||
| import { | |||
| useCheckoutData, | |||
| useCheckoutDataUpdate, | |||
| useCheckoutData, | |||
| useCheckoutDataUpdate, | |||
| } from '../../store/checkout-context'; | |||
| import PageWrapper from '../layout/page-wrapper/PageWrapper'; | |||
| import StepTitle from '../layout/steps-title/StepTitle'; | |||
| @@ -17,179 +17,179 @@ import StepTitle from '../layout/steps-title/StepTitle'; | |||
| let initialRender = true; | |||
| const ReviewContent = () => { | |||
| const { t } = useTranslation('review'); | |||
| const { checkoutStorage } = useCheckoutData(); | |||
| const { parseCheckoutValue, clearCheckout } = useCheckoutDataUpdate(); | |||
| const { clearCart } = useStoreUpdate(); | |||
| const [orderData, setOrderData] = useState({}); | |||
| const { t } = useTranslation('review'); | |||
| const { checkoutStorage } = useCheckoutData(); | |||
| const { parseCheckoutValue, clearCheckout } = useCheckoutDataUpdate(); | |||
| const { clearCart } = useStoreUpdate(); | |||
| const [orderData, setOrderData] = useState({}); | |||
| const router = useRouter(); | |||
| const router = useRouter(); | |||
| useEffect(() => { | |||
| if (initialRender) { | |||
| setOrderData(parseCheckoutValue()); | |||
| postOrder(parseCheckoutValue()); | |||
| initialRender = false; | |||
| return () => { | |||
| clearCheckout(); | |||
| clearCart(); | |||
| destroyCookie(null, 'checkout-session', { | |||
| path: '/', | |||
| }); | |||
| destroyCookie(null, 'shipping-session', { | |||
| path: '/', | |||
| }); | |||
| destroyCookie(null, 'review-session', { | |||
| path: '/', | |||
| }); | |||
| }; | |||
| } | |||
| }, [checkoutStorage]); | |||
| useEffect(() => { | |||
| if (initialRender) { | |||
| setOrderData(parseCheckoutValue()); | |||
| postOrder(parseCheckoutValue()); | |||
| initialRender = false; | |||
| return () => { | |||
| clearCheckout(); | |||
| clearCart(); | |||
| destroyCookie(null, 'checkout-session', { | |||
| path: '/', | |||
| }); | |||
| destroyCookie(null, 'shipping-session', { | |||
| path: '/', | |||
| }); | |||
| destroyCookie(null, 'review-session', { | |||
| path: '/', | |||
| }); | |||
| }; | |||
| } | |||
| }, [checkoutStorage]); | |||
| return ( | |||
| <PageWrapper> | |||
| <StepTitle | |||
| title="Review" | |||
| breadcrumbsArray={['Cart', 'Checkout', 'Shipping', 'Payment', 'Review']} | |||
| /> | |||
| <Box sx={{ ml: { xs: 2 }, mr: { xs: 2 }, mt: 6 }}> | |||
| <Box> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| color: 'primary.main', | |||
| fontWeight: 600, | |||
| fontSize: 22, | |||
| }} | |||
| > | |||
| {t('review:orderMsg')} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| fontWeight: 600, | |||
| mt: 2, | |||
| textAlign: 'center', | |||
| }} | |||
| > | |||
| {t('review:note')} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| mt: 4, | |||
| mb: 4, | |||
| fontSize: 44, | |||
| fontWeight: 600, | |||
| }} | |||
| > | |||
| {t('review:title')} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| my: 1, | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:date')} | |||
| {orderData.time} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:email')} | |||
| {orderData?.shippingAddress?.email} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:total')} | |||
| {orderData?.totalPrice?.toFixed(2)} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:shipping')} | |||
| {orderData?.shippingAddress?.address},{' '} | |||
| {orderData?.shippingAddress?.city},{' '} | |||
| {orderData?.shippingAddress?.country},{' '} | |||
| {orderData?.shippingAddress?.postcode} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Box | |||
| sx={{ | |||
| width: '100%', | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| mt: 2, | |||
| borderRadius: 2, | |||
| p: 1, | |||
| }} | |||
| > | |||
| <Button | |||
| variant="contained" | |||
| sx={{ | |||
| mt: 3, | |||
| mb: 2, | |||
| height: 50, | |||
| width: 150, | |||
| textTransform: 'none', | |||
| backgroundColor: '#CBA213', | |||
| color: 'white', | |||
| mr: 2, | |||
| fontSize: 16, | |||
| }} | |||
| onClick={() => { | |||
| router.push('/'); | |||
| }} | |||
| > | |||
| {t('review:back')} | |||
| </Button> | |||
| </Box> | |||
| </Box> | |||
| </Box> | |||
| </PageWrapper> | |||
| ); | |||
| return ( | |||
| <PageWrapper> | |||
| <StepTitle | |||
| title="Review" | |||
| breadcrumbsArray={['Cart', 'Checkout', 'Shipping', 'Payment', 'Review']} | |||
| /> | |||
| <Box sx={{ ml: { xs: 2 }, mr: { xs: 2 }, mt: 6 }}> | |||
| <Box> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| color: 'primary.main', | |||
| fontWeight: 600, | |||
| fontSize: 22, | |||
| }} | |||
| > | |||
| {t('review:orderMsg')} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| fontWeight: 600, | |||
| mt: 2, | |||
| textAlign: 'center', | |||
| }} | |||
| > | |||
| {t('review:note')} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| mt: 4, | |||
| mb: 4, | |||
| fontSize: 44, | |||
| fontWeight: 600, | |||
| }} | |||
| > | |||
| {t('review:title')} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| my: 1, | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:date')} | |||
| {orderData.time} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:email')} | |||
| {orderData?.shippingAddress?.email} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:total')} | |||
| {orderData?.totalPrice?.toFixed(2)} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| backgroundColor: '#f2f2f2', | |||
| ml: { md: 12 }, | |||
| mr: { md: 12 }, | |||
| borderRadius: 2, | |||
| p: 2, | |||
| my: 1, | |||
| }} | |||
| > | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| {t('review:shipping')} | |||
| {orderData?.shippingAddress?.address},{' '} | |||
| {orderData?.shippingAddress?.city},{' '} | |||
| {orderData?.shippingAddress?.country},{' '} | |||
| {orderData?.shippingAddress?.postcode} | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ mt: 1 }}> | |||
| <Box | |||
| sx={{ | |||
| width: '100%', | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| mt: 2, | |||
| borderRadius: 2, | |||
| p: 1, | |||
| }} | |||
| > | |||
| <Button | |||
| variant="contained" | |||
| sx={{ | |||
| mt: 3, | |||
| mb: 2, | |||
| height: 50, | |||
| width: 150, | |||
| textTransform: 'none', | |||
| backgroundColor: '#CBA213', | |||
| color: 'white', | |||
| mr: 2, | |||
| fontSize: 16, | |||
| }} | |||
| onClick={() => { | |||
| router.push('/'); | |||
| }} | |||
| > | |||
| {t('review:back')} | |||
| </Button> | |||
| </Box> | |||
| </Box> | |||
| </Box> | |||
| </PageWrapper> | |||
| ); | |||
| }; | |||
| export default ReviewContent; | |||
| @@ -1,8 +1,7 @@ | |||
| const validator = require('validator'); | |||
| import { ProductData as IProduct } from '../utils/interface/productInterface'; | |||
| import { OrderData as IOrder } from '../utils/interface/orderInterface'; | |||
| import { Schema, model, Types } from 'mongoose'; | |||
| import { Schema, model, Types, models } from 'mongoose'; | |||
| const OrderSchema = new Schema<IOrder>( | |||
| { | |||
| products: Array<IProduct>, | |||
| @@ -87,6 +86,6 @@ const OrderSchema = new Schema<IOrder>( | |||
| } | |||
| ); | |||
| const Order = model<IOrder>('Order', OrderSchema, 'Order'); | |||
| const Order = models.Order || model<IOrder>('Order', OrderSchema, 'Order'); | |||
| module.exports = Order; | |||
| @@ -1,5 +1,5 @@ | |||
| import { ProductData as IProduct } from '../utils/interface/productInterface'; | |||
| import { Schema, model } from 'mongoose'; | |||
| import { Schema, model, models } from 'mongoose'; | |||
| const ProductSchema = new Schema<IProduct>({ | |||
| category: { | |||
| @@ -68,6 +68,6 @@ const ProductSchema = new Schema<IProduct>({ | |||
| }, | |||
| }); | |||
| const Product = model<IProduct>('Product', ProductSchema); | |||
| const Product = models.Product || model<IProduct>('Product', ProductSchema); | |||
| module.exports = Product; | |||
| @@ -1,4 +1,4 @@ | |||
| import { Schema, model } from 'mongoose'; | |||
| import { Schema, model, models } from 'mongoose'; | |||
| import { QuestionData as IQusetion } from '../utils/interface/questionInterface'; | |||
| const validator = require('validator'); | |||
| @@ -35,5 +35,6 @@ const QuestionSchema = new Schema<IQusetion>({ | |||
| }, | |||
| }); | |||
| const Question = model<IQusetion>('Question', QuestionSchema, 'Questions'); | |||
| const Question = | |||
| models.Question || model<IQusetion>('Question', QuestionSchema, 'Questions'); | |||
| module.exports = Question; | |||
| @@ -1,4 +1,4 @@ | |||
| import { Schema, model, Model } from 'mongoose'; | |||
| import { Schema, model, Model, models } from 'mongoose'; | |||
| import { | |||
| hashPassword, | |||
| verifyPassword, | |||
| @@ -124,5 +124,5 @@ UserSchema.pre('save', async function (next) { | |||
| next(); | |||
| }); | |||
| const User = model<IUser, UserModel>('User', UserSchema, 'User'); | |||
| const User = models.User || model<IUser, UserModel>('User', UserSchema, 'User'); | |||
| module.exports = User; | |||
| @@ -19,6 +19,7 @@ | |||
| "@tanstack/react-query": "^4.10.3", | |||
| "@types/bcryptjs": "^2.4.2", | |||
| "@types/mongodb": "^4.0.7", | |||
| "@types/nookies": "^2.0.3", | |||
| "@types/validator": "^13.7.7", | |||
| "bcryptjs": "^2.4.3", | |||
| "formik": "^2.2.9", | |||
| @@ -3,37 +3,35 @@ import Credentials from 'next-auth/providers/credentials'; | |||
| import dbConnect from '../../../utils/helpers/dbHelpers'; | |||
| const User = require('../../../models/user'); | |||
| // @ts-ignore | |||
| export default NextAuth({ | |||
| session: { | |||
| strategy: 'jwt', | |||
| // @ts-ignore | |||
| jwt: true, | |||
| }, | |||
| callbacks: { | |||
| async jwt({ token, user, account, profile, isNewUser }) { | |||
| async jwt({ token, user }) { | |||
| return { ...token, ...user }; | |||
| }, | |||
| async session({ session, token, user }) { | |||
| return session; | |||
| // @ts-ignore | |||
| async session({ token }) { | |||
| return token; | |||
| }, | |||
| }, | |||
| providers: [ | |||
| Credentials({ | |||
| name: 'Credentials', | |||
| credentials: { | |||
| username: { label: 'Username', type: 'text' }, | |||
| password: { label: 'Password', type: 'password' }, | |||
| }, | |||
| // @ts-ignore | |||
| async authorize(credentials) { | |||
| if (credentials) { | |||
| await dbConnect(); | |||
| await dbConnect(); | |||
| const userData = await User.findByCredentials( | |||
| credentials.username, | |||
| credentials.password | |||
| ); | |||
| return { user: userData }; | |||
| } | |||
| return null; | |||
| // @ts-ignore | |||
| const userData = await User.findByCredentials( | |||
| // @ts-ignore | |||
| credentials.username, | |||
| // @ts-ignore | |||
| credentials.password | |||
| ); | |||
| return { user: userData }; | |||
| }, | |||
| }), | |||
| ], | |||
| @@ -1,17 +1,17 @@ | |||
| import { NextPage } from 'next'; | |||
| import { NextPage, GetStaticProps } from 'next'; | |||
| import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; | |||
| import CartContent from '../../components/cart-content/CartContent'; | |||
| const CartPage: NextPage = () => { | |||
| return <CartContent></CartContent>; | |||
| return <CartContent></CartContent>; | |||
| }; | |||
| export async function getStaticProps({ locale }: any) { | |||
| return { | |||
| props: { | |||
| ...(await serverSideTranslations(locale, ['cart'])), | |||
| }, | |||
| }; | |||
| } | |||
| export const getStaticProps: GetStaticProps = async ({ locale }: any) => { | |||
| return { | |||
| props: { | |||
| ...(await serverSideTranslations(locale, ['cart'])), | |||
| }, | |||
| }; | |||
| }; | |||
| export default CartPage; | |||
| @@ -1,4 +1,4 @@ | |||
| import { NextPage } from 'next'; | |||
| import { NextPage, GetServerSideProps } from 'next'; | |||
| import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; | |||
| import nookies from 'nookies'; | |||
| import CheckoutContent from '../../components/checkout-content/CheckoutContent'; | |||
| @@ -7,7 +7,7 @@ const CheckoutPage: NextPage = () => { | |||
| return <CheckoutContent></CheckoutContent>; | |||
| }; | |||
| export const getServerSideProps = async (ctx: any) => { | |||
| export const getServerSideProps: GetServerSideProps = async (ctx: any) => { | |||
| const cookies = nookies.get(ctx); | |||
| if (!cookies['checkout-session']) { | |||
| @@ -1,17 +1,40 @@ | |||
| import { createContext, useContext, useState } from 'react'; | |||
| import { createContext, useContext, useState, FC, ReactNode } from 'react'; | |||
| import { getStorage, setStorage } from '../utils/helpers/storage'; | |||
| const StorageContext = createContext({ | |||
| import { ProductData } from '../utils/interface/productInterface'; | |||
| export interface ICart { | |||
| product: ProductData; | |||
| quantity: number; | |||
| } | |||
| interface IStorage { | |||
| cartStorage: ICart[]; | |||
| totalPrice: number; | |||
| totalQuantity: number; | |||
| } | |||
| interface IStorageDispatch { | |||
| addCartValue: (x: ProductData, y: number) => void; | |||
| clearCart: () => void; | |||
| removeCartValue: (x: string) => void; | |||
| updateItemQuantity: (x: string, y: number) => void; | |||
| } | |||
| interface Props { | |||
| children: ReactNode; | |||
| } | |||
| const StorageContext = createContext<IStorage>({ | |||
| cartStorage: [], | |||
| totalPrice: 0, | |||
| totalQuantity: 0, | |||
| }); | |||
| const StorageDispatchContext = createContext({ | |||
| addCartValue: (product, quantity) => {}, | |||
| const StorageDispatchContext = createContext<IStorageDispatch>({ | |||
| addCartValue: (product: ProductData, quantity: number) => {}, | |||
| clearCart: () => {}, | |||
| removeCartValue: (productId) => {}, | |||
| setCartStorage: (cart) => {}, | |||
| updateItemQuantity: (productId, quantity) => {}, | |||
| removeCartValue: (productId: string) => {}, | |||
| updateItemQuantity: (productId: string, quantity: number) => {}, | |||
| }); | |||
| export const useStore = () => { | |||
| @@ -23,20 +46,20 @@ export const useStoreUpdate = () => { | |||
| const useStorage = () => { | |||
| const CART_KEY = 'cart-products'; | |||
| const [cartStorage, setCartStorage] = useState(getStorage(CART_KEY)); | |||
| const [totalPrice, setTotalPrice] = useState(() => { | |||
| const cart = getStorage(CART_KEY); | |||
| const [cartStorage, setCartStorage] = useState<ICart[]>(getStorage(CART_KEY)); | |||
| const [totalPrice, setTotalPrice] = useState<number>(() => { | |||
| const cart: ICart[] = getStorage(CART_KEY); | |||
| if (cart && cart.length) { | |||
| return cart | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| .reduce((accum: number, curValue: number) => accum + curValue); | |||
| } else { | |||
| return 0; | |||
| } | |||
| }); | |||
| const [totalQuantity, setTotalQuantity] = useState(() => { | |||
| const [totalQuantity, setTotalQuantity] = useState<number>(() => { | |||
| const cart = getStorage(CART_KEY); | |||
| if (cart && cart.length) { | |||
| @@ -46,11 +69,11 @@ const useStorage = () => { | |||
| } | |||
| }); | |||
| const addCartValue = (product, quantity) => { | |||
| const items = getStorage(CART_KEY); | |||
| const addCartValue = (product: ProductData, quantity: number) => { | |||
| const items: ICart[] = getStorage(CART_KEY); | |||
| if (!items) { | |||
| setStorage(CART_KEY, [{ product, quantity }]); | |||
| setStorage(CART_KEY, { product, quantity }); | |||
| } else { | |||
| const isItemDuplicate = items.some( | |||
| (item) => item.product.customID === product.customID | |||
| @@ -58,7 +81,7 @@ const useStorage = () => { | |||
| if (!isItemDuplicate) { | |||
| items.push({ product, quantity }); | |||
| setTotalQuantity((prevState) => prevState + 1); | |||
| setTotalQuantity((prevState: number) => prevState + 1); | |||
| setStorage(CART_KEY, items); | |||
| } else { | |||
| return; | |||
| @@ -74,9 +97,9 @@ const useStorage = () => { | |||
| setCartStorage(items); | |||
| }; | |||
| const updateItemQuantity = (productId, quantity) => { | |||
| const updateItemQuantity = (productId: string, quantity: number) => { | |||
| if (quantity < 0) return; | |||
| const items = getStorage(CART_KEY); | |||
| const items: ICart[] = getStorage(CART_KEY); | |||
| let updatedItems = items; | |||
| if (items) { | |||
| @@ -100,14 +123,14 @@ const useStorage = () => { | |||
| }; | |||
| const clearCart = () => { | |||
| setStorage(CART_KEY, []); | |||
| setStorage(CART_KEY, {}); | |||
| setTotalQuantity(0); | |||
| setTotalPrice(0); | |||
| setCartStorage([]); | |||
| }; | |||
| const removeCartValue = (productId) => { | |||
| const items = getStorage(CART_KEY); | |||
| const removeCartValue = (productId: string) => { | |||
| const items: ICart[] = getStorage(CART_KEY); | |||
| const newStorage = items?.filter( | |||
| (item) => item.product.customID !== productId | |||
| @@ -121,7 +144,7 @@ const useStorage = () => { | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| setTotalPrice(newTotalPrice); | |||
| } | |||
| setTotalQuantity((prevState) => prevState - 1); | |||
| setTotalQuantity((prevState: number) => prevState - 1); | |||
| setStorage(CART_KEY, newStorage); | |||
| setCartStorage(newStorage); | |||
| }; | |||
| @@ -138,7 +161,7 @@ const useStorage = () => { | |||
| }; | |||
| }; | |||
| const StorageProvider = ({ children }) => { | |||
| const StorageProvider: FC<Props> = ({ children }) => { | |||
| const { | |||
| cartStorage, | |||
| totalPrice, | |||
| @@ -157,7 +180,6 @@ const StorageProvider = ({ children }) => { | |||
| addCartValue, | |||
| clearCart, | |||
| removeCartValue, | |||
| setCartStorage, | |||
| updateItemQuantity, | |||
| }} | |||
| > | |||
| @@ -1,15 +1,63 @@ | |||
| import { createContext, useContext, useState } from 'react'; | |||
| import { createContext, useContext, useState, FC, ReactNode } from 'react'; | |||
| import { getSStorage, setSStorage } from '../utils/helpers/storage'; | |||
| const CheckoutContext = createContext({ | |||
| checkoutStorage: {}, | |||
| import { ShippingData } from '../utils/interface/orderInterface'; | |||
| import { ProductData } from '../utils/interface/productInterface'; | |||
| import { UserData } from '../utils/interface/userInterface'; | |||
| interface Props { | |||
| children: ReactNode; | |||
| } | |||
| interface Products { | |||
| product: ProductData; | |||
| quantity: number; | |||
| } | |||
| interface CheckoutData { | |||
| products: Products[]; | |||
| userInfo: ShippingData; | |||
| userID: string; | |||
| } | |||
| interface OrderData { | |||
| products: Array<ProductData>; | |||
| time: string; | |||
| shippingAddress: ShippingData; | |||
| totalPrice: number; | |||
| numberOfItems: number; | |||
| fulfilled: boolean; | |||
| owner: string; | |||
| stripeCheckoutId: string; | |||
| } | |||
| interface ICheckout { | |||
| checkoutStorage: CheckoutData; | |||
| } | |||
| interface ICheckoutDispatch { | |||
| addCheckoutValue: (x: Products[], y: ShippingData, z: string) => void; | |||
| changeContact: (x: string) => void; | |||
| changeShippingData: (x: ShippingData) => void; | |||
| clearCheckout: () => void; | |||
| parseCheckoutValue: () => OrderData; | |||
| } | |||
| const CheckoutContext = createContext<ICheckout>({ | |||
| checkoutStorage: {} as CheckoutData, | |||
| }); | |||
| const CheckoutDispatchContext = createContext({ | |||
| addCheckoutValue: (products, userInfo, userID) => {}, | |||
| changeContact: (email) => {}, | |||
| changeShippingData: (shippingData) => {}, | |||
| const CheckoutDispatchContext = createContext<ICheckoutDispatch>({ | |||
| addCheckoutValue: ( | |||
| products: Products[], | |||
| userInfo: ShippingData, | |||
| userID: string | |||
| ) => {}, | |||
| changeContact: (email: string) => {}, | |||
| changeShippingData: (shippingData: ShippingData) => {}, | |||
| clearCheckout: () => {}, | |||
| parseCheckoutValue: () => {}, | |||
| parseCheckoutValue: () => { | |||
| return {} as OrderData; | |||
| }, | |||
| }); | |||
| export const useCheckoutData = () => { | |||
| @@ -21,11 +69,15 @@ export const useCheckoutDataUpdate = () => { | |||
| const useCheckout = () => { | |||
| const CHECKOUT_KEY = 'checkout-data'; | |||
| const [checkoutStorage, setCheckoutStorage] = useState( | |||
| const [checkoutStorage, setCheckoutStorage] = useState<CheckoutData>( | |||
| getSStorage(CHECKOUT_KEY) | |||
| ); | |||
| const addCheckoutValue = (products, userInfo, userID) => { | |||
| const addCheckoutValue = ( | |||
| products: Products[], | |||
| userInfo: ShippingData, | |||
| userID: string | |||
| ) => { | |||
| setSStorage(CHECKOUT_KEY, { products, userInfo, userID }); | |||
| setCheckoutStorage({ products, userInfo, userID }); | |||
| @@ -33,7 +85,7 @@ const useCheckout = () => { | |||
| const clearCheckout = () => { | |||
| setSStorage(CHECKOUT_KEY, {}); | |||
| setCheckoutStorage({}); | |||
| setCheckoutStorage({} as CheckoutData); | |||
| }; | |||
| const parseCheckoutValue = () => { | |||
| @@ -58,8 +110,8 @@ const useCheckout = () => { | |||
| return dataToStore; | |||
| }; | |||
| const changeContact = (email) => { | |||
| const items = getSStorage(CHECKOUT_KEY); | |||
| const changeContact = (email: string) => { | |||
| const items: CheckoutData = getSStorage(CHECKOUT_KEY); | |||
| items.userInfo.email = email; | |||
| setSStorage(CHECKOUT_KEY, { ...items }); | |||
| @@ -67,10 +119,10 @@ const useCheckout = () => { | |||
| setCheckoutStorage(items); | |||
| }; | |||
| const changeShippingData = (shippingData) => { | |||
| const items = getSStorage(CHECKOUT_KEY); | |||
| const changeShippingData = (shippingData: ShippingData) => { | |||
| const items: CheckoutData = getSStorage(CHECKOUT_KEY); | |||
| items.userInfo = { email: items.userInfo.email, ...shippingData }; | |||
| items.userInfo = { ...shippingData, email: items.userInfo.email }; | |||
| setSStorage(CHECKOUT_KEY, { ...items }); | |||
| @@ -83,15 +135,13 @@ const useCheckout = () => { | |||
| parseCheckoutValue, | |||
| changeContact, | |||
| changeShippingData, | |||
| setCheckoutStorage, | |||
| checkoutStorage, | |||
| }; | |||
| }; | |||
| const CheckoutProvider = ({ children }) => { | |||
| const CheckoutProvider: FC<Props> = ({ children }) => { | |||
| const { | |||
| checkoutStorage, | |||
| setCheckoutStorage, | |||
| addCheckoutValue, | |||
| clearCheckout, | |||
| parseCheckoutValue, | |||
| @@ -103,7 +153,6 @@ const CheckoutProvider = ({ children }) => { | |||
| <CheckoutContext.Provider value={{ checkoutStorage }}> | |||
| <CheckoutDispatchContext.Provider | |||
| value={{ | |||
| setCheckoutStorage, | |||
| addCheckoutValue, | |||
| clearCheckout, | |||
| parseCheckoutValue, | |||
| @@ -1,17 +1,32 @@ | |||
| import { createContext, useContext, useState } from 'react'; | |||
| import { createContext, useContext, useState, FC, ReactNode } from 'react'; | |||
| import { | |||
| getStorage, | |||
| removeStorage, | |||
| setStorage, | |||
| } from '../utils/helpers/storage'; | |||
| import { UserData } from '../utils/interface/userInterface'; | |||
| const UserContext = createContext({ | |||
| userStorage: [], | |||
| interface Props { | |||
| children: ReactNode; | |||
| } | |||
| interface IUserContext { | |||
| userStorage: UserData; | |||
| } | |||
| interface IUserDispatch { | |||
| addUser: (x: UserData) => void; | |||
| clearUser: () => void; | |||
| updateUserInfo: (x: UserData) => void; | |||
| } | |||
| const UserContext = createContext<IUserContext>({ | |||
| userStorage: {} as UserData, | |||
| }); | |||
| const UserDispatchContext = createContext({ | |||
| addUser: (userData) => {}, | |||
| const UserDispatchContext = createContext<IUserDispatch>({ | |||
| addUser: (userData: UserData) => {}, | |||
| clearUser: () => {}, | |||
| updateUserInfo: (newUserData) => {}, | |||
| updateUserInfo: (newUserData: UserData) => {}, | |||
| }); | |||
| export const useUserData = () => { | |||
| @@ -25,12 +40,12 @@ const useUser = () => { | |||
| const USER_KEY = 'user-data'; | |||
| const [userStorage, setUserStorage] = useState(getStorage(USER_KEY)); | |||
| const addUser = (userData) => { | |||
| const addUser = (userData: UserData) => { | |||
| setStorage(USER_KEY, userData); | |||
| setUserStorage(userData); | |||
| }; | |||
| const updateUserInfo = (newUserData) => { | |||
| const updateUserInfo = (newUserData: UserData) => { | |||
| setStorage(USER_KEY, newUserData); | |||
| setUserStorage(newUserData); | |||
| }; | |||
| @@ -42,22 +57,19 @@ const useUser = () => { | |||
| return { | |||
| userStorage, | |||
| setUserStorage, | |||
| addUser, | |||
| updateUserInfo, | |||
| clearUser, | |||
| }; | |||
| }; | |||
| const UserProvider = ({ children }) => { | |||
| const { userStorage, setUserStorage, addUser, updateUserInfo, clearUser } = | |||
| useUser(); | |||
| const UserProvider: FC<Props> = ({ children }) => { | |||
| const { userStorage, addUser, updateUserInfo, clearUser } = useUser(); | |||
| return ( | |||
| <UserContext.Provider value={{ userStorage }}> | |||
| <UserDispatchContext.Provider | |||
| value={{ | |||
| setUserStorage, | |||
| addUser, | |||
| updateUserInfo, | |||
| clearUser, | |||
| @@ -15,6 +15,6 @@ | |||
| "jsx": "preserve", | |||
| "incremental": true | |||
| }, | |||
| "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], | |||
| "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types/**/*.ts"], | |||
| "exclude": ["node_modules"] | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| import NextAuth from 'next-auth'; | |||
| import { UserData } from '../utils/interface/userInterface'; | |||
| declare module 'next-auth' { | |||
| /** | |||
| * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context | |||
| */ | |||
| interface Session { | |||
| user: { | |||
| address: string; | |||
| address2: string; | |||
| city: string; | |||
| country: string; | |||
| fullName: string; | |||
| postcode: string; | |||
| email: string; | |||
| _id: string; | |||
| }; | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| export const setStorage = (key: string, value: string) => { | |||
| export const setStorage = (key: string, value: object) => { | |||
| window.localStorage.setItem(key, JSON.stringify(value)); | |||
| }; | |||
| @@ -19,7 +19,7 @@ export const removeStorage = (key: string) => { | |||
| window.localStorage.removeItem(key); | |||
| }; | |||
| export const setSStorage = (key: string, value: string) => { | |||
| export const setSStorage = (key: string, value: object) => { | |||
| window.sessionStorage.setItem(key, JSON.stringify(value)); | |||
| }; | |||
| @@ -2,18 +2,7 @@ import { ProductData } from './productInterface'; | |||
| import { UserData } from './userInterface'; | |||
| import { ObjectId } from 'mongodb'; | |||
| export interface OrderCard { | |||
| date: Date; | |||
| name: string; | |||
| totalPrice: number; | |||
| } | |||
| export interface OrderSummary { | |||
| totalPrice: number; | |||
| totalQuantity: number; | |||
| } | |||
| interface ShippingData extends UserData { | |||
| export interface ShippingData extends UserData { | |||
| email: string; | |||
| } | |||
| @@ -336,7 +336,14 @@ | |||
| core-js-pure "^3.25.1" | |||
| regenerator-runtime "^0.13.4" | |||
| "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": | |||
| "@babel/runtime@^7.10.2", "@babel/runtime@^7.18.9": | |||
| version "7.19.0" | |||
| resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" | |||
| integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== | |||
| dependencies: | |||
| regenerator-runtime "^0.13.4" | |||
| "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.19.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": | |||
| version "7.19.4" | |||
| resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" | |||
| integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== | |||
| @@ -801,9 +808,9 @@ | |||
| "@sendgrid/helpers" "^7.7.0" | |||
| "@stripe/stripe-js@^1.39.0": | |||
| version "1.41.0" | |||
| resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.41.0.tgz#986f3f222ba4466301f7809934afafafde9b28fd" | |||
| integrity sha512-9cbv1CO/fF37qDiHFCxkRgSJjlIZLV0bl+m6zu4dUObRnfYq5bpczCByOvhiSWtbrKqNhYL1j+otPSogRfSqGw== | |||
| version "1.39.0" | |||
| resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.39.0.tgz#b70d4d276862771c4b2c86558795358e08f0b77e" | |||
| integrity sha512-BR7yzewVSBQgRao3V4HsTldpO4HpJUKcIlMDmBQaHqXUvkMpXHgOMF8/CZgFVMACS+pqvZIMmGuy3YvZlLEA2w== | |||
| "@swc/[email protected]": | |||
| version "0.4.11" | |||
| @@ -881,6 +888,13 @@ | |||
| resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.3.tgz#ce750ab4017effa51aed6a7230651778d54e327c" | |||
| integrity sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w== | |||
| "@types/nookies@^2.0.3": | |||
| version "2.0.3" | |||
| resolved "https://registry.yarnpkg.com/@types/nookies/-/nookies-2.0.3.tgz#2173e8977a9163defc37fdcc3140a834f21373d1" | |||
| integrity sha512-+PO/CKwVx7gDG5Pjr+UU/hL6/vFm1ppgW8cilV4AreLEpjbz0wiFvZ5Nld1VIRTr/RiB/KFmiyOzr8GboJfpqg== | |||
| dependencies: | |||
| nookies "*" | |||
| "@types/parse-json@^4.0.0": | |||
| version "4.0.0" | |||
| resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" | |||
| @@ -1992,6 +2006,11 @@ follow-redirects@^1.14.8: | |||
| resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" | |||
| integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== | |||
| follow-redirects@^1.14.8: | |||
| version "1.15.2" | |||
| resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" | |||
| integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== | |||
| for-in@^1.0.2: | |||
| version "1.0.2" | |||
| resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" | |||
| @@ -2962,7 +2981,7 @@ node-releases@^2.0.6: | |||
| resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" | |||
| integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== | |||
| nookies@^2.5.2: | |||
| nookies@*, nookies@^2.5.2: | |||
| version "2.5.2" | |||
| resolved "https://registry.yarnpkg.com/nookies/-/nookies-2.5.2.tgz#cc55547efa982d013a21475bd0db0c02c1b35b27" | |||
| integrity sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA== | |||