Skip to content

Commit

Permalink
feat: CouponPage was added
Browse files Browse the repository at this point in the history
  • Loading branch information
vlasiuk-anatolii committed Jun 7, 2023
1 parent 0aec7c1 commit b87c4dc
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 20 deletions.
Binary file added frontend/public/images/coupons/30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/coupons/40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/coupons/50.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CartPage } from './pages/CartPage/CartPage';
import { HomePage } from './pages/HomePage/HomePage';
import { NotFoundPage } from './pages/NotFoundPage/NotFoundPage';
import { HistoryPage } from './pages/HistoryPage/HistoryPage';
import { CouponsPage } from './pages/CouponsPage/CouponsPage';

export const App = () => {

Expand All @@ -14,8 +15,10 @@ export const App = () => {
<Route path="/" element={(<HomePage />)} />
<Route path="/cart" element={<CartPage />} />
<Route path="/history" element={<HistoryPage />} />
<Route path="/coupons" element={<CouponsPage />} />
{/* <Route path="/product/:id" element={<ProductDetailsPage />} /> */}
<Route path="*" element={<NotFoundPage />} />

</Routes>
</div>
);
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/assets/coupon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ICoupon } from '../react-app-env';
import { v4 as uuidv4 } from 'uuid';

export const currentCoupons: ICoupon[] = [
{
id: '1',
name: 'Save 50%',
discount: 0.5,
code: uuidv4(),
stateUsing: 'unused',
imgUrl: 'images/coupons/50.png',
},
{
id: '2',
name: 'Save 40%',
discount: 0.4,
code: uuidv4(),
stateUsing: 'unused',
imgUrl: 'images/coupons/40.png',
},
{
id: '3',
name: 'Save 30%',
discount: 0.3,
code: uuidv4(),
stateUsing: 'unused',
imgUrl: 'images/coupons/30.png',
}
]
62 changes: 62 additions & 0 deletions frontend/src/components/CardCoupon/CardCoupon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { setError } from '../../store/actions';

type Props = {
id: string,
name: string,
discount: number,
code: string,
stateUsing: string,
imgUrl: string,
};

export const CardCoupon: React.FC<Props> = ({
id,
name,
discount,
code,
stateUsing,
imgUrl,
}) => {

const [isCopied, setIsCopied] = useState(false);

const handleCopyClick = () => {
navigator.clipboard.writeText(`${code}`)
.then(() => {
setIsCopied(true);
})
.catch((error) => {
setError(`Error copying to clipboard: ${error}`);
});
};

return (
<div className="m-2 rounded-3xl ring-1 ring-gray-200">
<div className="p-2 flex flex-wrap justify-center gap-2">

<div className="flex mb-2 lg:w-[400px] lg:h-[260px]">
<img className="rounded-3xl" src={imgUrl} alt={name} />
</div>

<div className="rounded-2xl bg-gray-50 p-3 text-center ring-1 ring-inset ring-gray-900/5">
<div className="mx-auto max-w-xs p-2">
<p className="text-xl font-semibold text-gray-600 mb-1"><span className="text-base">Name:</span> {name}</p>
<p className="text-xl font-semibold text-gray-600 mb-1"><span className="text-base">ID:</span> {id}</p>
<p className="text-xl font-semibold text-gray-600 mb-1"><span className="text-base">State:</span> {stateUsing}</p>
<p className="text-xl font-semibold text-gray-600 mb-1"><span className="text-base">Save:</span> {`${discount * 100} %`}</p>
<p className="text-xl font-semibold text-gray-600 mb-1"><span className="text-base">Code:</span> {`${code.slice(0, 6)}...`}</p>
</div>

{ stateUsing === 'unused' && <button
type="button"
onClick={handleCopyClick}
className="block w-full rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
{isCopied ? 'Copied!' : 'Copy to Clipboard'}
</button>}
</div>
</div>
</div>
)
}
72 changes: 59 additions & 13 deletions frontend/src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getSelectedCart } from '../../store/selectors';
import { getCoupons, getCurrentTotal, getSelectedCart } from '../../store/selectors';
import { useNavigate } from 'react-router-dom';
import { IOrder } from '../../react-app-env';
import { postOrder } from '../../api/api';
import { v4 as uuidv4 } from 'uuid';
import ReCAPTCHA from 'react-google-recaptcha';
import { clearCart, setError } from '../../store/actions';
import { clearCart, setCoupons, setError, setTotal } from '../../store/actions';
const SITE_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI';

