diff --git a/package-lock.json b/package-lock.json index 3af8300c..353016a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@mui/material": "^5.16.4", "@reduxjs/toolkit": "^2.2.5", "antd": "^5.19.2", + "aos": "^2.3.4", "axios": "^1.7.2", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -58,6 +59,7 @@ "@testing-library/dom": "^10.2.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", + "@types/aos": "^3.0.7", "@types/fork-ts-checker-webpack-plugin": "^0.4.5", "@types/jest": "^29.5.12", "@types/mini-css-extract-plugin": "^2.5.1", @@ -7491,6 +7493,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/aos": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/aos/-/aos-3.0.7.tgz", + "integrity": "sha512-sEhyFqvKauUJZDbvAB3Pggynrq6g+2PS4XB3tmUr+mDL1gfDJnwslUC4QQ7/l8UD+LWpr3RxZVR/rHoZrLqZVg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, @@ -8801,6 +8810,17 @@ "node": ">= 8" } }, + "node_modules/aos": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/aos/-/aos-2.3.4.tgz", + "integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==", + "license": "MIT", + "dependencies": { + "classlist-polyfill": "^1.0.3", + "lodash.debounce": "^4.0.6", + "lodash.throttle": "^4.0.1" + } + }, "node_modules/app-root-dir": { "version": "1.0.2", "dev": true, @@ -10666,6 +10686,12 @@ "dev": true, "license": "MIT" }, + "node_modules/classlist-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", + "integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==", + "license": "Unlicense" + }, "node_modules/classnames": { "version": "2.5.1", "license": "MIT" @@ -19843,7 +19869,6 @@ }, "node_modules/lodash.debounce": { "version": "4.0.8", - "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { @@ -19857,6 +19882,12 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, "node_modules/log-driver": { "version": "1.2.7", "dev": true, diff --git a/package.json b/package.json index ba875b81..89ddb57f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@mui/material": "^5.16.4", "@reduxjs/toolkit": "^2.2.5", "antd": "^5.19.2", + "aos": "^2.3.4", "axios": "^1.7.2", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -60,6 +61,7 @@ "@testing-library/dom": "^10.2.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", + "@types/aos": "^3.0.7", "@types/fork-ts-checker-webpack-plugin": "^0.4.5", "@types/jest": "^29.5.12", "@types/mini-css-extract-plugin": "^2.5.1", diff --git a/public/assets/images/23172.jpg b/public/assets/images/23172.jpg new file mode 100644 index 00000000..0f100dda Binary files /dev/null and b/public/assets/images/23172.jpg differ diff --git a/public/assets/images/add-cart-buy-now-online-commerce-graphic-concept.jpg b/public/assets/images/add-cart-buy-now-online-commerce-graphic-concept.jpg new file mode 100644 index 00000000..dd9f5cb3 Binary files /dev/null and b/public/assets/images/add-cart-buy-now-online-commerce-graphic-concept.jpg differ diff --git a/public/assets/images/cropped-image-woman-inputting-card-information-key-phone-laptop-while-shopping-online.jpg b/public/assets/images/cropped-image-woman-inputting-card-information-key-phone-laptop-while-shopping-online.jpg new file mode 100644 index 00000000..e79ff491 Binary files /dev/null and b/public/assets/images/cropped-image-woman-inputting-card-information-key-phone-laptop-while-shopping-online.jpg differ diff --git a/public/assets/images/cyber-monday-shopping-sales.jpg b/public/assets/images/cyber-monday-shopping-sales.jpg new file mode 100644 index 00000000..f4e761e8 Binary files /dev/null and b/public/assets/images/cyber-monday-shopping-sales.jpg differ diff --git a/public/assets/images/happy-man-with-handbags-dancing-after-shopping-spree.jpg b/public/assets/images/happy-man-with-handbags-dancing-after-shopping-spree.jpg new file mode 100644 index 00000000..0961433d Binary files /dev/null and b/public/assets/images/happy-man-with-handbags-dancing-after-shopping-spree.jpg differ diff --git a/public/assets/images/laptop-shopping-bags-online-shopping-concept.jpg b/public/assets/images/laptop-shopping-bags-online-shopping-concept.jpg new file mode 100644 index 00000000..e419aed7 Binary files /dev/null and b/public/assets/images/laptop-shopping-bags-online-shopping-concept.jpg differ diff --git a/public/assets/images/shopping-cart-gift-boxes.jpg b/public/assets/images/shopping-cart-gift-boxes.jpg new file mode 100644 index 00000000..6ebaab30 Binary files /dev/null and b/public/assets/images/shopping-cart-gift-boxes.jpg differ diff --git a/public/assets/images/still-life-say-no-fast-fashion.jpg b/public/assets/images/still-life-say-no-fast-fashion.jpg new file mode 100644 index 00000000..886e5c6e Binary files /dev/null and b/public/assets/images/still-life-say-no-fast-fashion.jpg differ diff --git a/public/assets/shoe1.jpeg b/public/assets/shoe1.jpeg deleted file mode 100644 index aa06ea37..00000000 Binary files a/public/assets/shoe1.jpeg and /dev/null differ diff --git a/public/assets/shoe2.jpeg b/public/assets/shoe2.jpeg deleted file mode 100644 index 45f67048..00000000 Binary files a/public/assets/shoe2.jpeg and /dev/null differ diff --git a/public/assets/shoe3.jpeg b/public/assets/shoe3.jpeg deleted file mode 100644 index aac735c5..00000000 Binary files a/public/assets/shoe3.jpeg and /dev/null differ diff --git a/public/assets/shoe4.jpeg b/public/assets/shoe4.jpeg deleted file mode 100644 index 2e6ea55a..00000000 Binary files a/public/assets/shoe4.jpeg and /dev/null differ diff --git a/src/App.scss b/src/App.scss index 55c86793..be22122e 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,5 +1,6 @@ @import './assets/styles/Colors.scss'; @import './assets/styles/style.scss'; +@import './assets/styles/toastify.scss'; @import './assets/styles/Sample.scss'; @import './assets/styles/signup.scss'; @import './assets/styles/Login.scss'; diff --git a/src/assets/styles/Colors.scss b/src/assets/styles/Colors.scss index 6a8af75a..be501a2e 100644 --- a/src/assets/styles/Colors.scss +++ b/src/assets/styles/Colors.scss @@ -26,9 +26,24 @@ $text-family: 'Averia Serif Libre'; $white-color: #FFFFFF; $border-color: #D9D9D9; $red-color:red; +$grayColor:gray; $primary-color-half: rgb(248,244,244); $red-color:red; $menu-hover: #FE975B; $green-color: rgb(3, 216, 3); $green-middle-color: rgba(3, 216, 3, 0.3); $red-middle-color: rgba(216, 8, 3, 0.3); + + +$toastify-color-light: #fff; +$toastify-color-dark: #121212; +$toastify-color-info: #3498db; +$toastify-color-success: orange; +$toastify-color-warning: #f1c40f; +$toastify-color-error: #e74c3c; + + +$toastify-icon-color-info: var($toastify-color-info); +$toastify-icon-color-success: var($toastify-color-success); +$toastify-icon-color-warning: var($toastify-color-warning); +$toastify-icon-color-error: var($toastify-color-error); \ No newline at end of file diff --git a/src/assets/styles/LandingPage.scss b/src/assets/styles/LandingPage.scss index a9b41e22..aea5f480 100644 --- a/src/assets/styles/LandingPage.scss +++ b/src/assets/styles/LandingPage.scss @@ -1,6 +1,7 @@ .landing-container { padding: 3rem 2rem 4rem 5rem; background-color: $container-color; + .loader { display: flex; justify-content: center; @@ -8,6 +9,44 @@ height: 100vh; } + .load-more { + display: flex; + justify-content: center; + align-items: center; + margin-top: 2rem; + padding: 1rem 2rem; + cursor: pointer; + + button { + background-color: $primary-color; + color: $white-color; + border: none; + padding: 0.8rem 1rem; + border-radius: 0.5rem; + cursor: pointer; + } + + button:hover { + background-color: $primary-color-dark; + padding: 0.8rem 2rem; + transition: all 0.5s ease-in-out; + animation: bounce 1s infinite; + + } + + @keyframes bounce { + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-10px); + } + } + } + .error-message { display: flex; justify-content: center; @@ -51,7 +90,8 @@ label { width: 15rem; } - .span{ + + .span { width: 20rem; } } diff --git a/src/assets/styles/Sample.scss b/src/assets/styles/Sample.scss index 581bc697..a7b7152f 100644 --- a/src/assets/styles/Sample.scss +++ b/src/assets/styles/Sample.scss @@ -1,31 +1,31 @@ .sampleImages1 { display: flex; - width: 100%; + width: 100vw; color: $white; background-color: $white; - z-index: -1; gap: 1rem; - height: 30rem; + height: 40rem; .sample1, .sample3 { flex: 1; display: flex; flex-direction: column; - width: 30%; + width: 20%; z-index: 100; + height: 40rem; gap: 1rem; } .sample2 { - flex: 1; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - width: 30%; - z-index: 100; + width: 40%; display: flex; position: relative; + img{ + width: 100%; + height: 100%; + object-fit: fill; + } .arrow { position: absolute; top: 50%; @@ -73,13 +73,13 @@ align-items: center; position: relative; z-index: 100; + height: 40rem; img{ width: 100%; - height: 14.5rem; - object-fit: cover; + height: 100%; } p { - font-size: 1.4rem; + font-size: 1.8rem; } button { @@ -103,6 +103,7 @@ flex-direction: column; align-items: flex-start; left: 3rem; + gap: 1rem; } button { @@ -118,6 +119,8 @@ align-items: flex-start; position: absolute; left: 3rem; + gap: 1rem; + } button { @@ -136,6 +139,8 @@ align-items: flex-end; position: absolute; right: 3rem; + gap: 1rem; + } button { @@ -144,7 +149,6 @@ } .accessories { - // background-image: url('../../public/assets/right-bottom.png'); .text-container { display: flex; @@ -152,8 +156,10 @@ align-items: flex-end; position: absolute; right: 3rem; + gap: 1rem; + p { - padding-right: 1.5rem; + padding-right: .1rem; } button { diff --git a/src/assets/styles/ViewCart.scss b/src/assets/styles/ViewCart.scss index 86d7670c..35402ef0 100644 --- a/src/assets/styles/ViewCart.scss +++ b/src/assets/styles/ViewCart.scss @@ -42,6 +42,7 @@ FaCheckSquare { border: 1px solid $border-color; border-radius: 10px; margin-bottom: 10px; + margin-left: 1rem; .title { display: flex; diff --git a/src/assets/styles/toastify.scss b/src/assets/styles/toastify.scss new file mode 100644 index 00000000..3fa58320 --- /dev/null +++ b/src/assets/styles/toastify.scss @@ -0,0 +1,44 @@ +.Toastify__toast-container { + z-index: 100; + width: 380px; + font-size: 1.5rem; + font-weight: 500; + .Toastify__toast--success { + font-size: medium; + text-align: center; + color: $white; + background: $toastify-color-success; + } + .Toastify__toast--info { + color: $white-color; + text-align: center; + background: $toastify-color-info; + } + .Toastify__toast--warning { + text-align: center; + color: yellow; + background: gray; + } + .Toastify__toast--error { + color: rgb(178, 3, 3); + text-align: center; + background:$toastify-color-error; + } + .Toastify__progress-bar--info { + background: blue; + } + .Toastify__progress-bar--success { + background: $primary-color-light; + } + .Toastify__progress-bar--warning { + background: var(--toastify-color-progress-warning); + } + .Toastify__progress-bar--error { + background: $red-color; + } + .Toastify__toast-icon{ + svg{ + fill: white; + } + } + } \ No newline at end of file diff --git a/src/components/layout/Sample.tsx b/src/components/layout/Sample.tsx index bd757f27..2975a3e6 100644 --- a/src/components/layout/Sample.tsx +++ b/src/components/layout/Sample.tsx @@ -12,8 +12,12 @@ import leftBottom from "../../../public/assets/images/left-bottom.png"; import rightBottom from "../../../public/assets/images/right-bottom.png"; const images = [ '/assets/middle.png', - '/assets/shoe2.jpeg', - '/assets/shoe3.jpeg' + '/assets/images/1293.jpg', + '/assets/images/add-cart-buy-now-online-commerce-graphic-concept.jpg', + '/assets/images/cropped-image-woman-inputting-card-information-key-phone-laptop-while-shopping-online.jpg', + '/assets/images/cyber-monday-shopping-sales.jpg', + '/assets/images/happy-man-with-handbags-dancing-after-shopping-spree.jpg', + '/assets/images/laptop-shopping-bags-online-shopping-concept.jpg', ]; const Sample: React.FC = () => { @@ -69,7 +73,8 @@ const Sample: React.FC = () => { -
+
+
diff --git a/src/components/product/Product.tsx b/src/components/product/Product.tsx index 02f9bc88..d24de3b0 100644 --- a/src/components/product/Product.tsx +++ b/src/components/product/Product.tsx @@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector } from '../../store/store'; import { createCart, getUserCarts } from "../../store/features/carts/cartSlice"; import { addProductToWishlist, removeProductFromWishlist, fetchWishlistProducts } from '../../store/features/wishlist/wishlistSlice'; import { toast } from "react-toastify"; +import aos from "aos" interface ProductProps { id: string; @@ -36,6 +37,9 @@ const Product: React.FC = ({ const isProductInWishlist = wishlist.some((item: any) => item.id === id); const [isInWishlist, setIsInWishlist] = useState(isProductInWishlist); + useEffect(()=>{ + aos.init({ once: true }); + },[]); useEffect(() => { setIsInWishlist(isProductInWishlist); }, [isProductInWishlist]); @@ -142,7 +146,7 @@ const Product: React.FC = ({ return ( -
+
{ const { products, isError, isSuccess, isLoading, message } = useAppSelector( (state: any) => state.products ); + const [visibleProducts, setVisibleProducts] = useState(20); useEffect(() => { dispatch(fetchProducts()); }, [dispatch]); @@ -60,6 +61,10 @@ const LandingPage: React.FC = () => { checkProductToCartPending(); }, []); + const handleLoadMore = () => { + setVisibleProducts((prevVisibleProducts) => prevVisibleProducts + 20); + }; + return ( <> @@ -81,7 +86,9 @@ const LandingPage: React.FC = () => {
{isSuccess && Array.isArray(products) && - products?.map((product: any) => ( + products + .slice(0,visibleProducts) + .map((product: any) => ( { /> ))}
+ {visibleProducts < products.length && (
+ +
)}
)}
diff --git a/src/pages/Products.tsx b/src/pages/Products.tsx index a22ec25d..39aa692d 100644 --- a/src/pages/Products.tsx +++ b/src/pages/Products.tsx @@ -21,6 +21,8 @@ const ProductsPage: React.FC = () => { const [maxPrice, setMaxPrice] = useState(0); const [minPrice, setMinPrice] = useState(0); + const [visibleProducts, setVisibleProducts] = useState(20); + useEffect(() => { dispatch(fetchProducts()); }, [dispatch]); @@ -81,6 +83,10 @@ const ProductsPage: React.FC = () => { return price >= priceRange[0] && price <= priceRange[1]; }); + const handleLoadMore = () => { + setVisibleProducts((prevVisibleProducts) => prevVisibleProducts + 20); + }; + return ( <> @@ -116,7 +122,9 @@ const ProductsPage: React.FC = () => {
{isSuccess && Array.isArray(filteredProducts) && - filteredProducts.map((product: any) => ( + filteredProducts + .slice(0,visibleProducts) + .map((product: any) => ( { /> ))}
+ {visibleProducts < products.length && (
+ +
)}
)}
diff --git a/src/pages/UserCartPaymentSuccess.tsx b/src/pages/UserCartPaymentSuccess.tsx new file mode 100644 index 00000000..1ac0c846 --- /dev/null +++ b/src/pages/UserCartPaymentSuccess.tsx @@ -0,0 +1,44 @@ +/* eslint-disable */ +import React, { useEffect, useState } from 'react'; +import { Meta } from '../components/Meta'; +import { useAppDispatch, useAppSelector } from '../store/store'; +import { PuffLoader, PulseLoader } from 'react-spinners'; +import { toast } from 'react-toastify'; +import { checkout, getUserCarts } from '../store/features/carts/cartSlice'; +import { + FaCheckSquare, + FaMinus, + FaPlus, + FaEdit, + FaTrash, + FaGift, + FaShippingFast, +} from 'react-icons/fa'; +import { useNavigate } from 'react-router-dom'; +import Product from '../components/product/Product'; +import { Box, LinearProgress } from '@mui/material'; +import { useLocation } from 'react-router-dom'; + +const UserCartPaymentSuccess: React.FC = () => { + const dispatch = useAppDispatch(); + const [isLoading, setIsLoading] = useState(true); + const [currentEndpoint, setCurrentEndpoint] = useState(''); + const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + toast.success('Cart payment success'); + navigate('/shopping-cart'); + }, [location.search, navigate]); + + return ( + <> + +
+ +
+ + ); +}; + +export default UserCartPaymentSuccess; diff --git a/src/pages/UserLogin.tsx b/src/pages/UserLogin.tsx index b708838a..f0c9979c 100644 --- a/src/pages/UserLogin.tsx +++ b/src/pages/UserLogin.tsx @@ -1,24 +1,24 @@ /* eslint-disable */ -import React, { useEffect, useRef, useState } from "react"; -import { FcGoogle } from "react-icons/fc"; -import { BiSolidShow } from "react-icons/bi"; -import { BiSolidHide } from "react-icons/bi"; -import { useAppDispatch, useAppSelector } from "../store/store"; -import { loginUser } from "../store/features/auth/authSlice"; -import { toast } from "react-toastify"; -import { useFormik } from "formik"; -import * as Yup from "yup"; -import { Link, useNavigate } from "react-router-dom"; -import { PulseLoader } from "react-spinners"; -import { addProductToWishlist } from "../store/features/wishlist/wishlistSlice"; -import authService from "../store/features/auth/authService"; +import React, { useEffect, useRef, useState } from 'react'; +import { FcGoogle } from 'react-icons/fc'; +import { BiSolidShow } from 'react-icons/bi'; +import { BiSolidHide } from 'react-icons/bi'; +import { useAppDispatch, useAppSelector } from '../store/store'; +import { loginUser } from '../store/features/auth/authSlice'; +import { toast } from 'react-toastify'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import { Link, useNavigate } from 'react-router-dom'; +import { PulseLoader } from 'react-spinners'; +import { addProductToWishlist } from '../store/features/wishlist/wishlistSlice'; +import authService from '../store/features/auth/authService'; import { joinRoom } from '../utils/socket/socket'; const LoginSchema = Yup.object().shape({ email: Yup.string() - .email("Email must be valid") - .required("Email is required"), - password: Yup.string().required("Password is required"), + .email('Email must be valid') + .required('Email is required'), + password: Yup.string().required('Password is required'), }); function UserLogin() { @@ -39,35 +39,39 @@ function UserLogin() { const formik = useFormik({ initialValues: { - email: "", - password: "", + email: '', + password: '', }, validationSchema: LoginSchema, onSubmit: async (values) => { + const { email } = values; + localStorage.setItem('loggedEmail', email); const action = await dispatch(loginUser(values)); if (loginUser.fulfilled.match(action)) { - const pendingWishlistProduct = localStorage.getItem("pendingWishlistProduct"); + const pendingWishlistProduct = localStorage.getItem( + 'pendingWishlistProduct' + ); if (pendingWishlistProduct) { await dispatch(addProductToWishlist(pendingWishlistProduct)); - localStorage.removeItem("pendingWishlistProduct"); + localStorage.removeItem('pendingWishlistProduct'); } } - } + }, }); useEffect(() => { - const token = localStorage.getItem("token"); + const token = localStorage.getItem('token'); if (token) { - navigate("/home"); + navigate('/home'); } }, [navigate]); useEffect( function () { if (isSuccess && token && isAuthenticated) { - localStorage.setItem("token", token); + localStorage.setItem('token', token); toast.success(message); - navigate("/home"); + navigate('/home'); formik.resetForm(); joinRoom(token); } @@ -121,7 +125,7 @@ function UserLogin() {
) ) : ( - "" + '' )}

@@ -161,11 +165,11 @@ function UserLogin() { ) : null} @@ -186,4 +190,4 @@ function UserLogin() { ); } -export default UserLogin; \ No newline at end of file +export default UserLogin; diff --git a/src/pages/UserViewCart.tsx b/src/pages/UserViewCart.tsx index c9a52fb4..ff423333 100644 --- a/src/pages/UserViewCart.tsx +++ b/src/pages/UserViewCart.tsx @@ -11,6 +11,10 @@ import { createCart, clearCart, clearCartProduct, + createProductStripe, + createSessionStripe, + updateCartStatus, + userSaveOrder, } from "../store/features/carts/cartSlice"; import { FaCheckSquare, @@ -32,6 +36,8 @@ import Product from "../components/product/Product"; import { Box, LinearProgress } from "@mui/material"; import Button from "@mui/material/Button"; import Dispatch from "react"; +import { fetchUserProfile } from "../store/features/user/userSlice"; +import { useLocation } from "react-router-dom"; const UserViewCart: React.FC = () => { const dispatch = useAppDispatch(); @@ -40,46 +46,184 @@ const UserViewCart: React.FC = () => { const [cartData, setCartData] = useState(null); const [cartResponseData, setCartResponseData] = useState(null); const [isLoggedOut, setIsLoggedOut] = useState(false); - const [checkoutData, setcheckoutData] = useState(null); + const [checkoutData, setCheckoutData] = useState(null); const [isCheckoutSuccess, setCheckoutSuccess] = useState(null); const [isPreloader, setIsPreloader] = useState(false); const [discount, setDiscount] = useState(0); const [totalProductPrice, setTotalProductPrice] = useState(0); - const [arrayOfProduct, setarrayOfProduct] = useState(null); + const [arrayOfProduct, setArrayOfProduct] = useState(null); const [quantities, setQuantities] = useState<{ [key: string]: number }>({}); const [open, setOpen] = useState(false); + const [cartToPay, setCartToPay] = useState(null); + const [amountToPay, setAmountToPay] = useState(null); + const [stripePrice, setStripePrice] = useState(""); + const [currentEndpoint, setCurrentEndpoint] = useState(""); const navigate = useNavigate(); + const location = useLocation(); const cartState = useAppSelector((state) => state.cart); useEffect(() => { - const fetchCarts = async () => { - try { - setIsLoading(true); - const response = await dispatch(getUserCarts()); - const response1 = await dispatch(getUserCarts()).unwrap(); - if (response.payload === "Not authorized") { - setIsLoggedOut(true); - toast.error("Please login first"); - navigate("/login"); - } - setCartResponseData(response1.data); - setIsLoading(false); - } catch (error: any) { - if (error === "Not authorized") { - setIsLoggedOut(true); - toast.error("Please login first"); - navigate("/login"); - } - console.error("Error fetching carts:", error); - setIsLoading(false); - setIsError(true); - toast.error(error.message); - } - }; fetchCarts(); + checkPayFailOrSuccess(); }, [dispatch]); + const fetchCarts = async () => { + try { + setIsLoading(true); + const response = await dispatch(getUserCarts()); + const response1 = await dispatch(getUserCarts()).unwrap(); + if (response.payload === "Not authorized") { + setIsLoggedOut(true); + toast.error("Please login first"); + navigate("/login"); + } + setCartResponseData(response1.data); + setIsLoading(false); + } catch (error: any) { + if (error === "Not authorized") { + setIsLoggedOut(true); + toast.error("Please login first"); + navigate("/login"); + } + console.error("Error fetching carts:", error); + setIsLoading(false); + setIsError(true); + toast.error(error.message); + } + }; + const handleCartCheckOut = async ( + cartId: string, + index: number, + productsArr: Array<{ + name: string; + description: string; + image: string; + price: string; + shopId: string; + }> + ) => { + setIsPreloader(true); + try { + const response = await dispatch(checkout(cartId)); + if (!response.payload) { + throw new Error( + "Checkout failed, Check your internet connection or try again later" + ); + } + localStorage.setItem("cartToPay", cartId); + + localStorage.setItem("productsToSave", JSON.stringify(productsArr)); + const totalCartAmount = response.payload.data.totalAmount; + const array = cartResponseData.carts[index]; + + setArrayOfProduct(array); + setCheckoutData(totalCartAmount); + + const totalProductPrice = array.products.reduce( + (acc, product) => acc + parseFloat(product.price), + 0 + ); + setTotalProductPrice(totalProductPrice); + + const names = productsArr.map((product) => product.name).join(", "); + const descriptions = productsArr + .map((product) => product.description) + .join(", "); + const image1 = productsArr[0]?.image; + const image2 = productsArr[1]?.image; + const shopidToSave: any = productsArr[0]?.shopId; + localStorage.setItem("shopIdToSave", shopidToSave); + + const unit_amount = Math.round(totalCartAmount * 100); + + setAmountToPay(unit_amount); + const stripeProduct = await dispatch( + createProductStripe({ + name: names, + description: descriptions, + image1, + image2, + unit_amount: unit_amount, + }) + ); + console.log(stripeProduct); + localStorage.setItem( + "stripePrice", + stripeProduct.payload.data.product.default_price + ); + + setCheckoutSuccess(true); + + toast.success("Checkout is done Successfully"); + } catch (error) { + console.error("Checkout failed", error); + toast.error("Checkout failed"); + } finally { + setIsPreloader(false); + } + }; + + const handlePayCart = async () => { + try { + setIsPreloader(true); + const profile: any = await dispatch(fetchUserProfile()); + const data = { + successUrl: + "https://e-commerce-ninja-fn-staging.netlify.app/shopping-cart?success", + cancelUrl: + "https://e-commerce-ninja-fn-staging.netlify.app/shopping-cart?cancel", + customerEmail: profile.payload.email, + price: localStorage.getItem("stripePrice"), + }; + const response = await dispatch(createSessionStripe(data)); + const url = response.payload.data.session.url; + window.location.href = url; + } catch (error) { + console.error("Checkout failed", error); + toast.error("Payment initialization failed, try again later"); + } finally { + setIsPreloader(false); + } + }; + + const checkPayFailOrSuccess = async () => { + const params = window.location.search.slice(1); + if (params === "success") { + setIsPreloader(true); + const cartId = localStorage.getItem("cartToPay"); + const products = localStorage.getItem("productsToSave"); + const shopId = localStorage.getItem("shopIdToSave"); + if (!cartId || !products || !shopId) { + navigate("/shopping-cart"); + toast.error("Unknown error occurred saving order"); + return; + } + const data = { + cartId: cartId, + status: "Paid", + }; + const cartStatus = await dispatch(updateCartStatus(data)); + const order = await dispatch( + userSaveOrder({ + cartId: cartId, + paymentMethodId: "Stripe", + products: products, + shopId: shopId, + }) + ); + toast.success("Cart Payment successful"); + localStorage.removeItem("cartToPay"); + localStorage.removeItem("productsToSave"); + localStorage.removeItem("shopIdToSave"); + navigate("/shopping-cart"); + setIsPreloader(false); + } else if (params === "cancel") { + toast.error("Payment cancelled successfully"); + navigate("/shopping-cart"); + setIsPreloader(false); + } + }; if (isLoading) { return ( @@ -92,7 +236,7 @@ const UserViewCart: React.FC = () => { if (isError) { return (

-

Failed to load cart products. Please try again later.

+

No cart found.

); } @@ -112,23 +256,6 @@ const UserViewCart: React.FC = () => { ); } - const handleCartCheckOut = async (cartId, index) => { - setIsPreloader(true); - const response = await dispatch(checkout(cartId)); - const array = cartResponseData?.carts[index]; - setarrayOfProduct(array); - setcheckoutData(response.payload.data.totalAmount); - const totalProductPrice = array.products.reduce( - (acc, product) => - acc + parseFloat(product.price) * (product.quantity || 1), - 0 - ); - setTotalProductPrice(totalProductPrice); - setCheckoutSuccess(true); - setIsPreloader(false); - toast.success("Checkout is done Successfully"); - }; - const handleAddProductToCart = async ( productId: string, quantity: number @@ -175,22 +302,18 @@ const UserViewCart: React.FC = () => { } }; const handleClearCart = async () => { - try { - await dispatch(clearCarts()).unwrap(); - const response1 = await dispatch(getUserCarts()).unwrap(); - setCartResponseData(response1.data); - - toast.success("Cart cleared successfully"); - setCartResponseData({ ...cartResponseData, carts: [] }); - setTotalProductPrice(0); - } catch (error) { - console.error("Error clearing cart:", error); - toast.error("Failed to clear the cart"); - } + setIsPreloader(true); + await dispatch(clearCarts()).unwrap(); + setIsPreloader(false); + await fetchCarts(); + toast.success("Cart cleared successfully"); + navigate("/shopping-cart"); + handleClose(); }; const handleClearSingleCart = async (cartId) => { try { + setIsPreloader(true); await dispatch(clearCart(cartId)); const response1 = await dispatch(getUserCarts()).unwrap(); setCheckoutSuccess(false); @@ -199,11 +322,13 @@ const UserViewCart: React.FC = () => { (cart) => cart.cartId !== cartId ); setCartResponseData({ ...cartResponseData, carts: remainingCarts }); - + setIsPreloader(false); toast.success("Cart cleared successfully"); } catch (error) { console.error("Error clearing cart:", error); toast.error("Failed to clear the cart"); + } finally { + setIsPreloader(false); } }; @@ -213,6 +338,7 @@ const UserViewCart: React.FC = () => { cartProductsList ) => { try { + setIsPreloader(true); if (cartProductsList.length <= 1) { await handleClearSingleCart(cartId); return; @@ -224,6 +350,8 @@ const UserViewCart: React.FC = () => { } catch (error) { toast.error("Failed to clear the product "); throw error; + } finally { + setIsPreloader(false); } }; @@ -249,61 +377,71 @@ const UserViewCart: React.FC = () => {
)} +
-
- -
- {cartResponseData?.carts?.length > 0 && - cartResponseData.carts.map((cart: any, index) => ( -
-
- -

Shopping Cart

+
+ +
+ {cartResponseData?.carts?.map((cart: any, index) => ( +
+
+ +

Shopping Cart

+ {cart.status === "Paid" ? ( + + ) : ( + )} - handleClearSingleCart(cart.cartId)} - /> -
-
- {cart.products.map((product: any) => { - return ( -
-
- -
-
- {product.name} -
-
-

navigate(`/product/${product.id}`)} - > - {product.name} -

-
-
- - {product.discount} - -
${product.price}
-
+ handleClearSingleCart(cart.cartId)} + /> +
+
+ {cart.products.map((product: any) => { + return ( +
+
+ +
+
+ {product.name} +
+
+

navigate(`/product/${product.id}`)} + > + {product.name} +

+
+
+ + {product.discount} + +
${product.price}
+
{product.description}
+
+ {cart.status !== "Paid" && (
-
+ )}
- ); - })} -
+
+ ); + })}
- ))} +
+ ))}
@@ -395,7 +534,7 @@ const UserViewCart: React.FC = () => {
Total amount:
${checkoutData.toFixed(2)}
- +
) : null} { return (
@@ -40,10 +41,19 @@ const AppRouter: React.FC = () => { } /> } /> } /> - } /> - } /> + } + /> + } + /> } /> - } /> + } + /> } /> } /> } /> @@ -51,6 +61,15 @@ const AppRouter: React.FC = () => { }/> } /> } /> + } /> + } /> + } /> + + }> + } /> + } /> + } /> + } /> }> } /> @@ -60,7 +79,14 @@ const AppRouter: React.FC = () => { } /> - }> + + + + } + > } /> } /> diff --git a/src/store/features/carts/cartService.tsx b/src/store/features/carts/cartService.tsx index fca91b95..913abf99 100644 --- a/src/store/features/carts/cartService.tsx +++ b/src/store/features/carts/cartService.tsx @@ -1,25 +1,25 @@ /* eslint-disable */ -import { axiosInstance } from "../../../utils/axios/axiosInstance"; +import { axiosInstance } from '../../../utils/axios/axiosInstance'; const createCart = async (productId: string, quantity: number) => { try { - const response = await axiosInstance.post("/api/cart/create-update-cart", { + const response = await axiosInstance.post('/api/cart/create-update-cart', { productId, quantity, }); return response.data; } catch (error) { - console.error("Error creating cart", error); + console.error('Error creating cart', error); throw error; } }; const getUserCarts = async () => { try { - const response = await axiosInstance.get("/api/cart/buyer-get-carts"); + const response = await axiosInstance.get('/api/cart/buyer-get-carts'); return response.data; } catch (error) { - console.error("Error getting user carts", error); + console.error('Error getting user carts', error); throw error; } }; @@ -30,39 +30,141 @@ const productCheckout = async (cartId: string) => { return response.data; }; const clearCart = async (cartId: string) => { -try { - const response = await axiosInstance.delete(`api/cart/buyer-clear-cart/${cartId}`); - return response.data; -} catch (error) { - throw error -} -} + try { + const response = await axiosInstance.delete( + `api/cart/buyer-clear-cart/${cartId}` + ); + return response.data; + } catch (error) { + throw error; + } +}; const clearCartProduct = async (cartId: string, productId: string) => { - try { - const response = await axiosInstance.delete(`api/cart/buyer-clear-cart-product/${cartId}/${productId}`); - return response.data; - } catch (error) { - throw error - } -} -const clearCarts = async () => { try { - const response = await axiosInstance.delete("/api/cart/buyer-clear-carts"); - return response + const response = await axiosInstance.delete( + `api/cart/buyer-clear-cart-product/${cartId}/${productId}` + ); + return response.data; + } catch (error) { + throw error; } - catch (error) { - console.error("Error clear carts", error); +}; +const clearCarts = async () => { + try { + const response = await axiosInstance.delete('/api/cart/buyer-clear-carts'); + console.log('MRR', response); + return response; + } catch (error) { + console.error('Error clear carts', error); throw error; } -} +}; + +const createStripeProduct = async (data) => { + const response = await axiosInstance.post('/api/cart/create-stripe-product', { + planInfo: { + active: true, + name: data.name, + 'images[0]': data.image1, + 'images[1]': data.image2, + url: 'https://e-commerce-ninja-fn-staging.netlify.app', + description: data.description, + default_price_data: { + unit_amount: data.unit_amount, + currency: 'usd', + }, + }, + }); + + return response.data; +}; +const createStripeSession = async (data) => { + const customer_email = data.email; + console.log('Cuatomer email', customer_email); + const response = await axiosInstance.post( + '/api/cart/checkout-stripe-session', + // { + // sessionInfo: { + // success_url: data.successUrl, + // cancel_url: data.cancelUrl, + // customer_email: data.customerEmail, + // mode: 'payment', + // ui_mode: 'hosted', + // payment_method_types: ['card'], + // line_items: [ + // { + // quantity: 1, + // price: data.price, + // }, + // ], + // }, + // sessionInfo: { + // success_url: + // 'http://localhost:3000/api-gateway/stripe/checkout-payment-succeeded', + // cancel_url: + // 'http://localhost:3000/api-gateway/stripe/checkout-payment-cancelled', + // customer_email: 'k.joshua800@gmail.com', + // mode: 'payment', + // ui_mode: 'hosted', + // payment_method_types: ['card'], + // line_items: [ + // { + // quantity: 1, + // price: 'price_1PjIQRP2sfrKqqIOWUYjolbZ', + // }, + // ], + // }, + // } + { + sessionInfo: { + success_url: data.successUrl, + cancel_url: data.cancelUrl, + customer_email: data.customerEmail, + mode: 'payment', + ui_mode: 'hosted', + payment_method_types: ['card'], + line_items: [ + { + quantity: 1, + price: data.price, + }, + ], + }, + } + ); + return response.data; +}; + +const updateCartStatus = async (data) => { + const response = await axiosInstance.put('/api/cart/update-cart-status', { + cartId: data.cartId, + status: data.status, + }); + return response.data; +}; + +const saveOrder = async (data) => { + const response = await axiosInstance.post('/api/cart/user-create-order', { + cartId: data.cartId, + paymentMethodId: data.paymentMethodId, + products: data.products, + status: 'pending', + shopId: data.shopId, + }); + return response.data; +}; const cartService = { createCart, getUserCarts, productCheckout, clearCarts, clearCart, - clearCartProduct + clearCartProduct, + createStripeProduct, + createStripeSession, + updateCartStatus, + saveOrder, }; export default cartService; diff --git a/src/store/features/carts/cartSlice.tsx b/src/store/features/carts/cartSlice.tsx index 91672243..b643bc63 100644 --- a/src/store/features/carts/cartSlice.tsx +++ b/src/store/features/carts/cartSlice.tsx @@ -1,15 +1,15 @@ /* eslint-disable */ -import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; -import cartService from "./cartService"; -import { getErrorMessage } from "../../../utils/axios/axiosInstance"; -import { iCartInitialResource } from "../../../utils/types/store"; +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import cartService from './cartService'; +import { getErrorMessage } from '../../../utils/axios/axiosInstance'; +import { iCartInitialResource } from '../../../utils/types/store'; const initialState: iCartInitialResource = { carts: [], isLoading: false, isError: false, isSuccess: false, - message: "", + message: '', isLoggedOut: false, cartCounter: 0, cartTotalMoney: 0, @@ -22,7 +22,7 @@ interface CreateCartParams { } export const createCart = createAsyncThunk( - "cart/create-cart", + 'cart/create-cart', async ({ productId, quantity }: CreateCartParams, thunkAPI) => { try { const cart = await cartService.createCart(productId, quantity); @@ -46,7 +46,7 @@ const calculateTotalPrice = (carts: any[]) => { }; export const getUserCarts = createAsyncThunk( - "cart/userGetCarts", + 'cart/userGetCarts', async (_, thunkAPI) => { try { const carts = await cartService.getUserCarts(); @@ -58,7 +58,7 @@ export const getUserCarts = createAsyncThunk( ); export const checkout = createAsyncThunk( - "cart/buyer-cart-checkout", + 'cart/buyer-cart-checkout', async (cartId: string, thunkApi) => { try { const response = await cartService.productCheckout(cartId); @@ -70,12 +70,12 @@ export const checkout = createAsyncThunk( ); export const clearCart = createAsyncThunk( - "cart/buyer-clear-cart", + 'cart/buyer-clear-cart', async (cartId: string, thunkApi) => { try { const response = await cartService.clearCart(cartId); - await cartService.getUserCarts() - + await cartService.getUserCarts(); + return response; } catch (error) { return thunkApi.rejectWithValue(getErrorMessage(error)); @@ -84,8 +84,11 @@ export const clearCart = createAsyncThunk( ); export const clearCartProduct = createAsyncThunk( - "cart/buyer-clear-cart-product", - async ({ cartId, productId }: { cartId: string; productId: string }, thunkApi) => { + 'cart/buyer-clear-cart-product', + async ( + { cartId, productId }: { cartId: string; productId: string }, + thunkApi + ) => { try { const response = await cartService.clearCartProduct(cartId, productId); return response; @@ -96,7 +99,7 @@ export const clearCartProduct = createAsyncThunk( ); export const clearCarts = createAsyncThunk( - "cart/userClearCarts", + 'cart/userClearCarts', async (_, thunkAPI) => { try { const response = await cartService.clearCarts(); @@ -107,8 +110,52 @@ export const clearCarts = createAsyncThunk( } ); +export const createProductStripe = createAsyncThunk( + 'cart/createCartProduct', + async (data: any, thunkApi) => { + try { + const response = await cartService.createStripeProduct(data); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } + } +); + +export const createSessionStripe = createAsyncThunk( + 'cart/createSessionStripe', + async (data: any, thunkApi) => { + try { + const response = await cartService.createStripeSession(data); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } + } +); +export const updateCartStatus = createAsyncThunk( + 'cart/updateCartStatus', + async (data: any, thunkApi) => { + try { + const response = await await cartService.updateCartStatus(data); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } + } +); + +export const userSaveOrder = createAsyncThunk('cart/saveOrder', async(data:any,thunkApi)=> { + try { + const response = await cartService.saveOrder(data); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}) + const cartSlice = createSlice({ - name: "cart", + name: 'cart', initialState, reducers: { addCart: (state, action) => { @@ -117,7 +164,10 @@ const cartSlice = createSlice({ usergetCarts: (state, action: PayloadAction) => { state.carts.push(action.payload); }, - updateCartProductQuantity: (state, action: PayloadAction<{ productId: string; quantity: number }>) => { + updateCartProductQuantity: ( + state, + action: PayloadAction<{ productId: string; quantity: number }> + ) => { const { productId, quantity } = action.payload; state.carts.forEach((cart) => { cart.products.forEach((product: any) => { @@ -136,32 +186,33 @@ const cartSlice = createSlice({ state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(createCart.fulfilled, (state, action) => { state.isLoading = false; state.isError = false; state.isSuccess = true; state.carts.push(action.payload); - state.message = "Cart created successfully"; + state.message = 'Cart created successfully'; }) .addCase(createCart.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(getUserCarts.pending, (state) => { state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(getUserCarts.fulfilled, (state, action) => { state.isLoading = false; state.isError = false; state.isSuccess = true; state.carts = action.payload.data.carts; + console.log(state.carts) let cartProductsTotal = 0; let cartTotalAmount = 0; let cartsProductsList = []; @@ -179,77 +230,76 @@ const cartSlice = createSlice({ }); state.cartProductslist = cartsProductsList; - state.message = ""; + state.message = ''; }) .addCase(getUserCarts.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(checkout.pending, (state) => { state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(checkout.fulfilled, (state, action) => { state.isLoading = false; state.isError = false; state.isSuccess = true; state.carts.push(action.payload); - state.message = ""; + state.message = ''; }) .addCase(checkout.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCart.pending, (state) => { state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCart.fulfilled, (state, action) => { state.isLoading = false; state.isError = false; state.isSuccess = true; - state.message = action.payload.message; }) .addCase(clearCart.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCartProduct.pending, (state) => { state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCartProduct.fulfilled, (state, action) => { state.isLoading = false; state.isError = false; state.isSuccess = true; state.carts.push(action.payload); - state.message = ""; + state.message = ''; }) .addCase(clearCartProduct.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCarts.pending, (state) => { state.isLoading = true; state.isError = false; state.isSuccess = false; - state.message = ""; + state.message = ''; }) .addCase(clearCarts.fulfilled, (state, action: PayloadAction) => { state.isLoading = false; @@ -269,5 +319,6 @@ const cartSlice = createSlice({ }, }); -export const { addCart, usergetCarts, updateCartProductQuantity } = cartSlice.actions; +export const { addCart, usergetCarts, updateCartProductQuantity } = + cartSlice.actions; export default cartSlice.reducer;