├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public ├── LogoMakr-4g0P6C.png ├── LogoMakr-6Tit9e.png ├── images │ ├── MSI-Logo.png │ ├── Razer-Logo-Horizontal-1-1.png │ ├── airpods.jpg │ ├── alexa.jpg │ ├── amd_r.png │ ├── asus_rog.png │ ├── brand_01.png │ ├── brand_02.png │ ├── brand_03.png │ ├── brand_04.png │ ├── camera.jpg │ ├── carousels │ │ ├── 1.jpg │ │ ├── 2.webp │ │ └── 3.webp │ ├── dell.png │ ├── hp_i.png │ ├── i_phone.png │ ├── mouse.jpg │ ├── p2.jpg │ ├── phone.jpg │ ├── playstation.jpg │ ├── sample.jpg │ └── sumsung_logo.png └── imgs │ ├── banana.jpg │ ├── book.jpg │ ├── car.jpg │ └── computer.jpg ├── src ├── App.tsx ├── LogoMakr-6Tit9e.png ├── components │ ├── UI │ │ ├── error-fallback.tsx │ │ ├── form-container.tsx │ │ ├── lazy-image.tsx │ │ ├── loader.tsx │ │ ├── message.tsx │ │ ├── meta.tsx │ │ ├── modal-container.tsx │ │ ├── paginate.tsx │ │ ├── rating.tsx │ │ ├── red-button.tsx │ │ └── table-contrainer.tsx │ ├── brands │ │ └── brands.tsx │ ├── carousels.tsx │ ├── contact.tsx │ ├── dashboard │ │ ├── sidebar.tsx │ │ └── topbar.tsx │ ├── footer │ │ ├── down-footer.tsx │ │ ├── footer.css │ │ └── footer.tsx │ ├── header.tsx │ ├── layouts │ │ ├── dashboard-layout.tsx │ │ └── default-layout.tsx │ ├── modals │ │ └── product-modal.tsx │ └── product-card.tsx ├── index.css ├── logo.svg ├── main.tsx ├── pages │ ├── cart │ │ ├── cart-page.tsx │ │ ├── checkout.tsx │ │ ├── order-details.tsx │ │ └── shipping-address.tsx │ ├── contact │ │ ├── contact.css │ │ └── contact.tsx │ ├── dashboard │ │ ├── dashboard-page.tsx │ │ ├── orders │ │ │ └── order-table.tsx │ │ ├── products │ │ │ ├── product-table.tsx │ │ │ └── product-update.tsx │ │ └── users │ │ │ └── users-table.tsx │ ├── home.tsx │ ├── product-details.tsx │ ├── products.tsx │ └── users │ │ ├── login.tsx │ │ ├── profile.tsx │ │ └── regitser.tsx ├── redux │ ├── cart │ │ └── cart-slice.ts │ ├── index.ts │ ├── orders │ │ ├── order-details.ts │ │ ├── slice-list.ts │ │ └── user-orders.ts │ ├── products │ │ ├── search-list.ts │ │ ├── slice-details.ts │ │ └── slice-list.ts │ └── users │ │ ├── login-slice.ts │ │ ├── user-details.ts │ │ └── user-list.ts ├── utils │ ├── admin-provider.tsx │ ├── auth-axios.ts │ ├── auth-provider.tsx │ ├── error.ts │ ├── helper.ts │ ├── interfaces.ts │ └── public-axios.ts └── vite-env.d.ts ├── techstack.md ├── techstack.yml ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist-ssr 12 | *.local 13 | .env 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/README.md -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 17 | Type Shop 18 | 19 | 20 |
21 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "train-ts1", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": " vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/free-solid-svg-icons": "^5.15.4", 12 | "@fortawesome/react-fontawesome": "^0.1.18", 13 | "@hookform/resolvers": "^2.9.3", 14 | "@reduxjs/toolkit": "^1.8.2", 15 | "@webpixels/css": "^1.1.92", 16 | "axios": "^0.27.2", 17 | "react": "^18.0.0", 18 | "react-bootstrap": "^2.4.0", 19 | "react-dom": "^18.0.0", 20 | "react-error-boundary": "^3.1.4", 21 | "react-helmet": "^6.1.0", 22 | "react-hook-form": "^7.33.0", 23 | "react-hot-toast": "^2.2.0", 24 | "react-icons": "^4.4.0", 25 | "react-lazy-load-image-component": "^1.6.0", 26 | "react-redux": "^8.0.2", 27 | "react-router-bootstrap": "^0.26.1", 28 | "react-router-dom": "^6.3.0", 29 | "react-stripe-checkout": "^2.6.3", 30 | "redux-persist": "^6.0.0", 31 | "yup": "^0.32.11" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.0.0", 35 | "@types/react-dom": "^18.0.0", 36 | "@types/react-helmet": "^6.1.5", 37 | "@types/react-lazy-load-image-component": "^1.6.3", 38 | "@types/react-router-bootstrap": "^0.24.5", 39 | "@types/redux-localstorage": "^1.0.8", 40 | "@vitejs/plugin-react": "^1.3.0", 41 | "typescript": "^4.6.3", 42 | "vite": "^2.9.9" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/LogoMakr-4g0P6C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/LogoMakr-4g0P6C.png -------------------------------------------------------------------------------- /public/LogoMakr-6Tit9e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/LogoMakr-6Tit9e.png -------------------------------------------------------------------------------- /public/images/MSI-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/MSI-Logo.png -------------------------------------------------------------------------------- /public/images/Razer-Logo-Horizontal-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/Razer-Logo-Horizontal-1-1.png -------------------------------------------------------------------------------- /public/images/airpods.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/airpods.jpg -------------------------------------------------------------------------------- /public/images/alexa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/alexa.jpg -------------------------------------------------------------------------------- /public/images/amd_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/amd_r.png -------------------------------------------------------------------------------- /public/images/asus_rog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/asus_rog.png -------------------------------------------------------------------------------- /public/images/brand_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/brand_01.png -------------------------------------------------------------------------------- /public/images/brand_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/brand_02.png -------------------------------------------------------------------------------- /public/images/brand_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/brand_03.png -------------------------------------------------------------------------------- /public/images/brand_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/brand_04.png -------------------------------------------------------------------------------- /public/images/camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/camera.jpg -------------------------------------------------------------------------------- /public/images/carousels/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/carousels/1.jpg -------------------------------------------------------------------------------- /public/images/carousels/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/carousels/2.webp -------------------------------------------------------------------------------- /public/images/carousels/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/carousels/3.webp -------------------------------------------------------------------------------- /public/images/dell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/dell.png -------------------------------------------------------------------------------- /public/images/hp_i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/hp_i.png -------------------------------------------------------------------------------- /public/images/i_phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/i_phone.png -------------------------------------------------------------------------------- /public/images/mouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/mouse.jpg -------------------------------------------------------------------------------- /public/images/p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/p2.jpg -------------------------------------------------------------------------------- /public/images/phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/phone.jpg -------------------------------------------------------------------------------- /public/images/playstation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/playstation.jpg -------------------------------------------------------------------------------- /public/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/sample.jpg -------------------------------------------------------------------------------- /public/images/sumsung_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/images/sumsung_logo.png -------------------------------------------------------------------------------- /public/imgs/banana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/imgs/banana.jpg -------------------------------------------------------------------------------- /public/imgs/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/imgs/book.jpg -------------------------------------------------------------------------------- /public/imgs/car.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/imgs/car.jpg -------------------------------------------------------------------------------- /public/imgs/computer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/public/imgs/computer.jpg -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; 2 | import CartPage from "./pages/cart/cart-page"; 3 | import Checkout from "./pages/cart/checkout"; 4 | import ShippingAddress from "./pages/cart/shipping-address"; 5 | import HomePage from "./pages/home"; 6 | import ProductDetails from "./pages/product-details"; 7 | import Login from "./pages/users/login"; 8 | import { Toaster } from "react-hot-toast"; 9 | import DashboardPage from "./pages/dashboard/dashboard-page"; 10 | import ProductTable from "./pages/dashboard/products/product-table"; 11 | import UserTable from "./pages/dashboard/users/users-table"; 12 | import ProductUpdate from "./pages/dashboard/products/product-update"; 13 | import Register from "./pages/users/regitser"; 14 | import Profile from "./pages/users/profile"; 15 | import Contact from "./pages/contact/contact"; 16 | import OrdersTable from "./pages/dashboard/orders/order-table"; 17 | import OrderDetails from "./pages/cart/order-details"; 18 | import Products from "./pages/products"; 19 | import AuthProvider from "./utils/auth-provider"; 20 | 21 | import { lazy, Suspense } from "react"; 22 | import { ErrorBoundary } from "react-error-boundary"; 23 | import Loader from "./components/UI/loader"; 24 | import ErrorFallback from "./components/UI/error-fallback"; 25 | import "react-lazy-load-image-component/src/effects/blur.css"; 26 | 27 | const DashboardLayout = lazy( 28 | () => import("./components/layouts/dashboard-layout") 29 | ); 30 | 31 | const App = () => { 32 | return ( 33 | 34 | 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | 45 | 46 | 47 | } 48 | /> 49 | 53 | 54 | 55 | } 56 | /> 57 | 61 | 62 | 63 | } 64 | /> 65 | } /> 66 | } /> 67 | 71 | 72 | 73 | } 74 | /> 75 | 76 | location.href === "/"} 81 | FallbackComponent={ErrorFallback} 82 | > 83 | }> 84 | 85 | 86 | 87 | } 88 | > 89 | } /> 90 | } /> 91 | } /> 92 | } /> 93 | } /> 94 | } /> 95 | 96 | 97 | } /> 98 | 99 | 100 | 101 | ); 102 | }; 103 | 104 | export default App; 105 | -------------------------------------------------------------------------------- /src/LogoMakr-6Tit9e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hicmtrex/TypeShop-Frontend/2ae6ef409bea398456a05f1fae9724c28cc2d4bc/src/LogoMakr-6Tit9e.png -------------------------------------------------------------------------------- /src/components/UI/error-fallback.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | error: any | unknown; 3 | resetErrorBoundary: any; 4 | }; 5 | 6 | const ErrorFallback = ({ error, resetErrorBoundary }: Props) => { 7 | return ( 8 |
9 |

Something went wrong:

10 |
{error?.message}
11 | 12 |
13 | ); 14 | }; 15 | export default ErrorFallback; 16 | -------------------------------------------------------------------------------- /src/components/UI/form-container.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Container, Row, Col, Card, Image } from "react-bootstrap"; 3 | import DefaultLayout from "../layouts/default-layout"; 4 | import ImageLazy from "./lazy-image"; 5 | 6 | type FormTypes = { 7 | children: ReactNode; 8 | title: string; 9 | image?: string; 10 | meta?: string; 11 | }; 12 | 13 | const FormContainer = (props: FormTypes) => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 |

21 | {props.title} 22 |

23 | {props.image && ( 24 | 28 | )} 29 | {props.children} 30 |
31 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default FormContainer; 39 | -------------------------------------------------------------------------------- /src/components/UI/lazy-image.tsx: -------------------------------------------------------------------------------- 1 | import { LazyLoadImage } from "react-lazy-load-image-component"; 2 | 3 | type Props = { 4 | className?: string; 5 | imageUrl?: string; 6 | style?: React.CSSProperties; 7 | }; 8 | 9 | const ImageLazy = ({ className, imageUrl, style }: Props) => { 10 | return ( 11 | 18 | ); 19 | }; 20 | 21 | export default ImageLazy; 22 | -------------------------------------------------------------------------------- /src/components/UI/loader.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "react-bootstrap"; 2 | 3 | const Loader = () => { 4 | return ( 5 |
13 | 14 | Loading... 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Loader; 21 | -------------------------------------------------------------------------------- /src/components/UI/message.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { Alert } from 'react-bootstrap'; 3 | 4 | type Props = { 5 | children: ReactNode; 6 | variant?: string; 7 | }; 8 | 9 | const Message = ({ children, variant = 'danger' }: Props) => { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export default Message; 18 | -------------------------------------------------------------------------------- /src/components/UI/meta.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | 4 | type Props = { 5 | title?: string; 6 | description?: string; 7 | keywords?: string; 8 | }; 9 | 10 | const Meta = ({ 11 | title = 'Welcome To Type-shop', 12 | description = 'the best online store :)', 13 | keywords, 14 | }: Props) => { 15 | return ( 16 | 17 | {title} 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default Meta; 25 | -------------------------------------------------------------------------------- /src/components/UI/modal-container.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { Col, Container, Modal, Row } from 'react-bootstrap'; 3 | 4 | type Props = { 5 | children: ReactNode; 6 | title: string; 7 | show: boolean; 8 | handleClose: () => void; 9 | }; 10 | 11 | const ModalContainer = ({ show, handleClose, children, title }: Props) => { 12 | return ( 13 | 14 | 15 | 16 | {title} 17 | 18 | 19 | 20 | 21 | 22 | {children} 23 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default ModalContainer; 31 | -------------------------------------------------------------------------------- /src/components/UI/paginate.tsx: -------------------------------------------------------------------------------- 1 | import { Pagination } from 'react-bootstrap'; 2 | import { LinkContainer } from 'react-router-bootstrap'; 3 | 4 | type Props = { 5 | pages: number; 6 | page: number; 7 | isAdmin?: boolean; 8 | keyword: string; 9 | }; 10 | 11 | const Paginate = ({ pages, page, isAdmin = false, keyword = '' }: Props) => { 12 | return ( 13 | <> 14 | {pages > 1 && ( 15 | 16 | {[...Array(pages).keys()].map((x) => ( 17 | 27 | {x + 1} 28 | 29 | ))} 30 | 31 | )} 32 | 33 | ); 34 | }; 35 | 36 | export default Paginate; 37 | -------------------------------------------------------------------------------- /src/components/UI/rating.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | value: number; 3 | text?: string; 4 | color?: string; 5 | }; 6 | 7 | const Rating = ({ value, text, color = '#f8e825' }: Props) => { 8 | return ( 9 |
10 | 11 | = 1 15 | ? 'fas fa-star' 16 | : value >= 0.5 17 | ? 'fas fa-star-half-alt' 18 | : 'far fa-star' 19 | } 20 | > 21 | 22 | 23 | = 2 27 | ? 'fas fa-star' 28 | : value >= 1.5 29 | ? 'fas fa-star-half-alt' 30 | : 'far fa-star' 31 | } 32 | > 33 | 34 | 35 | = 3 39 | ? 'fas fa-star' 40 | : value >= 2.5 41 | ? 'fas fa-star-half-alt' 42 | : 'far fa-star' 43 | } 44 | > 45 | 46 | 47 | = 4 51 | ? 'fas fa-star' 52 | : value >= 3.5 53 | ? 'fas fa-star-half-alt' 54 | : 'far fa-star' 55 | } 56 | > 57 | 58 | 59 | = 5 63 | ? 'fas fa-star' 64 | : value >= 4.5 65 | ? 'fas fa-star-half-alt' 66 | : 'far fa-star' 67 | } 68 | > 69 | 70 | {text && text} 71 |
72 | ); 73 | }; 74 | 75 | export default Rating; 76 | -------------------------------------------------------------------------------- /src/components/UI/red-button.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { Button } from 'react-bootstrap'; 3 | 4 | type ButtonType = { 5 | children: ReactNode; 6 | onClick?: () => void; 7 | className?: string; 8 | disabled?: boolean; 9 | }; 10 | 11 | const RedButton = ({ children, onClick, className, disabled }: ButtonType) => { 12 | return ( 13 | 22 | ); 23 | }; 24 | 25 | export default RedButton; 26 | -------------------------------------------------------------------------------- /src/components/UI/table-contrainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { Card, Table } from 'react-bootstrap'; 3 | 4 | type Props = { 5 | children: ReactNode; 6 | cols: any; 7 | }; 8 | 9 | const TableContainer = ({ children, cols }: Props) => { 10 | return ( 11 | 12 | 13 | 17 | 18 | {cols.map((col: any) => ( 19 | 22 | ))} 23 | 24 | 25 | {children} 26 |
20 | {col} 21 |
27 |
28 | ); 29 | }; 30 | 31 | export default TableContainer; 32 | -------------------------------------------------------------------------------- /src/components/brands/brands.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Container, Row } from 'react-bootstrap'; 2 | import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'; 3 | 4 | const Brands = () => { 5 | const images = [ 6 | 'MSI-Logo.png', 7 | 'sumsung_logo.png', 8 | 'i_phone.png', 9 | 'hp_i.png', 10 | '', 11 | ]; 12 | return ( 13 |
14 | 15 | 16 | 17 |

