Bladeren bron

feat: payment-process (cart-checkout-shipping)

payment-process
ntasicc 3 jaren geleden
bovenliggende
commit
9565b20a60

+ 31
- 4
components/cards/data-card/DataCard.jsx Bestand weergeven

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;

+ 7
- 0
components/cards/order-summary-card/OrderSummaryCard.jsx Bestand weergeven

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,
}), }),
}; };



+ 2
- 2
components/cart-content/CartContent.jsx Bestand weergeven

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>

+ 32
- 4
components/checkout-content/CheckoutContent.jsx Bestand weergeven

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>

+ 24
- 13
components/forms/shipping-details/ShippingDetailsForm.jsx Bestand weergeven

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;

+ 17
- 5
components/shipping-content/ShippingContent.jsx Bestand weergeven

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>

+ 28
- 12
models/order.js Bestand weergeven

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.'],

+ 1
- 0
models/user.js Bestand weergeven

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;
}; };

+ 14
- 2
pages/_app.js Bestand weergeven

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>

+ 70
- 0
store/checkout-context.js Bestand weergeven

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;

+ 21
- 0
utils/helpers/storage.js Bestand weergeven

} }
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);
};

Laden…
Annuleren
Opslaan