Skip to content

Commit

Permalink
Feat; impelement coupons' functionaties (#109)
Browse files Browse the repository at this point in the history
implement coupons table

fix build & lint error's

fix testing errors

rebase from develop
  • Loading branch information
wayneleon1 authored and niyobern committed Jul 26, 2024
1 parent 9cd870c commit 4fca9b0
Show file tree
Hide file tree
Showing 10 changed files with 2,868 additions and 1,240 deletions.
3,804 changes: 2,657 additions & 1,147 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/__test__/Checkout/checkout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ describe('checkoutSlice', () => {
it('should handle initial state', () => {
expect(store.getState().checkout).toEqual({
checkout: {
id: 31,
totalAmount: 160,
id: -1,
totalAmount: 0,
status: 'Pending',
couponCode: '',
deliveryInfo: {
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('checkoutSlice', () => {
city: 'Anytown',
zip: '12345',
},
id: 31,
id: -1,
orderDetails: [
{
id: 41,
Expand All @@ -102,7 +102,7 @@ describe('checkoutSlice', () => {
paid: true,
paymentInfo: null,
status: 'Pending',
totalAmount: 160,
totalAmount: 0,
trackingNumber: 'Tr280585',
updatedAt: '2024-07-22T11:01:20.291Z',
},
Expand Down
2 changes: 1 addition & 1 deletion src/__test__/home/productCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('ProductCard Component', () => {
expect(halfStar.length).toBe(1);

const emptyStar = screen.getAllByTestId('emptyStar');
expect(emptyStar.length).toBe(Math.floor(4 - mockProduct.averageRating));
expect(emptyStar.length).toBe(Math.floor(5 - mockProduct.averageRating));

const addToCartIcon = screen.getByTestId('addToCart');
expect(addToCartIcon).toBeInTheDocument();
Expand Down
2 changes: 1 addition & 1 deletion src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import checkoutSlice from '@/features/Checkout/checkoutSlice';

import ordersSliceReducer from '@/features/Orders/ordersSlice';
import contactReducer from '@/features/contact/contactSlice';
import couponsSliceReducer from '@/features/Coupons/CouponsFeature';
import userRoleSlice from '@/features/userRole/userRoleSlice';
import couponsSliceReducer from '@/features/Coupons/CouponsFeature';

export const store = configureStore({
reducer: {
Expand Down
17 changes: 12 additions & 5 deletions src/components/Cart/Cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,18 @@ export default function Cart() {
/>
))}
<div className="flex justify-end gap-20 py-6 items-center sticky bottom-0 bg-white">
<div className="flex gap-2 items-center">
<h2 className="text-2xl font-bold text-gray-900">Total:</h2>
<span className="text-xl font-medium text-primary">${total}</span>
</div>
<HSButton title="CHECKOUT" path="/checkout" />
{cartItems.length > 0 && (
<div className="flex gap-2 items-center">
<h2 className="text-2xl font-bold text-gray-900">Total:</h2>
<span className="text-xl font-medium text-primary">${total}</span>
</div>
)}
{cartItems.length === 0 && (
<h2 className="text-2xl font-bold text-gray-900">Cart Empty</h2>
)}
{cartItems.length > 0 && (
<HSButton title="CHECKOUT" path="/checkout" />
)}
</div>
</div>
<div className="flex flex-col gap-12">
Expand Down
38 changes: 32 additions & 6 deletions src/components/Checkout/Checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import CardInput, { Card } from './CardInput';
import { RootState } from '@/app/store';
import { fetchCartItems } from '@/features/Cart/cartSlice';
import { fetchCartItems, selectCartItems } from '@/features/Cart/cartSlice';
import { Checkout as CheckoutType } from '@/interfaces/checkout';
import {
selectCheckout,
placeOrder,
makePayment,
updateStatus,
resetState,
} from '@/features/Checkout/checkoutSlice';
import { useAppDispatch, useAppSelector } from '@/app/hooks';
import {
Expand Down Expand Up @@ -46,6 +47,12 @@ function Checkout() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const user = useAppSelector((state) => state.signIn.user);
const cartItems = useAppSelector((state: RootState) =>
selectCartItems(state)
);
const total = cartItems.reduce((acc, curr) => {
return acc + curr.product.salesPrice * curr.quantity;
}, 0);
function handleAdding() {
setAdding(!adding);
}
Expand All @@ -56,14 +63,17 @@ function Checkout() {
const { loading, error, paying } = checkoutState;
function handleSave(newCard: Card) {
setCards((prev) => [...prev, newCard]);
}

function applyCoupon(e: React.ChangeEvent<HTMLInputElement>) {
setCoupon(e.target.value);
const checkout: CheckoutType = {
deliveryInfo: {
address,
city,
zip: '12345',
},
couponCode: coupon,
couponCode: e.target.value,
email: user?.email || '',
firstName: user?.firstName || '',
lastName: user?.lastName || '',
Expand All @@ -72,7 +82,22 @@ function Checkout() {
}

function handlePayment() {
dispatch(makePayment(order.id));
if (order.id === -1) {
const checkout: CheckoutType = {
deliveryInfo: {
address,
city,
zip: '12345',
},
couponCode: '',
email: user?.email || '',
firstName: user?.firstName || '',
lastName: user?.lastName || '',
};
dispatch(placeOrder(checkout)).then((res) =>
dispatch(makePayment(res.payload.id))
);
} else dispatch(makePayment(order.id));
}

useEffect(() => {
Expand All @@ -82,6 +107,7 @@ function Checkout() {
showSuccessToast('Succesfully Paid');
dispatch(updateStatus(false));
dispatch(fetchCartItems());
dispatch(resetState());
navigate('/');
} else if (paying && error) {
showErrorToast(error || 'failed');
Expand Down Expand Up @@ -313,7 +339,7 @@ function Checkout() {
<input
type="text"
className="border border-gray-300 p-2 rounded-lg flex-grow mr-2 outline-none"
onChange={(e) => setCoupon(e.target.value)}
onChange={applyCoupon}
value={coupon}
/>
<button
Expand Down Expand Up @@ -369,7 +395,7 @@ function Checkout() {
<div className="mb-2">
<div className="flex justify-between py-2 text-xl">
<span className="text-gray-600">Total</span>
<span>${order.totalAmount}</span>
<span>${order.totalAmount || total}</span>
</div>
</div>

Expand All @@ -383,7 +409,7 @@ function Checkout() {
<div className="font-bold">
<div className="flex justify-between py-2 text-xl">
<span className="text-gray-600">Total Cost</span>
<span>${order.totalAmount}</span>
<span>${order.totalAmount || total}</span>
</div>
</div>
</div>
Expand Down
118 changes: 81 additions & 37 deletions src/components/home/ProductCard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import { FaRegHeart, FaHeart } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from '@/app/hooks';
import Cart from '@/interfaces/cart';
import {
addToWishlist,
removeFromWishlist,
} from '@/features/Products/ProductSlice';
import { Product } from '@/types/Product';
import { addCartItem } from '@/features/Cart/cartSlice';
import { addCartItem, removeCartItem } from '@/features/Cart/cartSlice';
import { showSuccessToast } from '@/utils/ToastConfig';

interface ProductCardProps {
product: Product;
}

function ProductCard({ product }: ProductCardProps) {
const [cartId, setCartId] = useState<number | null>(null);
const navigate = useNavigate();
const dispatch = useAppDispatch();
const { token } = useAppSelector((state) => state.signIn);
Expand All @@ -22,6 +26,26 @@ function ProductCard({ product }: ProductCardProps) {
return wishlistProds?.some((wishlistProd) => wishlistProd.id === prod.id);
};

function handleAddtoCart(e: React.MouseEvent<HTMLButtonElement>) {
const element = e.target as HTMLElement;
const [sibling, message, action] = element.classList.contains('bg-red-600')
? [
element.nextSibling,
'Product Removed From Cart',
dispatch(removeCartItem(cartId as number)),
]
: [
element.previousSibling,
'Product added to cart',
dispatch(addCartItem({ productId: product.id, quantity: 1 })),
];
(sibling as HTMLElement)?.style.setProperty('display', 'inline');
element.style.setProperty('display', 'none');
action.then((res) => {
setCartId((res.payload as Cart).id || null);
showSuccessToast(message);
});
}
return (
<div className="shadow-lg rounded-lg relative">
<button
Expand Down Expand Up @@ -103,38 +127,46 @@ function ProductCard({ product }: ProductCardProps) {
}
)}
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
viewBox="0 0 36 36"
data-testid="halfStar"
>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop
offset={`${(product.averageRating - Math.floor(product.averageRating)) * 100}%`}
style={{
stopColor: 'rgb(250 204 21)',
stopOpacity: 1,
}}
/>
<stop
offset={`${(product.averageRating - Math.floor(product.averageRating)) * 100}%`}
style={{
stopColor: 'rgb(156 163 175)',
stopOpacity: 1,
}}
/>
</linearGradient>
</defs>
<path
fill="url(#grad1)"
d="M27.287 34.627c-.404 0-.806-.124-1.152-.371L18 28.422l-8.135 5.834a1.97 1.97 0 0 1-2.312-.008a1.971 1.971 0 0 1-.721-2.194l3.034-9.792l-8.062-5.681a1.98 1.98 0 0 1-.708-2.203a1.978 1.978 0 0 1 1.866-1.363L12.947 13l3.179-9.549a1.976 1.976 0 0 1 3.749 0L23 13l10.036.015a1.975 1.975 0 0 1 1.159 3.566l-8.062 5.681l3.034 9.792a1.97 1.97 0 0 1-.72 2.194a1.957 1.957 0 0 1-1.16.379"
/>
</svg>
{product.averageRating % 1 !== 0 && (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
viewBox="0 0 36 36"
data-testid="halfStar"
>
<defs>
<linearGradient
id="grad1"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop
offset={`${(product.averageRating - Math.floor(product.averageRating)) * 100}%`}
style={{
stopColor: 'rgb(250 204 21)',
stopOpacity: 1,
}}
/>
<stop
offset={`${(product.averageRating - Math.floor(product.averageRating)) * 100}%`}
style={{
stopColor: 'rgb(156 163 175)',
stopOpacity: 1,
}}
/>
</linearGradient>
</defs>
<path
fill="url(#grad1)"
d="M27.287 34.627c-.404 0-.806-.124-1.152-.371L18 28.422l-8.135 5.834a1.97 1.97 0 0 1-2.312-.008a1.971 1.971 0 0 1-.721-2.194l3.034-9.792l-8.062-5.681a1.98 1.98 0 0 1-.708-2.203a1.978 1.978 0 0 1 1.866-1.363L12.947 13l3.179-9.549a1.976 1.976 0 0 1 3.749 0L23 13l10.036.015a1.975 1.975 0 0 1 1.159 3.566l-8.062 5.681l3.034 9.792a1.97 1.97 0 0 1-.72 2.194a1.957 1.957 0 0 1-1.16.379"
/>
</svg>
)}
</div>
</div>
{Array.from({ length: Math.floor(4 - product.averageRating) }).map(
{Array.from({ length: Math.floor(5 - product.averageRating) }).map(
(_, index) => {
return (
<div data-testid="emptyStar" key={index}>
Expand Down Expand Up @@ -162,13 +194,25 @@ function ProductCard({ product }: ProductCardProps) {
${product.regularPrice}
</span>
</div>
<button
type="button"
onClick={() =>
dispatch(addCartItem({ productId: product.id, quantity: 1 }))
}
>
<button type="button" onClick={handleAddtoCart}>
{' '}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="bg-red-600 text-white h-10 w-10 rounded p-2 cursor-pointer"
style={{ display: 'none', backgroundColor: 'red' }}
id="removeFromCart"
>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M20 12H4"
color="currentColor"
></path>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
className="text-white h-10 w-10 rounded p-2 cursor-pointer"
Expand Down
14 changes: 13 additions & 1 deletion src/features/Cart/cartSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,19 @@ export const fetchCartItems = createAsyncThunk(
Authorization: `Bearer ${tokenFromStorage}`,
},
});
return response.data.cartItems;
const res = response.data.cartItems;
res.reduce((acc: Cart[], curr: Cart) => {
const existingItem = acc.find(
(item) => item.product.id === curr.product.id
);
if (existingItem) {
existingItem.quantity += curr.quantity;
} else {
acc.push(curr);
}
return acc;
}, []);
return res;
}
);

Expand Down
8 changes: 6 additions & 2 deletions src/features/Checkout/checkoutSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export interface CheckoutState {
}

const initialOrder: Order = {
id: 31,
totalAmount: 160,
id: -1,
totalAmount: 0,
status: 'Pending',
couponCode: '',
deliveryInfo: {
Expand Down Expand Up @@ -123,6 +123,9 @@ const checkoutSlice = createSlice({
updateLastName: (state, action: PayloadAction<string>) => {
state.checkout.lastName = action.payload;
},
resetState: () => {
return initialState;
},
},
extraReducers: (builder) => {
builder
Expand Down Expand Up @@ -172,6 +175,7 @@ export const {
updateFirstName,
updateLastName,
updateStatus,
resetState,
} = checkoutSlice.actions;

export const selectCheckout = (state: RootState) => state.checkout;
Expand Down
Loading

0 comments on commit 4fca9b0

Please sign in to comment.