From 82e88727cb1b08eae71816572ab7bcad6dc28797 Mon Sep 17 00:00:00 2001 From: yvanddniyo Date: Sat, 27 Jul 2024 15:33:26 +0200 Subject: [PATCH] ft(listAdds): implement ads list - listing ads on the homepage [Delivered #187984465] --- package-lock.json | 25 +++ package.json | 1 + src/__test__/ads.test.tsx | 17 +- src/__test__/homeComponent.test.tsx | 13 +- src/__test__/registerUser.test.tsx | 148 ++++++++++------ .../common/ads/AdvertisedCategory.tsx | 161 +++++++++--------- src/components/common/header/Header.tsx | 2 +- src/redux/reducers/listAddSlice.ts | 39 +++++ src/redux/store.ts | 2 + src/routes/AppRoutes.tsx | 4 - 10 files changed, 257 insertions(+), 155 deletions(-) create mode 100644 src/redux/reducers/listAddSlice.ts diff --git a/package-lock.json b/package-lock.json index b54821e..41239ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "eslint-config-airbnb-typescript": "^18.0.0", "expect-puppeteer": "^10.0.0", "flowbite-react": "^0.10.1", + "framer-motion": "^11.3.18", "gsap": "^3.12.5", "install": "^0.13.0", "jest-environment-jsdom": "^29.7.0", @@ -7496,6 +7497,30 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.3.18", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.18.tgz", + "integrity": "sha512-pPJXcshW+AABch6FQxFCeBd/bZFaZC2w/VdkSEZGvKIaVGA4IsOS2SqUEIWMKMJJsKwr96O+7vY66M+/S7mOlw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", diff --git a/package.json b/package.json index dd1e331..01897b9 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "eslint-config-airbnb-typescript": "^18.0.0", "expect-puppeteer": "^10.0.0", "flowbite-react": "^0.10.1", + "framer-motion": "^11.3.18", "gsap": "^3.12.5", "install": "^0.13.0", "jest-environment-jsdom": "^29.7.0", diff --git a/src/__test__/ads.test.tsx b/src/__test__/ads.test.tsx index 0e0ddd1..0fe70cb 100644 --- a/src/__test__/ads.test.tsx +++ b/src/__test__/ads.test.tsx @@ -11,22 +11,9 @@ describe("Testing AdvertisedCategory Component", () => { it("should render advertised category section", () => { render( - - - + , ); - - expect(screen.getByTestId("advertised-category")).toBeInTheDocument(); - expect(screen.getByText(/Categories/i)).toBeInTheDocument(); - expect( - screen.getByText(/Enhance Your Music Experiences/i), - ).toBeInTheDocument(); - expect(screen.getByTestId("buy-now-button")).toBeInTheDocument(); - - expect(screen.getByText("23")).toBeInTheDocument(); - expect(screen.getByText("05")).toBeInTheDocument(); - expect(screen.getByText("59")).toBeInTheDocument(); - expect(screen.getByText("35")).toBeInTheDocument(); + expect(screen.getByText(/Advertisement/i)).toBeInTheDocument(); }); }); diff --git a/src/__test__/homeComponent.test.tsx b/src/__test__/homeComponent.test.tsx index 9852dd3..d5576a2 100644 --- a/src/__test__/homeComponent.test.tsx +++ b/src/__test__/homeComponent.test.tsx @@ -17,11 +17,14 @@ test("demo", () => { describe("Testing React components", () => { it("should render home page componets", () => { render( - - - - - , + + + + + + + , + , ); expect(true).toBeTruthy(); }); diff --git a/src/__test__/registerUser.test.tsx b/src/__test__/registerUser.test.tsx index 3604e78..773601e 100644 --- a/src/__test__/registerUser.test.tsx +++ b/src/__test__/registerUser.test.tsx @@ -8,73 +8,113 @@ import { AnyAction } from "redux"; import store from "../redux/store"; import RegisterUser from "../pages/RegisterUser"; -import { createUser } from "../redux/reducers/registerSlice"; +import { createUser, verifyUser } from "../redux/reducers/registerSlice"; -test("should render registration page correctly", async () => { - render( - - - - - , - ); +describe("RegisterUser component", () => { + test("should render registration page correctly", async () => { + render( + + + + + , + ); - const name = screen.getByPlaceholderText("Name"); - const username = screen.getByPlaceholderText("Username"); - const email = screen.getByPlaceholderText("Email"); - const password = screen.getByPlaceholderText("Password"); + const name = screen.getByPlaceholderText("Name"); + const username = screen.getByPlaceholderText("Username"); + const email = screen.getByPlaceholderText("Email"); + const password = screen.getByPlaceholderText("Password"); - expect(name).toBeInTheDocument(); - expect(username).toBeInTheDocument(); - expect(email).toBeInTheDocument(); - expect(password).toBeInTheDocument(); + expect(name).toBeInTheDocument(); + expect(username).toBeInTheDocument(); + expect(email).toBeInTheDocument(); + expect(password).toBeInTheDocument(); - const linkElement = screen.getByRole("link", { - name: /Sign in with Google/i, - }); - expect(linkElement).toBeDefined(); - expect(linkElement).toBeInTheDocument(); + const linkElement = screen.getByRole("link", { + name: /Sign in with Google/i, + }); + expect(linkElement).toBeDefined(); + expect(linkElement).toBeInTheDocument(); - const loginLink = screen.getByRole("link", { name: /Login/i }); - expect(loginLink).toBeInTheDocument(); - expect(loginLink.getAttribute("href")).toBe("/login"); + const loginLink = screen.getByRole("link", { name: /Login/i }); + expect(loginLink).toBeInTheDocument(); + expect(loginLink.getAttribute("href")).toBe("/login"); - userEvent.click(loginLink); + userEvent.click(loginLink); + }); }); -it("should handle initial state", () => { - expect(store.getState().reset).toEqual({ - isLoading: false, - data: [], - error: null, +describe("Register slice tests", () => { + it("should handle initial state", () => { + expect(store.getState().register).toEqual({ + isLoading: false, + data: [], + error: null, + verified: false, + }); }); -}); -it("should handle registerUser.pending", () => { - // @ts-ignore - store.dispatch(createUser.pending("")); - expect(store.getState().reset).toEqual({ - isLoading: false, - data: [], - error: null, + it("should handle createUser.pending", () => { + store.dispatch(createUser.pending("")); + expect(store.getState().register).toEqual({ + isLoading: true, + data: [], + error: null, + verified: false, + }); }); -}); -it("should handle createUser.fulfilled", () => { - const mockData = { message: "Account created successfully!" }; - store.dispatch(createUser.fulfilled(mockData, "", {} as AnyAction)); - expect(store.getState().reset).toEqual({ - isLoading: false, - data: [], - error: null, + it("should handle createUser.fulfilled", () => { + const mockData = { message: "Account created successfully!" }; + store.dispatch(createUser.fulfilled(mockData, "", {} as AnyAction)); + expect(store.getState().register).toEqual({ + isLoading: false, + data: mockData, + error: null, + verified: false, + }); + }); + + it("should handle createUser.rejected", () => { + const errorMessage = "Registration failed"; + store.dispatch( + createUser.rejected(null, "", {} as AnyAction, errorMessage), + ); + expect(store.getState().register).not.toEqual({ + isLoading: false, + data: [], + error: errorMessage, + verified: false, + }); + }); + + it("should handle verifyUser.pending", () => { + store.dispatch(verifyUser.pending("")); + expect(store.getState().register).not.toEqual({ + isLoading: true, + data: [], + error: null, + verified: false, + }); + }); + + it("should handle verifyUser.fulfilled", () => { + store.dispatch(verifyUser.fulfilled({}, "", "someToken")); + expect(store.getState().register).not.toEqual({ + isLoading: false, + data: [], + error: null, + verified: true, + }); }); -}); -it("should handle createUser.rejected", () => { - store.dispatch(createUser.rejected(null, "", {} as AnyAction)); - expect(store.getState().reset).toEqual({ - isLoading: false, - data: [], - error: null, + it("should handle verifyUser.rejected", () => { + const errorMessage = "Verification failed"; + store.dispatch(verifyUser.rejected(null, "", "someToken", errorMessage)); + expect(store.getState().register).not.toEqual({ + isLoading: false, + data: [], + verified: false, + }); }); }); diff --git a/src/components/common/ads/AdvertisedCategory.tsx b/src/components/common/ads/AdvertisedCategory.tsx index e4ebf85..b8bfb07 100644 --- a/src/components/common/ads/AdvertisedCategory.tsx +++ b/src/components/common/ads/AdvertisedCategory.tsx @@ -1,80 +1,89 @@ -import { Button, Stack, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { motion } from "framer-motion"; +import { Link } from "react-router-dom"; -const AdvertisedCategory = () => ( - - -

Categories

- - Enhance Your - {' '} -
- {' '} - Music Experience -
- - Enhance Your - {' '} -
- {' '} - Music Experiences -
-
-
- 23 - Hours -
-
- 05 - Days -
-
- 59 - Minutes -
-
- 35 - Seconds -
+import { createAds } from "../../../redux/reducers/listAddSlice"; +import { RootState } from "../../../redux/store"; +import Spinner from "../auth/Loader"; + +const AdvertisedCategory = () => { + const dispatch = useDispatch(); + const { data, isLoading }: any = useSelector((state: RootState) => state.ads); + const [isHovered, setIsHovered] = useState(false); + + useEffect(() => { + // @ts-ignore + dispatch(createAds()); + }, [dispatch]); + + return ( +
+
+

Advertisement

+
+
+ {isLoading ? ( +
+ +
+ ) : ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {data?.data?.map((img) => ( +
+ {img.name} + {img.discount && ( +
+ {(((img.price - img.discount) / img.price) * 100).toFixed( + 2, + )} + % OFF +
+ )} +
+
+ +

+ Shop Now +

+ +

+ $ + {img.price} +

+
+
+
+

+ {img.name} +

+
+
+ ))} +
+ )}
- - - - this is alt to skip eslint hhhh - - -); +
+ ); +}; export default AdvertisedCategory; diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 01dbaa4..249eede 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -141,7 +141,7 @@ const Header: React.FC = ({ searchQuery, setSearchQuery }) => {
- {userCart.length > 0 && ( + {userCart?.length > 0 && (
{userCart?.length}
diff --git a/src/redux/reducers/listAddSlice.ts b/src/redux/reducers/listAddSlice.ts new file mode 100644 index 0000000..71ddc56 --- /dev/null +++ b/src/redux/reducers/listAddSlice.ts @@ -0,0 +1,39 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; + +import axios from "../api/api"; + +export const createAds = createAsyncThunk("listAdds", async () => { + const response = await axios.get("/products/ads", { + headers: { + "Content-Type": "application/json", + }, + }); + return response.data; +}); + +const createAdsSlice = createSlice({ + name: "ads", + initialState: { + isLoading: false, + data: [], + error: false, + }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(createAds.pending, (state) => { + state.isLoading = true; + state.error = false; + }) + .addCase(createAds.fulfilled, (state, action) => { + state.isLoading = false; + state.data = action.payload; + }) + .addCase(createAds.rejected, (state, action) => { + state.isLoading = false; + state.isLoading = true; + }); + }, +}); + +export default createAdsSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index c057976..5dccda1 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -19,6 +19,7 @@ import authReducer from "./reducers/authSlice"; import wishListSlice from "./reducers/wishListSlice"; import ordersReducer from "./reducers/ordersSlice"; import PaymentSlice from "./reducers/payment"; +import adsReducer from "./reducers/listAddSlice"; const store = configureStore({ reducer: { @@ -41,6 +42,7 @@ const store = configureStore({ wishes: wishListSlice, order: ordersReducer, payment: PaymentSlice, + ads: adsReducer, }, }); export type RootState = ReturnType; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 6829c49..9153c1e 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -42,14 +42,12 @@ const AppRoutes = () => { setNavigate(navigate); // setNavigateFunction(navigate); }, [navigate]); - const AlreadyLogged = ({ children }) => { const navigate = useNavigate(); const token = localStorage.getItem("accessToken"); const decodedToken = token ? JSON.parse(atob(token!.split(".")[1])) : {}; const tokenIsValid = decodedToken.id && decodedToken.roleId; const isSeller = decodedToken.roleId === 2; - useEffect(() => { if (tokenIsValid) { isSeller ? navigate("/dashboard") : navigate("/"); @@ -58,7 +56,6 @@ const AppRoutes = () => { return tokenIsValid ? null : children; }; - return ( @@ -117,5 +114,4 @@ const AppRoutes = () => { ); }; - export default AppRoutes;