Our Brands

18 |

The best brands in the markets

19 | 20 | 21 | 22 | {/*Controls*/} 23 | 24 | 30 | 31 | 32 | 33 | {/*End Controls*/} 34 | {/*Carousel Wrapper*/} 35 |
36 |
41 | {/*Slides*/} 42 |
46 | {/*First slide*/} 47 |
48 |
49 |
50 | 51 | Brand Logo 56 | 57 |
58 |
59 | 60 | Brand Logo 65 | 66 |
67 |
68 | 69 | Brand Logo 74 | 75 |
76 |
77 | 78 | Brand Logo 83 | 84 |
85 |
86 |
87 | {/*End First slide*/} 88 | {/*Second slide*/} 89 |
90 |
91 |
92 | 93 | Brand Logo 98 | 99 |
100 |
101 | 102 | Brand Logo 107 | 108 |
109 |
110 | 111 | Brand Logo 116 | 117 |
118 |
119 | 120 | Brand Logo 125 | 126 |
127 |
128 |
129 | {/*End Second slide*/} 130 |
131 | {/*End Slides*/} 132 |
133 |
134 | {/*End Carousel Wrapper*/} 135 | {/*Controls*/} 136 |
137 | 143 | 144 | 145 |
146 | {/*End Controls*/} 147 |
148 | 149 |
150 |
151 |
152 | ); 153 | }; 154 | 155 | export default Brands; 156 | -------------------------------------------------------------------------------- /src/components/carousels.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Carousel } from "react-bootstrap"; 3 | import ImageLazy from "./UI/lazy-image"; 4 | 5 | const images = [ 6 | "/images/carousels/1.jpg", 7 | "/images/carousels/2.webp", 8 | "/images/carousels/3.webp", 9 | ]; 10 | 11 | const Carousels = () => { 12 | const [index, setIndex] = useState(0); 13 | 14 | const handleSelect = (selectedIndex: number) => { 15 | setIndex(selectedIndex); 16 | }; 17 | 18 | return ( 19 | 24 | {images.map((url, idx) => ( 25 | 26 | 27 | 28 | ))} 29 | 30 | ); 31 | }; 32 | 33 | export default Carousels; 34 | -------------------------------------------------------------------------------- /src/components/contact.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Container, Form, Row } from 'react-bootstrap'; 2 | 3 | const Contact = () => { 4 | return ( 5 |
6 | 7 |
8 |

Contact

9 |

10 | Magnam dolores commodi suscipit. Necessitatibus eius consequatur ex 11 | aliquid fuga eum quidem. Sit sint consectetur velit. Quisquam quos 12 | quisquam cupiditate. Et nemo qui impedit suscipit alias ea. 13 |

14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 |

Our Address

22 |

A108 Adam Street, New York, NY 535022

23 |
24 | 25 | 26 |
27 | 28 |

Email Us

29 |

30 | info@example.com 31 |
32 | contact@example.com 33 |

34 |
35 | 36 | 37 |
38 | 39 |

Call Us

40 |

41 | +1 5589 55488 55 42 |
43 | +1 6678 254445 41 44 |

45 |
46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 | 61 | 62 |
63 | 71 |
72 |
73 |
74 | 82 |
83 |
84 | 93 |
94 |
95 |
Loading
96 |
97 |
98 | Your message has been sent. Thank you! 99 |
100 |
101 |
102 | 103 |
104 | 105 | 106 | 107 | 108 |
109 | ); 110 | }; 111 | 112 | export default Contact; 113 | -------------------------------------------------------------------------------- /src/components/dashboard/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Container, Button, Nav } from 'react-bootstrap'; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import { HiUsers } from 'react-icons/hi'; 4 | import { AiFillDashboard } from 'react-icons/ai'; 5 | import { useAppDispatch, useAppSelector } from '../../redux'; 6 | import { reset } from '../../redux/cart/cart-slice'; 7 | import { userLogout } from '../../redux/users/login-slice'; 8 | import { NavLink } from 'react-router-dom'; 9 | 10 | const Sidebar = () => { 11 | const dispatch = useAppDispatch(); 12 | const navigate = useNavigate(); 13 | const { userInfo } = useAppSelector((state) => state.login); 14 | 15 | const onLogout = () => { 16 | dispatch(userLogout()); 17 | dispatch(reset()); 18 | navigate('/login'); 19 | }; 20 | 21 | return ( 22 | 29 | 30 | 40 | 44 |

45 | Type Shop 46 |

47 | 48 | 49 |
50 |
    51 |
  • 52 | 53 | Accueil 54 | 55 |
  • 56 |
  • 57 | 58 | Products 59 | 60 |
  • 61 | 62 |
  • 63 | 64 | Users 65 | 66 |
  • 67 |
  • 68 | 69 | Orders 70 | 71 |
  • 72 |
73 | 74 |
75 | 76 |
77 |
    78 |
  • 79 | 80 | Profile 81 | 82 |
  • 83 |
  • 84 | 85 | Logout 86 | 87 |
  • 88 |
89 |
90 | 91 | 92 | ); 93 | }; 94 | 95 | export default Sidebar; 96 | -------------------------------------------------------------------------------- /src/components/dashboard/topbar.tsx: -------------------------------------------------------------------------------- 1 | import { Container, Navbar } from 'react-bootstrap'; 2 | 3 | const Topbar = () => { 4 | return ( 5 |
9 | 10 |
11 |
12 |
13 |

14 | 15 |

16 |
17 |
18 | Admin Dashboard 19 |
20 |
21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | export default Topbar; 28 | -------------------------------------------------------------------------------- /src/components/footer/down-footer.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Container, Image, Row } from 'react-bootstrap'; 2 | import toast from 'react-hot-toast'; 3 | import './footer.css'; 4 | 5 | const DownFooter = () => { 6 | return ( 7 |
8 |
9 | 10 | 11 | 12 |

13 | 14 |

15 |

16 | 2 Allée Andersen 17 |
18 | 69780 Mions Lyon 19 |
20 | France 21 |
22 |
23 | Phone: 06 76 56 48 93 24 |
25 | Email: typeshop@me.com 26 |
27 |

28 | 29 | 30 |

Useful Links

31 | 50 | 51 | 52 |

Our Services

53 | 75 | 76 | 77 |

Join Our Newsletter

78 |

79 | Tamen quem nulla quae legam multos aute sint culpa legam noster 80 | magna 81 |

82 |
toast.success('thanks for yr Subscrition')}> 83 | 89 | 90 |
91 | 92 |
93 |
94 |
95 |
96 | ); 97 | }; 98 | 99 | export default DownFooter; 100 | -------------------------------------------------------------------------------- /src/components/footer/footer.css: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------- 2 | # Footer 3 | --------------------------------------------------------------*/ 4 | #footer { 5 | color: #fff; 6 | font-size: 14px; 7 | background: #111111; 8 | } 9 | #footer .footer-top { 10 | padding: 60px 0 30px 0; 11 | background: #1b1b1b; 12 | } 13 | #footer .footer-top .footer-contact { 14 | margin-bottom: 30px; 15 | } 16 | #footer .footer-top .footer-contact h3 { 17 | font-size: 26px; 18 | line-height: 1; 19 | font-weight: 700; 20 | } 21 | #footer .footer-top .footer-contact h3 span { 22 | color: #e03a3c; 23 | } 24 | #footer .footer-top .footer-contact p { 25 | font-size: 14px; 26 | line-height: 24px; 27 | margin-bottom: 0; 28 | font-family: 'Raleway', sans-serif; 29 | } 30 | #footer .footer-top h4 { 31 | font-size: 16px; 32 | font-weight: bold; 33 | position: relative; 34 | padding-bottom: 12px; 35 | } 36 | #footer .footer-top h4::after { 37 | content: ''; 38 | position: absolute; 39 | display: block; 40 | width: 20px; 41 | height: 2px; 42 | background: #e03a3c; 43 | bottom: 0; 44 | left: 0; 45 | } 46 | #footer .footer-top .footer-links { 47 | margin-bottom: 30px; 48 | } 49 | #footer .footer-top .footer-links ul { 50 | list-style: none; 51 | padding: 0; 52 | margin: 0; 53 | } 54 | #footer .footer-top .footer-links ul i { 55 | padding-right: 2px; 56 | color: white; 57 | font-size: 18px; 58 | line-height: 1; 59 | } 60 | #footer .footer-top .footer-links ul li { 61 | padding: 10px 0; 62 | display: flex; 63 | align-items: center; 64 | } 65 | #footer .footer-top .footer-links ul li:first-child { 66 | padding-top: 0; 67 | } 68 | #footer .footer-top .footer-links ul a { 69 | color: #aaaaaa; 70 | transition: 0.3s; 71 | display: inline-block; 72 | line-height: 1; 73 | } 74 | #footer .footer-top .footer-links ul a:hover { 75 | text-decoration: none; 76 | color: #fff; 77 | } 78 | #footer .footer-newsletter { 79 | font-size: 15px; 80 | } 81 | #footer .footer-newsletter h4 { 82 | font-size: 16px; 83 | font-weight: bold; 84 | position: relative; 85 | padding-bottom: 12px; 86 | } 87 | #footer .footer-newsletter form { 88 | margin-top: 30px; 89 | background: #fff; 90 | padding: 5px 10px; 91 | position: relative; 92 | border-radius: 4px; 93 | text-align: left; 94 | } 95 | #footer .footer-newsletter form input[type='email'] { 96 | border: 0; 97 | padding: 4px 8px; 98 | width: calc(100% - 100px); 99 | } 100 | #footer .footer-newsletter form input[type='submit'] { 101 | position: absolute; 102 | top: 0; 103 | right: -1px; 104 | bottom: 0; 105 | border: 0; 106 | background: none; 107 | font-size: 16px; 108 | padding: 0 20px; 109 | background: #e03a3c; 110 | color: #fff; 111 | transition: 0.3s; 112 | border-radius: 0 4px 4px 0; 113 | box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1); 114 | } 115 | #footer .footer-newsletter form input[type='submit']:hover { 116 | background: #e35052; 117 | } 118 | #footer .credits { 119 | padding-top: 5px; 120 | font-size: 13px; 121 | } 122 | #footer .social-links a { 123 | font-size: 18px; 124 | display: inline-block; 125 | background: #2b2b2b; 126 | color: #fff; 127 | line-height: 1; 128 | padding: 8px 0; 129 | margin-right: 4px; 130 | border-radius: 4px; 131 | text-align: center; 132 | width: 36px; 133 | height: 36px; 134 | transition: 0.3s; 135 | } 136 | #footer .social-links a:hover { 137 | background: #e03a3c; 138 | color: #fff; 139 | text-decoration: none; 140 | } 141 | -------------------------------------------------------------------------------- /src/components/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Image } from 'react-bootstrap'; 3 | 4 | const Footer = () => { 5 | return ( 6 |
7 |
8 |
9 | © Copyright{' '} 10 | 11 | Type Shop 12 | 13 | . All Rights Reserved 14 |
15 |
16 | Designed by Type Shop 17 |
18 |
19 | 36 |
37 | ); 38 | }; 39 | 40 | export default Footer; 41 | -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Nav, NavDropdown } from 'react-bootstrap'; 2 | import { Link, NavLink, useNavigate } from 'react-router-dom'; 3 | import { useAppDispatch, useAppSelector } from '../redux'; 4 | import { reset } from '../redux/cart/cart-slice'; 5 | import { userLogout } from '../redux/users/login-slice'; 6 | 7 | const Header = () => { 8 | const { userInfo } = useAppSelector((state) => state.login); 9 | const { cartItems } = useAppSelector((state) => state.cart); 10 | const dispatch = useAppDispatch(); 11 | const navigate = useNavigate(); 12 | 13 | const onLogout = () => { 14 | dispatch(userLogout()); 15 | dispatch(reset()); 16 | navigate('/login'); 17 | }; 18 | 19 | return ( 20 | <> 21 | 77 | 78 | 85 |
86 | {/* Logo */} 87 | 88 | ... 93 | 94 | {/* Navbar toggle */} 95 | 96 | {/* Collapse */} 97 | 98 | {/* Nav */} 99 |
100 | 106 | Home 107 | 108 | 109 | Product 110 | 111 | 112 | 113 | Contact 114 | 115 |
116 | {/* Right navigation */} 117 | 118 |
119 |
120 | 121 | 122 | 123 | 127 | 128 | 132 | {cartItems.length} 133 | 134 | 135 |
136 | {!userInfo ? ( 137 | <> 138 |
139 | 144 | Login 145 | 146 |
147 | 148 |
149 | 155 | Register 156 | 157 |
158 | 159 | ) : ( 160 |
} 162 | id='basic-nav-dropdown' 163 | > 164 | {userInfo.isAdmin && ( 165 | 166 | Dashboard 167 | 168 | )} 169 | 173 | Profile 174 | 175 | 176 | Logout 177 | 178 | )} 179 |
180 | 181 |
182 | 183 | 184 | ); 185 | }; 186 | 187 | export default Header; 188 | -------------------------------------------------------------------------------- /src/components/layouts/dashboard-layout.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useEffect } from 'react'; 2 | import { Container } from 'react-bootstrap'; 3 | import { Outlet, useNavigate } from 'react-router-dom'; 4 | import { useAppSelector } from '../../redux'; 5 | import Sidebar from '../dashboard/sidebar'; 6 | import Topbar from '../dashboard/topbar'; 7 | 8 | const DashboardLayout = () => { 9 | const { userInfo } = useAppSelector((state) => state.login); 10 | const navigate = useNavigate(); 11 | useEffect(() => { 12 | if (!userInfo && !userInfo?.isAdmin) { 13 | navigate('/'); 14 | } 15 | }, [userInfo]); 16 | 17 | return ( 18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default DashboardLayout; 35 | -------------------------------------------------------------------------------- /src/components/layouts/default-layout.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, ReactNode } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import Brands from '../brands/brands'; 4 | import Carousels from '../carousels'; 5 | import DownFooter from '../footer/down-footer'; 6 | import Footer from '../footer/footer'; 7 | import Header from '../header'; 8 | import Meta from '../UI/meta'; 9 | 10 | type LayoutProvider = { 11 | children: ReactNode; 12 | title?: string; 13 | description?: string; 14 | }; 15 | 16 | const DefaultLayout = ({ 17 | title = '', 18 | description = '', 19 | children, 20 | }: LayoutProvider) => { 21 | const location = useLocation(); 22 | const isHome = location.pathname === '/'; 23 | return ( 24 | 25 | 26 |
27 | {isHome && } 28 |
29 | {children} 30 |
31 | {isHome && } 32 | 36 | 37 | ); 38 | }; 39 | 40 | export default DefaultLayout; 41 | -------------------------------------------------------------------------------- /src/components/modals/product-modal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Form } from 'react-bootstrap'; 2 | import ModalContainer from '../UI/modal-container'; 3 | import { useForm } from 'react-hook-form'; 4 | import { yupResolver } from '@hookform/resolvers/yup'; 5 | import * as Yup from 'yup'; 6 | import authAxios from '../../utils/auth-axios'; 7 | import toast from 'react-hot-toast'; 8 | import { setError } from '../../utils/error'; 9 | import { ChangeEvent, useState } from 'react'; 10 | import { baseUrl } from '../../utils/helper'; 11 | 12 | type Props = { 13 | show: boolean; 14 | handleClose: () => void; 15 | setRefresh: any; 16 | }; 17 | 18 | type FormValues = { 19 | name: string; 20 | image: string; 21 | category: string; 22 | brand: string; 23 | price: number; 24 | description: string; 25 | }; 26 | 27 | const ProductModal = ({ show, handleClose, setRefresh }: Props) => { 28 | const validationSchema = Yup.object().shape({ 29 | name: Yup.string().required(), 30 | category: Yup.string().required(), 31 | brand: Yup.string().required(), 32 | price: Yup.number().required(), 33 | description: Yup.string().required(), 34 | }); 35 | const [image, setImage] = useState(''); 36 | const { 37 | register, 38 | handleSubmit, 39 | formState: { errors }, 40 | } = useForm({ 41 | resolver: yupResolver(validationSchema), 42 | }); 43 | 44 | const onChange = (e: ChangeEvent) => { 45 | if (e.target.files) { 46 | const file = e.target.files[0]; 47 | 48 | let formData = new FormData(); 49 | 50 | formData.append('image', file); 51 | 52 | authAxios.post('/uploads/image', formData).then((res) => { 53 | if (res.data) { 54 | setImage(`${baseUrl}${res.data}`); 55 | } 56 | }); 57 | } 58 | }; 59 | 60 | const onSubmit = (data: FormValues) => { 61 | authAxios 62 | .post('/products', { ...data, image }) 63 | .then((res) => { 64 | toast.success('Product has beend created'); 65 | setRefresh((prev: any) => (prev = !prev)); 66 | handleClose(); 67 | }) 68 | .catch((err) => toast.error(setError(err))); 69 | }; 70 | 71 | return ( 72 | 73 |
74 | 75 | Name 76 | 82 |

{errors.name?.message}

83 |
84 | 85 | Image 86 | 92 | 93 | 94 | Brand 95 | 101 |

{errors.brand?.message}

102 |
103 | 104 | Category 105 | 111 |

{errors.category?.message}

112 |
113 | 114 | 115 | Price 116 | 122 |

{errors.price?.message}

123 |
124 | 125 | Description 126 | 133 |

{errors.description?.message}

134 |
135 | 143 |
144 |
145 | ); 146 | }; 147 | 148 | export default ProductModal; 149 | -------------------------------------------------------------------------------- /src/components/product-card.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "react-bootstrap"; 2 | import { Link } from "react-router-dom"; 3 | import { formatCurrencry } from "../utils/helper"; 4 | import { ReviewTypes } from "../utils/interfaces"; 5 | import ImageLazy from "./UI/lazy-image"; 6 | 7 | export type Product = { 8 | _id: number | string; 9 | name: string; 10 | price: number; 11 | image: string; 12 | category: string; 13 | brand: string; 14 | description: string; 15 | qty: number; 16 | createdAt: Date; 17 | reviews: ReviewTypes[]; 18 | }; 19 | 20 | type Props = { 21 | product: Product; 22 | }; 23 | 24 | const ProductCard = ({ product }: Props) => { 25 | return ( 26 | 35 | 36 | 44 | 45 | 46 | 47 | {product.name} 48 |
49 | {formatCurrencry(product.price)} 50 |
51 |
52 | 53 |
54 | ); 55 | }; 56 | 57 | export default ProductCard; 58 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import "@webpixels/css"; 2 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap"); 3 | @import url("https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.4.0/font/bootstrap-icons.min.css"); 4 | 5 | * { 6 | box-sizing: border-box; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | body { 12 | background: #f1f1f1 !important; 13 | font-family: "Roboto", sans-serif; 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | color: inherit; 19 | } 20 | .rating span { 21 | margin: 0 2px; 22 | } 23 | 24 | #main { 25 | min-height: 80vh; 26 | } 27 | 28 | .form-label { 29 | font-size: 1.2rem; 30 | font-weight: bold; 31 | margin: 10px 0; 32 | } 33 | 34 | .carsouel__item .lazy-carousel { 35 | height: 38rem; 36 | } 37 | 38 | .nav-link span { 39 | font-size: 1.1rem; 40 | transition: all 0.2s ease; 41 | } 42 | 43 | .nav-link span:hover { 44 | font-weight: bold; 45 | color: #e03a3c !important; 46 | } 47 | 48 | .dropdown-item { 49 | transition: all 0.2s ease; 50 | } 51 | 52 | .dropdown-item:hover { 53 | color: #fff; 54 | background-color: #e03a3c !important; 55 | } 56 | 57 | @media (max-height: 760px) { 58 | .carsouel__item img { 59 | height: 12rem; 60 | } 61 | } 62 | 63 | section { 64 | padding: 60px 0; 65 | overflow: hidden; 66 | position: relative; 67 | } 68 | 69 | .icons__cart { 70 | transition: all 0.3 ease; 71 | } 72 | 73 | .icons__cart:active { 74 | transform: scale(1.1); 75 | } 76 | 77 | .stripe__container button { 78 | width: 100%; 79 | } 80 | 81 | /* Brand */ 82 | .brand-img { 83 | filter: grayscale(100%); 84 | opacity: 0.5; 85 | transition: 0.5s; 86 | height: 70px; 87 | width: 120px; 88 | } 89 | 90 | .carousel__item { 91 | overflow: hidden; 92 | } 93 | 94 | .carousel__image { 95 | object-fit: cover; 96 | height: 700px; 97 | width: 100vw; 98 | } 99 | 100 | @media (max-height: 760px) { 101 | .brand-img { 102 | height: 40px; 103 | width: 80px; 104 | } 105 | .carousel__image { 106 | height: 400px !important; 107 | width: 100vw; 108 | } 109 | } 110 | 111 | .brand-img:hover { 112 | filter: grayscale(0%); 113 | opacity: 1; 114 | } 115 | 116 | .tempaltemo-carousel .h1 { 117 | font-size: 0.5em !important; 118 | color: #000 !important; 119 | } 120 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | import { Provider } from 'react-redux'; 6 | import { persistor, store } from './redux'; 7 | import { PersistGate } from 'redux-persist/integration/react'; 8 | 9 | ReactDOM.createRoot(document.getElementById('root')!).render( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/pages/cart/cart-page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Container, 3 | Row, 4 | Col, 5 | ListGroup, 6 | Image, 7 | Button, 8 | Card, 9 | } from "react-bootstrap"; 10 | import { FaMinus, FaPlus } from "react-icons/fa"; 11 | import { Link } from "react-router-dom"; 12 | import { useNavigate } from "react-router-dom"; 13 | import DefaultLayout from "../../components/layouts/default-layout"; 14 | import Message from "../../components/UI/message"; 15 | import { useAppDispatch, useAppSelector } from "../../redux"; 16 | import { addToCart, removeFromCart } from "../../redux/cart/cart-slice"; 17 | import { formatCurrencry } from "../../utils/helper"; 18 | import ImageLazy from "../../components/UI/lazy-image"; 19 | 20 | const CartPage = () => { 21 | const { cartItems } = useAppSelector((state) => state.cart); 22 | const dispatch = useAppDispatch(); 23 | 24 | const navigate = useNavigate(); 25 | 26 | return ( 27 | 28 | 29 | {cartItems.length === 0 ? ( 30 | 31 | Your cart is empty 32 | 33 | Go Back 34 | 35 | 36 | ) : ( 37 | 38 | 39 | 40 | {cartItems.map((item) => ( 41 | 45 | 46 | 47 | 52 | 53 | {item.name} 54 | {item?.qty} 55 | 56 | {formatCurrencry(item.price * item.qty)} 57 | 58 | dispatch(addToCart(item))} 60 | size={"1.5rem"} 61 | style={{ backgroundColor: "#e03a3c" }} 62 | className="icons__cart m-2 rounded-circle text-white p-1 cursor-pointer" 63 | /> 64 | dispatch(removeFromCart(item))} 69 | /> 70 | 71 | 72 | 73 | ))} 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | SubTotal ( 82 | {cartItems.reduce((acc, item) => acc + item.qty, 0)}) item 83 | 84 | 85 | Total Price : 86 | 87 | {formatCurrencry( 88 | cartItems.reduce( 89 | (acc, item) => acc + item.price * item.qty, 90 | 0 91 | ) 92 | )} 93 | 94 | 95 | 96 | 105 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | )} 120 | 121 | 122 | ); 123 | }; 124 | 125 | export default CartPage; 126 | -------------------------------------------------------------------------------- /src/pages/cart/checkout.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Col, Container, Image, ListGroup, Row } from "react-bootstrap"; 2 | import toast from "react-hot-toast"; 3 | import { useNavigate } from "react-router-dom"; 4 | import DefaultLayout from "../../components/layouts/default-layout"; 5 | import RedButton from "../../components/UI/red-button"; 6 | import { useAppDispatch, useAppSelector } from "../../redux"; 7 | import { reset } from "../../redux/cart/cart-slice"; 8 | import authAxios from "../../utils/auth-axios"; 9 | import { setError } from "../../utils/error"; 10 | import { formatCurrencry } from "../../utils/helper"; 11 | import ImageLazy from "../../components/UI/lazy-image"; 12 | 13 | const Checkout = () => { 14 | const { shippingAddress, cartItems } = useAppSelector((state) => state.cart); 15 | const dispatch = useAppDispatch(); 16 | 17 | const navigate = useNavigate(); 18 | const itemsPrice = cartItems.reduce( 19 | (acc, item) => acc + item.qty * item.price, 20 | 0 21 | ); 22 | 23 | const taxPrice = itemsPrice * 0.1; 24 | 25 | const shippingPrice = itemsPrice >= 200 ? 0 : 30; 26 | 27 | const totalPrice = itemsPrice + taxPrice + shippingPrice; 28 | 29 | const onSubmit = () => { 30 | const order = { 31 | totalPrice, 32 | cartItems, 33 | shippingAddress, 34 | }; 35 | authAxios 36 | .post("/orders", order) 37 | .then((res) => { 38 | toast.success("your order has been created"); 39 | dispatch(reset()); 40 | navigate(`/orders/${res.data._id}`); 41 | }) 42 | .catch((err) => toast.error(setError(err))); 43 | }; 44 | 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |

