Преглед изворни кода

Merge branch 'master' of http://176.104.105.124:3000/ntasicc/coffee into product-page

product-page
Lazar Kostic пре 3 година
родитељ
комит
a7a3377e3d

+ 52
- 8
components/cards/cart-card/CartCard.jsx Прегледај датотеку

import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material'; import { Box, Button, ButtonGroup, Paper, Typography } from '@mui/material';
import Image from 'next/image'; import Image from 'next/image';
import PropType from 'prop-types';
import { useState } from 'react';

const CartCard = ({ product, initialQuantity, remove, updateQuantity }) => {
const [quantity, setQuantity] = useState(initialQuantity);

// useEffect(() => {
// updateQuantity(product?.customID, quantity);
// }, [quantity]);


const CartCard = () => {
return ( return (
<Paper <Paper
sx={{ sx={{
fontSize: 20, fontSize: 20,
}} }}
> >
Begin Mug in White
{product?.name}
</Typography> </Typography>
</Box> </Box>
<Box <Box
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
width: '20%', width: '20%',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}} }}
> >
fontSize: 17, fontSize: 17,
width: 25, width: 25,
}} }}
onClick={() => {}}
onClick={() => {
if (quantity > 0) {
updateQuantity(product?.customID, quantity - 1);
setQuantity((prevState) => prevState - 1);
}
}}
> >
- -
</Button> </Button>
width: 25, width: 25,
}} }}
> >
1
{quantity}
</Button> </Button>
<Button <Button
sx={{ sx={{
fontSize: 17, fontSize: 17,
width: 25, width: 25,
}} }}
onClick={() => {}}
onClick={() => {
updateQuantity(product?.customID, quantity + 1);
setQuantity((prevState) => prevState + 1);
}}
> >
+ +
</Button> </Button>
startIcon={ startIcon={
<Image src="/images/x.svg" alt="remove" width={15} height={15} /> <Image src="/images/x.svg" alt="remove" width={15} height={15} />
} }
onClick={() => remove(product.customID)}
> >
Remove Remove
</Button> </Button>
</Box> </Box>
<Box <Box
sx={{ ml: 3, display: 'flex', flexDirection: 'column', width: '20%' }}
sx={{
ml: 3,
display: 'flex',
flexDirection: 'column',
width: '20%',
justifyContent: 'center',
alignItems: 'center',
}}
> >
<Typography <Typography
sx={{ sx={{
width: '100%', width: '100%',
textAlign: 'center', textAlign: 'center',
height: 25, height: 25,
fontSize: 20,
fontSize: 18,
}} }}
> >
Total: $20
Price: ${product?.price}
</Typography> </Typography>
</Box> </Box>
</Paper> </Paper>
); );
}; };


CartCard.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,
}),
initialQuantity: PropType.number,
remove: PropType.func,
updateQuantity: PropType.func,
};
export default CartCard; export default CartCard;

+ 35
- 4
components/cart-content/CartContent.jsx Прегледај датотеку

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 { useStore, useStoreUpdate } from '../../store/cart-context';
import CartCard from '../cards/cart-card/CartCard'; import CartCard from '../cards/cart-card/CartCard';
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 { removeCartValue, updateItemQuantity } = useStoreUpdate();

const mapProductsToDom = () => {
if (cartStorage?.length) {
return cartStorage.map((element, i) => (
<CartCard
key={i}
product={element?.product}
initialQuantity={element?.quantity}
remove={removeCartValue}
updateQuantity={updateItemQuantity}
></CartCard>
));
} else {
return (
<Typography
sx={{
pl: 12,
mt: 6,
height: '100%',
textAlign: 'center',
fontSize: 45,
}}
>
Your cart is currently empty
</Typography>
);
}
};
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}>
</Breadcrumbs> </Breadcrumbs>
</Grid> </Grid>
<Grid item xs={8}> <Grid item xs={8}>
<CartCard></CartCard>
<CartCard></CartCard>
<CartCard></CartCard>
{mapProductsToDom()}
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<Box sx={{ width: '80%', mt: 2 }}> <Box sx={{ width: '80%', mt: 2 }}>
<OrderSummaryCard data={{ totalPrice: 60 }}></OrderSummaryCard>
<OrderSummaryCard
data={{ totalPrice: totalPrice }}
></OrderSummaryCard>
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>

+ 2
- 2
components/checkout-content/CheckoutContent.jsx Прегледај датотеку

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 ProfileContent = () => {
const CheckoutContent = () => {
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}>
); );
}; };


export default ProfileContent;
export default CheckoutContent;

+ 62
- 2
components/forms/register/RegisterForm.jsx Прегледај датотеку

import { useState } from 'react'; import { useState } from 'react';


import { FORGOT_PASSWORD_PAGE, LOGIN_PAGE } from '../../../constants/pages'; import { FORGOT_PASSWORD_PAGE, LOGIN_PAGE } from '../../../constants/pages';
import { createUser } from '../../../requests/accountRequests';
import { createUser } from '../../../requests/accounts/accountRequests';
import { registerSchema } from '../../../schemas/registerSchema'; import { registerSchema } from '../../../schemas/registerSchema';
import ErrorMessageComponent from '../../mui/ErrorMessageComponent'; import ErrorMessageComponent from '../../mui/ErrorMessageComponent';


