| import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material'; | import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material'; | ||||
| import Image from 'next/image'; | 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 ( | return ( | ||||
| <Paper | <Paper | ||||
| sx={{ | sx={{ | ||||
| fontSize: 20, | fontSize: 20, | ||||
| }} | }} | ||||
| > | > | ||||
| Begin Mug in White | |||||
| {product?.name} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| <Box | <Box | ||||
| display: 'flex', | display: 'flex', | ||||
| flexDirection: 'column', | flexDirection: 'column', | ||||
| width: '20%', | width: '20%', | ||||
| justifyContent: 'center', | |||||
| alignItems: 'center', | alignItems: 'center', | ||||
| }} | }} | ||||
| > | > | ||||
| fontSize: 17, | fontSize: 17, | ||||
| width: 25, | width: 25, | ||||
| }} | }} | ||||
| onClick={() => {}} | |||||
| onClick={() => { | |||||
| if (quantity > 0) { | |||||
| updateQuantity(product?.customID, quantity - 1); | |||||
| setQuantity((prevState) => prevState - 1); | |||||
| } | |||||
| }} | |||||
| > | > | ||||
| - | - | ||||
| </Button> | </Button> | ||||
| width: 25, | width: 25, | ||||
| }} | }} | ||||
| > | > | ||||
| 1 | |||||
| {quantity} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| sx={{ | sx={{ | ||||
| fontSize: 17, | fontSize: 17, | ||||
| width: 25, | width: 25, | ||||
| }} | }} | ||||
| onClick={() => {}} | |||||
| onClick={() => { | |||||
| updateQuantity(product?.customID, quantity + 1); | |||||
| setQuantity((prevState) => prevState + 1); | |||||
| }} | |||||
| > | > | ||||
| + | + | ||||
| </Button> | </Button> | ||||
| startIcon={ | startIcon={ | ||||
| <Image src="/images/x.svg" alt="remove" width={15} height={15} /> | <Image src="/images/x.svg" alt="remove" width={15} height={15} /> | ||||
| } | } | ||||
| onClick={() => remove(product.customID)} | |||||
| > | > | ||||
| Remove | Remove | ||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| <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 | <Typography | ||||
| sx={{ | sx={{ | ||||
| width: '100%', | width: '100%', | ||||
| textAlign: 'center', | textAlign: 'center', | ||||
| height: 25, | height: 25, | ||||
| fontSize: 20, | |||||
| fontSize: 18, | |||||
| }} | }} | ||||
| > | > | ||||
| Total: $20 | |||||
| Price: ${product?.price} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| </Paper> | </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; | export default CartCard; |
| import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material'; | import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material'; | ||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import { useStore, useStoreUpdate } from '../../store/cart-context'; | |||||
| import CartCard from '../cards/cart-card/CartCard'; | import CartCard from '../cards/cart-card/CartCard'; | ||||
| import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard'; | import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard'; | ||||
| const CartContent = () => { | 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 ( | return ( | ||||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| </Breadcrumbs> | </Breadcrumbs> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={8}> | <Grid item xs={8}> | ||||
| <CartCard></CartCard> | |||||
| <CartCard></CartCard> | |||||
| <CartCard></CartCard> | |||||
| {mapProductsToDom()} | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <Box sx={{ width: '80%', mt: 2 }}> | <Box sx={{ width: '80%', mt: 2 }}> | ||||
| <OrderSummaryCard data={{ totalPrice: 60 }}></OrderSummaryCard> | |||||
| <OrderSummaryCard | |||||
| data={{ totalPrice: totalPrice }} | |||||
| ></OrderSummaryCard> | |||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> |
| import DataCard from '../cards/data-card/DataCard'; | import DataCard from '../cards/data-card/DataCard'; | ||||
| import ShippingDetailsForm from '../forms/shipping-details/ShippingDetailsForm'; | import ShippingDetailsForm from '../forms/shipping-details/ShippingDetailsForm'; | ||||
| const ProfileContent = () => { | |||||
| const CheckoutContent = () => { | |||||
| return ( | return ( | ||||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default ProfileContent; | |||||
| export default CheckoutContent; |
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import { FORGOT_PASSWORD_PAGE, LOGIN_PAGE } from '../../../constants/pages'; | 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 { registerSchema } from '../../../schemas/registerSchema'; | ||||
| import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | ||||
| values.fullName, | values.fullName, | ||||
| values.username, | values.username, | ||||
| values.email, | values.email, | ||||
| values.password | |||||
| values.password, | |||||
| values.address, | |||||
| values.address2, | |||||
| values.city, | |||||
| values.country, | |||||
| values.postcode | |||||
| ); | ); | ||||
| console.log(result); | console.log(result); | ||||
| } catch (error) { | } catch (error) { | ||||
| email: '', | email: '', | ||||
| password: '', | password: '', | ||||
| confirmPassword: '', | confirmPassword: '', | ||||
| address: '', | |||||
| address2: '', | |||||
| city: '', | |||||
| country: '', | |||||
| postcode: '', | |||||
| }, | }, | ||||
| validationSchema: registerSchema, | validationSchema: registerSchema, | ||||
| onSubmit: submitHandler, | onSubmit: submitHandler, | ||||
| ), | ), | ||||
| }} | }} | ||||
| /> | /> | ||||
| <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 | <Button | ||||
| type="submit" | type="submit" | ||||
| variant="contained" | variant="contained" |
| import { Box, Button, Paper, TextField } from '@mui/material'; | import { Box, Button, Paper, TextField } from '@mui/material'; | ||||
| import { useFormik } from 'formik'; | import { useFormik } from 'formik'; | ||||
| import { useSession } from 'next-auth/react'; | |||||
| import PropType from 'prop-types'; | import PropType from 'prop-types'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | ||||
| const ShippingDetailsForm = ({ backBtn = false }) => { | const ShippingDetailsForm = ({ backBtn = false }) => { | ||||
| const [error] = useState({ hasError: false, errorMessage: '' }); | const [error] = useState({ hasError: false, errorMessage: '' }); | ||||
| const { data: session } = useSession(); | |||||
| const submitHandler = async (values) => { | const submitHandler = async (values) => { | ||||
| console.log(values); | console.log(values); | ||||
| }; | }; | ||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | 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, | validationSchema: shippingDetailsSchema, | ||||
| onSubmit: submitHandler, | onSubmit: submitHandler, | ||||
| onSubmit={formik.handleSubmit} | onSubmit={formik.handleSubmit} | ||||
| sx={{ position: 'relative', mt: 1, p: 1 }} | 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 | <TextField | ||||
| name="fullName" | name="fullName" | ||||
| label="Name" | label="Name" | ||||
| sx={{ mr: 1.5 }} | sx={{ mr: 1.5 }} | ||||
| /> | /> | ||||
| <TextField | <TextField | ||||
| name="city" | |||||
| label="City" | |||||
| name="postcode" | |||||
| label="Postal Code" | |||||
| margin="normal" | margin="normal" | ||||
| value={formik.values.city} | |||||
| value={formik.values.postcode} | |||||
| onChange={formik.handleChange} | 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 | fullWidth | ||||
| /> | /> | ||||
| </Box> | </Box> |
| import { Button, Typography } from '@mui/material'; | import { Button, Typography } from '@mui/material'; | ||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||
| import { useRouter } from 'next/router'; | |||||
| import { PRODUCTS_PAGE } from '../../constants/pages'; | |||||
| const Hero = () => { | const Hero = () => { | ||||
| const router = useRouter(); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Box sx={{ display: 'flex', width: '100%', height: '100vh' }}> | <Box sx={{ display: 'flex', width: '100%', height: '100vh' }}> | ||||
| sx={{ | sx={{ | ||||
| width: '100%', | width: '100%', | ||||
| display: 'flex', | display: 'flex', | ||||
| mt: 5, | |||||
| }} | }} | ||||
| > | > | ||||
| <Button | <Button | ||||
| ml: 10, | ml: 10, | ||||
| color: 'white', | color: 'white', | ||||
| }} | }} | ||||
| onClick={() => router.push(PRODUCTS_PAGE)} | |||||
| > | > | ||||
| {' '} | |||||
| Explore the Shop | Explore the Shop | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| /> | /> | ||||
| } | } | ||||
| > | > | ||||
| {' '} | |||||
| How to make | How to make | ||||
| </Button> | </Button> | ||||
| </Box> | </Box> |
| import Box from '@mui/material/Box'; | import Box from '@mui/material/Box'; | ||||
| import Typography from '@mui/material/Typography'; | import Typography from '@mui/material/Typography'; | ||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||
| const pages = ['Home', 'Menu', 'About', 'Store', 'Contact']; | |||||
| import Link from 'next/link'; | |||||
| import { BASE_PAGE, PRODUCTS_PAGE } from '../../../constants/pages'; | |||||
| const pages = [ | |||||
| <Link key="home" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| px: 1.5, | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Home | |||||
| </Typography> | |||||
| </Link>, | |||||
| <Link key="menu" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| px: 1.5, | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Menu | |||||
| </Typography> | |||||
| </Link>, | |||||
| <Link key="about" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| px: 1.5, | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| About | |||||
| </Typography> | |||||
| </Link>, | |||||
| <Link key="store" href={PRODUCTS_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| px: 1.5, | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Store | |||||
| </Typography> | |||||
| </Link>, | |||||
| <Link key="contact" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| px: 1.5, | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Contact | |||||
| </Typography> | |||||
| </Link>, | |||||
| ]; | |||||
| const Footer = () => { | const Footer = () => { | ||||
| return ( | return ( | ||||
| justifyContent: 'center', | justifyContent: 'center', | ||||
| }} | }} | ||||
| > | > | ||||
| {pages.map((page) => ( | |||||
| <Typography | |||||
| key={page} | |||||
| sx={{ | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| px: 1.5, | |||||
| color: 'primary.main', | |||||
| }} | |||||
| > | |||||
| {page} | |||||
| </Typography> | |||||
| ))} | |||||
| {pages.map((page) => page)} | |||||
| </Box> | </Box> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ |
| import Box from '@mui/material/Box'; | import Box from '@mui/material/Box'; | ||||
| import Typography from '@mui/material/Typography'; | import Typography from '@mui/material/Typography'; | ||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||
| const pages = ['Home', 'Menu', 'About', 'Store', 'Contact']; | |||||
| import Link from 'next/link'; | |||||
| import { useRouter } from 'next/router'; | |||||
| import { | |||||
| BASE_PAGE, | |||||
| CART_PAGE, | |||||
| PRODUCTS_PAGE, | |||||
| PROFILE_PAGE, | |||||
| } from '../../../constants/pages'; | |||||
| import { useStore } from '../../../store/cart-context'; | |||||
| const Navbar = () => { | const Navbar = () => { | ||||
| // const { data: session } = useSession(); | |||||
| // const [anchorElNav, setAnchorElNav] = useState(null); | |||||
| // const [anchorElUser, setAnchorElUser] = useState(null); | |||||
| // const handleOpenNavMenu = (event) => { | |||||
| // setAnchorElNav(event.currentTarget); | |||||
| // }; | |||||
| // const handleOpenUserMenu = (event) => { | |||||
| // setAnchorElUser(event.currentTarget); | |||||
| // }; | |||||
| // const handleCloseNavMenu = () => { | |||||
| // setAnchorElNav(null); | |||||
| // }; | |||||
| // const handleCloseUserMenu = () => { | |||||
| // setAnchorElUser(null); | |||||
| // }; | |||||
| // function logoutHandler() { | |||||
| // signOut(); | |||||
| // } | |||||
| const router = useRouter(); | |||||
| const { totalQuantity } = useStore(); | |||||
| return ( | return ( | ||||
| <AppBar | <AppBar | ||||
| position="absolute" | position="absolute" | ||||
| px: 10, | px: 10, | ||||
| }} | }} | ||||
| > | > | ||||
| {pages.map((page) => ( | |||||
| <Link key="home" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| mx: 'auto', | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: router.pathname === '/' ? 'white' : 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Home | |||||
| </Typography> | |||||
| </Link> | |||||
| <Link key="menu" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| mx: 'auto', | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: router.pathname === '/' ? 'white' : 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Menu | |||||
| </Typography> | |||||
| </Link> | |||||
| <Link key="about" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| mx: 'auto', | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: router.pathname === '/' ? 'white' : 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| About | |||||
| </Typography> | |||||
| </Link> | |||||
| <Link key="store" href={PRODUCTS_PAGE}> | |||||
| <Typography | <Typography | ||||
| key={page} | |||||
| textAlign="center" | textAlign="center" | ||||
| sx={{ mx: 'auto', fontSize: 20, fontWeight: 500, color: 'black' }} | |||||
| sx={{ | |||||
| mx: 'auto', | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: router.pathname === '/' ? 'white' : 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | > | ||||
| {page} | |||||
| Store | |||||
| </Typography> | </Typography> | ||||
| ))} | |||||
| </Link> | |||||
| <Link key="contact" href={BASE_PAGE}> | |||||
| <Typography | |||||
| textAlign="center" | |||||
| sx={{ | |||||
| mx: 'auto', | |||||
| fontSize: 20, | |||||
| fontWeight: 500, | |||||
| color: router.pathname === '/' ? 'white' : 'black', | |||||
| textDecoration: 'none', | |||||
| cursor: 'pointer', | |||||
| }} | |||||
| > | |||||
| Contact | |||||
| </Typography> | |||||
| </Link> | |||||
| </Box> | </Box> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| mx: 2, | mx: 2, | ||||
| cursor: 'pointer', | |||||
| }} | }} | ||||
| > | > | ||||
| <Image | |||||
| src="/images/profile.svg" | |||||
| alt="profile" | |||||
| width={24} | |||||
| height={24} | |||||
| /> | |||||
| <Link key="home" href={PROFILE_PAGE}> | |||||
| <Image | |||||
| src="/images/profile.svg" | |||||
| alt="profile" | |||||
| width={24} | |||||
| height={24} | |||||
| /> | |||||
| </Link> | |||||
| </Box> | </Box> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| mr: 6, | mr: 6, | ||||
| ml: 2, | ml: 2, | ||||
| cursor: 'pointer', | |||||
| }} | }} | ||||
| > | > | ||||
| <Image src="/images/cart.svg" alt="cart" width={24} height={24} /> | |||||
| <Link key="home" href={CART_PAGE}> | |||||
| <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> | </Box> | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| </AppBar> | </AppBar> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default Navbar; | export default Navbar; |
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import PropType from 'prop-types'; | import PropType from 'prop-types'; | ||||
| import { useStore, useStoreUpdate } from '../../../store/cart-context'; | |||||
| import ProductImage from './ProductImage'; | import ProductImage from './ProductImage'; | ||||
| import ProductInfo from './ProductInfo'; | import ProductInfo from './ProductInfo'; | ||||
| const FeaturedProduct = ({ bColor, image, side }) => { | |||||
| 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 ( | return ( | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| {side === 'left' ? ( | {side === 'left' ? ( | ||||
| <ProductImage image={image}></ProductImage> | <ProductImage image={image}></ProductImage> | ||||
| ) : ( | ) : ( | ||||
| <ProductInfo bColor={bColor} side={side}></ProductInfo> | |||||
| <ProductInfo | |||||
| bColor={bColor} | |||||
| side={side} | |||||
| data={data} | |||||
| addProductToCart={addProductToCart} | |||||
| inCart={inCart} | |||||
| ></ProductInfo> | |||||
| )} | )} | ||||
| {side === 'left' ? ( | {side === 'left' ? ( | ||||
| <ProductInfo bColor={bColor} side={side}></ProductInfo> | |||||
| <ProductInfo | |||||
| bColor={bColor} | |||||
| side={side} | |||||
| data={data} | |||||
| addProductToCart={addProductToCart} | |||||
| inCart={inCart} | |||||
| ></ProductInfo> | |||||
| ) : ( | ) : ( | ||||
| <ProductImage image={image}></ProductImage> | <ProductImage image={image}></ProductImage> | ||||
| )} | )} | ||||
| }; | }; | ||||
| FeaturedProduct.propTypes = { | FeaturedProduct.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, | |||||
| }), | |||||
| bColor: PropType.string, | bColor: PropType.string, | ||||
| image: PropType.string, | image: PropType.string, | ||||
| side: PropType.string, | side: PropType.string, |
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||
| import PropType from 'prop-types'; | import PropType from 'prop-types'; | ||||
| import { useState } from 'react'; | |||||
| const ProductInfo = ({ data, bColor, side, addProductToCart, inCart }) => { | |||||
| const [quantity, setQuantity] = useState(1); | |||||
| const ProductInfo = ({ bColor, side }) => { | |||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| pl: side === 'right' ? '10%' : 0, | pl: side === 'right' ? '10%' : 0, | ||||
| }} | }} | ||||
| > | > | ||||
| <Typography variant="h3" sx={{ height: 100, mt: 15, color: 'white' }}> | |||||
| Frapuccino coffee | |||||
| <Typography variant="h3" sx={{ height: 60, mt: 15, color: 'white' }}> | |||||
| {data.name} | |||||
| </Typography> | </Typography> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| color: 'white', | color: 'white', | ||||
| }} | }} | ||||
| > | > | ||||
| If you drink coffee regulary you will know the difference between fresh | |||||
| coffee and old coffee. Our goal is to provide the freshest coffee beans | |||||
| in each day. | |||||
| {data.description} | |||||
| </Typography> | </Typography> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| width: '100%', | width: '100%', | ||||
| display: 'flex', | display: 'flex', | ||||
| mt: 4, | |||||
| }} | }} | ||||
| > | > | ||||
| <ButtonGroup | <ButtonGroup | ||||
| fontSize: 20, | fontSize: 20, | ||||
| width: 50, | width: 50, | ||||
| }} | }} | ||||
| onClick={() => {}} | |||||
| onClick={() => { | |||||
| setQuantity((prevState) => prevState - 1); | |||||
| }} | |||||
| > | > | ||||
| - | - | ||||
| </Button> | </Button> | ||||
| width: 50, | width: 50, | ||||
| }} | }} | ||||
| > | > | ||||
| 1 | |||||
| {quantity} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| sx={{ | sx={{ | ||||
| fontSize: 20, | fontSize: 20, | ||||
| width: 50, | width: 50, | ||||
| }} | }} | ||||
| onClick={() => {}} | |||||
| onClick={() => { | |||||
| setQuantity((prevState) => prevState + 1); | |||||
| }} | |||||
| > | > | ||||
| + | + | ||||
| </Button> | </Button> | ||||
| width: 150, | width: 150, | ||||
| color: 'white', | color: 'white', | ||||
| }} | }} | ||||
| disabled={inCart} | |||||
| onClick={() => addProductToCart(quantity)} | |||||
| > | > | ||||
| Add to cart | |||||
| {inCart ? 'In Cart' : 'Add to cart'} | |||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| }; | }; | ||||
| ProductInfo.propTypes = { | ProductInfo.propTypes = { | ||||
| data: PropType.shape({ | |||||
| name: PropType.string, | |||||
| description: PropType.string, | |||||
| }), | |||||
| bColor: PropType.string, | bColor: PropType.string, | ||||
| side: PropType.string, | side: PropType.string, | ||||
| addProductToCart: PropType.func, | |||||
| inCart: PropType.Boolean | PropType.undefined, | |||||
| }; | }; | ||||
| export default ProductInfo; | export default ProductInfo; |
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import PropType from 'prop-types'; | |||||
| import FeaturedProduct from '../featured-product/FeaturedProduct'; | import FeaturedProduct from '../featured-product/FeaturedProduct'; | ||||
| const FeaturedProductsList = () => { | |||||
| const FeaturedProductsList = ({ featuredProducts }) => { | |||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| flexDirection: 'column', | flexDirection: 'column', | ||||
| }} | }} | ||||
| > | > | ||||
| <FeaturedProduct | |||||
| bColor="dark" | |||||
| image="/images/coffee-bag 1.png" | |||||
| side="left" | |||||
| ></FeaturedProduct> | |||||
| <FeaturedProduct | |||||
| bColor="light" | |||||
| image="/images/Item 2.png" | |||||
| side="right" | |||||
| ></FeaturedProduct> | |||||
| {featuredProducts.map((product, i) => { | |||||
| return ( | |||||
| <FeaturedProduct | |||||
| key={i} | |||||
| product={product} | |||||
| bColor={i % 2 === 0 ? 'dark' : 'light'} | |||||
| image="/images/Item 2.png" | |||||
| side={i % 2 === 0 ? 'left' : 'right'} | |||||
| ></FeaturedProduct> | |||||
| ); | |||||
| })} | |||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| }; | }; | ||||
| FeaturedProduct.propTypes = { | |||||
| featuredProducts: PropType.arrayOf( | |||||
| 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, | |||||
| }) | |||||
| ), | |||||
| }; | |||||
| export default FeaturedProductsList; | export default FeaturedProductsList; |
| import { Breadcrumbs, Button, Divider, Grid, Typography } from '@mui/material'; | |||||
| import { Box } from '@mui/system'; | |||||
| const ReviewContent = () => { | |||||
| return ( | |||||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | |||||
| <Grid item xs={12}> | |||||
| <Typography | |||||
| variant="h3" | |||||
| sx={{ pl: 12, mt: 12, height: '100%', color: 'primary.main' }} | |||||
| > | |||||
| Shipping | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Divider sx={{ backgroundColor: 'primary.main', mx: 12 }} /> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 4 }}> | |||||
| <Breadcrumbs | |||||
| aria-label="breadcrumb" | |||||
| separator="›" | |||||
| sx={{ pl: 12, fontSize: 20 }} | |||||
| > | |||||
| <Typography>Cart</Typography> | |||||
| <Typography>Checkout</Typography> | |||||
| <Typography>Shipping</Typography> | |||||
| <Typography>Payment</Typography> | |||||
| <Typography color="red">Review</Typography> | |||||
| </Breadcrumbs> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 1 }}> | |||||
| <Typography | |||||
| sx={{ | |||||
| width: '100%', | |||||
| textAlign: 'center', | |||||
| color: 'primary.main', | |||||
| fontWeight: 600, | |||||
| fontSize: 22, | |||||
| }} | |||||
| > | |||||
| ORDER COMPLETE SUCCESSFULLY | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 1 }}> | |||||
| <Typography | |||||
| sx={{ | |||||
| width: '100%', | |||||
| fontWeight: 600, | |||||
| mt: 2, | |||||
| textAlign: 'center', | |||||
| }} | |||||
| > | |||||
| Thank you for placing your order with us. We wll get to work on | |||||
| sending your order as soon as possible | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 1 }}> | |||||
| <Typography | |||||
| sx={{ | |||||
| width: '100%', | |||||
| textAlign: 'center', | |||||
| mt: 2, | |||||
| fontSize: 44, | |||||
| fontWeight: 600, | |||||
| }} | |||||
| > | |||||
| Order Summary | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Box | |||||
| sx={{ | |||||
| backgroundColor: '#f2f2f2', | |||||
| mt: 4, | |||||
| ml: 12, | |||||
| width: '85%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Order placed on: 05/09/2022 | |||||
| </Typography> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Box | |||||
| sx={{ | |||||
| backgroundColor: '#f2f2f2', | |||||
| ml: 12, | |||||
| width: '85%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Email: johndoe@test | |||||
| </Typography> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Box | |||||
| sx={{ | |||||
| backgroundColor: '#f2f2f2', | |||||
| ml: 12, | |||||
| width: '85%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Total: $60 | |||||
| </Typography> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Box | |||||
| sx={{ | |||||
| backgroundColor: '#f2f2f2', | |||||
| ml: 12, | |||||
| width: '85%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Shipping Address: 1684 Upton Avenue, Locke Mills, United Kingdom, | |||||
| 04255 | |||||
| </Typography> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Grid item xs={12} 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, | |||||
| }} | |||||
| > | |||||
| Back to Home | |||||
| </Button> | |||||
| </Box> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ReviewContent; |
| import { | |||||
| Breadcrumbs, | |||||
| Button, | |||||
| Checkbox, | |||||
| Divider, | |||||
| FormControlLabel, | |||||
| Grid, | |||||
| Typography, | |||||
| } from '@mui/material'; | |||||
| import { Box } from '@mui/system'; | |||||
| import DataCard from '../cards/data-card/DataCard'; | |||||
| const ShippingContent = () => { | |||||
| return ( | |||||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | |||||
| <Grid item xs={12}> | |||||
| <Typography | |||||
| variant="h3" | |||||
| sx={{ pl: 12, mt: 12, height: '100%', color: 'primary.main' }} | |||||
| > | |||||
| Shipping | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <Divider sx={{ backgroundColor: 'primary.main', mx: 12 }} /> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 4 }}> | |||||
| <Breadcrumbs | |||||
| aria-label="breadcrumb" | |||||
| separator="›" | |||||
| sx={{ pl: 12, fontSize: 20 }} | |||||
| > | |||||
| <Typography>Cart</Typography> | |||||
| <Typography>Checkout</Typography> | |||||
| <Typography color="red">Shipping</Typography> | |||||
| </Breadcrumbs> | |||||
| </Grid> | |||||
| <Grid item xs={12} sx={{ mt: 1 }}> | |||||
| <Typography sx={{ pl: 12, fontSize: 20 }}> | |||||
| The following fields will be used as the shipping details for your | |||||
| order | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={8}> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| backgroundColor: '#f2f2f2', | |||||
| alignItems: 'center', | |||||
| mt: 2, | |||||
| ml: 12, | |||||
| mb: 2, | |||||
| width: '90%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Contact | |||||
| </Typography> | |||||
| <Typography>johndoe@test.com | 0601234567</Typography> | |||||
| <Button | |||||
| sx={{ | |||||
| height: 35, | |||||
| width: 125, | |||||
| fontSize: 15, | |||||
| textTransform: 'none', | |||||
| backgroundColor: '#CBA213', | |||||
| color: 'white', | |||||
| }} | |||||
| > | |||||
| Change | |||||
| </Button> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| backgroundColor: '#f2f2f2', | |||||
| alignItems: 'center', | |||||
| ml: 12, | |||||
| mb: 2, | |||||
| width: '90%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||||
| Shipping to | |||||
| </Typography> | |||||
| <Typography>1684 Upton Avenue | Locke Mills</Typography> | |||||
| <Button | |||||
| sx={{ | |||||
| height: 35, | |||||
| width: 125, | |||||
| fontSize: 15, | |||||
| textTransform: 'none', | |||||
| backgroundColor: '#CBA213', | |||||
| color: 'white', | |||||
| }} | |||||
| > | |||||
| Change | |||||
| </Button> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| backgroundColor: '#f2f2f2', | |||||
| alignItems: 'center', | |||||
| ml: 12, | |||||
| mb: 2, | |||||
| width: '30%', | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <FormControlLabel | |||||
| control={<Checkbox checked disabled />} | |||||
| label="Free Shipping" | |||||
| sx={{ color: 'black', ml: 2 }} | |||||
| /> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| ml: 12, | |||||
| mb: 2, | |||||
| borderRadius: 2, | |||||
| p: 1, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| variant="contained" | |||||
| sx={{ | |||||
| mt: 3, | |||||
| mb: 2, | |||||
| height: 50, | |||||
| width: 150, | |||||
| textTransform: 'none', | |||||
| backgroundColor: 'primary.main', | |||||
| color: 'white', | |||||
| mr: 2, | |||||
| }} | |||||
| > | |||||
| Back to cart | |||||
| </Button> | |||||
| <Button | |||||
| type="submit" | |||||
| variant="contained" | |||||
| sx={{ | |||||
| mt: 3, | |||||
| mb: 2, | |||||
| backgroundColor: '#CBA213', | |||||
| height: 50, | |||||
| width: 200, | |||||
| textTransform: 'none', | |||||
| color: 'white', | |||||
| }} | |||||
| > | |||||
| Continue to payment | |||||
| </Button> | |||||
| </Box> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <Box sx={{ width: '80%', mt: 2 }}> | |||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| </Box> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ShippingContent; |
| export const BASE_PAGE = '/'; | export const BASE_PAGE = '/'; | ||||
| export const CHECKOUT_PAGE = '/checkout'; | export const CHECKOUT_PAGE = '/checkout'; | ||||
| export const CART_PAGE = '/cart'; | |||||
| export const SHIPPING_PAGE = '/shipping'; | |||||
| export const REVIEW_PAGE = '/review'; | |||||
| export const PRODUCTS_PAGE = '/products'; | |||||
| export const LOGIN_PAGE = '/auth'; | export const LOGIN_PAGE = '/auth'; | ||||
| export const PROFILE_PAGE = '/profile'; | export const PROFILE_PAGE = '/profile'; | ||||
| export const REGISTER_PAGE = '/auth/register'; | export const REGISTER_PAGE = '/auth/register'; |
| 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; |
| required: [true, 'Please provide an address.'], | required: [true, 'Please provide an address.'], | ||||
| trim: true, | trim: true, | ||||
| }, | }, | ||||
| phone: { | |||||
| address2: { | |||||
| type: String, | type: String, | ||||
| unique: [true, 'Phone number must be unique.'], | |||||
| required: [true, 'Please provide a phone number.'], | |||||
| trim: true, | trim: true, | ||||
| lowercase: true, | |||||
| validate(value) { | |||||
| if (!validator.isMobilePhone(value)) { | |||||
| throw new Error('Not a valid phone number'); | |||||
| } | |||||
| }, | |||||
| }, | }, | ||||
| postcode: { | postcode: { | ||||
| type: String, | type: String, | ||||
| required: [true, 'Please provide a postal code.'], | required: [true, 'Please provide a postal code.'], | ||||
| validate(value) { | |||||
| if (!validator.isPostalCode(value)) { | |||||
| throw new Error('Not a valid postal code'); | |||||
| } | |||||
| }, | |||||
| }, | }, | ||||
| }); | }); | ||||
| throw new Error('Unable to login'); | 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) { | UserSchema.pre('save', async function (next) { |
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import Layout from '../components/layout/base-layout/Layout'; | import Layout from '../components/layout/base-layout/Layout'; | ||||
| import CircularIndeterminate from '../components/loader/route-loader/CircularIndeterminate'; | import CircularIndeterminate from '../components/loader/route-loader/CircularIndeterminate'; | ||||
| import StorageProvider from '../store/cart-context'; | |||||
| import '../styles/globals.css'; | import '../styles/globals.css'; | ||||
| import theme from '../styles/muiTheme'; | import theme from '../styles/muiTheme'; | ||||
| <Hydrate state={pageProps.dehydratedState}> | <Hydrate state={pageProps.dehydratedState}> | ||||
| <SessionProvider session={session}> | <SessionProvider session={session}> | ||||
| <ThemeProvider theme={theme}> | <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> | </ThemeProvider> | ||||
| </SessionProvider> | </SessionProvider> | ||||
| </Hydrate> | </Hydrate> |
| session: { | session: { | ||||
| jwt: true, | jwt: true, | ||||
| }, | }, | ||||
| callbacks: { | |||||
| async jwt({ token, user }) { | |||||
| return { ...token, ...user }; | |||||
| }, | |||||
| async session({ session, user, token }) { | |||||
| return token; | |||||
| }, | |||||
| }, | |||||
| providers: [ | providers: [ | ||||
| Credentials({ | Credentials({ | ||||
| async authorize(credentials) { | async authorize(credentials) { | ||||
| await dbConnect(); | await dbConnect(); | ||||
| const user = await User.findByCredentials( | |||||
| const userData = await User.findByCredentials( | |||||
| credentials.username, | credentials.username, | ||||
| credentials.password | credentials.password | ||||
| ); | ); | ||||
| return { name: user.fullName }; | |||||
| return { user: userData }; | |||||
| }, | }, | ||||
| }), | }), | ||||
| ], | ], |
| switch (method) { | switch (method) { | ||||
| case 'POST': { | case 'POST': { | ||||
| try { | try { | ||||
| console.log(req.body); | |||||
| const user = await User.create(req.body); | const user = await User.create(req.body); | ||||
| res | res | ||||
| .status(201) | .status(201) |
| if (productCount === 0) { | if (productCount === 0) { | ||||
| res.status(200).json({ | res.status(200).json({ | ||||
| message: 'There are currently no products in our database.', | message: 'There are currently no products in our database.', | ||||
| Product: [], | |||||
| ProductCount: 0, | |||||
| product: [], | |||||
| productCount: 0, | |||||
| }); | }); | ||||
| break; | break; | ||||
| } | } | ||||
| if ((pageIndex - 1) * 4 >= productCount) { | |||||
| if ((pageIndex - 1) * 9 >= productCount) { | |||||
| throw new Error('Page does not exist!'); | throw new Error('Page does not exist!'); | ||||
| } | } | ||||
| const product = await Product.find({}) | const product = await Product.find({}) | ||||
| .skip((pageIndex - 1) * 4) | |||||
| .limit(4); | |||||
| .skip((pageIndex - 1) * 9) | |||||
| .limit(9); | |||||
| if (!product) { | if (!product) { | ||||
| throw new Error('There are currently no products in our database.'); | throw new Error('There are currently no products in our database.'); |
| if (productCount === 0) { | if (productCount === 0) { | ||||
| res.status(200).json({ | res.status(200).json({ | ||||
| message: 'There are currently no products in our database.', | message: 'There are currently no products in our database.', | ||||
| ProductIds: [], | |||||
| productIds: [], | |||||
| }); | }); | ||||
| break; | break; | ||||
| } | } |
| import Features from '../components/features/Features'; | import Features from '../components/features/Features'; | ||||
| import Hero from '../components/hero/Hero'; | import Hero from '../components/hero/Hero'; | ||||
| import FeaturedProductsList from '../components/products/featured-products-list/FeaturedPorductsList'; | import FeaturedProductsList from '../components/products/featured-products-list/FeaturedPorductsList'; | ||||
| import { getFeaturedProducts } from '../requests/products/featuredProductsRequest'; | |||||
| const Home = () => { | |||||
| const Home = (props) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Box sx={{ width: '100%', height: '100%' }}> | <Box sx={{ width: '100%', height: '100%' }}> | ||||
| <meta name="description" content="Random data with pagination..." /> | <meta name="description" content="Random data with pagination..." /> | ||||
| </Head> | </Head> | ||||
| <Hero></Hero> | <Hero></Hero> | ||||
| <FeaturedProductsList></FeaturedProductsList> | |||||
| <FeaturedProductsList | |||||
| featuredProducts={props.featuredProducts} | |||||
| ></FeaturedProductsList> | |||||
| <Features></Features> | <Features></Features> | ||||
| <CompanyInfo></CompanyInfo> | <CompanyInfo></CompanyInfo> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| }; | }; | ||||
| // export async function getStaticProps({ locale }) { | |||||
| // const queryClient = new QueryClient(); | |||||
| // await queryClient.prefetchQuery(['randomData', 1], () => getData(1)); | |||||
| // return { | |||||
| // props: { | |||||
| // dehydratedState: dehydrate(queryClient), | |||||
| // ...(await serverSideTranslations(locale, ['pagination'])), | |||||
| // }, | |||||
| // }; | |||||
| // } | |||||
| export async function getStaticProps() { | |||||
| try { | |||||
| const { message, featuredProducts } = await getFeaturedProducts(); | |||||
| return { | |||||
| props: { | |||||
| message, | |||||
| featuredProducts, | |||||
| }, | |||||
| }; | |||||
| } catch (error) { | |||||
| return { | |||||
| props: { | |||||
| errorMessage: error, | |||||
| featuredProducts: [], | |||||
| }, | |||||
| }; | |||||
| } | |||||
| } | |||||
| export default Home; | export default Home; |
| 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 ProfileContent from '../../components/profile-content/ProfileContent'; | ||||
| import { LOGIN_PAGE } from '../../constants/pages'; | |||||
| const ProfilePage = () => { | const ProfilePage = () => { | ||||
| const { data: session } = useSession(); | 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; | export default ProfilePage; |
| import ReviewContent from '../../components/review-content/ReviewContent'; | |||||
| const ReviewPage = () => { | |||||
| return <ReviewContent></ReviewContent>; | |||||
| }; | |||||
| export default ReviewPage; |
| import ShippingContent from '../../components/shipping-content/ShippingContent'; | |||||
| const ShippingPage = () => { | |||||
| return <ShippingContent></ShippingContent>; | |||||
| }; | |||||
| export default ShippingPage; |
| import apiEndpoints from '../apiEndpoints'; | 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, { | const response = await fetch(apiEndpoints.account.createUser, { | ||||
| method: 'POST', | method: 'POST', | ||||
| body: JSON.stringify({ fullName, username, email, password }), | |||||
| body: JSON.stringify({ | |||||
| fullName, | |||||
| username, | |||||
| email, | |||||
| password, | |||||
| address, | |||||
| address2, | |||||
| city, | |||||
| country, | |||||
| postcode, | |||||
| }), | |||||
| headers: { | headers: { | ||||
| 'Content-Type': 'application/json', | 'Content-Type': 'application/json', | ||||
| }, | }, |
| import * as Yup from "yup"; | |||||
| import * as Yup from 'yup'; | |||||
| export const registerSchema = Yup.object().shape({ | 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( | 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'), | |||||
| }); | }); |
| export const registerSchema = Yup.object().shape({ | export const registerSchema = Yup.object().shape({ | ||||
| fullName: Yup.string().required('Full name is required'), | fullName: Yup.string().required('Full name is required'), | ||||
| email: Yup.string().email().required('Email is required'), | |||||
| address: Yup.string().required('Address is required'), | address: Yup.string().required('Address is required'), | ||||
| address2: Yup.string(), | address2: Yup.string(), | ||||
| city: Yup.string().required('City is required'), | city: Yup.string().required('City is required'), | ||||
| country: Yup.string().required('Country name 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'), | |||||
| }); | }); |
| 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; |
| 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); | |||||
| }; |