Parcourir la source

feat: account page, cart page, shipping page

shipping
ntasicc il y a 3 ans
Parent
révision
2caaf4c60a

+ 141
- 0
components/cards/cart-card/CartCard.jsx Voir le fichier

@@ -0,0 +1,141 @@
import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material';
import Image from 'next/image';

const CartCard = () => {
return (
<Paper
sx={{
p: 1,
width: '88%',
mb: 2,
ml: 12,
backgroundColor: '#f2f2f2',
display: 'flex',
}}
elevation={3}
>
<Box sx={{ width: '30%' }}>
<Image
src="/images/coffee-mug.svg"
alt="profile"
width={500}
height={300}
/>
</Box>
<Box
sx={{
ml: -4,
mr: 2,
display: 'flex',
alignItems: 'center',
width: '30%',
}}
>
<Typography
sx={{
width: '100%',
textAlign: 'center',
height: 25,
fontWeight: 600,
fontSize: 20,
}}
>
Begin Mug in White
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '20%',
alignItems: 'center',
}}
>
<Typography
sx={{
width: '100%',
textAlign: 'center',
height: 16,
fontSize: 14,
}}
>
Quantity
</Typography>
<ButtonGroup
size="small"
aria-label="small outlined button group"
sx={{
height: 35,
width: 125,
mt: 1,
backgroundColor: 'primary.main',
color: 'white',
border: 0,
}}
>
<Button
sx={{
color: 'white',
fontSize: 17,
width: 25,
}}
onClick={() => {}}
>
-
</Button>
<Button
sx={{
color: 'white',
fontSize: 15,
width: 25,
}}
>
1
</Button>
<Button
sx={{
color: 'white',
fontSize: 17,
width: 25,
}}
onClick={() => {}}
>
+
</Button>
</ButtonGroup>
<Button
sx={{
height: 35,
mt: 1,
width: 125,
fontSize: 15,
textTransform: 'none',
backgroundColor: '#C6453E',
color: 'white',
}}
startIcon={
<Image src="/images/x.svg" alt="remove" width={15} height={15} />
}
>
Remove
</Button>
</Box>
<Box
sx={{ ml: 3, display: 'flex', flexDirection: 'column', width: '20%' }}
>
<Typography
sx={{
width: '100%',
textAlign: 'center',
height: 25,
fontSize: 20,
}}
>
Total: $20
</Typography>
</Box>
</Paper>
);
};

export default CartCard;

+ 45
- 22
components/cards/data-card/DataCard.jsx Voir le fichier

@@ -1,29 +1,52 @@
import { Divider, Paper, Typography } from '@mui/material';
import PropType from 'prop-types';
import { Box, Paper, Typography } from '@mui/material';
import Image from 'next/image';