values.fullName, values.fullName,
values.username, values.username,
values.email, values.email,
values.password
values.password,
values.address,
values.address2,
values.city,
values.country,
values.postcode
); );
console.log(result); console.log(result);
} catch (error) { } catch (error) {
email: '', email: '',
password: '', password: '',
confirmPassword: '', confirmPassword: '',
address: '',
address2: '',
city: '',
country: '',
postcode: '',
}, },
validationSchema: registerSchema, validationSchema: registerSchema,
onSubmit: submitHandler, onSubmit: submitHandler,
), ),
}} }}
/> />
<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="address"
label="Address2"
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
/>
<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
/>
<TextField
name="postcode"
label="Postal Code"
margin="normal"
value={formik.values.postcode}
onChange={formik.handleChange}
error={formik.touched.postcode && Boolean(formik.errors.postcode)}
helperText={formik.touched.postcode && formik.errors.postcode}
fullWidth
/>
<Button <Button
type="submit" type="submit"
variant="contained" variant="contained"

+ 13
- 24
components/forms/shipping-details/ShippingDetailsForm.jsx Прегледај датотеку

import { Box, Button, Paper, TextField } from '@mui/material'; import { Box, Button, Paper, TextField } from '@mui/material';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useSession } from 'next-auth/react';
import PropType from 'prop-types'; import PropType from 'prop-types';
import { useState } from 'react'; import { useState } from 'react';
import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema'; import { shippingDetailsSchema } from '../../../schemas/shippingDetailsSchema';


const ShippingDetailsForm = ({ backBtn = false }) => { const ShippingDetailsForm = ({ backBtn = false }) => {
const [error] = useState({ hasError: false, errorMessage: '' }); const [error] = useState({ hasError: false, errorMessage: '' });
const { data: session } = useSession();
const submitHandler = async (values) => { const submitHandler = async (values) => {
console.log(values); console.log(values);
}; };


const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
fullName: '',
email: '',
address: '',
address2: '',
city: '',
country: '',
poostalCode: '',
fullName: session.user.fullName,
address: session.user.address,
address2: session.user.address2,
city: session.user.city,
country: session.user.country,
postcode: session.user.postcode,
}, },
validationSchema: shippingDetailsSchema, validationSchema: shippingDetailsSchema,
onSubmit: submitHandler, onSubmit: submitHandler,
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
sx={{ position: 'relative', mt: 1, p: 1 }} 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 <TextField
name="fullName" name="fullName"
label="Name" label="Name"
sx={{ mr: 1.5 }} sx={{ mr: 1.5 }}
/> />
<TextField <TextField
name="city"
label="City"
name="postcode"
label="Postal Code"
margin="normal" margin="normal"
value={formik.values.city}
value={formik.values.postcode}
onChange={formik.handleChange} onChange={formik.handleChange}
error={formik.touched.city && Boolean(formik.errors.city)}
helperText={formik.touched.city && formik.errors.city}
error={formik.touched.postcode && Boolean(formik.errors.postcode)}
helperText={formik.touched.postcode && formik.errors.postcode}
fullWidth fullWidth
/> />
</Box> </Box>

+ 5
- 2
components/hero/Hero.jsx Прегледај датотеку

import { Button, Typography } from '@mui/material'; import { Button, 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 { PRODUCTS_PAGE } from '../../constants/pages';


const Hero = () => { const Hero = () => {
const router = useRouter();
return ( return (
<> <>
<Box sx={{ display: 'flex', width: '100%', height: '100vh' }}> <Box sx={{ display: 'flex', width: '100%', height: '100vh' }}>
sx={{ sx={{
width: '100%', width: '100%',
display: 'flex', display: 'flex',
mt: 5,
}} }}
> >
<Button <Button
ml: 10, ml: 10,
color: 'white', color: 'white',
}} }}
onClick={() => router.push(PRODUCTS_PAGE)}
> >
{' '}
Explore the Shop Explore the Shop
</Button> </Button>
<Button <Button
/> />
} }
> >
{' '}
How to make How to make
</Button> </Button>
</Box> </Box>

+ 81
- 14
components/layout/footer/Footer.jsx Прегледај датотеку

import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Image from 'next/image'; import Image from 'next/image';
const pages = ['Home', 'Menu', 'About', 'Store', 'Contact'];
import Link from 'next/link';
import { BASE_PAGE, PRODUCTS_PAGE } from '../../../constants/pages';

