| import { Box, Paper, Typography } from '@mui/material'; | import { Box, Paper, Typography } from '@mui/material'; | ||||
| import Image from 'next/image'; | import Image from 'next/image'; | ||||
| import PropType from 'prop-types'; | |||||
| const DataCard = () => { | |||||
| const DataCard = ({ data, quantity }) => { | |||||
| return ( | return ( | ||||
| <Paper | <Paper | ||||
| sx={{ | sx={{ | ||||
| fontSize: 20, | fontSize: 20, | ||||
| }} | }} | ||||
| > | > | ||||
| Begin Mug in White | |||||
| {data.name} - x{quantity} | |||||
| </Typography> | </Typography> | ||||
| <Typography | <Typography | ||||
| sx={{ | sx={{ | ||||
| fontSize: 14, | fontSize: 14, | ||||
| }} | }} | ||||
| > | > | ||||
| Simple and beautiful Begin mug. Perfect companion for your next | |||||
| delicious cup of coffee. | |||||
| {data.description} | |||||
| </Typography> | |||||
| <Typography | |||||
| sx={{ | |||||
| mt: 3, | |||||
| textAlign: 'right', | |||||
| fontSize: 14, | |||||
| }} | |||||
| > | |||||
| ${data.price} (per unit) | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| </Paper> | </Paper> | ||||
| ); | ); | ||||
| }; | }; | ||||
| DataCard.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, | |||||
| }), | |||||
| quantity: PropType.number, | |||||
| }; | |||||
| export default DataCard; | export default DataCard; |
| import { Button, Divider, Paper, Typography } from '@mui/material'; | import { Button, Divider, Paper, 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 PropType from 'prop-types'; | import PropType from 'prop-types'; | ||||
| const OrderSummaryCard = ({ data }) => { | const OrderSummaryCard = ({ data }) => { | ||||
| const router = useRouter(); | |||||
| return ( | return ( | ||||
| <Paper | <Paper | ||||
| sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }} | sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }} | ||||
| startIcon={ | startIcon={ | ||||
| <Image src="/images/lock.svg" alt="lock" width={18} height={18} /> | <Image src="/images/lock.svg" alt="lock" width={18} height={18} /> | ||||
| } | } | ||||
| disabled={data.totalQuantity > 0 ? false : true} | |||||
| onClick={() => { | |||||
| router.push('/checkout'); | |||||
| }} | |||||
| > | > | ||||
| Proceed to Checkout | Proceed to Checkout | ||||
| </Button> | </Button> | ||||
| OrderSummaryCard.propTypes = { | OrderSummaryCard.propTypes = { | ||||
| data: PropType.shape({ | data: PropType.shape({ | ||||
| totalPrice: PropType.number, | totalPrice: PropType.number, | ||||
| totalQuantity: PropType.number, | |||||
| }), | }), | ||||
| }; | }; | ||||
| 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 { cartStorage, totalPrice, totalQuantity } = useStore(); | |||||
| const { removeCartValue, updateItemQuantity } = useStoreUpdate(); | const { removeCartValue, updateItemQuantity } = useStoreUpdate(); | ||||
| const mapProductsToDom = () => { | const mapProductsToDom = () => { | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <Box sx={{ width: '80%', mt: 2 }}> | <Box sx={{ width: '80%', mt: 2 }}> | ||||
| <OrderSummaryCard | <OrderSummaryCard | ||||
| data={{ totalPrice: totalPrice }} | |||||
| data={{ totalPrice: totalPrice, totalQuantity: totalQuantity }} | |||||
| ></OrderSummaryCard> | ></OrderSummaryCard> | ||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> |
| 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 { useSession } from 'next-auth/react'; | |||||
| import { useRouter } from 'next/router'; | |||||
| import { useStore } from '../../store/cart-context'; | |||||
| import { useCheckoutDataUpdate } from '../../store/checkout-context'; | |||||
| 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 CheckoutContent = () => { | const CheckoutContent = () => { | ||||
| const { cartStorage } = useStore(); | |||||
| const { addCheckoutValue } = useCheckoutDataUpdate(); | |||||
| const { data: session } = useSession(); | |||||
| const router = useRouter(); | |||||
| const submitHandler = (formValues) => { | |||||
| addCheckoutValue( | |||||
| cartStorage, | |||||
| { ...formValues, email: session.user.email }, | |||||
| session.user._id | |||||
| ); | |||||
| router.push('/shipping'); | |||||
| }; | |||||
| 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}> | ||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={8}> | <Grid item xs={8}> | ||||
| <ShippingDetailsForm backBtn={true}></ShippingDetailsForm> | |||||
| <ShippingDetailsForm | |||||
| backBtn={true} | |||||
| isCheckout={true} | |||||
| submitHandler={submitHandler} | |||||
| ></ShippingDetailsForm> | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <Box sx={{ width: '80%', mt: 2 }}> | <Box sx={{ width: '80%', mt: 2 }}> | ||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| {cartStorage?.map((entry, i) => { | |||||
| return ( | |||||
| <DataCard | |||||
| key={i} | |||||
| data={entry.product} | |||||
| quantity={entry.quantity} | |||||
| ></DataCard> | |||||
| ); | |||||
| })} | |||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> |
| import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | ||||
| import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | ||||
| const ShippingDetailsForm = ({ backBtn = false }) => { | |||||
| const ShippingDetailsForm = ({ | |||||
| backBtn = false, | |||||
| isCheckout = false, | |||||
| submitHandler, | |||||
| }) => { | |||||
| const [error] = useState({ hasError: false, errorMessage: '' }); | const [error] = useState({ hasError: false, errorMessage: '' }); | ||||
| const { data: session } = useSession(); | const { data: session } = useSession(); | ||||
| const submitHandler = async (values) => { | |||||
| console.log(values); | |||||
| const formikSubmitHandler = async (values) => { | |||||
| console.log('hi'); | |||||
| submitHandler(values); | |||||
| }; | }; | ||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | initialValues: { | ||||
| fullName: session.user.fullName, | |||||
| address: session.user.address, | |||||
| address2: session.user.address2, | |||||
| city: session.user.city, | |||||
| country: session.user.country, | |||||
| postcode: session.user.postcode, | |||||
| fullName: session ? session.user.fullName : '', | |||||
| address: session ? session.user.address : '', | |||||
| address2: session ? session.user.address2 : '', | |||||
| city: session ? session.user.city : '', | |||||
| country: session ? session.user.country : '', | |||||
| postcode: session ? session.user.postcode : '', | |||||
| }, | }, | ||||
| validationSchema: shippingDetailsSchema, | validationSchema: shippingDetailsSchema, | ||||
| onSubmit: submitHandler, | |||||
| onSubmit: formikSubmitHandler, | |||||
| validateOnBlur: true, | validateOnBlur: true, | ||||
| enableReinitialize: true, | enableReinitialize: true, | ||||
| }); | }); | ||||
| return ( | return ( | ||||
| <Paper | <Paper | ||||
| sx={{ p: 3, width: '90%', ml: 12, backgroundColor: '#f2f2f2' }} | |||||
| sx={{ p: 3, width: '90%', ml: 12, mt: 2, backgroundColor: '#f2f2f2' }} | |||||
| elevation={3} | elevation={3} | ||||
| > | > | ||||
| <Box | <Box | ||||
| mb: 2, | mb: 2, | ||||
| backgroundColor: '#CBA213', | backgroundColor: '#CBA213', | ||||
| height: 50, | height: 50, | ||||
| width: 150, | |||||
| width: isCheckout ? 200 : 150, | |||||
| textTransform: 'none', | textTransform: 'none', | ||||
| color: 'white', | color: 'white', | ||||
| }} | }} | ||||
| onClick={() => { | |||||
| submitHandler; | |||||
| }} | |||||
| > | > | ||||
| Submit Details | |||||
| {isCheckout ? 'Proceed to shipping' : 'Submit Details'} | |||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| ShippingDetailsForm.propTypes = { | ShippingDetailsForm.propTypes = { | ||||
| backBtn: PropType.Boolean, | backBtn: PropType.Boolean, | ||||
| isCheckout: PropType.Boolean, | |||||
| submitHandler: PropType.func, | |||||
| }; | }; | ||||
| export default ShippingDetailsForm; | export default ShippingDetailsForm; |
| Typography, | Typography, | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import { useCheckoutData } from '../../store/checkout-context'; | |||||
| import DataCard from '../cards/data-card/DataCard'; | import DataCard from '../cards/data-card/DataCard'; | ||||
| const ShippingContent = () => { | const ShippingContent = () => { | ||||
| const { checkoutStorage } = useCheckoutData(); | |||||
| 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}> | ||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | ||||
| Contact | Contact | ||||
| </Typography> | </Typography> | ||||
| <Typography>johndoe@test.com | 0601234567</Typography> | |||||
| <Typography>{checkoutStorage?.userInfo.email}</Typography> | |||||
| <Button | <Button | ||||
| sx={{ | sx={{ | ||||
| height: 35, | height: 35, | ||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | ||||
| Shipping to | Shipping to | ||||
| </Typography> | </Typography> | ||||
| <Typography>1684 Upton Avenue | Locke Mills</Typography> | |||||
| <Typography> | |||||
| {checkoutStorage?.userInfo.address} |{' '} | |||||
| {checkoutStorage?.userInfo.city}{' '} | |||||
| {checkoutStorage?.userInfo.postcode} | |||||
| </Typography> | |||||
| <Button | <Button | ||||
| sx={{ | sx={{ | ||||
| height: 35, | height: 35, | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <Box sx={{ width: '80%', mt: 2 }}> | <Box sx={{ width: '80%', mt: 2 }}> | ||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| <DataCard></DataCard> | |||||
| {checkoutStorage?.products.map((entry, i) => { | |||||
| return ( | |||||
| <DataCard | |||||
| key={i} | |||||
| data={entry.product} | |||||
| quantity={entry.quantity} | |||||
| ></DataCard> | |||||
| ); | |||||
| })} | |||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> |
| const mongoose = require('mongoose'); | const mongoose = require('mongoose'); | ||||
| const validator = require('validator'); | const validator = require('validator'); | ||||
| const Product = require('./product'); | |||||
| const OrderSchema = new mongoose.Schema({ | const OrderSchema = new mongoose.Schema({ | ||||
| coffee: [ | |||||
| { | |||||
| name: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a name.'], | |||||
| }, | |||||
| customID: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a custom id.'], | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| products: [Product], | |||||
| time: { | time: { | ||||
| type: Date, | type: Date, | ||||
| required: [true, 'Please provide a date.'], | required: [true, 'Please provide a date.'], | ||||
| } | } | ||||
| }, | }, | ||||
| }, | }, | ||||
| shippingAddress: { | |||||
| country: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a country.'], | |||||
| trim: true, | |||||
| }, | |||||
| city: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a city.'], | |||||
| trim: true, | |||||
| }, | |||||
| address: { | |||||
| type: String, | |||||
| required: [true, 'Please provide an address.'], | |||||
| trim: true, | |||||
| }, | |||||
| address2: { | |||||
| type: String, | |||||
| trim: true, | |||||
| }, | |||||
| postcode: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a postal code.'], | |||||
| }, | |||||
| }, | |||||
| totalPrice: { | totalPrice: { | ||||
| type: Number, | type: Number, | ||||
| required: [true, 'Please provide a total price.'], | required: [true, 'Please provide a total price.'], |
| city: user.city, | city: user.city, | ||||
| country: user.country, | country: user.country, | ||||
| postcode: user.postcode, | postcode: user.postcode, | ||||
| _id: user._id, | |||||
| }; | }; | ||||
| return userData; | return userData; | ||||
| }; | }; |
| 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 StorageProvider from '../store/cart-context'; | ||||
| import CheckoutProvider from '../store/checkout-context'; | |||||
| import '../styles/globals.css'; | import '../styles/globals.css'; | ||||
| import theme from '../styles/muiTheme'; | import theme from '../styles/muiTheme'; | ||||
| const Providers = ({ components, children }) => ( | |||||
| <> | |||||
| {components.reduceRight( | |||||
| (acc, Comp) => ( | |||||
| <Comp>{acc}</Comp> | |||||
| ), | |||||
| children | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| function MyApp({ Component, pageProps: { session, ...pageProps } }) { | function MyApp({ Component, pageProps: { session, ...pageProps } }) { | ||||
| const [queryClient] = useState(() => new QueryClient()); | const [queryClient] = useState(() => new QueryClient()); | ||||
| <Hydrate state={pageProps.dehydratedState}> | <Hydrate state={pageProps.dehydratedState}> | ||||
| <SessionProvider session={session}> | <SessionProvider session={session}> | ||||
| <ThemeProvider theme={theme}> | <ThemeProvider theme={theme}> | ||||
| <StorageProvider> | |||||
| <Providers components={[CheckoutProvider, StorageProvider]}> | |||||
| <Layout> | <Layout> | ||||
| <Head> | <Head> | ||||
| <title>Coffee Shop</title> | <title>Coffee Shop</title> | ||||
| <CircularIndeterminate /> | <CircularIndeterminate /> | ||||
| <Component {...pageProps} /> | <Component {...pageProps} /> | ||||
| </Layout> | </Layout> | ||||
| </StorageProvider> | |||||
| </Providers> | |||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </SessionProvider> | </SessionProvider> | ||||
| </Hydrate> | </Hydrate> |
| import { createContext, useContext, useState } from 'react'; | |||||
| import { getSStorage, setSStorage } from '../utils/helpers/storage'; | |||||
| const CheckoutContext = createContext({ | |||||
| checkoutStorage: {}, | |||||
| }); | |||||
| const CheckoutDispatchContext = createContext({ | |||||
| addCheckoutValue: (products, userInfo, userID) => {}, | |||||
| clearCheckout: () => {}, | |||||
| }); | |||||
| export const useCheckoutData = () => { | |||||
| return useContext(CheckoutContext); | |||||
| }; | |||||
| export const useCheckoutDataUpdate = () => { | |||||
| return useContext(CheckoutDispatchContext); | |||||
| }; | |||||
| const useCheckout = () => { | |||||
| const CHECKOUT_KEY = 'checkout-data'; | |||||
| const [checkoutStorage, setCheckoutStorage] = useState( | |||||
| getSStorage(CHECKOUT_KEY) | |||||
| ); | |||||
| const addCheckoutValue = (products, userInfo, userID) => { | |||||
| console.log('hello from context'); | |||||
| const items = getSStorage(CHECKOUT_KEY); | |||||
| setSStorage(CHECKOUT_KEY, { products, userInfo, userID }); | |||||
| setCheckoutStorage(items); | |||||
| }; | |||||
| const clearCheckout = () => { | |||||
| setSStorage(CHECKOUT_KEY, {}); | |||||
| setCheckoutStorage({}); | |||||
| }; | |||||
| return { | |||||
| addCheckoutValue, | |||||
| clearCheckout, | |||||
| setCheckoutStorage, | |||||
| checkoutStorage, | |||||
| }; | |||||
| }; | |||||
| const CheckoutProvider = ({ children }) => { | |||||
| const { | |||||
| checkoutStorage, | |||||
| setCheckoutStorage, | |||||
| addCheckoutValue, | |||||
| clearCheckout, | |||||
| } = useCheckout(); | |||||
| return ( | |||||
| <CheckoutContext.Provider value={{ checkoutStorage }}> | |||||
| <CheckoutDispatchContext.Provider | |||||
| value={{ | |||||
| setCheckoutStorage, | |||||
| addCheckoutValue, | |||||
| clearCheckout, | |||||
| }} | |||||
| > | |||||
| {children} | |||||
| </CheckoutDispatchContext.Provider> | |||||
| </CheckoutContext.Provider> | |||||
| ); | |||||
| }; | |||||
| export default CheckoutProvider; |
| } | } | ||||
| window.localStorage.removeItem(key); | window.localStorage.removeItem(key); | ||||
| }; | }; | ||||
| export const setSStorage = (key, value) => { | |||||
| window.sessionStorage.setItem(key, JSON.stringify(value)); | |||||
| }; | |||||
| export const getSStorage = (key) => { | |||||
| if (typeof window === 'undefined') { | |||||
| return null; | |||||
| } | |||||
| const storedItems = window.sessionStorage.getItem(key); | |||||
| return storedItems ? JSON.parse(storedItems) : []; | |||||
| }; | |||||
| export const removeSStorage = (key) => { | |||||
| if (typeof window === 'undefined') { | |||||
| return null; | |||||
| } | |||||
| window.sessionStorage.removeItem(key); | |||||
| }; |