const DataCard = ({ data, t }) => {
const DataCard = () => {
return (
<Paper sx={{ p: 3, height: '100%' }} elevation={3}>
<Typography sx={{ fontWeight: 600 }}>{t('Name')}</Typography>
<Typography display="inline"> {data.name}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>{t('Age')}</Typography>
<Typography display="inline"> {data.age}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>{t('Gender')}</Typography>
<Typography display="inline"> {data.gender}</Typography>
<Divider />
<Paper
sx={{
p: 3,
width: '100%',
mb: 2,
backgroundColor: '#f2f2f2',
display: 'flex',
}}
elevation={3}
>
<Box sx={{ width: '30%', borderRadius: 4, overflow: 'hidden' }}>
<Image
src="/images/coffee-mug.svg"
alt="profile"
width={200}
height={250}
/>
</Box>
<Box
sx={{ ml: 3, display: 'flex', flexDirection: 'column', width: '60%' }}
>
<Typography
sx={{
width: '100%',
textAlign: 'center',
height: 25,
fontWeight: 600,
fontSize: 20,
}}
>
Begin Mug in White
</Typography>
<Typography
sx={{
mt: 3,
fontSize: 14,
}}
>
Simple and beautiful Begin mug. Perfect companion for your next
delicious cup of coffee.
</Typography>
</Box>
</Paper>
);
};

DataCard.propTypes = {
data: PropType.shape({
name: PropType.string,
age: PropType.number,
gender: PropType.string,
}),
t: PropType.func,
};

export default DataCard;

+ 28
- 0
components/cards/order-card/OrderCard.jsx Voir le fichier

@@ -0,0 +1,28 @@
import { Divider, Paper, Typography } from '@mui/material';
import PropType from 'prop-types';

const OrderCard = ({ data }) => {
return (
<Paper
sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f2f2f2' }}
elevation={3}
>
<Typography sx={{ fontWeight: 600 }}>
Order placed on {data.date}
</Typography>
<Divider />
<Typography sx={{ mt: 1 }}>By: {data.name}</Typography>
<Typography>Total: ${data.totalPrice}</Typography>
</Paper>
);
};

OrderCard.propTypes = {
data: PropType.shape({
date: PropType.string,
name: PropType.string,
totalPrice: PropType.number,
}),
};

export default OrderCard;

+ 57
- 0
components/cards/order-summary-card/OrderSummaryCard.jsx Voir le fichier

@@ -0,0 +1,57 @@
import { Button, Divider, Paper, Typography } from '@mui/material';
import { Box } from '@mui/system';
import Image from 'next/image';
import PropType from 'prop-types';

const OrderSummaryCard = ({ data }) => {
return (
<Paper
sx={{ p: 3, width: '100%', mb: 2, backgroundColor: '#f1f1f1' }}
elevation={3}
>
<Typography
sx={{
fontSize: 26,
color: 'primary.main',
textAlign: 'center',
width: '100%',
}}
>
Order Summary
</Typography>
<Typography sx={{ mt: 4 }}>Items total:${data.totalPrice}</Typography>
<Typography sx={{ mt: 1.5 }}>Shipping Costs: FREE</Typography>
<Typography sx={{ mt: 1.5, mb: 1.5 }}>
Total: ${data.totalPrice}
</Typography>
<Divider />
<Box sx={{ textAlign: 'center', mt: 4, width: '100%' }}>
<Button
sx={{
backgroundColor: '#0066ff',
color: 'white',
textTransform: 'none',
px: 2,
}}
startIcon={
<Image src="/images/lock.svg" alt="lock" width={18} height={18} />
}
>
Proceed to Checkout
</Button>
</Box>
<Typography sx={{ mt: 3, fontSize: 13 }}>
Once the checkout process begins you will have an hour to complete your
checkout otherwise you will be returned back to the cart to start over.
</Typography>
</Paper>
);
};

OrderSummaryCard.propTypes = {
data: PropType.shape({
totalPrice: PropType.number,
}),
};

export default OrderSummaryCard;

+ 44
- 0
components/cart-content/CartContent.jsx Voir le fichier

@@ -0,0 +1,44 @@
import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material';
import { Box } from '@mui/system';
import CartCard from '../cards/cart-card/CartCard';
import OrderSummaryCard from '../cards/order-summary-card/OrderSummaryCard';

const CartContent = () => {
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' }}
>
Items in Your Cart
</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 color="red">Cart</Typography>
<Typography></Typography>
</Breadcrumbs>
</Grid>
<Grid item xs={8}>
<CartCard></CartCard>
<CartCard></CartCard>
<CartCard></CartCard>
</Grid>
<Grid item xs={4}>
<Box sx={{ width: '80%', mt: 2 }}>
<OrderSummaryCard data={{ totalPrice: 60 }}></OrderSummaryCard>
</Box>
</Grid>
</Grid>
);
};

export default CartContent;

+ 50
- 0
components/checkout-content/CheckoutContent.jsx Voir le fichier

@@ -0,0 +1,50 @@
import { Breadcrumbs, Divider, Grid, Typography } from '@mui/material';
import { Box } from '@mui/system';
import DataCard from '../cards/data-card/DataCard';
import ShippingDetailsForm from '../forms/shipping-details/ShippingDetailsForm';

const ProfileContent = () => {
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' }}
>
Checkout
</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 color="red">Checkout</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}>
<ShippingDetailsForm backBtn={true}></ShippingDetailsForm>
</Grid>
<Grid item xs={4}>
<Box sx={{ width: '80%', mt: 2 }}>
<DataCard></DataCard>
<DataCard></DataCard>
<DataCard></DataCard>
</Box>
</Grid>
</Grid>
);
};

export default ProfileContent;

+ 166
- 0
components/forms/shipping-details/ShippingDetailsForm.jsx Voir le fichier

@@ -0,0 +1,166 @@
import { Box, Button, Paper, TextField } from '@mui/material';
import { useFormik } from 'formik';
import PropType from 'prop-types';
import { useState } from 'react';
import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema';
import ErrorMessageComponent from '../../mui/ErrorMessageComponent';