const pages = [
<Link key="home" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
px: 1.5,
fontSize: 20,
fontWeight: 500,
color: 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Home
</Typography>
</Link>,
<Link key="menu" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
px: 1.5,
fontSize: 20,
fontWeight: 500,
color: 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Menu
</Typography>
</Link>,
<Link key="about" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
px: 1.5,
fontSize: 20,
fontWeight: 500,
color: 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
About
</Typography>
</Link>,
<Link key="store" href={PRODUCTS_PAGE}>
<Typography
textAlign="center"
sx={{
px: 1.5,
fontSize: 20,
fontWeight: 500,
color: 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Store
</Typography>
</Link>,
<Link key="contact" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
px: 1.5,
fontSize: 20,
fontWeight: 500,
color: 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Contact
</Typography>
</Link>,
];


const Footer = () => { const Footer = () => {
return ( return (
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
{pages.map((page) => (
<Typography
key={page}
sx={{
fontSize: 20,
fontWeight: 500,
px: 1.5,
color: 'primary.main',
}}
>
{page}
</Typography>
))}
{pages.map((page) => page)}
</Box> </Box>
<Box <Box
sx={{ sx={{

+ 126
- 36
components/layout/navbar/Navbar.jsx Прегледај датотеку

import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Image from 'next/image'; import Image from 'next/image';
const pages = ['Home', 'Menu', 'About', 'Store', 'Contact'];
import Link from 'next/link';
import { useRouter } from 'next/router';
import {
BASE_PAGE,
CART_PAGE,
PRODUCTS_PAGE,
PROFILE_PAGE,
} from '../../../constants/pages';
import { useStore } from '../../../store/cart-context';


const Navbar = () => { const Navbar = () => {
// const { data: session } = useSession();
// const [anchorElNav, setAnchorElNav] = useState(null);
// const [anchorElUser, setAnchorElUser] = useState(null);

// const handleOpenNavMenu = (event) => {
// setAnchorElNav(event.currentTarget);
// };
// const handleOpenUserMenu = (event) => {
// setAnchorElUser(event.currentTarget);
// };

// const handleCloseNavMenu = () => {
// setAnchorElNav(null);
// };

// const handleCloseUserMenu = () => {
// setAnchorElUser(null);
// };

// function logoutHandler() {
// signOut();
// }

const router = useRouter();
const { totalQuantity } = useStore();
return ( return (
<AppBar <AppBar
position="absolute" position="absolute"
px: 10, px: 10,
}} }}
> >
{pages.map((page) => (
<Link key="home" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
mx: 'auto',
fontSize: 20,
fontWeight: 500,
color: router.pathname === '/' ? 'white' : 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Home
</Typography>
</Link>

<Link key="menu" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
mx: 'auto',
fontSize: 20,
fontWeight: 500,
color: router.pathname === '/' ? 'white' : 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Menu
</Typography>
</Link>

<Link key="about" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
mx: 'auto',
fontSize: 20,
fontWeight: 500,
color: router.pathname === '/' ? 'white' : 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
About
</Typography>
</Link>

<Link key="store" href={PRODUCTS_PAGE}>
<Typography <Typography
key={page}
textAlign="center" textAlign="center"
sx={{ mx: 'auto', fontSize: 20, fontWeight: 500, color: 'black' }}
sx={{
mx: 'auto',
fontSize: 20,
fontWeight: 500,
color: router.pathname === '/' ? 'white' : 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
> >
{page}
Store
</Typography> </Typography>
))}
</Link>

<Link key="contact" href={BASE_PAGE}>
<Typography
textAlign="center"
sx={{
mx: 'auto',
fontSize: 20,
fontWeight: 500,
color: router.pathname === '/' ? 'white' : 'black',
textDecoration: 'none',
cursor: 'pointer',
}}
>
Contact
</Typography>
</Link>
</Box> </Box>
<Box <Box
sx={{ sx={{
<Box <Box
sx={{ sx={{
mx: 2, mx: 2,
cursor: 'pointer',
}} }}
> >
<Image
src="/images/profile.svg"
alt="profile"
width={24}
height={24}
/>
<Link key="home" href={PROFILE_PAGE}>
<Image
src="/images/profile.svg"
alt="profile"
width={24}
height={24}
/>
</Link>
</Box> </Box>
<Box <Box
sx={{ sx={{
mr: 6, mr: 6,
ml: 2, ml: 2,
cursor: 'pointer',
}} }}
> >
<Image src="/images/cart.svg" alt="cart" width={24} height={24} />
<Link key="home" href={CART_PAGE}>
<Box>
<Box
sx={{
color: 'white',
zIndex: 3,
width: 20,
height: 20,
borderRadius: 20,
textAlign: 'center',
px: 0.5,
ml: 2.2,
mt: -1,
fontSize: 16,
position: 'absolute',
backgroundColor: 'primary.main',
}}
>
{totalQuantity}
</Box>
<Image
src="/images/cart.svg"
alt="cart"
width={24}
height={24}
/>
</Box>
</Link>
,
</Box> </Box>
</Box> </Box>
</Box> </Box>
</AppBar> </AppBar>
); );
}; };

export default Navbar; export default Navbar;

+ 40
- 3
components/products/featured-product/FeaturedProduct.jsx Прегледај датотеку

import { Box } from '@mui/system'; import { Box } from '@mui/system';
import PropType from 'prop-types'; import PropType from 'prop-types';
import { useStore, useStoreUpdate } from '../../../store/cart-context';
import ProductImage from './ProductImage'; import ProductImage from './ProductImage';
import ProductInfo from './ProductInfo'; import ProductInfo from './ProductInfo';


const FeaturedProduct = ({ bColor, image, side }) => {
const FeaturedProduct = ({ product, bColor, image, side }) => {
const data = { name: product.name, description: product.description };
const { addCartValue } = useStoreUpdate();
const { cartStorage } = useStore();
const addProductToCart = (quantity) => addCartValue(product, quantity);
const inCart = cartStorage?.some(
(item) => item.product.customID === product.customID
)
? true
: false;

return ( return (
<Box <Box
sx={{ sx={{
{side === 'left' ? ( {side === 'left' ? (
<ProductImage image={image}></ProductImage> <ProductImage image={image}></ProductImage>
) : ( ) : (
<ProductInfo bColor={bColor} side={side}></ProductInfo>
<ProductInfo
bColor={bColor}
side={side}
data={data}
addProductToCart={addProductToCart}
inCart={inCart}
></ProductInfo>
)} )}
{side === 'left' ? ( {side === 'left' ? (
<ProductInfo bColor={bColor} side={side}></ProductInfo>
<ProductInfo
bColor={bColor}
side={side}
data={data}
addProductToCart={addProductToCart}
inCart={inCart}
></ProductInfo>
) : ( ) : (
<ProductImage image={image}></ProductImage> <ProductImage image={image}></ProductImage>
)} )}
}; };


FeaturedProduct.propTypes = { FeaturedProduct.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,
}),
bColor: PropType.string, bColor: PropType.string,
image: PropType.string, image: PropType.string,
side: PropType.string, side: PropType.string,

+ 24
- 10
components/products/featured-product/ProductInfo.jsx Прегледај датотеку

import { Box } from '@mui/system'; import { Box } from '@mui/system';
import Image from 'next/image'; import Image from 'next/image';
import PropType from 'prop-types'; import PropType from 'prop-types';
import { useState } from 'react';

const ProductInfo = ({ data, bColor, side, addProductToCart, inCart }) => {
const [quantity, setQuantity] = useState(1);


const ProductInfo = ({ bColor, side }) => {
return ( return (
<Box <Box
sx={{ sx={{
pl: side === 'right' ? '10%' : 0, pl: side === 'right' ? '10%' : 0,
}} }}
> >
<Typography variant="h3" sx={{ height: 100, mt: 15, color: 'white' }}>
Frapuccino coffee
<Typography variant="h3" sx={{ height: 60, mt: 15, color: 'white' }}>
{data.name}
</Typography> </Typography>
<Box <Box
sx={{ sx={{
color: 'white', color: 'white',
}} }}
> >
If you drink coffee regulary you will know the difference between fresh
coffee and old coffee. Our goal is to provide the freshest coffee beans
in each day.
{data.description}
</Typography> </Typography>
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
display: 'flex', display: 'flex',
mt: 4,
}} }}
> >
<ButtonGroup <ButtonGroup
fontSize: 20, fontSize: 20,
width: 50, width: 50,
}} }}
onClick={() => {}}
onClick={() => {
setQuantity((prevState) => prevState - 1);
}}
> >
- -
</Button> </Button>
width: 50, width: 50,
}} }}
> >
1
{quantity}
</Button> </Button>
<Button <Button
sx={{ sx={{
fontSize: 20, fontSize: 20,
width: 50, width: 50,
}} }}
onClick={() => {}}
onClick={() => {
setQuantity((prevState) => prevState + 1);
}}
> >
+ +
</Button> </Button>
width: 150, width: 150,
color: 'white', color: 'white',
}} }}
disabled={inCart}
onClick={() => addProductToCart(quantity)}
> >
Add to cart
{inCart ? 'In Cart' : 'Add to cart'}
</Button> </Button>
</Box> </Box>
</Box> </Box>
}; };


ProductInfo.propTypes = { ProductInfo.propTypes = {
data: PropType.shape({
name: PropType.string,
description: PropType.string,
}),
bColor: PropType.string, bColor: PropType.string,
side: PropType.string, side: PropType.string,
addProductToCart: PropType.func,
inCart: PropType.Boolean | PropType.undefined,
}; };
export default ProductInfo; export default ProductInfo;

+ 32
- 11
components/products/featured-products-list/FeaturedPorductsList.jsx Прегледај датотеку

import { Box } from '@mui/system'; import { Box } from '@mui/system';
import PropType from 'prop-types';
import FeaturedProduct from '../featured-product/FeaturedProduct'; import FeaturedProduct from '../featured-product/FeaturedProduct';


const FeaturedProductsList = () => {
const FeaturedProductsList = ({ featuredProducts }) => {
return ( return (
<Box <Box
sx={{ sx={{
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
<FeaturedProduct
bColor="dark"
image="/images/coffee-bag 1.png"
side="left"
></FeaturedProduct>
<FeaturedProduct
bColor="light"
image="/images/Item 2.png"
side="right"
></FeaturedProduct>
{featuredProducts.map((product, i) => {
return (
<FeaturedProduct
key={i}
product={product}
bColor={i % 2 === 0 ? 'dark' : 'light'}
image="/images/Item 2.png"
side={i % 2 === 0 ? 'left' : 'right'}
></FeaturedProduct>
);
})}
</Box> </Box>
); );
}; };


FeaturedProduct.propTypes = {
featuredProducts: PropType.arrayOf(
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,
})
),
};

export default FeaturedProductsList; export default FeaturedProductsList;

+ 165
- 0
components/review-content/ReviewContent.jsx Прегледај датотеку

import { Breadcrumbs, Button, Divider, Grid, Typography } from '@mui/material';
import { Box } from '@mui/system';

const ReviewContent = () => {
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' }}
>
Shipping
</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>Checkout</Typography>
<Typography>Shipping</Typography>
<Typography>Payment</Typography>
<Typography color="red">Review</Typography>
</Breadcrumbs>
</Grid>
<Grid item xs={12} sx={{ mt: 1 }}>
<Typography
sx={{
width: '100%',
textAlign: 'center',
color: 'primary.main',
fontWeight: 600,
fontSize: 22,
}}
>
ORDER COMPLETE SUCCESSFULLY
</Typography>
</Grid>
<Grid item xs={12} sx={{ mt: 1 }}>
<Typography
sx={{
width: '100%',
fontWeight: 600,
mt: 2,
textAlign: 'center',
}}
>
Thank you for placing your order with us. We wll get to work on
sending your order as soon as possible
</Typography>
</Grid>
<Grid item xs={12} sx={{ mt: 1 }}>
<Typography
sx={{
width: '100%',
textAlign: 'center',
mt: 2,
fontSize: 44,
fontWeight: 600,
}}
>
Order Summary
</Typography>
</Grid>
<Grid item xs={12}>
<Box
sx={{
backgroundColor: '#f2f2f2',
mt: 4,
ml: 12,
width: '85%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Order placed on: 05/09/2022
</Typography>
</Box>
</Grid>
<Grid item xs={12}>
<Box
sx={{
backgroundColor: '#f2f2f2',
ml: 12,
width: '85%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Email: johndoe@test
</Typography>
</Box>
</Grid>
<Grid item xs={12}>
<Box
sx={{
backgroundColor: '#f2f2f2',
ml: 12,
width: '85%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Total: $60
</Typography>
</Box>
</Grid>
<Grid item xs={12}>
<Box
sx={{
backgroundColor: '#f2f2f2',
ml: 12,
width: '85%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Shipping Address: 1684 Upton Avenue, Locke Mills, United Kingdom,
04255
</Typography>
</Box>
</Grid>
<Grid item xs={12} sx={{ mt: 1 }}>
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
mt: 2,
borderRadius: 2,
p: 1,
}}
>
<Button
variant="contained"
sx={{
mt: 3,
mb: 2,
height: 50,
width: 150,
textTransform: 'none',
backgroundColor: '#CBA213',
color: 'white',
mr: 2,
fontSize: 16,
}}
>
Back to Home
</Button>
</Box>
</Grid>
</Grid>
);
};

export default ReviewContent;

+ 178
- 0
components/shipping-content/ShippingContent.jsx Прегледај датотеку

import {
Breadcrumbs,
Button,
Checkbox,
Divider,
FormControlLabel,
Grid,
Typography,
} from '@mui/material';
import { Box } from '@mui/system';
import DataCard from '../cards/data-card/DataCard';

const ShippingContent = () => {
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' }}
>
Shipping
</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>Checkout</Typography>
<Typography color="red">Shipping</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}>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
backgroundColor: '#f2f2f2',
alignItems: 'center',
mt: 2,
ml: 12,
mb: 2,
width: '90%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Contact
</Typography>
<Typography>johndoe@test.com | 0601234567</Typography>
<Button
sx={{
height: 35,

width: 125,
fontSize: 15,
textTransform: 'none',
backgroundColor: '#CBA213',
color: 'white',
}}
>
Change
</Button>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
backgroundColor: '#f2f2f2',
alignItems: 'center',
ml: 12,
mb: 2,
width: '90%',
borderRadius: 2,
p: 1,
}}
>
<Typography sx={{ fontSize: 18, fontWeight: 600 }}>
Shipping to
</Typography>
<Typography>1684 Upton Avenue | Locke Mills</Typography>
<Button
sx={{
height: 35,
width: 125,
fontSize: 15,
textTransform: 'none',
backgroundColor: '#CBA213',
color: 'white',
}}
>
Change
</Button>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
backgroundColor: '#f2f2f2',
alignItems: 'center',
ml: 12,
mb: 2,
width: '30%',
borderRadius: 2,
p: 1,
}}
>
<FormControlLabel
control={<Checkbox checked disabled />}
label="Free Shipping"
sx={{ color: 'black', ml: 2 }}
/>
</Box>
<Box
sx={{
display: 'flex',
ml: 12,
mb: 2,
borderRadius: 2,
p: 1,
}}
>
<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: 200,
textTransform: 'none',
color: 'white',
}}
>
Continue to payment
</Button>
</Box>
</Grid>
<Grid item xs={4}>
<Box sx={{ width: '80%', mt: 2 }}>
<DataCard></DataCard>
<DataCard></DataCard>
<DataCard></DataCard>
</Box>
</Grid>
</Grid>
);
};

export default ShippingContent;

+ 4
- 0
constants/pages.js Прегледај датотеку

export const BASE_PAGE = '/'; export const BASE_PAGE = '/';
export const CHECKOUT_PAGE = '/checkout'; export const CHECKOUT_PAGE = '/checkout';
export const CART_PAGE = '/cart';
export const SHIPPING_PAGE = '/shipping';
export const REVIEW_PAGE = '/review';
export const PRODUCTS_PAGE = '/products';
export const LOGIN_PAGE = '/auth'; export const LOGIN_PAGE = '/auth';
export const PROFILE_PAGE = '/profile'; export const PROFILE_PAGE = '/profile';
export const REGISTER_PAGE = '/auth/register'; export const REGISTER_PAGE = '/auth/register';

+ 24
- 0
hooks/useCalculateTotal.js Прегледај датотеку

import { useState } from 'react';
import { getStorage } from '../utils/helpers/storage';

const useCalculateTotal = () => {
const CART_KEY = 'cart-products';

const [total, setTotal] = useState(() => {
const cart = getStorage(CART_KEY);

if (cart && cart.length) {
return cart
.map((entry) => entry?.product.price * entry?.quantity)
.reduce((accum, curValue) => accum + curValue);
} else {
return 0;
}
});

return {
total,
};
};

export default useCalculateTotal;

+ 11
- 15
models/user.js Прегледај датотеку

required: [true, 'Please provide an address.'], required: [true, 'Please provide an address.'],
trim: true, trim: true,
}, },
phone: {
address2: {
type: String, type: String,
unique: [true, 'Phone number must be unique.'],
required: [true, 'Please provide a phone number.'],
trim: true, trim: true,
lowercase: true,
validate(value) {
if (!validator.isMobilePhone(value)) {
throw new Error('Not a valid phone number');
}
},
}, },
postcode: { postcode: {
type: String, type: String,
required: [true, 'Please provide a postal code.'], required: [true, 'Please provide a postal code.'],
validate(value) {
if (!validator.isPostalCode(value)) {
throw new Error('Not a valid postal code');
}
},
}, },
}); });


