| @@ -1,7 +1,8 @@ | |||
| import { Box, Paper, Typography } from '@mui/material'; | |||
| import Image from 'next/image'; | |||
| import PropType from 'prop-types'; | |||
| const DataCard = () => { | |||
| const DataCard = ({ data, quantity }) => { | |||
| return ( | |||
| <Paper | |||
| sx={{ | |||
| @@ -33,7 +34,7 @@ const DataCard = () => { | |||
| fontSize: 20, | |||
| }} | |||
| > | |||
| Begin Mug in White | |||
| {data.name} - x{quantity} | |||
| </Typography> | |||
| <Typography | |||
| sx={{ | |||
| @@ -41,12 +42,38 @@ const DataCard = () => { | |||
| 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> | |||
| </Box> | |||
| </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; | |||
| @@ -1,9 +1,11 @@ | |||
| import { Button, Divider, Paper, Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import { useRouter } from 'next/router'; | |||
| import PropType from 'prop-types'; | |||
| const OrderSummaryCard = ({ data }) => { | |||
| const router = useRouter(); | |||
| return ( | |||
| <Paper | |||
| sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }} | |||
| @@ -36,6 +38,10 @@ const OrderSummaryCard = ({ data }) => { | |||
| startIcon={ | |||
| <Image src="/images/lock.svg" alt="lock" width={18} height={18} /> | |||
| } | |||
| disabled={data.totalQuantity > 0 ? false : true} | |||
| onClick={() => { | |||
| router.push('/checkout'); | |||
| }} | |||
| > | |||
| Proceed to Checkout | |||
| </Button> | |||
| @@ -51,6 +57,7 @@ const OrderSummaryCard = ({ data }) => { | |||
| OrderSummaryCard.propTypes = { | |||
| data: PropType.shape({ | |||
| totalPrice: PropType.number, | |||
| totalQuantity: PropType.number, | |||
| }), | |||
| }; | |||
| @@ -5,7 +5,7 @@ import CartCard from '../cards/cart-card/CartCard'; | |||
| import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard'; | |||
| const CartContent = () => { | |||
| const { cartStorage, totalPrice } = useStore(); | |||
| const { cartStorage, totalPrice, totalQuantity } = useStore(); | |||
| const { removeCartValue, updateItemQuantity } = useStoreUpdate(); | |||
| const mapProductsToDom = () => { | |||
| @@ -64,7 +64,7 @@ const CartContent = () => { | |||
| <Grid item xs={4}> | |||
| <Box sx={{ width: '80%', mt: 2 }}> | |||
| <OrderSummaryCard | |||
| data={{ totalPrice: totalPrice }} | |||
| data={{ totalPrice: totalPrice, totalQuantity: totalQuantity }} | |||
| ></OrderSummaryCard> | |||
| </Box> | |||
| </Grid> | |||
| @@ -1,9 +1,27 @@ | |||
| import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material'; | |||
| 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 ShippingDetailsForm from '../forms/shipping-details/ShippingDetailsForm'; | |||
| 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 ( | |||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | |||
| <Grid item xs={12}> | |||
| @@ -34,13 +52,23 @@ const CheckoutContent = () => { | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={8}> | |||
| <ShippingDetailsForm backBtn={true}></ShippingDetailsForm> | |||
| <ShippingDetailsForm | |||
| backBtn={true} | |||
| isCheckout={true} | |||
| submitHandler={submitHandler} | |||
| ></ShippingDetailsForm> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <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> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -6,31 +6,37 @@ import { useState } from 'react'; | |||
| import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; | |||
| import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; | |||
| const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| const ShippingDetailsForm = ({ | |||
| backBtn = false, | |||
| isCheckout = false, | |||
| submitHandler, | |||
| }) => { | |||
| const [error] = useState({ hasError: false, errorMessage: '' }); | |||
| const { data: session } = useSession(); | |||
| const submitHandler = async (values) => { | |||
| console.log(values); | |||
| const formikSubmitHandler = async (values) => { | |||
| console.log('hi'); | |||
| submitHandler(values); | |||
| }; | |||
| const formik = useFormik({ | |||
| 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, | |||
| onSubmit: submitHandler, | |||
| onSubmit: formikSubmitHandler, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <Paper | |||
| sx={{ p: 3, width: '90%', ml: 12, backgroundColor: '#f2f2f2' }} | |||
| sx={{ p: 3, width: '90%', ml: 12, mt: 2, backgroundColor: '#f2f2f2' }} | |||
| elevation={3} | |||
| > | |||
| <Box | |||
| @@ -135,12 +141,15 @@ const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| mb: 2, | |||
| backgroundColor: '#CBA213', | |||
| height: 50, | |||
| width: 150, | |||
| width: isCheckout ? 200 : 150, | |||
| textTransform: 'none', | |||
| color: 'white', | |||
| }} | |||
| onClick={() => { | |||
| submitHandler; | |||
| }} | |||
| > | |||
| Submit Details | |||
| {isCheckout ? 'Proceed to shipping' : 'Submit Details'} | |||
| </Button> | |||
| </Box> | |||
| </Box> | |||
| @@ -150,6 +159,8 @@ const ShippingDetailsForm = ({ backBtn = false }) => { | |||
| ShippingDetailsForm.propTypes = { | |||
| backBtn: PropType.Boolean, | |||
| isCheckout: PropType.Boolean, | |||
| submitHandler: PropType.func, | |||
| }; | |||
| export default ShippingDetailsForm; | |||
| @@ -8,9 +8,11 @@ import { | |||
| Typography, | |||
| } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import { useCheckoutData } from '../../store/checkout-context'; | |||
| import DataCard from '../cards/data-card/DataCard'; | |||
| const ShippingContent = () => { | |||
| const { checkoutStorage } = useCheckoutData(); | |||
| return ( | |||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | |||
| <Grid item xs={12}> | |||
| @@ -59,7 +61,7 @@ const ShippingContent = () => { | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| Contact | |||
| </Typography> | |||
| <Typography>johndoe@test.com | 0601234567</Typography> | |||
| <Typography>{checkoutStorage?.userInfo.email}</Typography> | |||
| <Button | |||
| sx={{ | |||
| height: 35, | |||
| @@ -90,7 +92,11 @@ const ShippingContent = () => { | |||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | |||
| Shipping to | |||
| </Typography> | |||
| <Typography>1684 Upton Avenue | Locke Mills</Typography> | |||
| <Typography> | |||
| {checkoutStorage?.userInfo.address} |{' '} | |||
| {checkoutStorage?.userInfo.city}{' '} | |||
| {checkoutStorage?.userInfo.postcode} | |||
| </Typography> | |||
| <Button | |||
| sx={{ | |||
| height: 35, | |||
| @@ -166,9 +172,15 @@ const ShippingContent = () => { | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <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> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -1,19 +1,9 @@ | |||
| const mongoose = require('mongoose'); | |||
| const validator = require('validator'); | |||
| const Product = require('./product'); | |||
| 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: { | |||
| type: Date, | |||
| required: [true, 'Please provide a date.'], | |||
| @@ -23,6 +13,32 @@ const OrderSchema = new mongoose.Schema({ | |||
| } | |||
| }, | |||
| }, | |||
| 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: { | |||
| type: Number, | |||
| required: [true, 'Please provide a total price.'], | |||
| @@ -95,6 +95,7 @@ UserSchema.statics.findByCredentials = async (username, password) => { | |||
| city: user.city, | |||
| country: user.country, | |||
| postcode: user.postcode, | |||
| _id: user._id, | |||
| }; | |||
| return userData; | |||
| }; | |||
| @@ -11,9 +11,21 @@ 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 CheckoutProvider from '../store/checkout-context'; | |||
| import '../styles/globals.css'; | |||
| import theme from '../styles/muiTheme'; | |||
| const Providers = ({ components, children }) => ( | |||
| <> | |||
| {components.reduceRight( | |||
| (acc, Comp) => ( | |||
| <Comp>{acc}</Comp> | |||
| ), | |||
| children | |||
| )} | |||
| </> | |||
| ); | |||
| function MyApp({ Component, pageProps: { session, ...pageProps } }) { | |||
| const [queryClient] = useState(() => new QueryClient()); | |||
| @@ -22,7 +34,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { | |||
| <Hydrate state={pageProps.dehydratedState}> | |||
| <SessionProvider session={session}> | |||
| <ThemeProvider theme={theme}> | |||
| <StorageProvider> | |||
| <Providers components={[CheckoutProvider, StorageProvider]}> | |||
| <Layout> | |||
| <Head> | |||
| <title>Coffee Shop</title> | |||
| @@ -35,7 +47,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { | |||
| <CircularIndeterminate /> | |||
| <Component {...pageProps} /> | |||
| </Layout> | |||
| </StorageProvider> | |||
| </Providers> | |||
| </ThemeProvider> | |||
| </SessionProvider> | |||
| </Hydrate> | |||
| @@ -0,0 +1,70 @@ | |||
| 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; | |||
| @@ -18,3 +18,24 @@ export const removeStorage = (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); | |||
| }; | |||