From 01dfdea7a2c2e9c603adcaa041f2592067f1970c Mon Sep 17 00:00:00 2001 From: yvanddniyo Date: Fri, 12 Jul 2024 17:53:30 +0200 Subject: [PATCH] ft(order): A buyer and seller should be manage orders - A buyer should see their orders and status - A seller should see their orders made on their products [Delivers #187900465] --- .github/workflows/deploy.yml | 2 +- package-lock.json | 35 ++- package.json | 6 +- src/__test__/deleteNotify.test.tsx | 64 ++++ src/__test__/orders.test.ts | 69 +++++ src/components/common/auth/Loader.tsx | 2 +- src/components/common/header/Navbar.tsx | 3 + src/components/dashboard/SideBar.tsx | 9 +- .../dashboard/orders/SellerOrder.tsx | 287 ++++++++++++++++++ src/index.css | 4 + src/pages/BuyerOrders.tsx | 244 +++++++++++++++ src/pages/CartManagement.tsx | 1 + src/redux/api/api.ts | 7 +- src/redux/reducers/ordersSlice.ts | 92 ++++++ src/redux/store.ts | 2 + src/routes/AppRoutes.tsx | 4 + type.d.ts | 46 +++ 17 files changed, 861 insertions(+), 16 deletions(-) create mode 100644 src/__test__/deleteNotify.test.tsx create mode 100644 src/__test__/orders.test.ts create mode 100644 src/components/dashboard/orders/SellerOrder.tsx create mode 100644 src/pages/BuyerOrders.tsx create mode 100644 src/redux/reducers/ordersSlice.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c75d731..050f99a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,7 +26,7 @@ jobs: node-version: "20" - name: Install dependencies - run: npm install + run: npm install --force - name: Running test run: npm run test:coverage diff --git a/package-lock.json b/package-lock.json index 35197a3..4bc5a08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,6 @@ "@radix-ui/react-navigation-menu": "^1.1.4", "@reduxjs/toolkit": "^2.2.5", "@tanstack/react-query": "^5.45.1", - "@testing-library/jest-dom": "^6.4.6", - "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "axios": "^1.7.2", "axios-mock-adapter": "^1.22.0", @@ -34,10 +32,13 @@ "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", "jest-mock-extended": "^3.0.7", + "jwt-decode": "^4.0.0", + "moment": "^2.30.1", "node-fetch": "^3.3.2", "npm": "^10.8.1", "prop-types": "^15.8.1", "react": "^18.3.1", + "react-currency-format": "^1.1.0", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", "react-hook-form": "^7.51.5", @@ -58,7 +59,6 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@testing-library/dom": "^10.2.0", - "@testing-library/jest-dom": "^6.4.6", "@types/jest": "^29.5.12", "@types/node": "^20.14.8", "@types/react": "^18.2.66", @@ -3902,6 +3902,7 @@ "version": "16.0.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", "integrity": "sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==", + "dev": true, "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -10293,6 +10294,14 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10758,6 +10767,14 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14174,6 +14191,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-currency-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-currency-format/-/react-currency-format-1.1.0.tgz", + "integrity": "sha512-WWrEOIp/3GbDSk1wlhFXaBc7IHGT3IwL306DHbGP3GVr4YFa0iS5hHPbKjHa0haruGL4Ly+WG4/5jBHpUtgqZg==", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0-rc || ^16.0.0 || ^17.0.0", + "react-dom": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0-rc || ^16.0.0 || ^17.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", diff --git a/package.json b/package.json index 3d6b064..87a9ce6 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,6 @@ "@radix-ui/react-navigation-menu": "^1.1.4", "@reduxjs/toolkit": "^2.2.5", "@tanstack/react-query": "^5.45.1", - "@testing-library/jest-dom": "^6.4.6", - "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "axios": "^1.7.2", "axios-mock-adapter": "^1.22.0", @@ -42,10 +40,13 @@ "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", "jest-mock-extended": "^3.0.7", + "jwt-decode": "^4.0.0", + "moment": "^2.30.1", "node-fetch": "^3.3.2", "npm": "^10.8.1", "prop-types": "^15.8.1", "react": "^18.3.1", + "react-currency-format": "^1.1.0", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", "react-hook-form": "^7.51.5", @@ -66,7 +67,6 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@testing-library/dom": "^10.2.0", - "@testing-library/jest-dom": "^6.4.6", "@types/jest": "^29.5.12", "@types/node": "^20.14.8", "@types/react": "^18.2.66", diff --git a/src/__test__/deleteNotify.test.tsx b/src/__test__/deleteNotify.test.tsx new file mode 100644 index 0000000..42ea4a5 --- /dev/null +++ b/src/__test__/deleteNotify.test.tsx @@ -0,0 +1,64 @@ +import "@testing-library/jest-dom/jest-globals"; +import "@testing-library/jest-dom"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { BrowserRouter as Router } from "react-router-dom"; + +import store from "../redux/store"; +import DeleteNotify from "../components/common/notify/DeleteNotify"; + +describe("DeleteNotify component", () => { + const mockOnConfirm = jest.fn(); + const mockOnCancel = jest.fn(); + + beforeEach(() => { + render( + + + + + , + ); + }); + + test('renders the "Clear Cart" button', () => { + const clearCartButton = screen.getByText("Clear Cart"); + expect(clearCartButton).toBeInTheDocument(); + }); + + test("opens the modal when Clear Cart button is clicked", () => { + const clearCartButton = screen.getByText("Clear Cart"); + fireEvent.click(clearCartButton); + + const modalHeader = screen.getByText( + "Are you sure you want to delete this product?", + ); + expect(modalHeader).toBeInTheDocument(); + }); + + test("calls onConfirm and closes modal when is clicked", () => { + const clearCartButton = screen.getByText("Clear Cart"); + fireEvent.click(clearCartButton); + + const confirmButton = screen.getByText("Yes, I'm sure"); + fireEvent.click(confirmButton); + + expect(mockOnConfirm).toHaveBeenCalledTimes(1); + expect( + screen.queryByText("Are you sure you want to delete this product?"), + ).not.toBeInTheDocument(); + }); + + test('calls onCancel and closes modal when "No, cancel" is clicked', () => { + const clearCartButton = screen.getByText("Clear Cart"); + fireEvent.click(clearCartButton); + + const cancelButton = screen.getByText("No, cancel"); + fireEvent.click(cancelButton); + + expect(mockOnCancel).toHaveBeenCalledTimes(1); + expect( + screen.queryByText("Are you sure you want to delete this product?"), + ).not.toBeInTheDocument(); + }); +}); diff --git a/src/__test__/orders.test.ts b/src/__test__/orders.test.ts new file mode 100644 index 0000000..2d3d78e --- /dev/null +++ b/src/__test__/orders.test.ts @@ -0,0 +1,69 @@ +import { orders } from "../../type"; +import ordersReducer, { + fetchOrders, + updateOrderStatus, +} from "../redux/reducers/ordersSlice"; + +describe("orders reducer", () => { + const initialState = { + isLoading: false, + data: [] as unknown as orders, + error: false, + }; + + it("should handle initial state", () => { + expect(ordersReducer(undefined, { type: "unknown" })).toEqual(initialState); + }); + + it("should handle fetchOrders.pending", () => { + const action = { type: fetchOrders.pending.type }; + const state = ordersReducer(initialState, action); + expect(state).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it("should handle fetchOrders.fulfilled", () => { + const mockOrders = [ + { id: 1, status: "pending" }, + { id: 2, status: "completed" }, + ]; + const action = { type: fetchOrders.fulfilled.type, payload: mockOrders }; + const state = ordersReducer(initialState, action); + expect(state).toEqual({ + ...initialState, + isLoading: false, + data: mockOrders, + }); + }); + + it("should handle fetchOrders.rejected", () => { + const action = { type: fetchOrders.rejected.type }; + const state = ordersReducer(initialState, action); + expect(state).toEqual({ + ...initialState, + isLoading: false, + error: true, + }); + }); + + it("should handle updateOrderStatus.pending", () => { + const action = { type: updateOrderStatus.pending.type }; + const state = ordersReducer(initialState, action); + expect(state).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it("should handle updateOrderStatus.rejected", () => { + const action = { type: updateOrderStatus.rejected.type }; + const state = ordersReducer(initialState, action); + expect(state).toEqual({ + ...initialState, + isLoading: false, + error: true, + }); + }); +}); diff --git a/src/components/common/auth/Loader.tsx b/src/components/common/auth/Loader.tsx index 360f8ba..3197528 100644 --- a/src/components/common/auth/Loader.tsx +++ b/src/components/common/auth/Loader.tsx @@ -1,5 +1,5 @@ const Spinner = () => ( -
+
= ({ searchQuery, setSearchQuery }) => { New Arrival + + Orders + diff --git a/src/components/dashboard/SideBar.tsx b/src/components/dashboard/SideBar.tsx index a6b1980..5bdd87d 100644 --- a/src/components/dashboard/SideBar.tsx +++ b/src/components/dashboard/SideBar.tsx @@ -4,7 +4,7 @@ import { IoBriefcaseOutline, IoSettingsOutline } from "react-icons/io5"; import { AiFillProduct } from "react-icons/ai"; import { MdInsertChartOutlined } from "react-icons/md"; import { FiLogOut } from "react-icons/fi"; -import { NavLink, useLocation } from "react-router-dom"; +import { Link, NavLink, useLocation } from "react-router-dom"; import { FaCircle } from "react-icons/fa"; interface SidebarProps { @@ -14,7 +14,8 @@ interface SidebarProps { const SideBar: React.FC = ({ isOpen }) => { const location = useLocation(); - const getLinkClass = (path: string) => (location.pathname === path ? "text-primary" : "text-dark-gray"); + const getLinkClass = (path: string) => + (location.pathname === path ? "text-primary" : "text-dark-gray"); return (
= ({ isOpen }) => { Add product diff --git a/src/components/dashboard/orders/SellerOrder.tsx b/src/components/dashboard/orders/SellerOrder.tsx new file mode 100644 index 0000000..22d6e01 --- /dev/null +++ b/src/components/dashboard/orders/SellerOrder.tsx @@ -0,0 +1,287 @@ +import React, { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import moment from "moment"; +import { jwtDecode } from "jwt-decode"; +import CurrencyFormat from "react-currency-format"; + +import Layout from "../../layouts/SellerLayout"; +import { useAppDispatch } from "../../../redux/hooks"; +import { RootState } from "../../../redux/store"; +import Spinner from "../../common/auth/Loader"; +import { + fetchOrders, + updateOrderStatus, +} from "../../../redux/reducers/ordersSlice"; +import { ProductOrders } from "../../../../type"; +import Warning from "../../common/notify/Warning"; + +const SellerOrder = () => { + const token: any = localStorage.getItem("accessToken"); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedProduct, setSelectedProduct] = useState( + null, + ); + const [isLoading, setIsLoading] = useState(true); + + const openModal = (product: ProductOrders) => { + setSelectedProduct(product); + setIsModalOpen(true); + }; + + const closeModal = () => { + setSelectedProduct(null); + setIsModalOpen(false); + }; + + const dispatch = useAppDispatch(); + const { data, error } = useSelector((state: RootState) => state.order); + const loading = useSelector((state: RootState) => state.order.isLoading); + + useEffect(() => { + setTimeout(() => { + setIsLoading(false); + }, 2000); + dispatch(fetchOrders()); + }, [dispatch]); + + if (isLoading) { + return ; + } + if (error) { + return ( +
+

+ Cannot get orders for you, unknow error occured. + + {" "} + check your network or server error 😓 + +

+
+ ); + } + + let decoded; + try { + decoded = jwtDecode(token); + } catch (e) { + return ; + } + // @ts-ignore + if (decoded.roleId !== 2) { + return ( +
+

+ You're not allowed to perform this action 🚫 +

+
+ ); + } + + const handleStatusChange = (orderId: number, status: string) => { + setTimeout(() => { + dispatch(fetchOrders()); + dispatch(fetchOrders()); + }, 3000); + dispatch(updateOrderStatus({ orderId, status })); + }; + + return ( + +
+
+

Orders

+
+
+ {data.length === 0 ? ( +
+

Oops, you have no order yet 😢

+
+ ) : ( + + + + + + + + + + + + + {data?.map((order: any, index) => ( + + + + + + + + + + ))} + +
+ Customer Name + + Email + + Last Order + + Spent + + Status + + Action +
+
+ users +

{order.order?.buyer.name}

+
+
+

{order.order?.buyer.email}

+
+

+ {moment(order.order?.deliveryDate).format( + "DD MMM, YYYY", + )} +

+
+

+ + total + product.product.price, + 0, + )} + displayType="text" + thousandSeparator + prefix="RWF" + /> +

+
+ {order.products && order.products.length > 0 ? ( +

+ {loading ? "Loading..." : order.products[0].status} +

+ ) : ( +

No Products

+ )} +
+ + + openModal(order.products)} + > + View + +
+ )} + {isModalOpen && selectedProduct && ( +
+
e.stopPropagation()} + > +

Product Details

+ + + + + + + + + + + {selectedProduct.map((productItem: any) => ( + + + + + + + ))} + +
+ Product Name + + Price + + Quantity + + Total +
+ {productItem.product.name} + + + + {productItem.quantity} + + +
+ +
+
+ )} +
+
+
+ ); +}; + +export default SellerOrder; diff --git a/src/index.css b/src/index.css index ce002bd..44fdfce 100644 --- a/src/index.css +++ b/src/index.css @@ -56,3 +56,7 @@ height: 100%; object-fit: cover; } + +.hide::-webkit-scrollbar { + display: none; +} diff --git a/src/pages/BuyerOrders.tsx b/src/pages/BuyerOrders.tsx new file mode 100644 index 0000000..95136e3 --- /dev/null +++ b/src/pages/BuyerOrders.tsx @@ -0,0 +1,244 @@ +import React, { useEffect, useState } from "react"; +import moment from "moment"; +import { useSelector } from "react-redux"; +import { jwtDecode } from "jwt-decode"; +import CurrencyFormat from "react-currency-format"; + +import { ProductOrders } from "../../type"; +import { useAppDispatch } from "../redux/hooks"; +import { RootState } from "../redux/store"; +import Spinner from "../components/common/auth/Loader"; +import Warning from "../components/common/notify/Warning"; +import { fetchOrders } from "../redux/reducers/ordersSlice"; + +const SellerOrder = () => { + const token: any = localStorage.getItem("accessToken"); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedProduct, setSelectedProduct] = useState( + null, + ); + + const openModal = (product) => { + setSelectedProduct(product); + setIsModalOpen(true); + }; + + const closeModal = () => { + setSelectedProduct(null); + setIsModalOpen(false); + }; + + const dispatch = useAppDispatch(); + const { data, isLoading, error } = useSelector( + (state: RootState) => state.order, + ); + let decoded; + try { + decoded = jwtDecode(token); + } catch (e) { + return ; + } + + if (decoded.roleId !== 1) { + return ( +
+

+ You're not allowed to perform this action 🚫 +

+
+ ); + } + + useEffect(() => { + dispatch(fetchOrders()); + }, [dispatch]); + + const getOrderStatus = (products) => + (products.every((product) => product.status === "Delivered") + ? "Complete" + : "Pending"); + + if (isLoading) { + return ; + } + if (error) { + return ( +
+

+ Cannot get orders for you, unknow error occured. + + {" "} + check your network or server error 😓 + +

+
+ ); + } + + return ( +
+
+

Orders

+
+
+ {data.length === 0 ? ( +
+

Oops, you have no order yet 😢

+
+ ) : ( + + + + + + + + + + + + {data?.map((order: any) => ( + + + + + + + + ))} + +
NameEmail + Delivery Date + + Status + + Details +
+
+ normal +

{order.order.buyer.name}

+
+
+

{order.order.buyer.email}

+
+

+ {moment(order.order.deliveryDate).format("DD MMM, YYYY")} +

+
+ {getOrderStatus(order.products)} + + openModal(order.products)} + > + View + +
+ )} + {isModalOpen && selectedProduct && ( +
+
e.stopPropagation()} + > +

Product Details

+ + + + + + + + + + + + {selectedProduct?.map((productItem) => ( + + + + + + + + ))} + +
+ Product Name + + Price + + Quantity + + Total + + Status +
+
+ +

+ {productItem.product.name} +

+
+
+ + + {productItem.quantity} + + + +

+ {productItem.status} +

+
+ +
+
+ )} +
+
+ ); +}; + +export default SellerOrder; diff --git a/src/pages/CartManagement.tsx b/src/pages/CartManagement.tsx index ea182d8..ed1af86 100644 --- a/src/pages/CartManagement.tsx +++ b/src/pages/CartManagement.tsx @@ -50,6 +50,7 @@ const CartManagement: React.FC = () => {
); } + console.log(userCart); const handleDelete = async () => { await dispatch(cartDelete()); diff --git a/src/redux/api/api.ts b/src/redux/api/api.ts index 837915a..e507cb8 100644 --- a/src/redux/api/api.ts +++ b/src/redux/api/api.ts @@ -8,9 +8,6 @@ import api from "./action"; let navigateFunction = null; -export const setNavigateFunction = (navigate) => { - navigateFunction = navigate; -}; const redirectToLogin = (navigate) => { setTimeout(() => { navigate("/login"); @@ -37,5 +34,7 @@ api.interceptors.response.use( return Promise.reject(error); }, ); - export default api; +export const setNavigateFunction = (navigate) => { + navigateFunction = navigate; +}; diff --git a/src/redux/reducers/ordersSlice.ts b/src/redux/reducers/ordersSlice.ts new file mode 100644 index 0000000..e248ca0 --- /dev/null +++ b/src/redux/reducers/ordersSlice.ts @@ -0,0 +1,92 @@ +import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; + +import axios from "../api/api"; +import { orders } from "../../../type"; + +export const fetchOrders = createAsyncThunk< +orders, +void, +{ rejectValue: string } +>("orders/fetchOrders", async (_, { rejectWithValue }) => { + try { + const response = await axios.get("/orders", { + headers: { + Authorization: `Bearer ${localStorage.getItem("accessToken")}`, + }, + }); + + return response.data.orders as orders; + } catch (error: any) { + return rejectWithValue(error.data.message); + } +}); + +export const updateOrderStatus = createAsyncThunk< +any, +{ orderId: number; status: string }, +{ rejectValue: string } +>( + "orders/updateOrderStatus", + async ({ orderId, status }, { rejectWithValue }) => { + try { + const response = await axios.patch( + `/orders/${orderId}/status`, + { status }, + { + headers: { + Authorization: `Bearer ${localStorage.getItem("accessToken")}`, + }, + }, + ); + console.log(response.data.updatedItems); + return response.data.updatedItems; + } catch (error: any) { + if ( + error.response + && error.response.data + && error.response.data.message + ) { + return rejectWithValue(error.response.data.message); + } + return rejectWithValue("An unexpected error occurred."); + } + }, +); + +const ordersSlice = createSlice({ + name: "orders", + initialState: { + isLoading: false, + data: [] as unknown as orders, + error: false, + }, + reducers: {}, + extraReducers(builder) { + builder + .addCase(fetchOrders.pending, (state) => { + state.isLoading = true; + }) + .addCase(fetchOrders.fulfilled, (state, action) => { + state.isLoading = false; + state.data = action.payload; + }) + .addCase(fetchOrders.rejected, (state, action) => { + state.isLoading = false; + state.error = true; + }) + .addCase(updateOrderStatus.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateOrderStatus.fulfilled, (state, action) => { + state.isLoading = false; + state.data = state.data.map((order) => + (order.id === action.payload.id ? action.payload : order)); + }) + .addCase(updateOrderStatus.rejected, (state, action) => { + state.isLoading = false; + state.error = true; + }); + }, +}); + +export default ordersSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index d3f193a..1827488 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -12,6 +12,7 @@ import updateProfileSlice from "./reducers/updateProfileSlice"; import productsReducer from "./reducers/productsSlice"; import cartsReducer from "./reducers/cartSlice"; import reviewSlice from "./reducers/reviewSlice"; +import ordersReducer from "./reducers/ordersSlice"; const store = configureStore({ reducer: { @@ -27,6 +28,7 @@ const store = configureStore({ products: productsReducer, cart: cartsReducer, review: reviewSlice, + order: ordersReducer, }, }); export type RootState = ReturnType; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index c2a78a2..69b8200 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -22,6 +22,8 @@ import Analytics from "../dashboard/admin/Analytics"; import Dashboard from "../dashboard/admin/Dashboard"; import CartManagement from "../pages/CartManagement"; import { setNavigateFunction } from "../redux/api/api"; +import SellerOrder from "../components/dashboard/orders/SellerOrder"; +import BuyerOrders from "../pages/BuyerOrders"; const AppRoutes = () => { const navigate = useNavigate(); @@ -52,6 +54,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> } /> @@ -72,6 +75,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/type.d.ts b/type.d.ts index 8f43a42..78a5f49 100644 --- a/type.d.ts +++ b/type.d.ts @@ -79,6 +79,52 @@ export interface UserCart { updatedAt: string; } +export interface Buyer { + id: number; + name: string; + username: string; + email: string; + password: string; +} + +export interface Product { + productId: number; + productName: string; + name: string; + price: number; + quantity: number; +} + +export interface Item { + itemId: number; + quantity: number; +} + +export interface orders { + [x: string]: any; + id: number; + buyerId: number; + buyer: Buyer; + createdAt: string; + deliveryDate: string; + items: Item[]; + products: Product[]; + status: string; + updatedAt: string; +} + +export interface ProductOrders { + map( + arg0: (product: any) => import("react/jsx-runtime").JSX.Element, + ): import("react").ReactNode; + order: any; + name: any; + product: any; + products: Product[]; + price: any; + quantity: number; +} + export interface CartState { isLoading: boolean; data: UserCart | null;