throw new Error('Unable to login'); throw new Error('Unable to login');
} }


return user;
const userData = {
fullName: user.fullName,
email: user.email,
address: user.address,
address2: user.address2,
city: user.city,
country: user.country,
postcode: user.postcode,
};
return userData;
}; };


UserSchema.pre('save', async function (next) { UserSchema.pre('save', async function (next) {

+ 15
- 12
pages/_app.js Прегледај датотеку

import { useState } from 'react'; import { useState } from 'react';
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 '../styles/globals.css'; import '../styles/globals.css';
import theme from '../styles/muiTheme'; import theme from '../styles/muiTheme';


<Hydrate state={pageProps.dehydratedState}> <Hydrate state={pageProps.dehydratedState}>
<SessionProvider session={session}> <SessionProvider session={session}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Layout>
<Head>
<title>Coffee Shop</title>
<meta name="description" content="NextJS template" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</Head>
<CircularIndeterminate />
<Component {...pageProps} />
</Layout>
<StorageProvider>
<Layout>
<Head>
<title>Coffee Shop</title>
<meta name="description" content="NextJS template" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</Head>
<CircularIndeterminate />
<Component {...pageProps} />
</Layout>
</StorageProvider>
</ThemeProvider> </ThemeProvider>
</SessionProvider> </SessionProvider>
</Hydrate> </Hydrate>

+ 10
- 2
pages/api/auth/[...nextauth].js Прегледај датотеку

session: { session: {
jwt: true, jwt: true,
}, },
callbacks: {
async jwt({ token, user }) {
return { ...token, ...user };
},
async session({ session, user, token }) {
return token;
},
},
providers: [ providers: [
Credentials({ Credentials({
async authorize(credentials) { async authorize(credentials) {
await dbConnect(); await dbConnect();


const user = await User.findByCredentials(
const userData = await User.findByCredentials(
credentials.username, credentials.username,
credentials.password credentials.password
); );
return { name: user.fullName };
return { user: userData };
}, },
}), }),
], ],

+ 1
- 0
pages/api/auth/signup.js Прегледај датотеку

switch (method) { switch (method) {
case 'POST': { case 'POST': {
try { try {
console.log(req.body);
const user = await User.create(req.body); const user = await User.create(req.body);
res res
.status(201) .status(201)

+ 5
- 5
pages/api/product/index.js Прегледај датотеку

if (productCount === 0) { if (productCount === 0) {
res.status(200).json({ res.status(200).json({
message: 'There are currently no products in our database.', message: 'There are currently no products in our database.',
Product: [],
ProductCount: 0,
product: [],
productCount: 0,
}); });
break; break;
} }


if ((pageIndex - 1) * 4 >= productCount) {
if ((pageIndex - 1) * 9 >= productCount) {
throw new Error('Page does not exist!'); throw new Error('Page does not exist!');
} }


const product = await Product.find({}) const product = await Product.find({})
.skip((pageIndex - 1) * 4)
.limit(4);
.skip((pageIndex - 1) * 9)
.limit(9);


if (!product) { if (!product) {
throw new Error('There are currently no products in our database.'); throw new Error('There are currently no products in our database.');

+ 1
- 1
pages/api/product/productIds.js Прегледај датотеку

if (productCount === 0) { if (productCount === 0) {
res.status(200).json({ res.status(200).json({
message: 'There are currently no products in our database.', message: 'There are currently no products in our database.',
ProductIds: [],
productIds: [],
}); });
break; break;
} }

+ 23
- 14
pages/index.js Прегледај датотеку

import Features from '../components/features/Features'; import Features from '../components/features/Features';
import Hero from '../components/hero/Hero'; import Hero from '../components/hero/Hero';
import FeaturedProductsList from '../components/products/featured-products-list/FeaturedPorductsList'; import FeaturedProductsList from '../components/products/featured-products-list/FeaturedPorductsList';
import { getFeaturedProducts } from '../requests/products/featuredProductsRequest';


const Home = () => {
const Home = (props) => {
return ( return (
<> <>
<Box sx={{ width: '100%', height: '100%' }}> <Box sx={{ width: '100%', height: '100%' }}>
<meta name="description" content="Random data with pagination..." /> <meta name="description" content="Random data with pagination..." />
</Head> </Head>
<Hero></Hero> <Hero></Hero>
<FeaturedProductsList></FeaturedProductsList>
<FeaturedProductsList
featuredProducts={props.featuredProducts}
></FeaturedProductsList>
<Features></Features> <Features></Features>
<CompanyInfo></CompanyInfo> <CompanyInfo></CompanyInfo>
</Box> </Box>
); );
}; };


// export async function getStaticProps({ locale }) {
// const queryClient = new QueryClient();

// await queryClient.prefetchQuery(['randomData', 1], () => getData(1));

// return {
// props: {
// dehydratedState: dehydrate(queryClient),
// ...(await serverSideTranslations(locale, ['pagination'])),
// },
// };
// }
export async function getStaticProps() {
try {
const { message, featuredProducts } = await getFeaturedProducts();
return {
props: {
message,
featuredProducts,
},
};
} catch (error) {
return {
props: {
errorMessage: error,
featuredProducts: [],
},
};
}
}


export default Home; export default Home;

+ 30
- 16
pages/profile/index.js Прегледај датотеку

import { useSession } from 'next-auth/react';
import { Button } from '@mui/material';
import { getSession, signOut, useSession } from 'next-auth/react';
import ProfileContent from '../../components/profile-content/ProfileContent'; import ProfileContent from '../../components/profile-content/ProfileContent';
import { LOGIN_PAGE } from '../../constants/pages';


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


return <ProfileContent></ProfileContent>;
console.log(session);

function logoutHandler() {
signOut();
}
return (
<>
<ProfileContent></ProfileContent>
<Button color="inherit" onClick={logoutHandler}>
Logout
</Button>
</>
);
}; };


// 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; export default ProfilePage;

+ 7
- 0
pages/review/index.js Прегледај датотеку

import ReviewContent from '../../components/review-content/ReviewContent';

const ReviewPage = () => {
return <ReviewContent></ReviewContent>;
};

export default ReviewPage;

+ 7
- 0
pages/shipping/index.js Прегледај датотеку

import ShippingContent from '../../components/shipping-content/ShippingContent';

const ShippingPage = () => {
return <ShippingContent></ShippingContent>;
};

export default ShippingPage;

+ 22
- 2
requests/accounts/accountRequests.js Прегледај датотеку

import apiEndpoints from '../apiEndpoints'; import apiEndpoints from '../apiEndpoints';


export const createUser = async (fullName, username, email, password) => {
export const createUser = async (
fullName,
username,
email,
password,
address,
address2,
city,
country,
postcode
) => {
const response = await fetch(apiEndpoints.account.createUser, { const response = await fetch(apiEndpoints.account.createUser, {
method: 'POST', method: 'POST',
body: JSON.stringify({ fullName, username, email, password }),
body: JSON.stringify({
fullName,
username,
email,
password,
address,
address2,
city,
country,
postcode,
}),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },

+ 12
- 7
schemas/registerSchema.js Прегледај датотеку

import * as Yup from "yup";
import * as Yup from 'yup';


export const registerSchema = Yup.object().shape({ export const registerSchema = Yup.object().shape({
fullName: Yup.string().required("Full name is required"),
username: Yup.string().required("Username is required"),
email: Yup.string().email().required("Email is required"),
password: Yup.string().required("Password is required"),
fullName: Yup.string().required('Full name is required'),
username: Yup.string().required('Username is required'),
email: Yup.string().email().required('Email is required'),
password: Yup.string().required('Password is required'),
confirmPassword: Yup.string().oneOf( confirmPassword: Yup.string().oneOf(
[Yup.ref("password"), null],
"Passwords must match"
[Yup.ref('password'), null],
'Passwords must match'
), ),
address: Yup.string().required('Address is required'),
address2: Yup.string(),
city: Yup.string().required('City is required'),
country: Yup.string().required('Country is required'),
postcode: Yup.string().required('Postal code is required'),
}); });

+ 1
- 2
schemas/shippingDetailsSchema.js Прегледај датотеку



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

+ 168
- 0
store/cart-context.js Прегледај датотеку

import { createContext, useContext, useState } from 'react';
import { getStorage, setStorage } from '../utils/helpers/storage';

const StorageContext = createContext({
cartStorage: [],
totalPrice: 0,
totalQuantity: 0,
});
const StorageDispatchContext = createContext({
addCartValue: (product, quantity) => {},
clearCart: () => {},
removeCartValue: (productId) => {},
setCartStorage: (cart) => {},
updateItemQuantity: (productId, quantity) => {},
});

export const useStore = () => {
return useContext(StorageContext);
};
export const useStoreUpdate = () => {
return useContext(StorageDispatchContext);
};

const useStorage = () => {
const CART_KEY = 'cart-products';
const [cartStorage, setCartStorage] = useState(getStorage(CART_KEY));
const [totalPrice, setTotalPrice] = useState(() => {
const cart = getStorage(CART_KEY);

if (cart && cart.length) {
return cart
.map((entry) => entry?.product.price * entry?.quantity)
.reduce((accum, curValue) => accum + curValue);
} else {
return 0;
}
});

const [totalQuantity, setTotalQuantity] = useState(() => {
const cart = getStorage(CART_KEY);

if (cart && cart.length) {
return cart.length;
} else {
return 0;
}
});

const addCartValue = (product, quantity) => {
const items = getStorage(CART_KEY);

if (!items) {
setStorage(CART_KEY, [{ product, quantity }]);
} else {
const isItemDuplicate = items.some(
(item) => item.product.customID === product.customID
);

if (!isItemDuplicate) {
items.push({ product, quantity });
setTotalQuantity((prevState) => prevState + 1);
setStorage(CART_KEY, items);
} else {
return;
}
}

const newTotalPrice = items
.map((entry) => entry?.product.price * entry?.quantity)
.reduce((accum, curValue) => accum + curValue);

setTotalPrice(newTotalPrice);

setCartStorage(items);
};

const updateItemQuantity = (productId, quantity) => {
if (quantity < 0) return;
const items = getStorage(CART_KEY);
let updatedItems = items;

if (items) {
updatedItems = items.map((entry) => {
if (entry?.product.customID === productId) {
console.log('true');
entry.quantity = quantity;
}
return entry;
});

setStorage(CART_KEY, updatedItems);
}

const newTotalPrice = updatedItems
.map((entry) => entry?.product.price * entry?.quantity)
.reduce((accum, curValue) => accum + curValue);

setTotalPrice(newTotalPrice);
setCartStorage(updatedItems);
};

const clearCart = () => {
setStorage(CART_KEY, []);
setCartStorage([]);
};

const removeCartValue = (productId) => {
const items = getStorage(CART_KEY);

const newStorage = items?.filter(
(item) => item.product.customID !== productId
);

if (newStorage.length === 0) {
setTotalPrice(0);
} else {
const newTotalPrice = newStorage
.map((entry) => entry?.product.price * entry?.quantity)
.reduce((accum, curValue) => accum + curValue);
setTotalPrice(newTotalPrice);
}
setTotalQuantity((prevState) => prevState - 1);
setStorage(CART_KEY, newStorage);
setCartStorage(newStorage);
};

return {
addCartValue,
cartStorage,
totalPrice,
totalQuantity,
clearCart,
removeCartValue,
setCartStorage,
updateItemQuantity,
};
};

const StorageProvider = ({ children }) => {
const {
cartStorage,
totalPrice,
totalQuantity,
addCartValue,
clearCart,
setCartStorage,
removeCartValue,
updateItemQuantity,
} = useStorage();

return (
<StorageContext.Provider value={{ cartStorage, totalPrice, totalQuantity }}>
<StorageDispatchContext.Provider
value={{
addCartValue,
clearCart,
removeCartValue,
setCartStorage,
updateItemQuantity,
}}
>
{children}
</StorageDispatchContext.Provider>
</StorageContext.Provider>
);
};

export default StorageProvider;

+ 20
- 0
utils/helpers/storage.js Прегледај датотеку

export const setStorage = (key, value) => {
window.localStorage.setItem(key, JSON.stringify(value));
};

export const getStorage = (key) => {
if (typeof window === 'undefined') {
return null;
}

const storedItems = window.localStorage.getItem(key);

return storedItems ? JSON.parse(storedItems) : [];
};

export const removeStorage = (key) => {
if (typeof window === 'undefined') {
return null;
}
window.localStorage.removeItem(key);
};

Loading…
Откажи
Сачувај