55 | Address: 56 | 57 | {shippingAddress?.address} {shippingAddress?.city}{" "} 58 | {shippingAddress?.postalCode} 59 | 60 |

61 |
62 |

Items

63 | {cartItems.map((item) => ( 64 | 65 | 66 | 67 | 72 | 73 | {item.name} 74 | {item?.qty} 75 | 76 | {formatCurrencry(item.price * item.qty)} 77 | 78 | 79 | 80 | ))} 81 |
82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | SubTotal ( 91 | {cartItems.reduce((acc, item) => acc + item.qty, 0)}) item 92 | 93 | 94 | Total Price : 95 | 96 | {formatCurrencry( 97 | cartItems.reduce( 98 | (acc, item) => acc + item.price * item.qty, 99 | 0 100 | ) 101 | )} 102 | 103 | 104 | 105 | Tax Price 106 | {formatCurrencry(taxPrice)} 107 | 108 | 109 | Shipping Price 110 | {formatCurrencry(shippingPrice)} 111 | 112 | 113 | Total Price 114 | {formatCurrencry(totalPrice)} 115 | 116 | 117 | 122 | Place order 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 |
131 |
132 | ); 133 | }; 134 | 135 | export default Checkout; 136 | -------------------------------------------------------------------------------- /src/pages/cart/order-details.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Card, Col, Container, Image, ListGroup, Row } from "react-bootstrap"; 3 | import { useNavigate, useParams } from "react-router-dom"; 4 | import DefaultLayout from "../../components/layouts/default-layout"; 5 | import Loader from "../../components/UI/loader"; 6 | import { useAppDispatch, useAppSelector } from "../../redux"; 7 | import { getOrderById } from "../../redux/orders/order-details"; 8 | import { formatCurrencry } from "../../utils/helper"; 9 | import Stripe from "react-stripe-checkout"; 10 | import authAxios from "../../utils/auth-axios"; 11 | import toast from "react-hot-toast"; 12 | import { setError } from "../../utils/error"; 13 | import ImageLazy from "../../components/UI/lazy-image"; 14 | 15 | const OrderDetails = () => { 16 | const { order, loading } = useAppSelector((state) => state.orderDetail); 17 | const dispatch = useAppDispatch(); 18 | const { id } = useParams(); 19 | 20 | const itemsPrice: number | undefined = order?.cartItems.reduce( 21 | (acc, item) => acc + item.qty * item.price, 22 | 0 23 | ); 24 | const navigate = useNavigate(); 25 | 26 | const taxPrice = itemsPrice ? itemsPrice * 0.1 : 0; 27 | 28 | const shippingPrice = itemsPrice ? (itemsPrice >= 200 ? 0 : 30) : 0; 29 | 30 | const totalPrice = itemsPrice && itemsPrice + taxPrice + shippingPrice; 31 | 32 | const handlePayment = (token: any) => { 33 | authAxios 34 | .post("/orders/stripe", { 35 | token: token.id, 36 | amount: order?.totalPrice, 37 | }) 38 | .then((res) => { 39 | authAxios.put(`/orders/${order?._id}`).then((res) => { 40 | toast.success("you have been paid successfully🙂"); 41 | navigate("/"); 42 | }); 43 | }) 44 | .catch((error) => toast.error(setError(error))); 45 | }; 46 | const tokenHandler = (token: any) => { 47 | handlePayment(token); 48 | }; 49 | 50 | useEffect(() => { 51 | dispatch(getOrderById(id)); 52 | }, [dispatch, id]); 53 | 54 | return ( 55 | 56 | 57 |

Payment

58 | 59 | {loading ? ( 60 | 61 | ) : ( 62 | 63 | 64 | 65 | 66 |

Order Summery

67 | 68 | {order?.cartItems.map((item) => ( 69 | 70 | 71 | 72 | 77 | 78 | 79 | {item.name} 80 | 81 | {item?.qty} 82 | 83 | {formatCurrencry(item.price * item.qty)} 84 | 85 | 86 | ))} 87 | 88 |
89 |
90 | 91 | 92 | 93 | 94 |

Payment

95 | 96 | 97 | SubTotal ( 98 | {order?.cartItems.reduce( 99 | (acc, item) => acc + item.qty, 100 | 0 101 | )} 102 | ) item 103 | 104 | 105 | Total Price : 106 | 107 | {formatCurrencry( 108 | order?.cartItems.reduce( 109 | (acc, item) => acc + item.price * item.qty, 110 | 0 111 | ) 112 | )} 113 | 114 | 115 | 116 | Tax Price 117 | {formatCurrencry(taxPrice)} 118 | 119 | 120 | Shipping Price 121 | {formatCurrencry(shippingPrice)} 122 | 123 | 124 |
125 | Total Price 126 | {formatCurrencry(totalPrice)} 127 |
128 |
129 | {!order?.isPaid && ( 130 | 131 | 141 | 142 | )} 143 |
144 |
145 |
146 | 147 |
148 | )} 149 |
150 |
151 | ); 152 | }; 153 | 154 | export default OrderDetails; 155 | -------------------------------------------------------------------------------- /src/pages/cart/shipping-address.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Button, Form } from 'react-bootstrap'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import FormContainer from '../../components/UI/form-container'; 5 | import { useAppDispatch, useAppSelector } from '../../redux'; 6 | import { saveAddress } from '../../redux/cart/cart-slice'; 7 | import { AddressTypes } from '../../utils/interfaces'; 8 | 9 | const ShippingAddress = () => { 10 | const { shippingAddress } = useAppSelector((state) => state.cart); 11 | const dispatch = useAppDispatch(); 12 | 13 | const navigate = useNavigate(); 14 | const [formData, setFormData] = useState({ 15 | address: '', 16 | city: '', 17 | postalCode: '', 18 | country: '', 19 | }); 20 | 21 | const onChange = (e: React.ChangeEvent) => { 22 | setFormData((prevState) => ({ 23 | ...prevState, 24 | [e.target.name]: e.target.value, 25 | })); 26 | }; 27 | 28 | const onSubmit = (e: React.FormEvent) => { 29 | e.preventDefault(); 30 | dispatch( 31 | saveAddress({ 32 | address: formData.address, 33 | city: formData.city, 34 | postalCode: formData.postalCode, 35 | country: formData.country, 36 | }) 37 | ); 38 | navigate('/checkout'); 39 | }; 40 | 41 | useEffect(() => { 42 | if (shippingAddress) return navigate('/checkout'); 43 | }, [shippingAddress]); 44 | 45 | return ( 46 | 47 |
48 | 49 | Address 50 | 56 | 57 | 58 | City 59 | 65 | 66 | 67 | Postal Code 68 | 74 | 75 | 76 | Country 77 | 83 | 84 | 92 |
93 |
94 | ); 95 | }; 96 | 97 | export default ShippingAddress; 98 | -------------------------------------------------------------------------------- /src/pages/contact/contact.css: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------- 2 | # Contact 3 | --------------------------------------------------------------*/ 4 | 5 | .contact .info-box { 6 | color: #444444; 7 | text-align: center; 8 | box-shadow: 0 0 30px rgba(214, 215, 216, 0.6); 9 | padding: 30px 0 32px 0; 10 | border-radius: 4px; 11 | } 12 | .contact .info-box i { 13 | font-size: 32px; 14 | color: #e03a3c; 15 | border-radius: 50%; 16 | padding: 8px; 17 | border: 2px dotted #f8d4d5; 18 | } 19 | .contact .info-box h3 { 20 | font-size: 20px; 21 | color: #777777; 22 | font-weight: 700; 23 | margin: 10px 0; 24 | } 25 | .contact .info-box p { 26 | padding: 0; 27 | line-height: 24px; 28 | font-size: 14px; 29 | margin-bottom: 0; 30 | } 31 | .contact .php-email-form { 32 | box-shadow: 0 0 30px rgba(214, 215, 216, 0.6); 33 | padding: 30px; 34 | border-radius: 4px; 35 | } 36 | 37 | .contact .php-email-form .error-message { 38 | display: none; 39 | color: #fff; 40 | background: #ed3c0d; 41 | text-align: left; 42 | padding: 15px; 43 | font-weight: 600; 44 | } 45 | .contact .php-email-form .error-message br + br { 46 | margin-top: 25px; 47 | } 48 | .contact .php-email-form .sent-message { 49 | display: none; 50 | color: #fff; 51 | background: #18d26e; 52 | text-align: center; 53 | padding: 15px; 54 | font-weight: 600; 55 | } 56 | .contact .php-email-form .loading { 57 | display: none; 58 | background: #fff; 59 | text-align: center; 60 | padding: 15px; 61 | } 62 | .contact .php-email-form .loading:before { 63 | content: ''; 64 | display: inline-block; 65 | border-radius: 50%; 66 | width: 24px; 67 | height: 24px; 68 | margin: 0 10px -6px 0; 69 | border: 3px solid #18d26e; 70 | border-top-color: #eee; 71 | -webkit-animation: animate-loading 1s linear infinite; 72 | animation: animate-loading 1s linear infinite; 73 | } 74 | .contact .php-email-form .form-group { 75 | margin-bottom: 25px; 76 | } 77 | .contact .php-email-form input, 78 | .contact .php-email-form textarea { 79 | box-shadow: none; 80 | font-size: 14px; 81 | border-radius: 4px; 82 | } 83 | .contact .php-email-form input:focus, 84 | .contact .php-email-form textarea:focus { 85 | border-color: #111111; 86 | } 87 | .contact .php-email-form input { 88 | padding: 10px 15px; 89 | } 90 | .contact .php-email-form textarea { 91 | padding: 12px 15px; 92 | } 93 | .contact .php-email-form button[type='submit'] { 94 | background: #e03a3c; 95 | border: 0; 96 | padding: 10px 32px; 97 | color: #fff; 98 | transition: 0.4s; 99 | border-radius: 4px; 100 | } 101 | .contact .php-email-form button[type='submit']:hover { 102 | background: #e35052; 103 | } 104 | @-webkit-keyframes animate-loading { 105 | 0% { 106 | transform: rotate(0deg); 107 | } 108 | 100% { 109 | transform: rotate(360deg); 110 | } 111 | } 112 | @keyframes animate-loading { 113 | 0% { 114 | transform: rotate(0deg); 115 | } 116 | 100% { 117 | transform: rotate(360deg); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/pages/contact/contact.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Container, Form, Row } from 'react-bootstrap'; 2 | import toast from 'react-hot-toast'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import DefaultLayout from '../../components/layouts/default-layout'; 5 | import './contact.css'; 6 | 7 | const Contact = () => { 8 | const navigate = useNavigate(); 9 | return ( 10 | 11 |
12 | 13 |
14 |

