| @@ -1,7 +1,15 @@ | |||
| import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material'; | |||
| import Image from 'next/image'; | |||
| import PropType from 'prop-types'; | |||
| import { useState } from 'react'; | |||
| const CartCard = ({ product, initialQuantity, remove, updateQuantity }) => { | |||
| const [quantity, setQuantity] = useState(initialQuantity); | |||
| // useEffect(() => { | |||
| // updateQuantity(product?.customID, quantity); | |||
| // }, [quantity]); | |||
| const CartCard = () => { | |||
| return ( | |||
| <Paper | |||
| sx={{ | |||
| @@ -40,7 +48,7 @@ const CartCard = () => { | |||
| fontSize: 20, | |||
| }} | |||
| > | |||
| Begin Mug in White | |||
| {product?.name} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| @@ -48,6 +56,7 @@ const CartCard = () => { | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| width: '20%', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| @@ -79,7 +88,12 @@ const CartCard = () => { | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| onClick={() => {}} | |||
| onClick={() => { | |||
| if (quantity > 0) { | |||
| updateQuantity(product?.customID, quantity - 1); | |||
| setQuantity((prevState) => prevState - 1); | |||
| } | |||
| }} | |||
| > | |||
| - | |||
| </Button> | |||
| @@ -90,7 +104,7 @@ const CartCard = () => { | |||
| width: 25, | |||
| }} | |||
| > | |||
| 1 | |||
| {quantity} | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| @@ -98,7 +112,10 @@ const CartCard = () => { | |||
| fontSize: 17, | |||
| width: 25, | |||
| }} | |||
| onClick={() => {}} | |||
| onClick={() => { | |||
| updateQuantity(product?.customID, quantity + 1); | |||
| setQuantity((prevState) => prevState + 1); | |||
| }} | |||
| > | |||
| + | |||
| </Button> | |||
| @@ -116,26 +133,53 @@ const CartCard = () => { | |||
| startIcon={ | |||
| <Image src="/images/x.svg" alt="remove" width={15} height={15} /> | |||
| } | |||
| onClick={() => remove(product.customID)} | |||
| > | |||
| Remove | |||
| </Button> | |||
| </Box> | |||
| <Box | |||
| sx={{ ml: 3, display: 'flex', flexDirection: 'column', width: '20%' }} | |||
| sx={{ | |||
| ml: 3, | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| width: '20%', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| <Typography | |||
| sx={{ | |||
| width: '100%', | |||
| textAlign: 'center', | |||
| height: 25, | |||
| fontSize: 20, | |||
| fontSize: 18, | |||
| }} | |||
| > | |||
| Total: $20 | |||
| Price: ${product?.price} | |||
| </Typography> | |||
| </Box> | |||
| </Paper> | |||
| ); | |||
| }; | |||
| CartCard.propTypes = { | |||
| product: PropType.shape({ | |||
| category: PropType.string, | |||
| name: PropType.string, | |||
| image: PropType.string, | |||
| description: PropType.string, | |||
| place: PropType.string, | |||
| people: PropType.string, | |||
| process: PropType.string, | |||
| pairing: PropType.string, | |||
| available: PropType.Boolean, | |||
| isFeatured: PropType.Boolean, | |||
| price: PropType.number, | |||
| customID: PropType.string, | |||
| }), | |||
| initialQuantity: PropType.number, | |||
| remove: PropType.func, | |||
| updateQuantity: PropType.func, | |||
| }; | |||
| export default CartCard; | |||
| @@ -1,9 +1,40 @@ | |||
| import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import { useStore, useStoreUpdate } from '../../store/cart-context'; | |||
| import CartCard from '../cards/cart-card/CartCard'; | |||
| import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard'; | |||
| const CartContent = () => { | |||
| const { cartStorage, totalPrice } = useStore(); | |||
| const { removeCartValue, updateItemQuantity } = useStoreUpdate(); | |||
| const mapProductsToDom = () => { | |||
| if (cartStorage?.length) { | |||
| return cartStorage.map((element, i) => ( | |||
| <CartCard | |||
| key={i} | |||
| product={element?.product} | |||
| initialQuantity={element?.quantity} | |||
| remove={removeCartValue} | |||
| updateQuantity={updateItemQuantity} | |||
| ></CartCard> | |||
| )); | |||
| } else { | |||
| return ( | |||
| <Typography | |||
| sx={{ | |||
| pl: 12, | |||
| mt: 6, | |||
| height: '100%', | |||
| textAlign: 'center', | |||
| fontSize: 45, | |||
| }} | |||
| > | |||
| Your cart is currently empty | |||
| </Typography> | |||
| ); | |||
| } | |||
| }; | |||
| return ( | |||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | |||
| <Grid item xs={12}> | |||
| @@ -28,13 +59,13 @@ const CartContent = () => { | |||
| </Breadcrumbs> | |||
| </Grid> | |||
| <Grid item xs={8}> | |||
| <CartCard></CartCard> | |||
| <CartCard></CartCard> | |||
| <CartCard></CartCard> | |||
| {mapProductsToDom()} | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <Box sx={{ width: '80%', mt: 2 }}> | |||
| <OrderSummaryCard data={{ totalPrice: 60 }}></OrderSummaryCard> | |||
| <OrderSummaryCard | |||
| data={{ totalPrice: totalPrice }} | |||
| ></OrderSummaryCard> | |||
| </Box> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -14,7 +14,7 @@ import Link from 'next/link'; | |||
| import { useState } from 'react'; | |||
| import { FORGOT_PASSWORD_PAGE, LOGIN_PAGE } from '../../../constants/pages'; | |||
| import { createUser } from '../../../requests/accountRequests'; | |||
| import { createUser } from '../../../requests/accounts/accountRequests'; | |||
| import { registerSchema } from '../../../schemas/registerSchema'; | |||
| import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | |||
| @@ -39,7 +39,12 @@ const RegisterForm = () => { | |||
| values.fullName, | |||
| values.username, | |||
| values.email, | |||
| values.password | |||
| values.password, | |||
| values.address, | |||
| values.address2, | |||
| values.city, | |||
| values.country, | |||
| values.postcode | |||
| ); | |||
| console.log(result); | |||
| } catch (error) { | |||
| @@ -54,6 +59,11 @@ const RegisterForm = () => { | |||
| email: '', | |||
| password: '', | |||
| confirmPassword: '', | |||
| address: '', | |||
| address2: '', | |||
| city: '', | |||
| country: '', | |||
| postcode: '', | |||
| }, | |||
| validationSchema: registerSchema, | |||
| onSubmit: submitHandler, | |||
| @@ -158,6 +168,56 @@ const RegisterForm = () => { | |||
| ), | |||
| }} | |||
| /> | |||
| <TextField | |||
| name="address" | |||
| label="Address" | |||
| margin="normal" | |||
| value={formik.values.address} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.address && Boolean(formik.errors.address)} | |||
| helperText={formik.touched.address && formik.errors.address} | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="address" | |||
| label="Address2" | |||
| margin="normal" | |||
| value={formik.values.address2} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.address2 && Boolean(formik.errors.address2)} | |||
| helperText={formik.touched.address2 && formik.errors.address2} | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="city" | |||
| label="City" | |||
| margin="normal" | |||
| value={formik.values.city} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.city && Boolean(formik.errors.city)} | |||
| helperText={formik.touched.city && formik.errors.city} | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="country" | |||
| label="Country" | |||
| margin="normal" | |||
| value={formik.values.country} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.country && Boolean(formik.errors.country)} | |||
| helperText={formik.touched.country && formik.errors.country} | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="postcode" | |||
| label="Postal Code" | |||
| margin="normal" | |||
| value={formik.values.postcode} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.postcode && Boolean(formik.errors.postcode)} | |||
| helperText={formik.touched.postcode && formik.errors.postcode} | |||
| fullWidth | |||
| /> | |||
| <Button | |||
| type="submit" | |||
| variant="contained" | |||
| @@ -1,5 +1,6 @@ | |||
| import { Box, Button, Paper, TextField } from '@mui/material'; | |||
| import { useFormik } from 'formik'; | |||
| import { useSession } from 'next-auth/react'; | |||
| import PropType from 'prop-types'; | |||
| import { useState } from 'react'; | |||
| import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | |||
| @@ -7,20 +8,19 @@ import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | |||
| const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| const [error] = useState({ hasError: false, errorMessage: '' }); | |||
| const { data: session } = useSession(); | |||
| const submitHandler = async (values) => { | |||
| console.log(values); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| fullName: '', | |||
| email: '', | |||
| address: '', | |||
| address2: '', | |||
| city: '', | |||
| country: '', | |||
| poostalCode: '', | |||
| fullName: session.user.fullName, | |||
| address: session.user.address, | |||
| address2: session.user.address2, | |||
| city: session.user.city, | |||
| country: session.user.country, | |||
| postcode: session.user.postcode, | |||
| }, | |||
| validationSchema: shippingDetailsSchema, | |||
| onSubmit: submitHandler, | |||
| @@ -46,17 +46,6 @@ const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| onSubmit={formik.handleSubmit} | |||
| sx={{ position: 'relative', mt: 1, p: 1 }} | |||
| > | |||
| <TextField | |||
| name="email" | |||
| label="Email" | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="fullName" | |||
| label="Name" | |||
| @@ -110,13 +99,13 @@ const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| sx={{ mr: 1.5 }} | |||
| /> | |||
| <TextField | |||
| name="city" | |||
| label="City" | |||
| name="postcode" | |||
| label="Postal Code" | |||
| margin="normal" | |||
| value={formik.values.city} | |||
| value={formik.values.postcode} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.city && Boolean(formik.errors.city)} | |||
| helperText={formik.touched.city && formik.errors.city} | |||
| error={formik.touched.postcode && Boolean(formik.errors.postcode)} | |||
| helperText={formik.touched.postcode && formik.errors.postcode} | |||
| fullWidth | |||
| /> | |||
| </Box> | |||
| @@ -10,10 +10,11 @@ import { | |||
| PRODUCTS_PAGE, | |||
| PROFILE_PAGE, | |||
| } from '../../../constants/pages'; | |||
| import { useStore } from '../../../store/cart-context'; | |||
| const Navbar = () => { | |||
| const router = useRouter(); | |||
| const { totalQuantity } = useStore(); | |||
| return ( | |||
| <AppBar | |||
| position="absolute" | |||
| @@ -150,7 +151,32 @@ const Navbar = () => { | |||
| }} | |||
| > | |||
| <Link key="home" href={CART_PAGE}> | |||
| <Image src="/images/cart.svg" alt="cart" width={24} height={24} /> | |||
| <Box> | |||
| <Box | |||
| sx={{ | |||
| color: 'white', | |||
| zIndex: 3, | |||
| width: 20, | |||
| height: 20, | |||
| borderRadius: 20, | |||
| textAlign: 'center', | |||
| px: 0.5, | |||
| ml: 2.2, | |||
| mt: -1, | |||
| fontSize: 16, | |||
| position: 'absolute', | |||
| backgroundColor: 'primary.main', | |||
| }} | |||
| > | |||
| {totalQuantity} | |||
| </Box> | |||
| <Image | |||
| src="/images/cart.svg" | |||
| alt="cart" | |||
| width={24} | |||
| height={24} | |||
| /> | |||
| </Box> | |||
| </Link> | |||
| , | |||
| </Box> | |||
| @@ -1,10 +1,20 @@ | |||
| import { Box } from '@mui/system'; | |||
| import PropType from 'prop-types'; | |||
| import { useStore, useStoreUpdate } from '../../../store/cart-context'; | |||
| import ProductImage from './ProductImage'; | |||
| import ProductInfo from './ProductInfo'; | |||
| const FeaturedProduct = ({ product, bColor, image, side }) => { | |||
| const data = { name: product.name, description: product.description }; | |||
| const { addCartValue } = useStoreUpdate(); | |||
| const { cartStorage } = useStore(); | |||
| const addProductToCart = (quantity) => addCartValue(product, quantity); | |||
| const inCart = cartStorage?.some( | |||
| (item) => item.product.customID === product.customID | |||
| ) | |||
| ? true | |||
| : false; | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| @@ -18,10 +28,22 @@ const FeaturedProduct = ({ product, bColor, image, side }) => { | |||
| {side === 'left' ? ( | |||
| <ProductImage image={image}></ProductImage> | |||
| ) : ( | |||
| <ProductInfo bColor={bColor} side={side} data={data}></ProductInfo> | |||
| <ProductInfo | |||
| bColor={bColor} | |||
| side={side} | |||
| data={data} | |||
| addProductToCart={addProductToCart} | |||
| inCart={inCart} | |||
| ></ProductInfo> | |||
| )} | |||
| {side === 'left' ? ( | |||
| <ProductInfo bColor={bColor} side={side} data={data}></ProductInfo> | |||
| <ProductInfo | |||
| bColor={bColor} | |||
| side={side} | |||
| data={data} | |||
| addProductToCart={addProductToCart} | |||
| inCart={inCart} | |||
| ></ProductInfo> | |||
| ) : ( | |||
| <ProductImage image={image}></ProductImage> | |||
| )} | |||
| @@ -2,8 +2,11 @@ import { Button, ButtonGroup, Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import PropType from 'prop-types'; | |||
| import { useState } from 'react'; | |||
| const ProductInfo = ({ data, bColor, side, addProductToCart, inCart }) => { | |||
| const [quantity, setQuantity] = useState(1); | |||
| const ProductInfo = ({ data, bColor, side }) => { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| @@ -64,7 +67,9 @@ const ProductInfo = ({ data, bColor, side }) => { | |||
| fontSize: 20, | |||
| width: 50, | |||
| }} | |||
| onClick={() => {}} | |||
| onClick={() => { | |||
| setQuantity((prevState) => prevState - 1); | |||
| }} | |||
| > | |||
| - | |||
| </Button> | |||
| @@ -75,7 +80,7 @@ const ProductInfo = ({ data, bColor, side }) => { | |||
| width: 50, | |||
| }} | |||
| > | |||
| 1 | |||
| {quantity} | |||
| </Button> | |||
| <Button | |||
| sx={{ | |||
| @@ -83,7 +88,9 @@ const ProductInfo = ({ data, bColor, side }) => { | |||
| fontSize: 20, | |||
| width: 50, | |||
| }} | |||
| onClick={() => {}} | |||
| onClick={() => { | |||
| setQuantity((prevState) => prevState + 1); | |||
| }} | |||
| > | |||
| + | |||
| </Button> | |||
| @@ -95,8 +102,10 @@ const ProductInfo = ({ data, bColor, side }) => { | |||
| width: 150, | |||
| color: 'white', | |||
| }} | |||
| disabled={inCart} | |||
| onClick={() => addProductToCart(quantity)} | |||
| > | |||
| Add to cart | |||
| {inCart ? 'In Cart' : 'Add to cart'} | |||
| </Button> | |||
| </Box> | |||
| </Box> | |||
| @@ -110,5 +119,7 @@ ProductInfo.propTypes = { | |||
| }), | |||
| bColor: PropType.string, | |||
| side: PropType.string, | |||
| addProductToCart: PropType.func, | |||
| inCart: PropType.Boolean | PropType.undefined, | |||
| }; | |||
| export default ProductInfo; | |||
| @@ -0,0 +1,24 @@ | |||
| import { useState } from 'react'; | |||
| import { getStorage } from '../utils/helpers/storage'; | |||
| const useCalculateTotal = () => { | |||
| const CART_KEY = 'cart-products'; | |||
| const [total, setTotal] = useState(() => { | |||
| const cart = getStorage(CART_KEY); | |||
| if (cart && cart.length) { | |||
| return cart | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| } else { | |||
| return 0; | |||
| } | |||
| }); | |||
| return { | |||
| total, | |||
| }; | |||
| }; | |||
| export default useCalculateTotal; | |||
| @@ -58,26 +58,13 @@ const UserSchema = new mongoose.Schema({ | |||
| required: [true, 'Please provide an address.'], | |||
| trim: true, | |||
| }, | |||
| phone: { | |||
| address2: { | |||
| type: String, | |||
| unique: [true, 'Phone number must be unique.'], | |||
| required: [true, 'Please provide a phone number.'], | |||
| trim: true, | |||
| lowercase: true, | |||
| validate(value) { | |||
| if (!validator.isMobilePhone(value)) { | |||
| throw new Error('Not a valid phone number'); | |||
| } | |||
| }, | |||
| }, | |||
| postcode: { | |||
| type: String, | |||
| required: [true, 'Please provide a postal code.'], | |||
| validate(value) { | |||
| if (!validator.isPostalCode(value)) { | |||
| throw new Error('Not a valid postal code'); | |||
| } | |||
| }, | |||
| }, | |||
| }); | |||
| @@ -100,7 +87,16 @@ UserSchema.statics.findByCredentials = async (username, password) => { | |||
| throw new Error('Unable to login'); | |||
| } | |||
| return user; | |||
| const userData = { | |||
| fullName: user.fullName, | |||
| email: user.email, | |||
| address: user.address, | |||
| address2: user.address2, | |||
| city: user.city, | |||
| country: user.country, | |||
| postcode: user.postcode, | |||
| }; | |||
| return userData; | |||
| }; | |||
| UserSchema.pre('save', async function (next) { | |||
| @@ -10,6 +10,7 @@ import Head from 'next/head'; | |||
| import { useState } from 'react'; | |||
| import Layout from '../components/layout/base-layout/Layout'; | |||
| import CircularIndeterminate from '../components/loader/route-loader/CircularIndeterminate'; | |||
| import StorageProvider from '../store/cart-context'; | |||
| import '../styles/globals.css'; | |||
| import theme from '../styles/muiTheme'; | |||
| @@ -21,18 +22,20 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { | |||
| <Hydrate state={pageProps.dehydratedState}> | |||
| <SessionProvider session={session}> | |||
| <ThemeProvider theme={theme}> | |||
| <Layout> | |||
| <Head> | |||
| <title>Coffee Shop</title> | |||
| <meta name="description" content="NextJS template" /> | |||
| <meta | |||
| name="viewport" | |||
| content="width=device-width, initial-scale=1" | |||
| /> | |||
| </Head> | |||
| <CircularIndeterminate /> | |||
| <Component {...pageProps} /> | |||
| </Layout> | |||
| <StorageProvider> | |||
| <Layout> | |||
| <Head> | |||
| <title>Coffee Shop</title> | |||
| <meta name="description" content="NextJS template" /> | |||
| <meta | |||
| name="viewport" | |||
| content="width=device-width, initial-scale=1" | |||
| /> | |||
| </Head> | |||
| <CircularIndeterminate /> | |||
| <Component {...pageProps} /> | |||
| </Layout> | |||
| </StorageProvider> | |||
| </ThemeProvider> | |||
| </SessionProvider> | |||
| </Hydrate> | |||
| @@ -7,16 +7,24 @@ export default NextAuth({ | |||
| session: { | |||
| jwt: true, | |||
| }, | |||
| callbacks: { | |||
| async jwt({ token, user }) { | |||
| return { ...token, ...user }; | |||
| }, | |||
| async session({ session, user, token }) { | |||
| return token; | |||
| }, | |||
| }, | |||
| providers: [ | |||
| Credentials({ | |||
| async authorize(credentials) { | |||
| await dbConnect(); | |||
| const user = await User.findByCredentials( | |||
| const userData = await User.findByCredentials( | |||
| credentials.username, | |||
| credentials.password | |||
| ); | |||
| return { name: user.fullName }; | |||
| return { user: userData }; | |||
| }, | |||
| }), | |||
| ], | |||
| @@ -9,6 +9,7 @@ async function handler(req, res) { | |||
| switch (method) { | |||
| case 'POST': { | |||
| try { | |||
| console.log(req.body); | |||
| const user = await User.create(req.body); | |||
| res | |||
| .status(201) | |||
| @@ -1,27 +1,41 @@ | |||
| import { useSession } from 'next-auth/react'; | |||
| import { Button } from '@mui/material'; | |||
| import { getSession, signOut, useSession } from 'next-auth/react'; | |||
| import ProfileContent from '../../components/profile-content/ProfileContent'; | |||
| import { LOGIN_PAGE } from '../../constants/pages'; | |||
| const ProfilePage = () => { | |||
| const { data: session } = useSession(); | |||
| return <ProfileContent></ProfileContent>; | |||
| console.log(session); | |||
| function logoutHandler() { | |||
| signOut(); | |||
| } | |||
| return ( | |||
| <> | |||
| <ProfileContent></ProfileContent> | |||
| <Button color="inherit" onClick={logoutHandler}> | |||
| Logout | |||
| </Button> | |||
| </> | |||
| ); | |||
| }; | |||
| // export async function getServerSideProps(context) { | |||
| // const session = await getSession({ req: context.req }); | |||
| export async function getServerSideProps(context) { | |||
| const session = await getSession({ req: context.req }); | |||
| // if (!session) { | |||
| // return { | |||
| // redirect: { | |||
| // destination: LOGIN_PAGE, | |||
| // permanent: false, | |||
| // }, | |||
| // }; | |||
| // } | |||
| if (!session) { | |||
| return { | |||
| redirect: { | |||
| destination: LOGIN_PAGE, | |||
| permanent: false, | |||
| }, | |||
| }; | |||
| } | |||
| // return { | |||
| // props: { session }, | |||
| // }; | |||
| // } | |||
| return { | |||
| props: { session }, | |||
| }; | |||
| } | |||
| export default ProfilePage; | |||
| @@ -1,9 +1,29 @@ | |||
| import apiEndpoints from '../apiEndpoints'; | |||
| export const createUser = async (fullName, username, email, password) => { | |||
| export const createUser = async ( | |||
| fullName, | |||
| username, | |||
| email, | |||
| password, | |||
| address, | |||
| address2, | |||
| city, | |||
| country, | |||
| postcode | |||
| ) => { | |||
| const response = await fetch(apiEndpoints.account.createUser, { | |||
| method: 'POST', | |||
| body: JSON.stringify({ fullName, username, email, password }), | |||
| body: JSON.stringify({ | |||
| fullName, | |||
| username, | |||
| email, | |||
| password, | |||
| address, | |||
| address2, | |||
| city, | |||
| country, | |||
| postcode, | |||
| }), | |||
| headers: { | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| @@ -1,12 +1,17 @@ | |||
| import * as Yup from "yup"; | |||
| import * as Yup from 'yup'; | |||
| export const registerSchema = Yup.object().shape({ | |||
| fullName: Yup.string().required("Full name is required"), | |||
| username: Yup.string().required("Username is required"), | |||
| email: Yup.string().email().required("Email is required"), | |||
| password: Yup.string().required("Password is required"), | |||
| fullName: Yup.string().required('Full name is required'), | |||
| username: Yup.string().required('Username is required'), | |||
| email: Yup.string().email().required('Email is required'), | |||
| password: Yup.string().required('Password is required'), | |||
| confirmPassword: Yup.string().oneOf( | |||
| [Yup.ref("password"), null], | |||
| "Passwords must match" | |||
| [Yup.ref('password'), null], | |||
| 'Passwords must match' | |||
| ), | |||
| address: Yup.string().required('Address is required'), | |||
| address2: Yup.string(), | |||
| city: Yup.string().required('City is required'), | |||
| country: Yup.string().required('Country is required'), | |||
| postcode: Yup.string().required('Postal code is required'), | |||
| }); | |||
| @@ -2,10 +2,9 @@ import * as Yup from 'yup'; | |||
| export const registerSchema = Yup.object().shape({ | |||
| fullName: Yup.string().required('Full name is required'), | |||
| email: Yup.string().email().required('Email is required'), | |||
| address: Yup.string().required('Address is required'), | |||
| address2: Yup.string(), | |||
| city: Yup.string().required('City is required'), | |||
| country: Yup.string().required('Country name is required'), | |||
| postalCode: Yup.string().required('Postal code name is required'), | |||
| postcode: Yup.string().required('Postal code name is required'), | |||
| }); | |||
| @@ -0,0 +1,168 @@ | |||
| import { createContext, useContext, useState } from 'react'; | |||
| import { getStorage, setStorage } from '../utils/helpers/storage'; | |||
| const StorageContext = createContext({ | |||
| cartStorage: [], | |||
| totalPrice: 0, | |||
| totalQuantity: 0, | |||
| }); | |||
| const StorageDispatchContext = createContext({ | |||
| addCartValue: (product, quantity) => {}, | |||
| clearCart: () => {}, | |||
| removeCartValue: (productId) => {}, | |||
| setCartStorage: (cart) => {}, | |||
| updateItemQuantity: (productId, quantity) => {}, | |||
| }); | |||
| export const useStore = () => { | |||
| return useContext(StorageContext); | |||
| }; | |||
| export const useStoreUpdate = () => { | |||
| return useContext(StorageDispatchContext); | |||
| }; | |||
| const useStorage = () => { | |||
| const CART_KEY = 'cart-products'; | |||
| const [cartStorage, setCartStorage] = useState(getStorage(CART_KEY)); | |||
| const [totalPrice, setTotalPrice] = useState(() => { | |||
| const cart = getStorage(CART_KEY); | |||
| if (cart && cart.length) { | |||
| return cart | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| } else { | |||
| return 0; | |||
| } | |||
| }); | |||
| const [totalQuantity, setTotalQuantity] = useState(() => { | |||
| const cart = getStorage(CART_KEY); | |||
| if (cart && cart.length) { | |||
| return cart.length; | |||
| } else { | |||
| return 0; | |||
| } | |||
| }); | |||
| const addCartValue = (product, quantity) => { | |||
| const items = getStorage(CART_KEY); | |||
| if (!items) { | |||
| setStorage(CART_KEY, [{ product, quantity }]); | |||
| } else { | |||
| const isItemDuplicate = items.some( | |||
| (item) => item.product.customID === product.customID | |||
| ); | |||
| if (!isItemDuplicate) { | |||
| items.push({ product, quantity }); | |||
| setTotalQuantity((prevState) => prevState + 1); | |||
| setStorage(CART_KEY, items); | |||
| } else { | |||
| return; | |||
| } | |||
| } | |||
| const newTotalPrice = items | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| setTotalPrice(newTotalPrice); | |||
| setCartStorage(items); | |||
| }; | |||
| const updateItemQuantity = (productId, quantity) => { | |||
| if (quantity < 0) return; | |||
| const items = getStorage(CART_KEY); | |||
| let updatedItems = items; | |||
| if (items) { | |||
| updatedItems = items.map((entry) => { | |||
| if (entry?.product.customID === productId) { | |||
| console.log('true'); | |||
| entry.quantity = quantity; | |||
| } | |||
| return entry; | |||
| }); | |||
| setStorage(CART_KEY, updatedItems); | |||
| } | |||
| const newTotalPrice = updatedItems | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| setTotalPrice(newTotalPrice); | |||
| setCartStorage(updatedItems); | |||
| }; | |||
| const clearCart = () => { | |||
| setStorage(CART_KEY, []); | |||
| setCartStorage([]); | |||
| }; | |||
| const removeCartValue = (productId) => { | |||
| const items = getStorage(CART_KEY); | |||
| const newStorage = items?.filter( | |||
| (item) => item.product.customID !== productId | |||
| ); | |||
| if (newStorage.length === 0) { | |||
| setTotalPrice(0); | |||
| } else { | |||
| const newTotalPrice = newStorage | |||
| .map((entry) => entry?.product.price * entry?.quantity) | |||
| .reduce((accum, curValue) => accum + curValue); | |||
| setTotalPrice(newTotalPrice); | |||
| } | |||
| setTotalQuantity((prevState) => prevState - 1); | |||
| setStorage(CART_KEY, newStorage); | |||
| setCartStorage(newStorage); | |||
| }; | |||
| return { | |||
| addCartValue, | |||
| cartStorage, | |||
| totalPrice, | |||
| totalQuantity, | |||
| clearCart, | |||
| removeCartValue, | |||
| setCartStorage, | |||
| updateItemQuantity, | |||
| }; | |||
| }; | |||
| const StorageProvider = ({ children }) => { | |||
| const { | |||
| cartStorage, | |||
| totalPrice, | |||
| totalQuantity, | |||
| addCartValue, | |||
| clearCart, | |||
| setCartStorage, | |||
| removeCartValue, | |||
| updateItemQuantity, | |||
| } = useStorage(); | |||
| return ( | |||
| <StorageContext.Provider value={{ cartStorage, totalPrice, totalQuantity }}> | |||
| <StorageDispatchContext.Provider | |||
| value={{ | |||
| addCartValue, | |||
| clearCart, | |||
| removeCartValue, | |||
| setCartStorage, | |||
| updateItemQuantity, | |||
| }} | |||
| > | |||
| {children} | |||
| </StorageDispatchContext.Provider> | |||
| </StorageContext.Provider> | |||
| ); | |||
| }; | |||
| export default StorageProvider; | |||
| @@ -0,0 +1,20 @@ | |||
| export const setStorage = (key, value) => { | |||
| window.localStorage.setItem(key, JSON.stringify(value)); | |||
| }; | |||
| export const getStorage = (key) => { | |||
| if (typeof window === 'undefined') { | |||
| return null; | |||
| } | |||
| const storedItems = window.localStorage.getItem(key); | |||
| return storedItems ? JSON.parse(storedItems) : []; | |||
| }; | |||
| export const removeStorage = (key) => { | |||
| if (typeof window === 'undefined') { | |||
| return null; | |||
| } | |||
| window.localStorage.removeItem(key); | |||
| }; | |||