const ShippingDetailsForm = ({ backBtn = false }) => {
const [error] = useState({ hasError: false, errorMessage: '' });

const submitHandler = async (values) => {
console.log(values);
};

const formik = useFormik({
initialValues: {
fullName: '',
email: '',
address: '',
address2: '',
city: '',
country: '',
poostalCode: '',
},
validationSchema: shippingDetailsSchema,
onSubmit: submitHandler,
validateOnBlur: true,
enableReinitialize: true,
});

return (
<Paper
sx={{ p: 3, width: '90%', ml: 12, backgroundColor: '#f2f2f2' }}
elevation={3}
>
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
}}
>
{error.hasError && <ErrorMessageComponent error={error.errorMessage} />}
<Box
component="form"
onSubmit={formik.handleSubmit}
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
name="fullName"
label="Name"
margin="normal"
value={formik.values.fullName}
onChange={formik.handleChange}
error={formik.touched.fullName && Boolean(formik.errors.fullName)}
helperText={formik.touched.fullName && formik.errors.fullName}
fullWidth
/>
<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="address2"
label="Address Line 2"
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
/>
<Box sx={{ display: 'flex' }}>
<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
sx={{ mr: 1.5 }}
/>
<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
/>
</Box>

{backBtn && (
<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: 150,
textTransform: 'none',
color: 'white',
}}
>
Submit Details
</Button>
</Box>
</Box>
</Paper>
);
};

ShippingDetailsForm.propTypes = {
backBtn: PropType.Boolean,
};

export default ShippingDetailsForm;

+ 1
- 1
components/layout/navbar/Navbar.jsx Voir le fichier

@@ -54,7 +54,7 @@ const Navbar = () => {
<Typography
key={page}
textAlign="center"
sx={{ mx: 'auto', fontSize: 20, fontWeight: 500 }}
sx={{ mx: 'auto', fontSize: 20, fontWeight: 500, color: 'black' }}
>
{page}
</Typography>

+ 45
- 0
components/profile-content/ProfileContent.jsx Voir le fichier

@@ -0,0 +1,45 @@
import { Grid, Typography } from '@mui/material';
import { Box } from '@mui/system';
import OrderCard from '../cards/order-card/OrderCard';
import ShippingDetailsForm from '../forms/shipping-details/ShippingDetailsForm';

const ProfileContent = () => {
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' }}
>
Welcome to your user account
</Typography>
</Grid>
<Grid item xs={8} sx={{ mt: 4 }}>
<Typography sx={{ pl: 12, fontSize: 20 }}>
Save details for later
</Typography>
</Grid>
<Grid item xs={4} sx={{ mt: 4 }}>
<Typography sx={{ fontSize: 20 }}>Previous Orders</Typography>
</Grid>
<Grid item xs={8}>
<ShippingDetailsForm></ShippingDetailsForm>
</Grid>
<Grid item xs={4}>
<Box sx={{ width: '60%', mt: 2 }}>
<OrderCard
data={{ date: '2022-09-02', name: 'John Doe', totalPrice: 30 }}
></OrderCard>
<OrderCard
data={{ date: '2022-09-02', name: 'John Doe', totalPrice: 30 }}
></OrderCard>
<OrderCard
data={{ date: '2022-09-02', name: 'John Doe', totalPrice: 30 }}
></OrderCard>
</Box>
</Grid>
</Grid>
);
};

export default ProfileContent;

+ 1
- 0
constants/pages.js Voir le fichier

@@ -1,4 +1,5 @@
export const BASE_PAGE = '/';
export const CHECKOUT_PAGE = '/checkout';
export const LOGIN_PAGE = '/auth';
export const PROFILE_PAGE = '/profile';
export const REGISTER_PAGE = '/auth/register';

+ 1
- 1
pages/_app.js Voir le fichier