Contact

15 |

16 | Magnam dolores commodi suscipit. Necessitatibus eius consequatur 17 | ex aliquid fuga eum quidem. Sit sint consectetur velit. Quisquam 18 | quos quisquam cupiditate. Et nemo qui impedit suscipit alias ea. 19 |

20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 |

Our Address

28 |

A108 Adam Street, New York, NY 535022

29 |
30 | 31 | 32 |
33 | 34 |

Email Us

35 |

36 | info@example.com 37 |
38 | contact@example.com 39 |

40 |
41 | 42 | 43 |
44 | 45 |

Call Us

46 |

47 | +1 5589 55488 55 48 |
49 | +1 6678 254445 41 50 |

51 |
52 | 53 |
54 | 55 | 56 |
) => { 58 | e.preventDefault(); 59 | toast.success('Thanks for your feedback 🙂'); 60 | navigate('/'); 61 | }} 62 | className='php-email-form bg-white' 63 | > 64 | 65 | 66 | 74 | 75 |
76 | 84 |
85 |
86 |
87 | 95 |
96 |
97 | 106 |
107 |
108 |
Loading
109 |
110 |
111 | Your message has been sent. Thank you! 112 |
113 |
114 |
115 | 116 |
117 | 118 | 119 | 120 | 121 |
122 |
123 | ); 124 | }; 125 | 126 | export default Contact; 127 | -------------------------------------------------------------------------------- /src/pages/dashboard/dashboard-page.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Row, Col, Card } from "react-bootstrap"; 3 | import { useAppDispatch, useAppSelector } from "../../redux"; 4 | import { getOrdersList } from "../../redux/orders/slice-list"; 5 | import { formatCurrencry } from "../../utils/helper"; 6 | 7 | const DashboardPage = () => { 8 | const { total } = useAppSelector((state) => state.productFilter); 9 | const { orders } = useAppSelector((state) => state.orders); 10 | const { users } = useAppSelector((state) => state.userList); 11 | const dispatch = useAppDispatch(); 12 | 13 | const getTotalCost = () => { 14 | let total = 0; 15 | if (!orders) return 500.3; 16 | orders.map((item: any) => { 17 | if (!item) return; 18 | total += item.totalPrice; 19 | }); 20 | return total; 21 | }; 22 | 23 | const totalPrice = getTotalCost(); 24 | 25 | useEffect(() => { 26 | dispatch(getOrdersList()); 27 | }, [dispatch]); 28 | 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Price 38 | 39 | 40 | {formatCurrencry(totalPrice)} 41 | 42 | 43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 13% 53 | 54 | 55 | Depuis le mois dernier 56 | 57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Clients 68 | 69 | 70 | {users?.length && users?.length} 71 | 72 | 73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 | 81 | 82 | 30% 83 | 84 | 85 | Depuis le mois dernier 86 | 87 |
88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Products 98 | 99 | {total} 100 | 101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 | 109 | 110 | -5% 111 | 112 | 113 | Depuis le mois dernier 114 | 115 |
116 |
117 |
118 | 119 |
120 | ); 121 | }; 122 | 123 | export default DashboardPage; 124 | -------------------------------------------------------------------------------- /src/pages/dashboard/orders/order-table.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Button, Row } from 'react-bootstrap'; 3 | import toast from 'react-hot-toast'; 4 | import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; 5 | import Loader from '../../../components/UI/loader'; 6 | import TableContainer from '../../../components/UI/table-contrainer'; 7 | import { useAppDispatch, useAppSelector } from '../../../redux'; 8 | import { getOrdersList } from '../../../redux/orders/slice-list'; 9 | import authAxios from '../../../utils/auth-axios'; 10 | import { setError } from '../../../utils/error'; 11 | import { formatCurrencry, getDate } from '../../../utils/helper'; 12 | 13 | function OrdersTable() { 14 | const dispatch = useAppDispatch(); 15 | const { orders, loading } = useAppSelector((state) => state.orders); 16 | const [refresh, setRefresh] = useState(false); 17 | const cols = [ 18 | 'Order_id', 19 | 'TotalPrice', 20 | 'Address', 21 | 'Status', 22 | 'Created At', 23 | 'Delete', 24 | ]; 25 | 26 | const onDelete = (id: string | number) => { 27 | if (window.confirm('are you sure?')) { 28 | authAxios 29 | .delete(`/orders/${id}`) 30 | .then((res) => { 31 | toast.success(res.data); 32 | setRefresh((prev) => (prev = !prev)); 33 | }) 34 | .catch((e) => toast.error(setError(e))); 35 | } 36 | }; 37 | 38 | useEffect(() => { 39 | dispatch(getOrdersList()); 40 | }, [dispatch, refresh]); 41 | 42 | return ( 43 | <> 44 | {loading ? ( 45 | 46 | ) : ( 47 | 48 |

49 | Orders List 50 |

51 | 52 | {orders.map((order) => ( 53 | 54 | {order._id} 55 | 56 | {formatCurrencry(order?.totalPrice)} 57 | {order?.shippingAddress?.address} 58 | 59 | {order.isPaid ? ( 60 | 61 | ) : ( 62 | 63 | )} 64 | 65 | {getDate(order?.createdAt)} 66 | 67 | 74 | 75 | 76 | ))} 77 | 78 |
79 | )} 80 | 81 | ); 82 | } 83 | 84 | export default OrdersTable; 85 | -------------------------------------------------------------------------------- /src/pages/dashboard/products/product-table.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Button, Image, Row } from 'react-bootstrap'; 3 | import toast from 'react-hot-toast'; 4 | import { FaEdit, FaTrash } from 'react-icons/fa'; 5 | import { Link, useParams } from 'react-router-dom'; 6 | import ProductModal from '../../../components/modals/product-modal'; 7 | import Loader from '../../../components/UI/loader'; 8 | import Paginate from '../../../components/UI/paginate'; 9 | import TableContainer from '../../../components/UI/table-contrainer'; 10 | import { useAppDispatch, useAppSelector } from '../../../redux'; 11 | import { getFilterProducts } from '../../../redux/products/search-list'; 12 | import authAxios from '../../../utils/auth-axios'; 13 | import { setError } from '../../../utils/error'; 14 | import { formatCurrencry, getDate } from '../../../utils/helper'; 15 | 16 | // Then, use it in a component. 17 | function ProductTable() { 18 | const dispatch = useAppDispatch(); 19 | const { products, page, pages, loading } = useAppSelector( 20 | (state) => state.productFilter 21 | ); 22 | const [refresh, setRefresh] = useState(false); 23 | const [show, setShow] = useState(false); 24 | const params = useParams(); 25 | const pageNumber = params.pageNumber || 1; 26 | 27 | const onOpen = () => setShow(true); 28 | const onClose = () => setShow(false); 29 | 30 | const cols = [ 31 | 'image', 32 | 'name', 33 | 'brand', 34 | 'category', 35 | 'price', 36 | 'created At', 37 | 'options', 38 | ]; 39 | 40 | const onDelete = (id: string | number) => { 41 | if (window.confirm('are you sure?')) { 42 | authAxios 43 | .delete(`/products/${id}`) 44 | .then((res) => { 45 | toast.success('Product has beend deleted'); 46 | setRefresh((prev) => (prev = !prev)); 47 | }) 48 | .catch((e) => toast.error(setError(e))); 49 | } 50 | }; 51 | 52 | useEffect(() => { 53 | dispatch(getFilterProducts({ n: pageNumber, b: '', c: '', q: '' })); 54 | }, [dispatch, pageNumber, refresh]); 55 | 56 | return ( 57 | <> 58 | {loading ? ( 59 | 60 | ) : ( 61 | 62 |

63 | Product List 64 | 72 |

