| > | > | ||||
| Order Summary | Order Summary | ||||
| </Typography> | </Typography> | ||||
| <Typography sx={{ mt: 4 }}>Items total:${data.totalPrice}</Typography> | |||||
| <Typography sx={{ mt: 4 }}> | |||||
| Items total:${data.totalPrice.toFixed(2)} | |||||
| </Typography> | |||||
| <Typography sx={{ mt: 1.5 }}>Shipping Costs: FREE</Typography> | <Typography sx={{ mt: 1.5 }}>Shipping Costs: FREE</Typography> | ||||
| <Typography sx={{ mt: 1.5, mb: 1.5 }}> | <Typography sx={{ mt: 1.5, mb: 1.5 }}> | ||||
| Total: ${data.totalPrice} | |||||
| Total: ${data.totalPrice.toFixed(2)} | |||||
| </Typography> | </Typography> | ||||
| <Divider /> | <Divider /> | ||||
| <Box sx={{ textAlign: 'center', mt: 4, width: '100%' }}> | <Box sx={{ textAlign: 'center', mt: 4, width: '100%' }}> |
| import { | import { | ||||
| Alert, | |||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| Container, | Container, | ||||
| Grid, | Grid, | ||||
| Snackbar, | |||||
| TextField, | TextField, | ||||
| Typography, | Typography, | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import { useTranslation } from 'next-i18next'; | import { useTranslation } from 'next-i18next'; | ||||
| import Link from 'next/link'; | import Link from 'next/link'; | ||||
| import PropType from 'prop-types'; | import PropType from 'prop-types'; | ||||
| import React from 'react'; | |||||
| import React, { useState } from 'react'; | |||||
| import { BASE_PAGE } from '../../../constants/pages'; | import { BASE_PAGE } from '../../../constants/pages'; | ||||
| import { postQuestion } from '../../../requests/question/postQuestionRequest'; | import { postQuestion } from '../../../requests/question/postQuestionRequest'; | ||||
| import { contactPageSchema } from '../../../schemas/contactSchema'; | import { contactPageSchema } from '../../../schemas/contactSchema'; | ||||
| const ContactPageForm = () => { | const ContactPageForm = () => { | ||||
| const { t } = useTranslation('forms', 'contact', 'common'); | const { t } = useTranslation('forms', 'contact', 'common'); | ||||
| const [open, setOpen] = useState(false); | |||||
| //const [error] = useState({ hasError: false, errorMessage: '' }); | //const [error] = useState({ hasError: false, errorMessage: '' }); | ||||
| const handleSubmit = async (values) => { | const handleSubmit = async (values) => { | ||||
| try { | try { | ||||
| postQuestion(values); | postQuestion(values); | ||||
| setOpen(true); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); | console.log(error); | ||||
| } | } | ||||
| }; | }; | ||||
| const handleCloseNotification = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | initialValues: { | ||||
| firstName: '', | firstName: '', | ||||
| return ( | return ( | ||||
| <Container component="main" maxWidth="md" sx={{ mb: '60px' }}> | <Container component="main" maxWidth="md" sx={{ mb: '60px' }}> | ||||
| <Snackbar | |||||
| anchorOrigin={{ vertical: 'top', horizontal: 'center' }} | |||||
| open={open} | |||||
| autoHideDuration={3000} | |||||
| onClose={handleCloseNotification} | |||||
| > | |||||
| <Alert | |||||
| onClose={handleCloseNotification} | |||||
| severity="success" | |||||
| sx={{ width: '100%', backgroundColor: 'green', color: 'white' }} | |||||
| > | |||||
| Question submitted! | |||||
| </Alert> | |||||
| </Snackbar> | |||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| marginTop: 32, | marginTop: 32, |
| import ProductImage from './ProductImage'; | import ProductImage from './ProductImage'; | ||||
| import ProductInfo from './ProductInfo'; | import ProductInfo from './ProductInfo'; | ||||
| const FeaturedProduct = ({ product, bColor, image, side }) => { | |||||
| const FeaturedProduct = ({ product, bColor, side }) => { | |||||
| const matches = useMediaQuery('(min-width: 900px)'); | const matches = useMediaQuery('(min-width: 900px)'); | ||||
| const data = { name: product.name, description: product.description }; | const data = { name: product.name, description: product.description }; | ||||
| const { addCartValue } = useStoreUpdate(); | const { addCartValue } = useStoreUpdate(); | ||||
| sx={{ display: { md: 'flex' }, alignItems: { md: 'center' } }} | sx={{ display: { md: 'flex' }, alignItems: { md: 'center' } }} | ||||
| > | > | ||||
| {side === 'left' ? ( | {side === 'left' ? ( | ||||
| <ProductImage image={image}></ProductImage> | |||||
| <ProductImage image={product.image}></ProductImage> | |||||
| ) : !matches ? ( | ) : !matches ? ( | ||||
| <ProductImage image={image}></ProductImage> | |||||
| <ProductImage image={product.image}></ProductImage> | |||||
| ) : ( | ) : ( | ||||
| <ProductInfo | <ProductInfo | ||||
| bColor={bColor} | bColor={bColor} | ||||
| inCart={inCart} | inCart={inCart} | ||||
| ></ProductInfo> | ></ProductInfo> | ||||
| ) : ( | ) : ( | ||||
| <ProductImage image={image}></ProductImage> | |||||
| <ProductImage image={product.image}></ProductImage> | |||||
| )} | )} | ||||
| </Container> | </Container> | ||||
| </Box> | </Box> | ||||
| customID: PropType.string, | customID: PropType.string, | ||||
| }), | }), | ||||
| bColor: PropType.string, | bColor: PropType.string, | ||||
| image: PropType.string, | |||||
| side: PropType.string, | side: PropType.string, | ||||
| }; | }; | ||||
| export default FeaturedProduct; | export default FeaturedProduct; |
| key={i} | key={i} | ||||
| product={product} | product={product} | ||||
| bColor={i % 2 === 0 ? 'dark' : 'light'} | bColor={i % 2 === 0 ? 'dark' : 'light'} | ||||
| image="/images/Item 2.png" | |||||
| side={i % 2 === 0 ? 'left' : 'right'} | side={i % 2 === 0 ? 'left' : 'right'} | ||||
| ></FeaturedProduct> | ></FeaturedProduct> | ||||
| ); | ); |
| import { Grid, Typography } from '@mui/material'; | |||||
| import { Alert, Grid, Snackbar, Typography } from '@mui/material'; | |||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import { useSession } from 'next-auth/react'; | import { useSession } from 'next-auth/react'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| const { data: session } = useSession(); | const { data: session } = useSession(); | ||||
| const { updateUserInfo } = useUserUpdate(); | const { updateUserInfo } = useUserUpdate(); | ||||
| const [enableBtn, setEnableBtn] = useState(true); | const [enableBtn, setEnableBtn] = useState(true); | ||||
| const [open, setOpen] = useState(false); | |||||
| const updateUserHandler = async (values) => { | const updateUserHandler = async (values) => { | ||||
| try { | try { | ||||
| setEnableBtn(false); | setEnableBtn(false); | ||||
| updateUserInfo(values); | updateUserInfo(values); | ||||
| await updateUser(values, session.user._id); | await updateUser(values, session.user._id); | ||||
| setOpen(true); | |||||
| setTimeout(() => { | |||||
| setEnableBtn(true); | |||||
| }, 5000); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); | console.log(error); | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| } | } | ||||
| }; | }; | ||||
| const handleCloseNotification = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| const mapOrdersToDom = () => | const mapOrdersToDom = () => | ||||
| orders.slice(-4).map((order, i) => ( | orders.slice(-4).map((order, i) => ( | ||||
| <OrderCard | <OrderCard | ||||
| return ( | return ( | ||||
| <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | <Grid container spacing={2} sx={{ py: 10, height: '100%', width: '100%' }}> | ||||
| <Snackbar | |||||
| anchorOrigin={{ vertical: 'top', horizontal: 'center' }} | |||||
| open={open} | |||||
| autoHideDuration={3000} | |||||
| onClose={handleCloseNotification} | |||||
| > | |||||
| <Alert | |||||
| onClose={handleCloseNotification} | |||||
| severity="success" | |||||
| sx={{ width: '100%', backgroundColor: 'green', color: 'white' }} | |||||
| > | |||||
| User profile updated! | |||||
| </Alert> | |||||
| </Snackbar> | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Typography | <Typography | ||||
| variant="h3" | variant="h3" |
| }} | }} | ||||
| > | > | ||||
| <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | <Typography sx={{ fontSize: 18, fontWeight: 600 }}> | ||||
| Total: ${orderData?.totalPrice} | |||||
| Total: ${orderData?.totalPrice.toFixed(2)} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> |
| handleClose(); | handleClose(); | ||||
| }; | }; | ||||
| console.log(checkoutStorage); | |||||
| const handleStripePayment = () => { | const handleStripePayment = () => { | ||||
| stripe({ | stripe({ | ||||
| lineItems: [ | |||||
| { | |||||
| price: 'price_1Lg4MsDY7dvAcw2f1CGQaFFR', | |||||
| quantity: 1, | |||||
| }, | |||||
| ], | |||||
| lineItems: checkoutStorage.products.map((el) => ({ | |||||
| price: el.product.stripeID, | |||||
| quantity: el.quantity, | |||||
| })), | |||||
| userInfo: checkoutStorage.userInfo, | |||||
| }); | }); | ||||
| setCookie(null, 'review-session', 'active', { | setCookie(null, 'review-session', 'active', { | ||||
| maxAge: 3600, | maxAge: 3600, |
| import { loadStripe } from '@stripe/stripe-js'; | import { loadStripe } from '@stripe/stripe-js'; | ||||
| export const useStripe = async ({ lineItems }) => { | |||||
| export const useStripe = async ({ lineItems, userInfo }) => { | |||||
| let stripePromise = null; | let stripePromise = null; | ||||
| const getStripe = () => { | const getStripe = () => { | ||||
| await stripe.redirectToCheckout({ | await stripe.redirectToCheckout({ | ||||
| mode: 'payment', | mode: 'payment', | ||||
| customer_email: userInfo.email, | |||||
| metadata: { | |||||
| name: userInfo.fullName, | |||||
| address: userInfo.address, | |||||
| addressLine2: userInfo.address2, | |||||
| city: userInfo.city, | |||||
| country: userInfo.country, | |||||
| postcode: userInfo.postcode, | |||||
| }, | |||||
| lineItems, | lineItems, | ||||
| successUrl: `${window.location.origin}/review`, | successUrl: `${window.location.origin}/review`, | ||||
| cancelUrl: `${window.location.origin}/cart`, | cancelUrl: `${window.location.origin}/cart`, |
| required: [true, 'Please provide a custom id.'], | required: [true, 'Please provide a custom id.'], | ||||
| unique: [true, 'Custom id must be unique'], | unique: [true, 'Custom id must be unique'], | ||||
| }, | }, | ||||
| stripeID: { | |||||
| type: String, | |||||
| required: [true, 'Please provide a stripe id.'], | |||||
| unique: [true, 'Stripe id must be unique'], | |||||
| }, | |||||
| }); | }); | ||||
| const Product = | const Product = |
| }, | }, | ||||
| email: { | email: { | ||||
| type: String, | type: String, | ||||
| unique: [true, 'Email must be unique.'], | |||||
| required: [true, 'Please provide an email.'], | required: [true, 'Please provide an email.'], | ||||
| trim: true, | trim: true, | ||||
| lowercase: true, | lowercase: true, | ||||
| unique: false, | |||||
| validate(value) { | validate(value) { | ||||
| if (!validator.isEmail(value)) { | if (!validator.isEmail(value)) { | ||||
| throw new Error('Email is invalid'); | throw new Error('Email is invalid'); |