瀏覽代碼

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

payment-process
ntasicc 3 年之前
父節點
當前提交
9565b20a60

+ 31
- 4
components/cards/data-card/DataCard.jsx 查看文件

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

+ 7
- 0
components/cards/order-summary-card/OrderSummaryCard.jsx 查看文件

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


+ 2
- 2
components/cart-content/CartContent.jsx 查看文件

@@ -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>

+ 32
- 4
components/checkout-content/CheckoutContent.jsx 查看文件

@@ -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>

+ 24
- 13
components/forms/shipping-details/ShippingDetailsForm.jsx 查看文件

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

+ 17
- 5
components/shipping-content/ShippingContent.jsx 查看文件

@@ -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>

+ 28
- 12
models/order.js 查看文件

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

+ 1
- 0
models/user.js 查看文件

@@ -95,6 +95,7 @@ UserSchema.statics.findByCredentials = async (username, password) => {
city: user.city,
country: user.country,
postcode: user.postcode,
_id: user._id,
};
return userData;
};

+ 14
- 2
pages/_app.js 查看文件

@@ -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>

+ 70
- 0
store/checkout-context.js 查看文件

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

+ 21
- 0
utils/helpers/storage.js 查看文件

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

Loading…
取消
儲存