73 | 74 | {products.map((product) => ( 75 | 76 | 77 | 78 | 79 | {product.name} 80 | {product.brand} 81 | {product.category} 82 | {formatCurrencry(product.price)} 83 | {getDate(product?.createdAt)} 84 | 85 | 89 | 90 | 91 | 98 | 99 | {/* {product?.created_at} */} 100 | 101 | ))} 102 | 103 |
104 | )} 105 | 106 | 107 | 108 | ); 109 | } 110 | 111 | export default ProductTable; 112 | -------------------------------------------------------------------------------- /src/pages/dashboard/products/product-update.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, Col, Form, Row } from 'react-bootstrap'; 2 | import { useForm } from 'react-hook-form'; 3 | import { yupResolver } from '@hookform/resolvers/yup'; 4 | import * as Yup from 'yup'; 5 | import { useAppSelector } from '../../../redux'; 6 | import { useNavigate, useParams } from 'react-router-dom'; 7 | import authAxios from '../../../utils/auth-axios'; 8 | import toast from 'react-hot-toast'; 9 | import { setError } from '../../../utils/error'; 10 | 11 | type FormValues = { 12 | name: string; 13 | image: string; 14 | category: string; 15 | brand: string; 16 | price: number; 17 | description: string; 18 | }; 19 | const ProductUpdate = () => { 20 | const { products } = useAppSelector((state) => state.productFilter); 21 | 22 | const { id } = useParams(); 23 | const navigate = useNavigate(); 24 | const product = products.find((p) => p._id === id); 25 | const validationSchema = Yup.object().shape({ 26 | name: Yup.string().required(), 27 | image: Yup.string().required(), 28 | category: Yup.string().required(), 29 | brand: Yup.string().required(), 30 | price: Yup.number().required(), 31 | description: Yup.string().required(), 32 | }); 33 | console.log(product); 34 | const { 35 | register, 36 | handleSubmit, 37 | formState: { errors }, 38 | } = useForm({ 39 | resolver: yupResolver(validationSchema), 40 | }); 41 | 42 | const onSubmit = (data: FormValues) => { 43 | authAxios 44 | .put(`/products/${product?._id}`, data) 45 | .then((res) => { 46 | toast.success('Product has beend updated'); 47 | navigate('/dashboard/product-list'); 48 | }) 49 | .catch((err) => toast.error(setError(err))); 50 | }; 51 | 52 | return ( 53 | <> 54 | 55 | 56 | 57 |

Update Product

58 | 59 |
60 | 61 | Name 62 | 70 |

{errors.name?.message}

71 |
72 | 73 | Image 74 | 82 |

{errors.image?.message}

83 |
84 | 85 | Brand 86 | 94 |

{errors.brand?.message}

95 |
96 | 97 | Category 98 | 106 |

{errors.category?.message}

107 |
108 | 109 | 110 | Price 111 | 118 |

{errors.price?.message}

119 |
120 | 121 | Description 122 | 131 |

132 | {errors.description?.message} 133 |

134 |
135 | 138 |
139 |
140 |
141 | 142 |
143 | 144 | ); 145 | }; 146 | 147 | export default ProductUpdate; 148 | -------------------------------------------------------------------------------- /src/pages/dashboard/users/users-table.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Button, Pagination, Row } from "react-bootstrap"; 3 | import toast from "react-hot-toast"; 4 | import { FaCheck, FaTimes, FaTrash } from "react-icons/fa"; 5 | import Loader from "../../../components/UI/loader"; 6 | import TableContainer from "../../../components/UI/table-contrainer"; 7 | import { useAppDispatch, useAppSelector } from "../../../redux"; 8 | import { getUsersList } from "../../../redux/users/user-list"; 9 | import authAxios from "../../../utils/auth-axios"; 10 | import { setError } from "../../../utils/error"; 11 | import { getDate } from "../../../utils/helper"; 12 | 13 | const UserTable = () => { 14 | const dispatch = useAppDispatch(); 15 | const { 16 | users, 17 | loading, 18 | page: curPage, 19 | pages, 20 | } = useAppSelector((state) => state.userList); 21 | 22 | const cols = ["name", "email", "created At", "admin", "promote", "delete"]; 23 | const [refresh, setRefresh] = useState(false); 24 | const [search, setSearch] = useState(""); 25 | const [page, setPage] = useState(curPage); 26 | 27 | const onDelete = (id: string | number) => { 28 | if (window.confirm("are you sure?")) { 29 | authAxios 30 | .delete(`/users/${id}`) 31 | .then((res) => { 32 | toast.success("user has beend deleted"); 33 | setRefresh((prev) => (prev = !prev)); 34 | }) 35 | .catch((e) => toast.error(setError(e))); 36 | } 37 | }; 38 | 39 | const onPromote = (id: string | number) => { 40 | if (window.confirm("are you sure?")) { 41 | authAxios 42 | .post(`/users/promote/${id}`) 43 | .then((res) => { 44 | toast.success("user has beend promoted"); 45 | setRefresh((prev) => (prev = !prev)); 46 | }) 47 | .catch((e) => toast.error(setError(e))); 48 | } 49 | }; 50 | 51 | useEffect(() => { 52 | dispatch(getUsersList({ page, query: search })); 53 | }, [dispatch, refresh, page, search]); 54 | 55 | return ( 56 | <> 57 | {loading ? ( 58 | 59 | ) : ( 60 | 61 |

62 | User List 63 | {/* */} 64 |

65 | 66 | {users?.map((user) => ( 67 | 68 | {user.name} 69 | {user.email} 70 | {getDate(user.createdAt)} 71 | 72 | {user.isAdmin ? ( 73 | 74 | ) : ( 75 | 76 | )} 77 | 78 | 79 | {" "} 80 | {!user?.isAdmin && ( 81 | 89 | )} 90 | 91 | 92 | 99 | 100 | 101 | ))} 102 | 103 | 104 | {[...Array(pages).keys()].map((x) => ( 105 | setPage(x + 1)} 109 | > 110 | {x + 1} 111 | 112 | ))} 113 | 114 |
115 | )} 116 | 117 | ); 118 | }; 119 | 120 | export default UserTable; 121 | -------------------------------------------------------------------------------- /src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Col, Container, Row } from "react-bootstrap"; 3 | import DefaultLayout from "../components/layouts/default-layout"; 4 | import ProductCard from "../components/product-card"; 5 | import Loader from "../components/UI/loader"; 6 | import { useAppDispatch, useAppSelector } from "../redux"; 7 | import { getProducts } from "../redux/products/slice-list"; 8 | import { trackWindowScroll } from "react-lazy-load-image-component"; 9 | 10 | const HomePage = () => { 11 | const dispatch = useAppDispatch(); 12 | const { products, loading } = useAppSelector((state) => state.productList); 13 | 14 | useEffect(() => { 15 | dispatch(getProducts()); 16 | }, [dispatch]); 17 | 18 | return ( 19 | 20 | 21 |

22 | Least Products 23 |

24 | {loading || !products ? ( 25 | 26 | ) : ( 27 | 28 | {products.map((product) => ( 29 | 30 | 31 | 32 | ))} 33 | 34 | )} 35 |
36 |
37 | ); 38 | }; 39 | 40 | export default trackWindowScroll(HomePage); 41 | -------------------------------------------------------------------------------- /src/pages/product-details.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | Button, 4 | Card, 5 | Col, 6 | Container, 7 | Form, 8 | ListGroup, 9 | Row, 10 | } from "react-bootstrap"; 11 | import toast from "react-hot-toast"; 12 | import { Link } from "react-router-dom"; 13 | import { useNavigate, useParams } from "react-router-dom"; 14 | import DefaultLayout from "../components/layouts/default-layout"; 15 | import { Product } from "../components/product-card"; 16 | import Loader from "../components/UI/loader"; 17 | import Message from "../components/UI/message"; 18 | import Rating from "../components/UI/rating"; 19 | import RedButton from "../components/UI/red-button"; 20 | import { useAppDispatch, useAppSelector } from "../redux"; 21 | import { addToCart } from "../redux/cart/cart-slice"; 22 | import { getProductById } from "../redux/products/slice-details"; 23 | import authAxios from "../utils/auth-axios"; 24 | import { setError } from "../utils/error"; 25 | import { formatCurrencry, getDate } from "../utils/helper"; 26 | import ImageLazy from "../components/UI/lazy-image"; 27 | 28 | const ProductDetails = () => { 29 | const dispatch = useAppDispatch(); 30 | const { product, loading } = useAppSelector((state) => state.productDetail); 31 | const { userInfo } = useAppSelector((state) => state.login); 32 | const params = useParams(); 33 | const { id } = params; 34 | const navigate = useNavigate(); 35 | const [rating, setRating] = useState(0); 36 | const [comment, setComment] = useState(""); 37 | const [refresh, setRefresh] = useState(false); 38 | 39 | const onAdd = () => { 40 | dispatch(addToCart(product as Product)); 41 | navigate("/cart"); 42 | }; 43 | 44 | const onSubmit = (e: React.FormEvent) => { 45 | e.preventDefault(); 46 | const review = { 47 | comment, 48 | rating, 49 | }; 50 | authAxios 51 | .post(`/products/${product?._id}/reviews`, review) 52 | .then((res) => { 53 | toast.success("thank you for the comment 🙂"); 54 | setRefresh((prev) => (prev = !prev)); 55 | }) 56 | .catch((err) => toast.error(setError(err))); 57 | }; 58 | 59 | useEffect(() => { 60 | dispatch(getProductById(id)); 61 | window.scrollTo(0, 0); 62 | }, [id, dispatch, refresh]); 63 | 64 | return ( 65 | 66 | {loading || !product ? ( 67 | 68 | ) : ( 69 | 70 | 71 | 72 | 73 |
74 | 83 |
84 |
85 | 86 | 87 | 91 | 92 |

{product?.name}

93 |
94 | 95 | {" "} 96 |
97 | Price: 98 | {formatCurrencry(product.price)} 99 |
100 |
101 | 102 | 103 |
104 | Category: 105 | {product.category} 106 |
107 |
108 | 109 |
110 | Brand: 111 | {product.brand} 112 |
113 |
114 | {product.description} 115 | 116 | 117 | Add To Cart 118 | 119 | 120 |
121 | 122 |
123 | 124 | 125 | 126 | 127 |

Reviews

128 | 129 | {product.reviews.map((review) => ( 130 | 131 |
132 | {review.name} 133 | 134 |

{getDate(review.createdAt)}

135 |
136 |

{review.comment}

137 |
138 | ))} 139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 |

Comment

147 | {userInfo ? ( 148 |
149 | 150 | Rating 151 | setRating(e.target.value)} 154 | as="select" 155 | > 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Comment 165 | setComment(e.target.value)} 168 | as={"textarea"} 169 | rows={3} 170 | /> 171 | 172 | 180 |
181 | ) : ( 182 | 183 | You must login first to feedback{" "} 184 | 185 | Login Now 186 | 187 | 188 | )} 189 |
190 |
191 | 192 |
193 |
194 | )} 195 |
196 | ); 197 | }; 198 | 199 | export default ProductDetails; 200 | -------------------------------------------------------------------------------- /src/pages/products.tsx: -------------------------------------------------------------------------------- 1 | //test 2 | import { useEffect, useState } from "react"; 3 | import { 4 | Row, 5 | Container, 6 | Col, 7 | Card, 8 | Form, 9 | ListGroup, 10 | FormSelect, 11 | } from "react-bootstrap"; 12 | import { useParams } from "react-router-dom"; 13 | import DefaultLayout from "../components/layouts/default-layout"; 14 | import ProductCard from "../components/product-card"; 15 | import Paginate from "../components/UI/paginate"; 16 | import { useAppDispatch, useAppSelector } from "../redux"; 17 | import { getFilterProducts } from "../redux/products/search-list"; 18 | import { trackWindowScroll } from "react-lazy-load-image-component"; 19 | 20 | const Products = () => { 21 | const params = useParams(); 22 | const { products, categories, brands, page, pages } = useAppSelector( 23 | (state) => state.productFilter 24 | ); 25 | const dispatch = useAppDispatch(); 26 | const [brand, setBrand] = useState(""); 27 | const [category, setCategory] = useState(""); 28 | const [search, setSearch] = useState(""); 29 | const keyword = params.keyword; 30 | 31 | const pageNumber = params.pageNumber || 1; 32 | 33 | const reset = () => { 34 | setBrand(""); 35 | setCategory(""); 36 | setSearch(""); 37 | }; 38 | 39 | useEffect(() => { 40 | dispatch( 41 | getFilterProducts({ n: pageNumber, b: brand, c: category, q: search }) 42 | ); 43 | }, [dispatch, pageNumber, brand, search, category]); 44 | 45 | return ( 46 | 47 | 48 | 49 | 50 |

Filter

51 | 52 | 53 | 54 |

Category

55 | { 58 | if (e.target.value === "All") { 59 | reset(); 60 | } else { 61 | setCategory(e.target.value); 62 | } 63 | }} 64 | > 65 | 66 | All 67 | {categories.map((categorie: any) => ( 68 | 71 | ))} 72 | 73 |
74 | 75 |

Brand