interface FormValues {
Expand All @@ -18,13 +18,15 @@ interface FormValues {

interface Props {
address: string,
totalValue: string,
}

export const Form: React.FC<Props> = ({ address, totalValue }) => {
export const Form: React.FC<Props> = ({ address }) => {
const totalValue = useSelector(getCurrentTotal);
const [isRecaptchaVerified, setRecaptchaVerified] = useState(false);
const [errors, setErrors] = useState<Partial<FormValues>>({});
const [submitted, setSubmitted] = useState(false);
const [isCodeApplied, setIsCodeAplied] = useState(false);
const [couponCode, setCouponeCode] = useState('');
const [formValues, setFormValues] = useState<FormValues>({
name: '',
address: address,
Expand All @@ -34,7 +36,8 @@ export const Form: React.FC<Props> = ({ address, totalValue }) => {

const currentProductsInCart = useSelector(getSelectedCart);
const dispatch = useDispatch();
const navigate= useNavigate();
const navigate = useNavigate();
const coupons = useSelector(getCoupons);

const handleRecaptchaChange = (response: string | null) => {
if (response) {
Expand Down Expand Up @@ -69,7 +72,7 @@ export const Form: React.FC<Props> = ({ address, totalValue }) => {
}
setErrors(errors);

return Object.keys(errors).length === 0; // Повертає true, якщо форма валідна
return Object.keys(errors).length === 0;
};

const isValidEmail = (email: string) => {
Expand All @@ -95,32 +98,50 @@ export const Form: React.FC<Props> = ({ address, totalValue }) => {
}));
};

const handleCouponeCode = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
setCouponeCode(value);
};

const handleApplingCode = () => {
const isExistCoupon = coupons.find(item => item.code === couponCode);
if (isExistCoupon) {
const newTotalValue = (+totalValue * (1 - isExistCoupon.discount)).toFixed(2);
setIsCodeAplied(true);
dispatch(setTotal(newTotalValue));
isExistCoupon.stateUsing = 'used';
dispatch(setCoupons([...coupons, isExistCoupon]));
} else {
setIsCodeAplied(false);
}
};


const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (isRecaptchaVerified) {
if (validateForm()) {
setSubmitted(true);

const objToSend: IOrder = {
...formValues,
total: totalValue,
order: currentProductsInCart,
id: uuidv4(),
}

postOrder(objToSend);

setFormValues({
name: '',
address: '',
email: '',
phone: '',
});
setErrors({});


}

dispatch(setError(''));
navigate('/');
dispatch(clearCart(undefined));
Expand All @@ -130,8 +151,8 @@ export const Form: React.FC<Props> = ({ address, totalValue }) => {
};

useEffect(() => {
setFormValues({ ...formValues, address })
}, [address]);
setFormValues({ ...formValues, address });
}, [address, totalValue]);

return (
<form onSubmit={handleSubmit} className="mx-auto mt-4 max-w-xl">
Expand Down Expand Up @@ -214,6 +235,31 @@ export const Form: React.FC<Props> = ({ address, totalValue }) => {
</div>
</div>

<div className="bg-white p-6 my-4 rounded-3xl shadow-lg ring-1 ring-gray-900/5">
<label htmlFor="phone-number" className="block text-sm font-semibold leading-6 text-gray-900">
Coupon code:
</label>
<div className="my-2.5">
<input
type="text"
name="coupon"
id="coupon"
value={couponCode}
onChange={handleCouponeCode}
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>

</div>
<button
type="button"
disabled={isCodeApplied}
onClick={handleApplingCode}
className={`block w-full rounded-md ${isCodeApplied ? 'bg-gray-400' : 'bg-indigo-600' } px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm ${!isCodeApplied && 'hover:bg-indigo-500'} focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600`}
>
{isCodeApplied ? 'Applied!' : 'Apply'}
</button>
</div>

<div className="bg-white p-6 my-4 rounded-3xl shadow-lg ring-1 ring-gray-900/5">
<div className="flex items-center">
<ReCAPTCHA
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Dialog, Disclosure, Popover, Transition } from '@headlessui/react';
import logo from '../../images/svg/logo.svg';
import cart from '../../images/svg/cart.svg';
import history from '../../images/history.png';
import coupon from '../../images/svg/coupon.svg';

import { Error } from '../Error/Error';
import {
Bars3Icon,
Expand Down Expand Up @@ -127,6 +129,10 @@ export function Header() {
<NavLink to="/history" className="relative p-1.5" title='Show history'>
<img className="h-8 w-auto" src={history} alt="icon-history" />
</NavLink>

<NavLink to="/coupons" className="relative p-1.5" title='Show current coupons'>
<img className="h-8 w-auto" src={coupon} alt="icon-coupon" />
</NavLink>
</div>
</Popover.Group>
</nav>
Expand Down Expand Up @@ -188,6 +194,10 @@ export function Header() {
<NavLink to="/history" className="relative p-1.5" title='Show history'>
<img className="h-8 w-auto" src={history} alt="icon-history" />
</NavLink>

<NavLink to="/coupons" className="relative p-1.5" title='Show current coupons'>
<img className="h-8 w-auto" src={coupon} alt="icon-coupon" />
</NavLink>
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/images/svg/coupon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 4 additions & 5 deletions frontend/src/pages/CartPage/CartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import React, { useEffect, useState } from 'react'
import logo from '../../images/svg/logo.svg';
import home from '../../images/svg/home.svg';
import { CardForCart } from '../../components/CardForCart/CardForCart';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { getSelectedCart, getAllShops, getError } from '../../store/selectors';
import { IObjectForCart } from '../../react-app-env';
import { Loader } from '@googlemaps/js-api-loader';
import { APP_KEYS } from '../../consts';
import { Error } from '../../components/Error/Error';
import { setError } from '../../store/actions';
import { setError, setTotal } from '../../store/actions';
import { Form } from '../../components/Form/Form';

export function CartPage() {
const dispatch = useDispatch();
const currentProductsInCart = useSelector(getSelectedCart);
let productsForRender: IObjectForCart[] = [];
const shops = useSelector(getAllShops);
const error = useSelector(getError);
const [totalValue, setTotalValue] = useState('0');
const [getRoute, setGetRoute] = useState(false);
const [distanse, setDistance] = useState(0);
const [timeDelivery, setTimeDelivery] = useState(0);
Expand Down Expand Up @@ -151,7 +151,7 @@ export function CartPage() {
}

useEffect(() => {
setTotalValue(getTotalValue());
dispatch(setTotal(getTotalValue()));
}, [currentProductsInCart]);

useEffect(() => {
Expand Down Expand Up @@ -230,7 +230,6 @@ export function CartPage() {

<Form
address={address}
totalValue={totalValue}
/>
</div>
</div>
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/pages/CouponsPage/CouponsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useEffect } from 'react'
import logo from '../../images/svg/logo.svg';
import home from '../../images/svg/home.svg';
import { useDispatch, useSelector } from 'react-redux';
import { getCoupons, getError } from '../../store/selectors';
import { Error } from '../../components/Error/Error';
import { setCoupons } from '../../store/actions';
import { CardCoupon } from '../../components/CardCoupon/CardCoupon';
import { currentCoupons } from '../../assets/coupon';

export function CouponsPage() {
const dispatch = useDispatch();
const error = useSelector(getError);
const coupons = useSelector(getCoupons);

useEffect(() => {
dispatch(setCoupons(currentCoupons));
}, []);

return (
<>
{error && <Error />}
<div className="bg-white p-6 m-4 rounded-3xl shadow-lg ring-1 ring-gray-900/5">
<a href="#" className=" flex m-1.5 p-1.5">
<span className="sr-only">VAM</span>
<img className="h-8 w-auto mr-3" src={logo} alt="logo" />
<img className="h-8 w-auto" src={home} alt="home" title="Back to home" />
</a>
</div>

<div className="flex flex-wrap justify-center p-6 m-4 rounded-3xl bg-white shadow-lg ring-1 ring-gray-900/5">

{coupons.length === 0
? <p className="p-6"> No coupons!</p>
: coupons.map((item) => {
return (
<div key={item.id} className="p-6 m-4 rounded-3xl bg-white shadow-lg ring-1 ring-gray-900/5">
<div className="text-xl w-8 h-8 bg-black rounded-full font-bold text-white text-center align mb-2">{item.id}</div>
<CardCoupon
key={item.id}
id={item.id}
name={item.name}
discount={item.discount}
code={item.code}
stateUsing={item.stateUsing}
imgUrl={item.imgUrl}
/>
</div>
)
})}

</div >
</>
)
}
Loading

0 comments on commit b87c4dc

Please sign in to comment.