@@ -23,7 +23,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) {
<ThemeProvider theme={theme}>
<Layout>
<Head>
<title>NextJS template</title>
<title>Coffee Shop</title>
<meta name="description" content="NextJS template" />
<meta
name="viewport"

+ 7
- 0
pages/cart/index.js Voir le fichier

@@ -0,0 +1,7 @@
import CartContent from '../../components/cart-content/CartContent';

const CartPage = () => {
return <CartContent></CartContent>;
};

export default CartPage;

+ 7
- 0
pages/checkout/index.js Voir le fichier

@@ -0,0 +1,7 @@
import CheckoutContent from '../../components/checkout-content/CheckoutContent';

const CheckoutPage = () => {
return <CheckoutContent></CheckoutContent>;
};

export default CheckoutPage;

+ 17
- 18
pages/profile/index.js Voir le fichier

@@ -1,28 +1,27 @@
import { getSession, useSession } from 'next-auth/react';
import ProfileCard from '../../components/cards/profile-card/ProfileCard';
import { LOGIN_PAGE } from '../../constants/pages';
import { useSession } from 'next-auth/react';
import ProfileContent from '../../components/profile-content/ProfileContent';

const ProfilePage = () => {
const { data: session } = useSession();

return <ProfileCard profileData={{ name: session.user.name }} />;
return <ProfileContent></ProfileContent>;
};

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;

+ 9
- 0
public/images/coffee-mug.svg
Fichier diff supprimé car celui-ci est trop grand
Voir le fichier


+ 3
- 0
public/images/lock.svg Voir le fichier

@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.8043 6.49485V3.80412C12.8043 1.70651 11.0978 0 9.00021 0C6.90259 0 5.19608 1.70651 5.19608 3.80412V6.49485H2.59814V18H15.4023V6.49485H12.8043ZM6.30948 3.80412C6.30948 2.32044 7.51652 1.1134 9.00021 1.1134C10.4839 1.1134 11.6909 2.32044 11.6909 3.80412V6.49485H6.30948V3.80412ZM14.2889 16.8866H3.71155V7.60825H14.2889V16.8866Z" fill="white"/>
</svg>

+ 3
- 0
public/images/x.svg Voir le fichier

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.14623 4.15914C7.23998 4.0655 7.36706 4.01291 7.49956 4.01291C7.63206 4.01291 7.75914 4.0655 7.85289 4.15914L11.3329 7.63914L14.8129 4.15914C14.8587 4.11001 14.9139 4.07061 14.9752 4.04328C15.0365 4.01596 15.1027 4.00126 15.1699 4.00008C15.237 3.99889 15.3037 4.01124 15.366 4.03639C15.4282 4.06154 15.4848 4.09897 15.5323 4.14645C15.5797 4.19393 15.6172 4.25048 15.6423 4.31274C15.6675 4.375 15.6798 4.44169 15.6786 4.50882C15.6774 4.57596 15.6627 4.64216 15.6354 4.7035C15.6081 4.76483 15.5687 4.82003 15.5196 4.86581L12.0396 8.34581L15.5196 11.8258C15.5687 11.8716 15.6081 11.9268 15.6354 11.9881C15.6627 12.0494 15.6774 12.1157 15.6786 12.1828C15.6798 12.2499 15.6675 12.3166 15.6423 12.3789C15.6172 12.4411 15.5797 12.4977 15.5323 12.5452C15.4848 12.5926 15.4282 12.6301 15.366 12.6552C15.3037 12.6804 15.237 12.6927 15.1699 12.6915C15.1027 12.6903 15.0365 12.6757 14.9752 12.6483C14.9139 12.621 14.8587 12.5816 14.8129 12.5325L11.3329 9.05247L7.85289 12.5325C7.75811 12.6208 7.63275 12.6689 7.50321 12.6666C7.37368 12.6643 7.25009 12.6118 7.15848 12.5202C7.06687 12.4286 7.0144 12.305 7.01211 12.1755C7.00982 12.046 7.05791 11.9206 7.14623 11.8258L10.6262 8.34581L7.14623 4.86581C7.05259 4.77206 7 4.64497 7 4.51247C7 4.37997 7.05259 4.25289 7.14623 4.15914Z" fill="white"/>
</svg>

+ 11
- 0
schemas/shippingDetailsSchema.js Voir le fichier

@@ -0,0 +1,11 @@
import * as Yup from 'yup';

export const registerSchema = Yup.object().shape({
fullName: Yup.string().required('Full name is required'),
email: Yup.string().email().required('Email is required'),
address: Yup.string().required('Address is required'),
address2: Yup.string(),
city: Yup.string().required('City is required'),
country: Yup.string().required('Country name is required'),
postalCode: Yup.string().required('Postal code name is required'),
});

+ 0
- 1
styles/globals.css Voir le fichier

@@ -4,7 +4,6 @@
* {
box-sizing: border-box;
margin: 0;
height: 100%;
}

body {

Chargement…
Annuler
Enregistrer