76 | { 79 | if (e.target.value === "All") { 80 | reset(); 81 | } else { 82 | setBrand(e.target.value); 83 | } 84 | }} 85 | > 86 | 87 | All 88 | {brands.map((brand: any) => ( 89 | 92 | ))} 93 | 94 |
95 |
96 |
97 | 98 | 99 | 100 | 101 |
102 |
103 | setSearch(e.target.value)} 105 | className="me-2" 106 | placeholder="Search..." 107 | value={search} 108 | /> 109 |
110 |
111 |
112 | 113 | {products.map((product) => ( 114 | 115 | 116 | 117 | ))} 118 | 119 | 120 |
121 | 127 |
128 |
129 | ); 130 | }; 131 | 132 | export default trackWindowScroll(Products); 133 | -------------------------------------------------------------------------------- /src/pages/users/login.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import FormContainer from "../../components/UI/form-container"; 3 | import { Button, Form } from "react-bootstrap"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | import { useForm } from "react-hook-form"; 6 | import { yupResolver } from "@hookform/resolvers/yup"; 7 | import * as Yup from "yup"; 8 | import { useAppDispatch, useAppSelector } from "../../redux"; 9 | import { userLogin } from "../../redux/users/login-slice"; 10 | 11 | type FormValues = { 12 | email: string; 13 | password: string; 14 | }; 15 | 16 | const Login = () => { 17 | const dispatch = useAppDispatch(); 18 | const navigate = useNavigate(); 19 | const { userInfo, loading } = useAppSelector((state) => state.login); 20 | const validationSchema = Yup.object().shape({ 21 | email: Yup.string().required("Email is required").email("Email is invalid"), 22 | password: Yup.string() 23 | .required("Password is required") 24 | .min(6, "Password must be at least 6 characters") 25 | .max(40, "Password must not exceed 40 characters"), 26 | }); 27 | 28 | const { 29 | register, 30 | handleSubmit, 31 | formState: { errors }, 32 | } = useForm({ 33 | resolver: yupResolver(validationSchema), 34 | }); 35 | 36 | const onSubmit = (data: FormValues) => { 37 | dispatch(userLogin(data)); 38 | }; 39 | 40 | useEffect(() => { 41 | if (userInfo) { 42 | navigate("/"); 43 | } 44 | }, [userInfo]); 45 | 46 | return ( 47 | 52 |
53 | 54 | Email 55 | 56 | 62 |

{errors.email?.message}

63 |
64 | 65 | 66 | Mot de Passe 67 | 73 |

{errors.password?.message}

74 | 75 | Dont have an Account ? Register 76 | 77 |
78 | 79 | 88 |
89 |
90 | ); 91 | }; 92 | 93 | export default Login; 94 | -------------------------------------------------------------------------------- /src/pages/users/profile.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Container, Row, Col, Button, Form, Card } from 'react-bootstrap'; 3 | import { Link, useParams } from 'react-router-dom'; 4 | import DefaultLayout from '../../components/layouts/default-layout'; 5 | import Loader from '../../components/UI/loader'; 6 | import TableContainer from '../../components/UI/table-contrainer'; 7 | import { useAppDispatch, useAppSelector } from '../../redux'; 8 | import { getUserBydId } from '../../redux/users/user-details'; 9 | import { useForm } from 'react-hook-form'; 10 | import { yupResolver } from '@hookform/resolvers/yup'; 11 | import * as Yup from 'yup'; 12 | import authAxios from '../../utils/auth-axios'; 13 | import toast from 'react-hot-toast'; 14 | import { setError } from '../../utils/error'; 15 | import { getUserOrder } from '../../redux/orders/user-orders'; 16 | import { formatCurrencry, getDate } from '../../utils/helper'; 17 | import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; 18 | import { GrView } from 'react-icons/gr'; 19 | 20 | type FormValues = { 21 | email: string; 22 | name: string; 23 | password: string; 24 | confirmPassword: string; 25 | }; 26 | 27 | const Profile = () => { 28 | const dispatch = useAppDispatch(); 29 | const { user, loading } = useAppSelector((state) => state.userDetails); 30 | const { orders, loading: orderLoading } = useAppSelector( 31 | (state) => state.userOrder 32 | ); 33 | 34 | const { id } = useParams(); 35 | const [refresh, setRefresh] = useState(false); 36 | const validationSchema = Yup.object().shape({ 37 | name: Yup.string().required(), 38 | email: Yup.string().required('Email is required').email('Email is invalid'), 39 | password: Yup.string(), 40 | confirmPassword: Yup.string().oneOf( 41 | [Yup.ref('password'), null], 42 | 'Confirm Password does not match' 43 | ), 44 | }); 45 | 46 | const { 47 | register, 48 | handleSubmit, 49 | formState: { errors }, 50 | } = useForm({ 51 | resolver: yupResolver(validationSchema), 52 | }); 53 | 54 | const onSubmit = (data: FormValues) => { 55 | const update = { 56 | name: data.name, 57 | email: data.email, 58 | password: data.password === '' ? null : data.password, 59 | }; 60 | authAxios 61 | .put(`/users/${user?._id}`, update) 62 | .then((res) => { 63 | toast.success('user has been updated'); 64 | setRefresh((prev) => (prev = !prev)); 65 | }) 66 | .catch((err) => toast.error(setError(err))); 67 | }; 68 | 69 | const onDelete = (id: string | number) => { 70 | if (window.confirm('are you sure?')) { 71 | authAxios 72 | .delete(`/orders/${id}`) 73 | .then((res) => { 74 | toast.success(res.data); 75 | setRefresh((prev) => (prev = !prev)); 76 | }) 77 | .catch((e) => toast.error(setError(e))); 78 | } 79 | }; 80 | 81 | useEffect(() => { 82 | dispatch(getUserBydId(id)); 83 | dispatch(getUserOrder()); 84 | }, [dispatch, id, refresh]); 85 | 86 | const cols = ['Order id', 'Price', 'Address', 'Paid', 'Date', 'Options']; 87 | 88 | return ( 89 | 90 | 91 | {loading || !user || orderLoading || !orders ? ( 92 | 93 | ) : ( 94 | 95 | 96 | 97 |

User Profile

98 | 99 | 100 |
101 | 102 | Username 103 | 110 |

{errors.name?.message}

111 |
112 | 113 | 114 | Email Address 115 | 122 |

123 | {errors.email?.message} 124 |

125 |
126 | 127 | 128 | Password 129 | 135 |

136 | {errors.password?.message} 137 |

138 |
139 | 140 | 141 | Confirm Password 142 | 150 |

151 | {errors.confirmPassword?.message} 152 |

153 |
154 | 155 | 163 |
164 |
165 |
166 | 167 | 168 | 169 | {orders.map((order) => ( 170 | 171 | {order._id} 172 | 173 | {formatCurrencry(order?.totalPrice)} 174 | {order?.shippingAddress?.address} 175 | 176 | {order.isPaid ? ( 177 | 178 | ) : ( 179 | 180 | )} 181 | 182 | {getDate(order?.createdAt)} 183 | 184 | 188 | 189 | 190 | 197 | 198 | 199 | ))} 200 | 201 | 202 |
203 | )} 204 |
205 |
206 | ); 207 | }; 208 | 209 | export default Profile; 210 | -------------------------------------------------------------------------------- /src/pages/users/regitser.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import FormContainer from "../../components/UI/form-container"; 3 | import { Button, Form } from "react-bootstrap"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | import { useForm } from "react-hook-form"; 6 | import { yupResolver } from "@hookform/resolvers/yup"; 7 | import * as Yup from "yup"; 8 | import publicAxios from "../../utils/public-axios"; 9 | import toast from "react-hot-toast"; 10 | import { setError } from "../../utils/error"; 11 | import { useAppSelector } from "../../redux"; 12 | 13 | type FormValues = { 14 | name: string; 15 | email: string; 16 | password: string; 17 | confirmPassword: string; 18 | }; 19 | 20 | const validationSchema = Yup.object().shape({ 21 | name: Yup.string() 22 | .required("Username is required") 23 | .min(6, "Username must be at least 6 characters") 24 | .max(20, "Username must not exceed 20 characters"), 25 | email: Yup.string().required("Email is required").email("Email is invalid"), 26 | password: Yup.string() 27 | .required("Password is required") 28 | .min(6, "Password must be at least 6 characters") 29 | .max(40, "Password must not exceed 40 characters"), 30 | confirmPassword: Yup.string() 31 | .required("Confirm Password is required") 32 | .oneOf([Yup.ref("password"), null], "Confirm Password does not match"), 33 | }); 34 | 35 | const Register = () => { 36 | const navigate = useNavigate(); 37 | const { loading } = useAppSelector((state) => state.login); 38 | 39 | const { 40 | register, 41 | handleSubmit, 42 | formState: { errors }, 43 | } = useForm({ 44 | resolver: yupResolver(validationSchema), 45 | }); 46 | 47 | const onSubmit = (data: FormValues) => { 48 | publicAxios 49 | .post("/users/register", data) 50 | .then((res) => { 51 | toast.success("you have been registred , please login"); 52 | navigate("/login"); 53 | }) 54 | .catch((err) => toast.error(setError(err))); 55 | }; 56 | 57 | return ( 58 | 63 |
64 | 65 | Username 66 | 71 |

{errors.name?.message}

72 |
73 | 74 | Email 75 | 76 | 82 |

{errors.email?.message}

83 |
84 | 85 | Mot de Passe 86 | 87 | 93 |

{errors.password?.message}

94 |
95 | 96 | Confirm Password 97 | 98 | 104 |

{errors.confirmPassword?.message}

105 | 106 | Already have an Account ? Login 107 | 108 |
109 | 110 | 119 |
120 |
121 | ); 122 | }; 123 | 124 | export default Register; 125 | -------------------------------------------------------------------------------- /src/redux/cart/cart-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { Product } from '../../components/product-card'; 3 | import { AddressTypes } from '../../utils/interfaces'; 4 | 5 | export interface CartSliceState { 6 | cartItems: Product[]; 7 | shippingAddress: AddressTypes | null; 8 | } 9 | 10 | const initialState: CartSliceState = { 11 | cartItems: [], 12 | shippingAddress: null, 13 | }; 14 | 15 | export const cartSlice = createSlice({ 16 | name: 'cart-items', 17 | initialState: initialState, 18 | 19 | reducers: { 20 | addToCart: (state: CartSliceState, action: PayloadAction) => { 21 | const product = action.payload; 22 | const exist = state.cartItems.find( 23 | (item: any) => item._id == product._id 24 | ); 25 | 26 | if (exist) { 27 | state.cartItems = state.cartItems.map((item: any) => 28 | item._id == product._id ? { ...product, qty: item.qty + 1 } : item 29 | ); 30 | } else { 31 | state.cartItems = [...state.cartItems, { ...product, qty: 1 }]; 32 | } 33 | }, 34 | removeFromCart: (state: CartSliceState, action: PayloadAction) => { 35 | const product = action.payload; 36 | const exist = state.cartItems.find( 37 | (item: any) => item._id == product._id 38 | ); 39 | 40 | if (exist && exist.qty === 1) { 41 | state.cartItems = state.cartItems.filter( 42 | (item: any) => item._id !== product._id 43 | ); 44 | } else { 45 | state.cartItems = state.cartItems.map((item: any) => 46 | item._id == product._id ? { ...product, qty: item.qty - 1 } : item 47 | ); 48 | } 49 | }, 50 | saveAddress: ( 51 | state: CartSliceState, 52 | action: PayloadAction 53 | ) => { 54 | state.shippingAddress = action.payload; 55 | }, 56 | reset: (state: any) => { 57 | state.cartItems = []; 58 | state.shippingAddress = null; 59 | }, 60 | }, 61 | }); 62 | 63 | // Action creators are generated for each case reducer function 64 | export const { addToCart, removeFromCart, saveAddress, reset } = 65 | cartSlice.actions; 66 | 67 | export default cartSlice; 68 | -------------------------------------------------------------------------------- /src/redux/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, combineReducers, compose } from '@reduxjs/toolkit'; 2 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 3 | import cartSlice from './cart/cart-slice'; 4 | import { productListSlice } from './products/slice-list'; 5 | import storage from 'redux-persist/lib/storage'; 6 | import { persistStore, persistReducer } from 'redux-persist'; 7 | import orderListSlice from './orders/slice-list'; 8 | import productDetailsSlice from './products/slice-details'; 9 | import loginSlice from './users/login-slice'; 10 | import { authorizationProvider } from '../utils/auth-axios'; 11 | import userDetailsSlice from './users/user-details'; 12 | import userListSlice from './users/user-list'; 13 | import userOrderSlice from './orders/user-orders'; 14 | import orderDetailSlice from './orders/order-details'; 15 | import productFilterSlice from './products/search-list'; 16 | 17 | const reducers = combineReducers({ 18 | productList: productListSlice.reducer, 19 | cart: cartSlice.reducer, 20 | productDetail: productDetailsSlice.reducer, 21 | productFilter: productFilterSlice.reducer, 22 | //auth 23 | login: loginSlice.reducer, 24 | userDetails: userDetailsSlice.reducer, 25 | userList: userListSlice.reducer, 26 | //orders 27 | orders: orderListSlice.reducer, 28 | userOrder: userOrderSlice.reducer, 29 | orderDetail: orderDetailSlice.reducer, 30 | }); 31 | 32 | const persistConfig = { 33 | key: 'root', 34 | storage, 35 | }; 36 | const persistedReducer = persistReducer(persistConfig, reducers); 37 | 38 | export const store = configureStore({ 39 | reducer: persistedReducer, 40 | middleware: (getDefaultMiddleware) => 41 | getDefaultMiddleware({ 42 | serializableCheck: false, 43 | }), 44 | }); 45 | 46 | export const persistor = persistStore(store); 47 | 48 | // Infer the `RootState` and `AppDispatch` types from the store itself 49 | export type RootState = ReturnType; 50 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 51 | export type AppDispatch = typeof store.dispatch; 52 | 53 | export const useAppDispatch: () => AppDispatch = useDispatch; 54 | export const useAppSelector: TypedUseSelectorHook = useSelector; 55 | 56 | authorizationProvider(store); 57 | -------------------------------------------------------------------------------- /src/redux/orders/order-details.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import toast from 'react-hot-toast'; 3 | import authAxios from '../../utils/auth-axios'; 4 | import { setError } from '../../utils/error'; 5 | import { Ordertypes } from '../../utils/interfaces'; 6 | 7 | export interface OrderSliceState { 8 | order: Ordertypes | null; 9 | loading: boolean; 10 | error: null | object; 11 | } 12 | 13 | const initialState: OrderSliceState = { 14 | order: null, 15 | loading: false, 16 | error: null, 17 | }; 18 | 19 | export const getOrderById = createAsyncThunk( 20 | 'orders/:id', 21 | async (id?: string) => { 22 | try { 23 | const { data } = await authAxios.get(`/orders/${id}`); 24 | return data; 25 | } catch (error: any) { 26 | const message = setError(error); 27 | toast.error(message); 28 | } 29 | } 30 | ); 31 | 32 | export const orderDetailSlice = createSlice({ 33 | name: 'orders-detail', 34 | initialState, 35 | reducers: {}, 36 | extraReducers: (builder) => { 37 | builder.addCase(getOrderById.pending, (state) => { 38 | state.loading = true; 39 | }); 40 | builder.addCase(getOrderById.fulfilled, (state, action) => { 41 | state.loading = false; 42 | state.order = action.payload; 43 | }); 44 | builder.addCase(getOrderById.rejected, (state) => { 45 | state.loading = false; 46 | }); 47 | }, 48 | }); 49 | 50 | export default orderDetailSlice; 51 | -------------------------------------------------------------------------------- /src/redux/orders/slice-list.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createSlice, 3 | createAsyncThunk, 4 | PayloadAction, 5 | CaseReducer, 6 | createEntityAdapter, 7 | } from '@reduxjs/toolkit'; 8 | import toast from 'react-hot-toast'; 9 | import { Product } from '../../components/product-card'; 10 | import authAxios from '../../utils/auth-axios'; 11 | import { setError } from '../../utils/error'; 12 | 13 | type Ordertypes = { 14 | _id: string; 15 | user: string; 16 | shippingAddress: { 17 | address: string; 18 | city: string; 19 | postalCode: string; 20 | country: string; 21 | }; 22 | cartItems: Product[]; 23 | totalPrice: number; 24 | isPaid: boolean; 25 | createdAt: Date; 26 | }; 27 | 28 | export interface OrderSliceState { 29 | orders: Ordertypes[]; 30 | loading: boolean; 31 | error: null | object; 32 | totalPrice: number; 33 | } 34 | 35 | const initialState: OrderSliceState = { 36 | orders: [], 37 | loading: false, 38 | error: null, 39 | totalPrice: 0, 40 | }; 41 | 42 | export const getOrdersList = createAsyncThunk('orders/list', async () => { 43 | try { 44 | const { data } = await authAxios.get('/orders'); 45 | return data; 46 | } catch (error: any) { 47 | const message = setError(error); 48 | toast.error(message); 49 | } 50 | }); 51 | 52 | const getOrderPrice = createEntityAdapter({ 53 | selectId: (state) => 54 | state.orders.reduce((acc, order) => acc + order.totalPrice, 0), 55 | }); 56 | 57 | export const orderListSlice = createSlice({ 58 | name: 'orders-list', 59 | initialState, 60 | reducers: {}, 61 | extraReducers: (builder) => { 62 | builder.addCase(getOrdersList.pending, (state) => { 63 | // Add user to the state array 64 | state.loading = true; 65 | }); 66 | builder.addCase(getOrdersList.fulfilled, (state, action) => { 67 | state.loading = false; 68 | state.orders = action.payload; 69 | }); 70 | builder.addCase(getOrdersList.rejected, (state) => { 71 | state.loading = false; 72 | }); 73 | }, 74 | }); 75 | 76 | export default orderListSlice; 77 | -------------------------------------------------------------------------------- /src/redux/orders/user-orders.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import toast from 'react-hot-toast'; 3 | import { Product } from '../../components/product-card'; 4 | import authAxios from '../../utils/auth-axios'; 5 | import { setError } from '../../utils/error'; 6 | 7 | type Ordertypes = { 8 | _id: string; 9 | user: string; 10 | shippingAddress: { 11 | address: string; 12 | city: string; 13 | postalCode: string; 14 | country: string; 15 | }; 16 | cartItems: Product[]; 17 | totalPrice: number; 18 | isPaid: boolean; 19 | createdAt: Date; 20 | }; 21 | 22 | export interface OrderSliceState { 23 | orders: Ordertypes[]; 24 | loading: boolean; 25 | error: null | object; 26 | } 27 | 28 | const initialState: OrderSliceState = { 29 | orders: [], 30 | loading: false, 31 | error: null, 32 | }; 33 | 34 | export const getUserOrder = createAsyncThunk('users/orders', async () => { 35 | try { 36 | const { data } = await authAxios.get('/orders/orders-user'); 37 | return data; 38 | } catch (error: any) { 39 | const message = setError(error); 40 | toast.error(message); 41 | } 42 | }); 43 | 44 | export const userOrderSlice = createSlice({ 45 | name: 'user-orders', 46 | initialState, 47 | reducers: {}, 48 | extraReducers: (builder) => { 49 | builder.addCase(getUserOrder.pending, (state) => { 50 | // Add user to the state array 51 | state.loading = true; 52 | }); 53 | builder.addCase(getUserOrder.fulfilled, (state, action) => { 54 | state.loading = false; 55 | state.orders = action.payload; 56 | }); 57 | builder.addCase(getUserOrder.rejected, (state) => { 58 | state.loading = false; 59 | }); 60 | }, 61 | }); 62 | 63 | // Action creators are generated for each case reducer function 64 | 65 | export default userOrderSlice; 66 | -------------------------------------------------------------------------------- /src/redux/products/search-list.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import toast from 'react-hot-toast'; 3 | import { Product } from '../../components/product-card'; 4 | import { setError } from '../../utils/error'; 5 | import publicAxios from '../../utils/public-axios'; 6 | 7 | export interface ProductSliceState { 8 | products: Product[]; 9 | loading: boolean; 10 | error: null | object; 11 | pages: number; 12 | page: number; 13 | categories: string[]; 14 | brands: string[]; 15 | total: number; 16 | } 17 | 18 | const products: Product[] | [] = []; 19 | 20 | const initialState: ProductSliceState = { 21 | products: products, 22 | loading: false, 23 | error: null, 24 | categories: [], 25 | brands: [], 26 | page: 1, 27 | pages: 1, 28 | total: 1, 29 | }; 30 | 31 | export const getFilterProducts = createAsyncThunk( 32 | 'products/filter', 33 | async (u: any) => { 34 | try { 35 | const { data } = await publicAxios.get( 36 | `/products/search?page=${u.n}&brand=${u.b}&category=${u.c}&query=${u.q}` 37 | ); 38 | return data; 39 | } catch (error: any) { 40 | const message = setError(error); 41 | toast.error(message); 42 | } 43 | } 44 | ); 45 | 46 | export const productFilterSlice = createSlice({ 47 | name: 'products-filter', 48 | initialState, 49 | reducers: {}, 50 | extraReducers: (builder) => { 51 | builder.addCase(getFilterProducts.pending, (state) => { 52 | state.loading = true; 53 | }); 54 | builder.addCase(getFilterProducts.fulfilled, (state, action) => { 55 | state.loading = false; 56 | state.products = action.payload.productDocs; 57 | state.page = action.payload.page; 58 | state.pages = action.payload.pages; 59 | state.brands = action.payload.brands; 60 | state.categories = action.payload.categories; 61 | state.total = action.payload.countProducts; 62 | }); 63 | builder.addCase(getFilterProducts.rejected, (state) => { 64 | state.loading = false; 65 | }); 66 | }, 67 | }); 68 | 69 | export default productFilterSlice; 70 | -------------------------------------------------------------------------------- /src/redux/products/slice-details.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; 2 | import { Product } from '../../components/product-card'; 3 | import publicAxios from '../../utils/public-axios'; 4 | 5 | export interface ProductSliceState { 6 | product: Product | null; 7 | loading: boolean; 8 | error: null | object; 9 | } 10 | 11 | const initialState: ProductSliceState = { 12 | product: null, 13 | loading: false, 14 | error: null, 15 | }; 16 | 17 | export const getProductById = createAsyncThunk( 18 | 'products/details', 19 | async (id: string | undefined) => { 20 | try { 21 | const res = await publicAxios.get(`/products/${id}`); 22 | if (res.data) { 23 | return res.data; 24 | } 25 | } catch (error) {} 26 | } 27 | ); 28 | 29 | export const productDetailsSlice = createSlice({ 30 | name: 'product-detail', 31 | initialState, 32 | reducers: {}, 33 | extraReducers: (builder) => { 34 | builder.addCase(getProductById.pending, (state) => { 35 | // Add user to the state array 36 | state.loading = true; 37 | }); 38 | builder.addCase(getProductById.fulfilled, (state, action) => { 39 | state.loading = false; 40 | state.product = action.payload; 41 | }); 42 | builder.addCase( 43 | getProductById.rejected, 44 | (state, action: PayloadAction) => { 45 | state.loading = false; 46 | state.error = action.payload; 47 | } 48 | ); 49 | }, 50 | }); 51 | 52 | // Action creators are generated for each case reducer function 53 | 54 | export default productDetailsSlice; 55 | -------------------------------------------------------------------------------- /src/redux/products/slice-list.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import toast from 'react-hot-toast'; 3 | import { Product } from '../../components/product-card'; 4 | import { setError } from '../../utils/error'; 5 | import publicAxios from '../../utils/public-axios'; 6 | 7 | export interface ProductSliceState { 8 | products: Product[]; 9 | loading: boolean; 10 | error: null | object; 11 | } 12 | 13 | const products: Product[] | [] = []; 14 | 15 | const initialState: ProductSliceState = { 16 | products: products, 17 | loading: false, 18 | error: null, 19 | }; 20 | 21 | export const getProducts = createAsyncThunk('products/list', async () => { 22 | try { 23 | const { data } = await publicAxios.get('/products'); 24 | return data; 25 | } catch (error: any) { 26 | const message = setError(error); 27 | toast.error(message); 28 | } 29 | }); 30 | 31 | export const productListSlice = createSlice({ 32 | name: 'products-list', 33 | initialState, 34 | reducers: {}, 35 | extraReducers: (builder) => { 36 | builder.addCase(getProducts.pending, (state) => { 37 | state.loading = true; 38 | }); 39 | builder.addCase(getProducts.fulfilled, (state, action) => { 40 | state.loading = false; 41 | state.products = action.payload; 42 | }); 43 | builder.addCase(getProducts.rejected, (state) => { 44 | state.loading = false; 45 | }); 46 | }, 47 | }); 48 | 49 | export default productListSlice; 50 | -------------------------------------------------------------------------------- /src/redux/users/login-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; 2 | import toast from "react-hot-toast"; 3 | import { setError } from "../../utils/error"; 4 | import publicAxios from "../../utils/public-axios"; 5 | 6 | type User = { 7 | email: string; 8 | password: string; 9 | }; 10 | 11 | type UserInfo = { 12 | _id: string; 13 | email: string; 14 | name: string; 15 | isAdmin: Boolean; 16 | createdAt: Date; 17 | }; 18 | 19 | export interface UserSliceState { 20 | userInfo?: UserInfo | null; 21 | loading: boolean; 22 | error: null | object; 23 | } 24 | 25 | const initialState: UserSliceState = { 26 | userInfo: null, 27 | loading: false, 28 | error: null, 29 | }; 30 | 31 | export const userLogin = createAsyncThunk( 32 | "users/login", 33 | async (user: User, thunkAPI) => { 34 | try { 35 | const res = await publicAxios.post("/users/login", user); 36 | if (res.data) { 37 | toast.success(`Bienvenue 👏 ${res.data.name}`); 38 | return res.data; 39 | } 40 | } catch (error: any) { 41 | const message = setError(error); 42 | toast.error(message); 43 | thunkAPI.rejectWithValue(message); 44 | } 45 | } 46 | ); 47 | 48 | export const loginSlice = createSlice({ 49 | name: "auth-login", 50 | initialState, 51 | reducers: { 52 | userLogout: (state: UserSliceState) => { 53 | state.userInfo = null; 54 | }, 55 | }, 56 | extraReducers: (builder) => { 57 | builder.addCase(userLogin.pending, (state) => { 58 | state.loading = true; 59 | }); 60 | builder.addCase(userLogin.fulfilled, (state, action) => { 61 | state.loading = false; 62 | state.userInfo = action.payload; 63 | }); 64 | builder.addCase(userLogin.rejected, (state) => { 65 | state.loading = false; 66 | }); 67 | }, 68 | }); 69 | 70 | export const { userLogout } = loginSlice.actions; 71 | 72 | export default loginSlice; 73 | -------------------------------------------------------------------------------- /src/redux/users/user-details.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { User } from '../../utils/interfaces'; 3 | import publicAxios from '../../utils/public-axios'; 4 | 5 | interface ProductSliceState { 6 | user: User | null; 7 | loading: boolean; 8 | error: null | object; 9 | } 10 | 11 | const initialState: ProductSliceState = { 12 | user: null, 13 | loading: false, 14 | error: null, 15 | }; 16 | 17 | export const getUserBydId = createAsyncThunk( 18 | 'users/:id', 19 | async (id: string | undefined) => { 20 | try { 21 | const res = await publicAxios.get(`/users/${id}`); 22 | if (res.data) { 23 | return res.data; 24 | } 25 | } catch (error) {} 26 | } 27 | ); 28 | 29 | export const userDetailsSlice = createSlice({ 30 | name: 'user-detail', 31 | initialState, 32 | reducers: {}, 33 | extraReducers: (builder) => { 34 | builder.addCase(getUserBydId.pending, (state) => { 35 | // Add user to the state array 36 | state.loading = true; 37 | }); 38 | builder.addCase(getUserBydId.fulfilled, (state, action) => { 39 | state.loading = false; 40 | state.user = action.payload; 41 | }); 42 | builder.addCase(getUserBydId.rejected, (state) => { 43 | state.loading = false; 44 | }); 45 | }, 46 | }); 47 | 48 | // Action creators are generated for each case reducer function 49 | 50 | export default userDetailsSlice; 51 | -------------------------------------------------------------------------------- /src/redux/users/user-list.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; 2 | import toast from "react-hot-toast"; 3 | import authAxios from "../../utils/auth-axios"; 4 | import { setError } from "../../utils/error"; 5 | import { User } from "../../utils/interfaces"; 6 | 7 | interface ProductSliceState { 8 | users: User[]; 9 | loading: boolean; 10 | error: null | object; 11 | pages: number; 12 | page: number; 13 | } 14 | 15 | const initialState: ProductSliceState = { 16 | users: [], 17 | pages: 1, 18 | page: 1, 19 | loading: false, 20 | error: null, 21 | }; 22 | 23 | export const getUsersList = createAsyncThunk( 24 | "users/list", 25 | async (filter: any) => { 26 | try { 27 | const res = await authAxios.get( 28 | `/users?query=${filter.query}&page=${filter.page}` 29 | ); 30 | if (res.data) { 31 | return res.data; 32 | } 33 | } catch (error: any) { 34 | const message = setError(error); 35 | toast.error(message); 36 | } 37 | } 38 | ); 39 | 40 | export const userListSlice = createSlice({ 41 | name: "user-list", 42 | initialState, 43 | reducers: {}, 44 | extraReducers: (builder) => { 45 | builder.addCase(getUsersList.pending, (state) => { 46 | state.loading = true; 47 | }); 48 | builder.addCase(getUsersList.fulfilled, (state, action) => { 49 | state.loading = false; 50 | state.users = action.payload.users; 51 | state.page = action.payload.page; 52 | state.pages = action.payload.pages; 53 | }); 54 | builder.addCase(getUsersList.rejected, (state) => { 55 | state.loading = false; 56 | }); 57 | }, 58 | }); 59 | 60 | // Action creators are generated for each case reducer function 61 | 62 | export default userListSlice; 63 | -------------------------------------------------------------------------------- /src/utils/admin-provider.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, ReactNode } from 'react'; 2 | import { Navigate } from 'react-router-dom'; 3 | import { useAppSelector } from '../redux'; 4 | 5 | type Props = { 6 | children: ReactNode; 7 | }; 8 | 9 | const AdminProvider = ({ children }: Props) => { 10 | const { userInfo } = useAppSelector((state) => state.login); 11 | 12 | if (userInfo && userInfo.isAdmin) { 13 | return <>{children}; 14 | } else { 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | }; 22 | 23 | export default AdminProvider; 24 | -------------------------------------------------------------------------------- /src/utils/auth-axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { baseUrl } from './helper'; 3 | 4 | const authAxios = axios.create({ 5 | baseURL: `${baseUrl}/api`, 6 | }); 7 | 8 | export const authorizationProvider = (store: any) => { 9 | authAxios.interceptors.request.use((config: any) => { 10 | const token = store.getState().login.userInfo.token; 11 | config.headers.Authorization = `Bearer ${token}`; 12 | return config; 13 | }); 14 | }; 15 | 16 | export default authAxios; 17 | -------------------------------------------------------------------------------- /src/utils/auth-provider.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, ReactNode } from 'react'; 2 | import { Navigate } from 'react-router-dom'; 3 | import { useAppSelector } from '../redux'; 4 | 5 | type Props = { 6 | children: ReactNode; 7 | }; 8 | 9 | const AuthProvider = ({ children }: Props) => { 10 | const { userInfo } = useAppSelector((state) => state.login); 11 | 12 | if (!userInfo) { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | } else { 19 | return <> {children} ; 20 | } 21 | }; 22 | 23 | export default AuthProvider; 24 | -------------------------------------------------------------------------------- /src/utils/error.ts: -------------------------------------------------------------------------------- 1 | type Error = { 2 | response?: { 3 | data?: { 4 | message: string; 5 | }; 6 | }; 7 | message?: string; 8 | }; 9 | 10 | export const setError = (error: Error) => { 11 | const message = 12 | (error.response && error.response.data && error.response.data.message) || 13 | error.message || 14 | error.toString(); 15 | 16 | return message; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | const CURRENCRY_FORMATTER = new Intl.NumberFormat(undefined, { 2 | currency: "USD", 3 | style: "currency", 4 | }); 5 | 6 | export const formatCurrencry = (number: any) => { 7 | return CURRENCRY_FORMATTER.format(number); 8 | }; 9 | 10 | export const getDate = (date: Date) => { 11 | return new Date(date).toLocaleDateString("en"); 12 | }; 13 | 14 | export const baseUrl = import.meta.env.VITE_API_URL; 15 | -------------------------------------------------------------------------------- /src/utils/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../components/product-card'; 2 | 3 | export interface Ordertypes { 4 | _id: string; 5 | user: string; 6 | shippingAddress: { 7 | address: string; 8 | city: string; 9 | postalCode: string; 10 | country: string; 11 | }; 12 | cartItems: Product[]; 13 | totalPrice: number; 14 | isPaid: boolean; 15 | createdAt: Date; 16 | } 17 | 18 | export type ReviewTypes = { 19 | _id: string; 20 | createdAt: Date; 21 | rating: number; 22 | comment: string; 23 | name: string; 24 | user: string; 25 | }; 26 | 27 | export type User = { 28 | _id: string; 29 | name: string; 30 | email: string; 31 | isAdmin: boolean; 32 | createdAt: Date; 33 | }; 34 | 35 | export type AddressTypes = { 36 | address: string; 37 | city: string; 38 | postalCode: string; 39 | country: string; 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/public-axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { baseUrl } from './helper'; 3 | 4 | const publicAxios = axios.create({ 5 | baseURL: `${baseUrl}/api`, 6 | }); 7 | 8 | export default publicAxios; 9 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /techstack.md: -------------------------------------------------------------------------------- 1 | 38 |
39 | 40 | # Tech Stack File 41 | ![](https://img.stackshare.io/repo.svg "repo") [hicmtrex/TypeShop-Frontend](https://github.com/hicmtrex/TypeShop-Frontend)![](https://img.stackshare.io/public_badge.svg "public") 42 |

