Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cart, product and checkout #135

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
7 changes: 6 additions & 1 deletion src/components/home/categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,12 @@ function CategoriesSection() {
</div>
</div>
<ProductsList
focused={focused === -1 ? 'all' : categories[focused].name}
focused={
focused === -1
? 'all'
: categories.find((category) => category.id === focused)
?.name || categories[0].name
}
/>
</main>
</div>
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
Loading
Loading