43 | |26
Tools used|01/05/24
Report generated| 44 | |------|------| 45 |
46 | 47 | ## Languages (3) 48 | 49 | 56 | 57 | 64 | 65 | 72 | 73 | 74 |
50 | CSS 3 51 |
52 | CSS 3 53 |
54 | 55 |
58 | JavaScript 59 |
60 | JavaScript 61 |
62 | 63 |
66 | TypeScript 67 |
68 | TypeScript 69 |
70 | 71 |
75 | 76 | ## Frameworks (4) 77 | 78 | 85 | 86 | 93 | 94 | 101 | 102 | 109 | 110 | 111 |
79 | Bootstrap 80 |
81 | Bootstrap 82 |
83 | v5.1.3 84 |
87 | React 88 |
89 | React 90 |
91 | v18.2.0 92 |
95 | React Router 96 |
97 | React Router 98 |
99 | v6.3.0 100 |
103 | Redux 104 |
105 | Redux 106 |
107 | v3.7.2 108 |
112 | 113 | ## Data (1) 114 | 115 | 122 | 123 | 124 |
116 | Redux Persist 117 |
118 | Redux Persist 119 |
120 | v6.0.0 121 |
125 | 126 | ## DevOps (3) 127 | 128 | 135 | 136 | 143 | 144 | 151 | 152 | 153 |
129 | Git 130 |
131 | Git 132 |
133 | 134 |
137 | Vite 138 |
139 | Vite 140 |
141 | 142 |
145 | npm 146 |
147 | npm 148 |
149 | 150 |
154 | 155 | ## Other (2) 156 | 157 | 164 | 165 | 172 | 173 | 174 |
158 | axios 159 |
160 | axios 161 |
162 | v0.27.2 163 |
166 | yup 167 |
168 | yup 169 |
170 | 171 |
175 | 176 | 177 | ## Open source packages (13) 178 | 179 | ## npm (13) 180 | 181 | |NAME|VERSION|LAST UPDATED|LAST UPDATED BY|LICENSE|VULNERABILITIES| 182 | |:------|:------|:------|:------|:------|:------| 183 | |[@fortawesome/free-solid-svg-icons](https://www.npmjs.com/@fortawesome/free-solid-svg-icons)|v5.15.4|06/29/22|Firstname Lastname |CC-BY-4.0,MIT|N/A| 184 | |[@fortawesome/react-fontawesome](https://www.npmjs.com/@fortawesome/react-fontawesome)|v0.1.18|02/03/23|Firstname Lastname |MIT|N/A| 185 | |[@types/react](https://www.npmjs.com/@types/react)|v18.0.14|02/03/23|Firstname Lastname |MIT|N/A| 186 | |[@types/react-dom](https://www.npmjs.com/@types/react-dom)|v18.0.5|02/03/23|Firstname Lastname |MIT|N/A| 187 | |[react-bootstrap](https://www.npmjs.com/react-bootstrap)|v2.4.0|02/03/23|Firstname Lastname |MIT|N/A| 188 | |[react-dom](https://www.npmjs.com/react-dom)|v18.2.0|02/03/23|Firstname Lastname |MIT|N/A| 189 | |[react-error-boundary](https://www.npmjs.com/react-error-boundary)|v3.1.4|02/03/23|Firstname Lastname |N/A|N/A| 190 | |[react-helmet](https://www.npmjs.com/react-helmet)|v6.1.0|02/03/23|Firstname Lastname |MIT|N/A| 191 | |[react-icons](https://www.npmjs.com/react-icons)|v4.4.0|02/03/23|Firstname Lastname |MIT|N/A| 192 | |[react-redux](https://www.npmjs.com/react-redux)|v8.0.2|02/03/23|Firstname Lastname |MIT|N/A| 193 | |[react-router-dom](https://www.npmjs.com/react-router-dom)|v6.3.0|02/03/23|Firstname Lastname |MIT|N/A| 194 | |[vite](https://www.npmjs.com/vite)|v2.9.12|07/04/22|Firstname Lastname |N/A|[CVE-2023-34092](https://github.com/advisories/GHSA-353f-5xf4-qw67) (High)
[CVE-2022-35204](https://github.com/advisories/GHSA-mv48-hcvh-8jj8) (Moderate)| 195 | |[yup](https://www.npmjs.com/yup)|v0.32.11|06/29/22|Firstname Lastname |MIT|N/A| 196 | 197 |
198 |
199 | 200 | Generated via [Stack File](https://github.com/marketplace/stack-file) 201 | -------------------------------------------------------------------------------- /techstack.yml: -------------------------------------------------------------------------------- 1 | repo_name: hicmtrex/TypeShop-Frontend 2 | report_id: ffe31342850035f1657c88fc916329c4 3 | version: 0.1 4 | repo_type: Public 5 | timestamp: '2024-01-05T08:17:44+00:00' 6 | requested_by: hicmtrex 7 | provider: github 8 | branch: main 9 | detected_tools_count: 26 10 | tools: 11 | - name: CSS 3 12 | description: The latest evolution of the Cascading Style Sheets language 13 | website_url: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS3 14 | open_source: true 15 | hosted_saas: false 16 | category: Languages & Frameworks 17 | sub_category: Languages 18 | image_url: https://img.stackshare.io/service/6727/css.png 19 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend 20 | detection_source: Repo Metadata 21 | - name: JavaScript 22 | description: Lightweight, interpreted, object-oriented language with first-class 23 | functions 24 | website_url: https://developer.mozilla.org/en-US/docs/Web/JavaScript 25 | open_source: true 26 | hosted_saas: false 27 | category: Languages & Frameworks 28 | sub_category: Languages 29 | image_url: https://img.stackshare.io/service/1209/javascript.jpeg 30 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package.json 31 | detection_source: package.json 32 | last_updated_by: Firstname Lastname 33 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 34 | - name: TypeScript 35 | description: A superset of JavaScript that compiles to clean JavaScript output 36 | website_url: http://www.typescriptlang.org 37 | license: Apache-2.0 38 | open_source: true 39 | hosted_saas: false 40 | category: Languages & Frameworks 41 | sub_category: Languages 42 | image_url: https://img.stackshare.io/service/1612/bynNY5dJ.jpg 43 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend 44 | detection_source: Repo Metadata 45 | - name: Bootstrap 46 | description: Simple and flexible HTML, CSS, and JS for popular UI components and 47 | interactions 48 | website_url: http://getbootstrap.com/ 49 | version: 5.1.3 50 | license: MIT 51 | open_source: true 52 | hosted_saas: false 53 | category: Languages & Frameworks 54 | sub_category: Front-End Frameworks 55 | image_url: https://img.stackshare.io/service/1101/C9QJ7V3X.png 56 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 57 | detection_source: package.json 58 | last_updated_by: Firstname Lastname 59 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 60 | - name: React 61 | description: A JavaScript library for building user interfaces 62 | website_url: https://reactjs.org/ 63 | version: 18.2.0 64 | license: MIT 65 | open_source: true 66 | hosted_saas: false 67 | category: Libraries 68 | sub_category: Javascript UI Libraries 69 | image_url: https://img.stackshare.io/service/1020/OYIaJ1KK.png 70 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 71 | detection_source: package.json 72 | last_updated_by: Firstname Lastname 73 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 74 | - name: React Router 75 | description: A complete routing solution for React.js 76 | website_url: https://github.com/rackt/react-router 77 | version: 6.3.0 78 | license: MIT 79 | open_source: true 80 | hosted_saas: false 81 | category: Libraries 82 | sub_category: JavaScript Framework Components 83 | image_url: https://img.stackshare.io/service/3350/8261421.png 84 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 85 | detection_source: package.json 86 | last_updated_by: Firstname Lastname 87 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 88 | - name: Redux 89 | description: Predictable state container for JavaScript apps 90 | website_url: https://redux.js.org/ 91 | version: 3.7.2 92 | open_source: true 93 | hosted_saas: false 94 | category: Libraries 95 | sub_category: State Management Library 96 | image_url: https://img.stackshare.io/service/4074/13142323.png 97 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 98 | detection_source: package.json 99 | last_updated_by: Firstname Lastname 100 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 101 | - name: Redux Persist 102 | description: A library to persist and rehydrate a redux store 103 | website_url: https://github.com/rt2zz/redux-persist 104 | version: 6.0.0 105 | license: MIT 106 | open_source: true 107 | hosted_saas: false 108 | category: Data Stores 109 | sub_category: Javascript Utilities & Libraries 110 | image_url: https://img.stackshare.io/service/6740/no-img-open-source.png 111 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 112 | detection_source: package.json 113 | last_updated_by: Firstname Lastname 114 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 115 | - name: Git 116 | description: Fast, scalable, distributed revision control system 117 | website_url: http://git-scm.com/ 118 | open_source: true 119 | hosted_saas: false 120 | category: Build, Test, Deploy 121 | sub_category: Version Control System 122 | image_url: https://img.stackshare.io/service/1046/git.png 123 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend 124 | detection_source: Repo Metadata 125 | - name: Vite 126 | description: Native-ESM powered web dev build tool 127 | website_url: https://vitejs.dev/ 128 | license: MIT 129 | open_source: true 130 | hosted_saas: false 131 | category: Build, Test, Deploy 132 | sub_category: JS Build Tools / JS Task Runners 133 | image_url: https://img.stackshare.io/service/21547/default_1aeac791cde11ff66cc0b20dcc6144eeb185c905.png 134 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package.json 135 | detection_source: package.json 136 | last_updated_by: Firstname Lastname 137 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 138 | - name: npm 139 | description: The package manager for JavaScript. 140 | website_url: https://www.npmjs.com/ 141 | open_source: false 142 | hosted_saas: false 143 | category: Build, Test, Deploy 144 | sub_category: Front End Package Manager 145 | image_url: https://img.stackshare.io/service/1120/lejvzrnlpb308aftn31u.png 146 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package.json 147 | detection_source: package.json 148 | last_updated_by: Firstname Lastname 149 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 150 | - name: axios 151 | description: Promise based HTTP client for the browser and node.js 152 | website_url: https://github.com/mzabriskie/axios 153 | version: 0.27.2 154 | license: MIT 155 | open_source: true 156 | hosted_saas: false 157 | category: Libraries 158 | sub_category: Javascript Utilities & Libraries 159 | image_url: https://img.stackshare.io/no-img-open-source.png 160 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 161 | detection_source: package.json 162 | last_updated_by: Firstname Lastname 163 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 164 | - name: yup 165 | website_url: https://github.com/jquense/yup 166 | open_source: false 167 | hosted_saas: false 168 | image_url: https://img.stackshare.io/service/10756/339286.png 169 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package.json 170 | detection_source: package.json 171 | last_updated_by: Firstname Lastname 172 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 173 | - name: "@fortawesome/free-solid-svg-icons" 174 | description: The iconic font, CSS, and SVG framework 175 | package_url: https://www.npmjs.com/@fortawesome/free-solid-svg-icons 176 | version: 5.15.4 177 | license: CC-BY-4.0,MIT 178 | open_source: true 179 | hosted_saas: false 180 | category: Libraries 181 | sub_category: npm Packages 182 | image_url: https://img.stackshare.io/package/16683/default_6dad4e42e12d47cc6edfbdea036dae12f91abebb.png 183 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 184 | detection_source: package.json 185 | last_updated_by: Firstname Lastname 186 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 187 | - name: "@fortawesome/react-fontawesome" 188 | description: Official React component for Font Awesome 5 189 | package_url: https://www.npmjs.com/@fortawesome/react-fontawesome 190 | version: 0.1.18 191 | license: MIT 192 | open_source: true 193 | hosted_saas: false 194 | category: Libraries 195 | sub_category: npm Packages 196 | image_url: https://img.stackshare.io/package/16936/default_277303773c2bdb575ee8f4bfe869f0a33d6755b2.png 197 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 198 | detection_source: package.json 199 | last_updated_by: Firstname Lastname 200 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 201 | - name: "@types/react" 202 | description: TypeScript definitions for React 203 | package_url: https://www.npmjs.com/@types/react 204 | version: 18.0.14 205 | license: MIT 206 | open_source: true 207 | hosted_saas: false 208 | category: Libraries 209 | sub_category: npm Packages 210 | image_url: https://img.stackshare.io/package/15894/default_1d65e37e65b7f80761374f0202776043277d505d.png 211 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 212 | detection_source: package.json 213 | last_updated_by: Firstname Lastname 214 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 215 | - name: "@types/react-dom" 216 | description: TypeScript definitions for React 217 | package_url: https://www.npmjs.com/@types/react-dom 218 | version: 18.0.5 219 | license: MIT 220 | open_source: true 221 | hosted_saas: false 222 | category: Libraries 223 | sub_category: npm Packages 224 | image_url: https://img.stackshare.io/package/15946/default_54b691c123fc8979741e800e4dcd3936c0f3b246.png 225 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 226 | detection_source: package.json 227 | last_updated_by: Firstname Lastname 228 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 229 | - name: react-bootstrap 230 | description: Bootstrap 3 components built with React 231 | package_url: https://www.npmjs.com/react-bootstrap 232 | version: 2.4.0 233 | license: MIT 234 | open_source: true 235 | hosted_saas: false 236 | category: Libraries 237 | sub_category: npm Packages 238 | image_url: https://img.stackshare.io/package/16383/default_92302d1bbf4c0f67b862869662b4f69002c94aad.png 239 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 240 | detection_source: package.json 241 | last_updated_by: Firstname Lastname 242 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 243 | - name: react-dom 244 | description: React package for working with the DOM 245 | package_url: https://www.npmjs.com/react-dom 246 | version: 18.2.0 247 | license: MIT 248 | open_source: true 249 | hosted_saas: false 250 | category: Libraries 251 | sub_category: npm Packages 252 | image_url: https://img.stackshare.io/package/15808/default_14fd11531839d935f920b6d55bd6f3528c890ad7.png 253 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 254 | detection_source: package.json 255 | last_updated_by: Firstname Lastname 256 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 257 | - name: react-error-boundary 258 | description: Simple reusable React error boundary component 259 | package_url: https://www.npmjs.com/react-error-boundary 260 | version: 3.1.4 261 | open_source: false 262 | hosted_saas: false 263 | category: Build, Test, Deploy 264 | sub_category: Package Managers 265 | image_url: https://img.stackshare.io/package/npm/image.png 266 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 267 | detection_source: package.json 268 | last_updated_by: Firstname Lastname 269 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 270 | - name: react-helmet 271 | description: A document head manager for React 272 | package_url: https://www.npmjs.com/react-helmet 273 | version: 6.1.0 274 | license: MIT 275 | open_source: true 276 | hosted_saas: false 277 | category: Libraries 278 | sub_category: npm Packages 279 | image_url: https://img.stackshare.io/package/16513/default_4e3259350c525d1d859fa80a938000081d7c9db8.png 280 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 281 | detection_source: package.json 282 | last_updated_by: Firstname Lastname 283 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 284 | - name: react-icons 285 | description: SVG React icons of popular icon packs using ES6 imports 286 | package_url: https://www.npmjs.com/react-icons 287 | version: 4.4.0 288 | license: MIT 289 | open_source: true 290 | hosted_saas: false 291 | category: Libraries 292 | sub_category: npm Packages 293 | image_url: https://img.stackshare.io/package/16909/default_7b9968788548874538c601457e8dcd9c74bd2051.png 294 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 295 | detection_source: package.json 296 | last_updated_by: Firstname Lastname 297 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 298 | - name: react-redux 299 | description: Official React bindings for Redux 300 | package_url: https://www.npmjs.com/react-redux 301 | version: 8.0.2 302 | license: MIT 303 | open_source: true 304 | hosted_saas: false 305 | category: Libraries 306 | sub_category: npm Packages 307 | image_url: https://img.stackshare.io/package/15984/default_f49d4c116f8ea0155f4d92673b084378bba02760.png 308 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 309 | detection_source: package.json 310 | last_updated_by: Firstname Lastname 311 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 312 | - name: react-router-dom 313 | description: DOM bindings for React Router 314 | package_url: https://www.npmjs.com/react-router-dom 315 | version: 6.3.0 316 | license: MIT 317 | open_source: true 318 | hosted_saas: false 319 | category: Libraries 320 | sub_category: npm Packages 321 | image_url: https://img.stackshare.io/package/16025/default_e25d1fbb04a118c79fb444294461417342bd03bf.png 322 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 323 | detection_source: package.json 324 | last_updated_by: Firstname Lastname 325 | last_updated_on: 2023-02-03 15:16:02.000000000 Z 326 | - name: vite 327 | description: Native-ESM powered web dev build tool 328 | package_url: https://www.npmjs.com/vite 329 | version: 2.9.12 330 | open_source: false 331 | hosted_saas: false 332 | category: Build, Test, Deploy 333 | sub_category: Package Managers 334 | image_url: https://img.stackshare.io/package/npm/image.png 335 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 336 | detection_source: package.json 337 | last_updated_by: Firstname Lastname 338 | last_updated_on: 2022-07-04 00:57:11.000000000 Z 339 | vulnerabilities: 340 | - name: Vite Server Options (server.fs.deny) can be bypassed using double forward-slash 341 | (//) 342 | cve_id: CVE-2023-34092 343 | cve_url: https://github.com/advisories/GHSA-353f-5xf4-qw67 344 | detected_date: Nov 23 345 | severity: high 346 | first_patched: 2.9.16 347 | - name: Vitejs Vite before v2.9.13 vulnerable to directory traversal via crafted 348 | URL to victim's service 349 | cve_id: CVE-2022-35204 350 | cve_url: https://github.com/advisories/GHSA-mv48-hcvh-8jj8 351 | detected_date: Nov 23 352 | severity: moderate 353 | first_patched: 2.9.13 354 | - name: yup 355 | description: Dead simple Object schema validation 356 | package_url: https://www.npmjs.com/yup 357 | version: 0.32.11 358 | license: MIT 359 | open_source: true 360 | hosted_saas: false 361 | category: Libraries 362 | sub_category: npm Packages 363 | image_url: https://img.stackshare.io/package/17215/default_37d9ccc57ae4d32e7ff46614e4f8bb2c4ddc7964.png 364 | detection_source_url: https://github.com/hicmtrex/TypeShop-Frontend/blob/main/package-lock.json 365 | detection_source: package.json 366 | last_updated_by: Firstname Lastname 367 | last_updated_on: 2022-06-29 16:20:27.000000000 Z 368 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | --------------------------------------------------------------------------------