├── public ├── _redirects ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── scss │ ├── App.scss │ ├── abstracts │ │ ├── _index.scss │ │ ├── _mixins.scss │ │ └── _variables.scss │ ├── _flex-grid.scss │ └── _global.scss ├── assets │ ├── images │ │ ├── avatar.jpg │ │ ├── digikalaLogo.png │ │ ├── apple-airpods.jpg │ │ ├── figma-sketch-v3.png │ │ ├── bag.svg │ │ ├── digikala.svg │ │ ├── D-digikala.svg │ │ └── logo.svg │ └── fonts │ │ ├── fonts │ │ ├── eot │ │ │ ├── iranyekanwebblack.eot │ │ │ ├── iranyekanwebbold.eot │ │ │ ├── iranyekanweblight.eot │ │ │ ├── iranyekanwebmedium.eot │ │ │ ├── iranyekanwebthin.eot │ │ │ ├── iranyekanwebregular.eot │ │ │ ├── iranyekanwebextrablack.eot │ │ │ └── iranyekanwebextrabold.eot │ │ ├── ttf │ │ │ ├── iranyekanwebblack.ttf │ │ │ ├── iranyekanwebbold.ttf │ │ │ ├── iranyekanweblight.ttf │ │ │ ├── iranyekanwebmedium.ttf │ │ │ ├── iranyekanwebthin.ttf │ │ │ ├── iranyekanwebregular.ttf │ │ │ ├── iranyekanwebextrablack.ttf │ │ │ └── iranyekanwebextrabold.ttf │ │ └── woff │ │ │ ├── iranyekanwebbold.woff │ │ │ ├── iranyekanwebthin.woff │ │ │ ├── iranyekanwebblack.woff │ │ │ ├── iranyekanweblight.woff │ │ │ ├── iranyekanwebmedium.woff │ │ │ ├── iranyekanwebextrabold.woff │ │ │ ├── iranyekanwebregular.woff │ │ │ └── iranyekanwebextrablack.woff │ │ └── css │ │ ├── style.css │ │ └── fontiran.css ├── constants │ ├── index.ts │ ├── images.ts │ ├── data.ts │ └── tables.ts ├── interfaces │ ├── IsummData.ts │ └── Itable.ts ├── pages │ ├── BlankPage.tsx │ ├── Login.tsx │ ├── Dashboard.tsx │ ├── NotFound.tsx │ ├── Customers.tsx │ ├── ProductEdit.tsx │ ├── CustomerEdit.tsx │ └── Products.tsx ├── components │ ├── UI │ │ ├── card │ │ │ ├── Card.tsx │ │ │ └── Card.module.scss │ │ ├── loadingSpinner │ │ │ ├── LoadingSpinner.tsx │ │ │ └── LoadingSpinner.module.scss │ │ ├── badge │ │ │ └── Badge.tsx │ │ ├── dropdown │ │ │ ├── Dropdown.module.scss │ │ │ └── Dropdown.tsx │ │ ├── button │ │ │ ├── Button.tsx │ │ │ └── Button.module.scss │ │ ├── input │ │ │ ├── Input.module.scss │ │ │ └── Input.tsx │ │ └── modal │ │ │ ├── Modal.module.scss │ │ │ └── Modal.tsx │ ├── summary │ │ ├── Summary.module.scss │ │ ├── SummaryBox.module.scss │ │ ├── SummaryBox.tsx │ │ └── Summary.tsx │ ├── topnav │ │ ├── rightBox │ │ │ ├── TopNavRightBox.tsx │ │ │ ├── profile │ │ │ │ ├── Profile.module.scss │ │ │ │ └── Profile.tsx │ │ │ ├── TopNavRightBox.module.scss │ │ │ ├── themeBox │ │ │ │ ├── ThemeBox.module.scss │ │ │ │ └── ThemeBox.tsx │ │ │ └── langBox │ │ │ │ ├── LangBox.module.scss │ │ │ │ └── LangBox.tsx │ │ ├── searchBox │ │ │ ├── SearchBox.tsx │ │ │ └── SearchBox.module.scss │ │ ├── TopNav.module.scss │ │ └── TopNav.tsx │ ├── tables │ │ ├── DashboardTables.module.scss │ │ ├── DashboardTables.tsx │ │ └── customTable │ │ │ ├── CustomTable.module.scss │ │ │ └── CustomTable.tsx │ ├── chart │ │ ├── LineChart.tsx │ │ ├── BarChart.tsx │ │ ├── Chart.module.scss │ │ └── Chart.tsx │ ├── login │ │ ├── Login.module.scss │ │ └── Login.tsx │ ├── sidebar │ │ ├── Sidebar.tsx │ │ └── Sidebar.module.scss │ └── edit │ │ ├── editCustomer │ │ ├── EditCustomer.module.scss │ │ └── EditCustomer.tsx │ │ └── editProduct │ │ ├── EditProduct.tsx │ │ └── EditProduct.module.scss ├── route │ └── ProtectedRoute.tsx ├── layout │ ├── AuthLayout.tsx │ ├── MainLayout.tsx │ └── MainLayout.module.scss ├── hook │ ├── useWindowDimensions.ts │ └── useFetch.ts ├── index.tsx ├── store │ ├── sidebarContext.tsx │ ├── loginContext.tsx │ ├── langContext.tsx │ └── themeContext.tsx ├── config │ └── sidebarNav.ts ├── App.tsx └── locale.tsx ├── netlify.toml ├── declarations.d.ts ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/scss/App.scss: -------------------------------------------------------------------------------- 1 | @forward './global'; 2 | @forward './flex-grid'; -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from="/*" 3 | to="/index.html" 4 | status=200 -------------------------------------------------------------------------------- /src/scss/abstracts/_index.scss: -------------------------------------------------------------------------------- 1 | @forward './variables'; 2 | @forward './mixins'; 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/images/avatar.jpg -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as images } from "./images"; 2 | export { default as data } from "./data"; 3 | -------------------------------------------------------------------------------- /src/assets/images/digikalaLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/images/digikalaLogo.png -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.scss" { 2 | const content: { [className: string]: string }; 3 | export = content; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/images/apple-airpods.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/images/apple-airpods.jpg -------------------------------------------------------------------------------- /src/assets/images/figma-sketch-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/images/figma-sketch-v3.png -------------------------------------------------------------------------------- /src/interfaces/IsummData.ts: -------------------------------------------------------------------------------- 1 | export interface IsummData { 2 | icon: string; 3 | text: string; 4 | amount: string; 5 | currency: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebblack.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebblack.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebbold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebbold.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanweblight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanweblight.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebmedium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebmedium.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebthin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebthin.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebblack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebblack.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebbold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanweblight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanweblight.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebmedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebmedium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebthin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebthin.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebbold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebbold.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebthin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebthin.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebregular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebregular.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebregular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebregular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebblack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebblack.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanweblight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanweblight.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebmedium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebmedium.woff -------------------------------------------------------------------------------- /src/pages/BlankPage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function BlankPage() { 4 | return
BlankPage
; 5 | } 6 | 7 | export default BlankPage; 8 | -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebextrablack.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebextrablack.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/eot/iranyekanwebextrabold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/eot/iranyekanwebextrabold.eot -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebextrablack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebextrablack.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/ttf/iranyekanwebextrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/ttf/iranyekanwebextrabold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebextrabold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebextrabold.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebregular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebregular.woff -------------------------------------------------------------------------------- /src/assets/fonts/fonts/woff/iranyekanwebextrablack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/admin-panel/HEAD/src/assets/fonts/fonts/woff/iranyekanwebextrablack.woff -------------------------------------------------------------------------------- /src/components/UI/card/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classes from "./Card.module.scss"; 3 | const Card: React.FC = (props) => { 4 | return
{props.children}
; 5 | }; 6 | 7 | export default Card; 8 | -------------------------------------------------------------------------------- /src/pages/Login.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoginBox from "../components/login/Login"; 3 | 4 | function Login() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default Login; 13 | -------------------------------------------------------------------------------- /src/components/summary/Summary.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | .summary { 3 | margin: 2rem 0; 4 | 5 | &__box { 6 | @include flex(center, space-between); 7 | flex-wrap: wrap; 8 | margin-top: 1rem; 9 | margin-right: -1rem; 10 | margin-left: -1rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/UI/loadingSpinner/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import classes from "./LoadingSpinner.module.scss"; 2 | 3 | const LoadingSpinner = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default LoadingSpinner; 12 | -------------------------------------------------------------------------------- /src/components/UI/badge/Badge.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | type TBadge = { 5 | // type: string; 6 | content: string; 7 | }; 8 | const Badge: React.FC = (props) => { 9 | const { t } = useTranslation(); 10 | return {t(props.content)}; 11 | }; 12 | 13 | export default Badge; 14 | -------------------------------------------------------------------------------- /src/constants/images.ts: -------------------------------------------------------------------------------- 1 | const images = { 2 | logo: require("../assets/images/digikalaLogo.png"), 3 | avt: require("../assets/images/avatar.jpg"), 4 | logKey: require("../assets/images/Reset password-pana.svg"), 5 | dashboard: require("../assets/images/Revenue-cuate.svg"), 6 | notFound: require("../assets/images/Oops 404 Error with a broken robot-cuate.svg"), 7 | }; 8 | 9 | export default images; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/scss/abstracts/_mixins.scss: -------------------------------------------------------------------------------- 1 | @use './variables' as *; 2 | 3 | @mixin flex($alignItem: center, $justifyContent: center) { 4 | display: flex; 5 | align-items: $alignItem; 6 | justify-content: $justifyContent; 7 | } 8 | 9 | @mixin mobile { 10 | @media only screen and (max-width: $mobile-width) { 11 | @content; 12 | } 13 | } 14 | 15 | @mixin tablet { 16 | @media only screen and (max-width: $tablet-width) { 17 | @content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/UI/dropdown/Dropdown.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .select { 4 | padding: 0.8rem 0.5rem 0.8rem 1rem; 5 | margin: 0 0.8rem; 6 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); 7 | border-radius: $smallBorderRadius; 8 | outline: none; 9 | border: none; 10 | border-right: 1rem solid transparent; 11 | cursor: pointer; 12 | } 13 | 14 | html[dir="rtl"] { 15 | .select { 16 | border-right: none; 17 | border-left: 1rem solid transparent; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/route/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | import Login from "../pages/Login"; 4 | import LoginContext from "../store/loginContext"; 5 | 6 | // interface Props { 7 | // isAuthenticated: boolean; 8 | // authenticationPath: string; 9 | // outlet: JSX.Element; 10 | // } 11 | const ProtectedRoute: React.FC = (props) => { 12 | const loginCtx = useContext(LoginContext); 13 | return loginCtx.isLogin ? : ; 14 | }; 15 | 16 | export default ProtectedRoute; 17 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/TopNavRightBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ThemeBox from "./themeBox/ThemeBox"; 3 | import LangBox from "./langBox/LangBox"; 4 | import Profile from "./profile/Profile"; 5 | 6 | import classes from "./TopNavRightBox.module.scss"; 7 | 8 | function TopNavRightBox() { 9 | return ( 10 |
11 |
12 | 13 | 14 |
15 | 16 |
17 | ); 18 | } 19 | 20 | export default TopNavRightBox; 21 | -------------------------------------------------------------------------------- /src/pages/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import Summary from "../components/summary/Summary"; 4 | import SaleChart from "../components/chart/Chart"; 5 | import DashboardTables from "../components/tables/DashboardTables"; 6 | 7 | function Dashboard() { 8 | const { t } = useTranslation(); 9 | return ( 10 |
11 |

{t("dashboard")}

12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default Dashboard; 20 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/profile/Profile.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../../scss/abstracts/" as *; 2 | 3 | .profile { 4 | @include flex(); 5 | // margin-left: auto; 6 | &__avatar { 7 | display: flex; 8 | width: 40px; 9 | height: 40px; 10 | border-radius: 50%; 11 | overflow: hidden; 12 | margin: 0 0.5rem; 13 | & img { 14 | object-fit: cover; 15 | } 16 | } 17 | 18 | &__userName { 19 | font-size: $fontSizeBodyLarge; 20 | white-space: nowrap; 21 | } 22 | &__role { 23 | font-size: $fontSizeBodyMedium; 24 | color: darkgray; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/layout/AuthLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import LoginContext from "../store/loginContext"; 3 | 4 | import { Navigate, Outlet, useLocation } from "react-router-dom"; 5 | 6 | const AuthLayout = () => { 7 | const location = useLocation(); 8 | const loginCtx = useContext(LoginContext); 9 | 10 | return loginCtx.isLogin ? ( 11 | 12 | ) : ( 13 | 18 | ); 19 | }; 20 | 21 | export default AuthLayout; 22 | -------------------------------------------------------------------------------- /src/components/UI/button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import classes from "./Button.module.scss"; 4 | 5 | interface Props { 6 | type?: "button" | "submit"; 7 | onClick?: (e: React.MouseEvent) => void; 8 | outline?: boolean; 9 | } 10 | const Button: React.FC = (props) => { 11 | return ( 12 | 21 | ); 22 | }; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/TopNavRightBox.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .topNavBox_right { 4 | @include flex(center, space-evenly); 5 | flex: 1; 6 | @media screen and (max-width: 975px) { 7 | flex: 2; 8 | } 9 | @media screen and (max-width: 325px) { 10 | flex-wrap: wrap; 11 | } 12 | } 13 | .wrapper { 14 | flex: 1; 15 | @include flex(center, flex-end); 16 | @media screen and (max-width: 975px) { 17 | justify-content: space-evenly; 18 | } 19 | @media screen and (max-width: 325px) { 20 | order: 2; 21 | margin-top: 0.5rem; 22 | justify-content: space-between; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "downlevelIteration": true, 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx" 19 | }, 20 | "include": ["src", "declarations.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/components/summary/SummaryBox.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | .summary__box { 3 | margin: 0 1rem 1.8rem; 4 | flex: 1 1 350px; 5 | 6 | &__wrapper { 7 | @include flex(); 8 | margin: 1rem 0; 9 | flex: 1; 10 | flex-wrap: wrap; 11 | } 12 | 13 | &__icon { 14 | margin: 0 1rem; 15 | } 16 | &__info__amount { 17 | display: flex; 18 | margin-top: 0.3rem; 19 | flex-wrap: wrap; 20 | & h4 { 21 | margin-top: 1rem; 22 | font-size: 32px; 23 | font-weight: 400; 24 | white-space: nowrap; 25 | } 26 | & sup { 27 | font-size: $fontSizeBodyLarge; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/themeBox/ThemeBox.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../../scss/abstracts/" as *; 2 | .themeBox { 3 | width: 40px; 4 | height: 22px; 5 | background-color: var(--text-color); 6 | border-radius: 30px; 7 | padding: 1px; 8 | position: relative; 9 | margin: 0 2rem; 10 | cursor: pointer; 11 | @include mobile { 12 | margin: 0; 13 | } 14 | } 15 | 16 | .toggle { 17 | width: 18px; 18 | height: 18px; 19 | background-color: var(--card-bgc); 20 | border-radius: 50%; 21 | position: absolute; 22 | left: 2px; 23 | top: 2px; 24 | transition: all 0.3s ease-in-out; 25 | &.darkMode { 26 | left: 50%; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/themeBox/ThemeBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import ThemeContext from "../../../../store/themeContext"; 3 | import classes from "./ThemeBox.module.scss"; 4 | 5 | function ThemeBox() { 6 | // const [theme, setTheme] = useState("light"); 7 | const themeCtx = useContext(ThemeContext); 8 | let theme = themeCtx.theme; 9 | return ( 10 |
themeCtx.toggleTheme()}> 11 |
16 |
17 | ); 18 | } 19 | 20 | export default ThemeBox; 21 | -------------------------------------------------------------------------------- /src/components/topnav/searchBox/SearchBox.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from "@iconify/react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | import classes from "./SearchBox.module.scss"; 5 | 6 | function SearchBox() { 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 |
11 | 16 | 22 |
23 | ); 24 | } 25 | 26 | export default SearchBox; 27 | -------------------------------------------------------------------------------- /src/hook/useWindowDimensions.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | function getWindowDimensions() { 4 | const { innerWidth: width, innerHeight: height } = window; 5 | return { 6 | width, 7 | height, 8 | }; 9 | } 10 | 11 | export default function useWindowDimensions() { 12 | const [windowDimensions, setWindowDimensions] = useState( 13 | getWindowDimensions() 14 | ); 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowDimensions(getWindowDimensions()); 19 | } 20 | 21 | window.addEventListener("resize", handleResize); 22 | return () => window.removeEventListener("resize", handleResize); 23 | }, []); 24 | 25 | return windowDimensions; 26 | } 27 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { SidebarContextProvider } from "./store/sidebarContext"; 5 | import { LangContextProvider } from "./store/langContext"; 6 | import { ThemeContextProvider } from "./store/themeContext"; 7 | import { LoginContextProvider } from "./store/loginContext"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | , 19 | document.getElementById("root") 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/profile/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { images } from "../../../../constants"; 2 | import classes from "./Profile.module.scss"; 3 | import { useTranslation } from "react-i18next"; 4 | 5 | function Profile() { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 |
10 |
11 | avatar 12 |
13 |
14 |

{t("zahraMirzaei")}

15 | {/* {t("admin")} */} 16 |
17 |
18 | ); 19 | } 20 | 21 | export default Profile; 22 | -------------------------------------------------------------------------------- /src/components/UI/button/Button.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/index" as *; 2 | 3 | .btn { 4 | font: inherit; 5 | border-radius: $smallBorderRadius; 6 | padding: 0.8rem 3rem; 7 | cursor: pointer; 8 | margin: 0 0.5rem; 9 | } 10 | 11 | .button { 12 | border: none; 13 | background: var(--secondaryColor); 14 | color: gainsboro; 15 | 16 | &:hover, 17 | &:active { 18 | background-color: darken(#3c4b6d, 10%); 19 | } 20 | 21 | &:focus { 22 | outline: none; 23 | } 24 | } 25 | 26 | .outline { 27 | background-color: transparent; 28 | border: 1px solid var(--secondaryColor); 29 | color: var(--text-color); 30 | } 31 | 32 | html[dir="rtl"] { 33 | .btn { 34 | font-family: iranyekan, "IRANSans", "Tahoma"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/store/sidebarContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | type SidebarContextObj = { isOpen: boolean; toggleSidebar: () => void }; 4 | 5 | const SidebarContext = React.createContext({ 6 | isOpen: true, 7 | toggleSidebar: () => {}, 8 | }); 9 | 10 | export const SidebarContextProvider: React.FC = (props) => { 11 | const [isOpen, setIsOpen] = useState(true); 12 | function ToggleSidebar() { 13 | setIsOpen((prev) => !prev); 14 | } 15 | 16 | const contextValue: SidebarContextObj = { 17 | isOpen, 18 | toggleSidebar: ToggleSidebar, 19 | }; 20 | return ( 21 | 22 | {props.children} 23 | 24 | ); 25 | }; 26 | 27 | export default SidebarContext; 28 | -------------------------------------------------------------------------------- /src/components/UI/loadingSpinner/LoadingSpinner.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .spinner__wrapper { 4 | // @include flex(center, center); 5 | display: block; 6 | text-align: center; 7 | } 8 | 9 | .spinner { 10 | display: inline-block; 11 | width: 110px; 12 | height: 110px; 13 | } 14 | .spinner:after { 15 | content: " "; 16 | display: block; 17 | width: 110px; 18 | height: 110px; 19 | margin: 8px; 20 | border-radius: 50%; 21 | border: 10px solid var(--primaryColor); 22 | border-color: var(--primaryColor) transparent var(--primaryColor) transparent; 23 | animation: spinner 1.2s linear infinite; 24 | } 25 | @keyframes spinner { 26 | 0% { 27 | transform: rotate(0deg); 28 | } 29 | 100% { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/langBox/LangBox.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../../scss/abstracts/" as *; 2 | .lang { 3 | position: relative; 4 | } 5 | .lanBox { 6 | @include flex(); 7 | margin: 0 0.8rem; 8 | cursor: pointer; 9 | } 10 | .lang_slc { 11 | padding: 0.3rem; 12 | } 13 | .lang_menu { 14 | width: 100px; 15 | font-size: 12px; 16 | background-color: var(--card-bgc); 17 | position: absolute; 18 | top: 160%; 19 | opacity: 0; 20 | user-select: none; 21 | padding: 0.5rem; 22 | box-shadow: 1px 0 8px 0 rgba(0, 0, 0, 0.1); 23 | transition: all 0.3s ease-in-out; 24 | & :first-child { 25 | margin-bottom: 0.5rem; 26 | } 27 | & div:hover { 28 | font-weight: bold; 29 | cursor: pointer; 30 | } 31 | &.show { 32 | top: 120%; 33 | opacity: 1; 34 | user-select: auto; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/store/loginContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocalStorage } from "usehooks-ts"; 3 | 4 | type TContext = { 5 | isLogin: boolean; 6 | toggleLogin: () => void; 7 | }; 8 | 9 | const LoginContext = React.createContext({ 10 | isLogin: false, 11 | toggleLogin: () => {}, 12 | }); 13 | 14 | export const LoginContextProvider: React.FC = (props) => { 15 | const [isLogin, setIsLogin] = useLocalStorage("isLogin", false); 16 | 17 | function toggleLogin() { 18 | setIsLogin((prev) => !prev); 19 | } 20 | 21 | const loginValue: TContext = { 22 | isLogin: isLogin, 23 | toggleLogin: toggleLogin, 24 | }; 25 | 26 | return ( 27 | 28 | {props.children} 29 | 30 | ); 31 | }; 32 | 33 | export default LoginContext; 34 | -------------------------------------------------------------------------------- /src/components/UI/input/Input.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | .form__control { 3 | position: relative; 4 | margin-bottom: 2rem; 5 | // border: 1px solid red; 6 | label { 7 | position: absolute; 8 | top: -30%; 9 | left: 10px; 10 | background-color: var(--bgc); 11 | padding: 0.3rem; 12 | white-space: nowrap; 13 | } 14 | 15 | input { 16 | padding: 1rem 1.5rem; 17 | border: 1px solid gainsboro; 18 | outline: gray; 19 | background-color: var(--bgc); 20 | border-radius: $smallBorderRadius; 21 | min-width: 300px; 22 | box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.05); 23 | @media screen and (max-width: 300px) { 24 | min-width: auto; 25 | } 26 | } 27 | } 28 | 29 | html[dir="rtl"] { 30 | .form__control { 31 | label { 32 | left: auto; 33 | right: 10px; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/UI/card/Card.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .card { 4 | @include flex(stretch, flex-start); 5 | background-color: var(--bgc); 6 | border-radius: $mediumBorderRadius; 7 | box-shadow: $mainBoxShadow; 8 | padding: 1rem 2rem; 9 | flex: 1; 10 | overflow: auto; 11 | max-width: 2000px; 12 | 13 | &::-webkit-scrollbar { 14 | height: 0.8rem; 15 | } 16 | 17 | &::-webkit-scrollbar-track { 18 | background-color: var(--bgc); 19 | margin-block: 0.5rem; 20 | border-radius: 100vw; 21 | } 22 | 23 | &::-webkit-scrollbar-thumb { 24 | background-color: var(--primaryColor); 25 | border-radius: 100vw; 26 | } 27 | 28 | @supports (scrollbar-calor: var(--primaryColor) var(--secondaryColor)) { 29 | * { 30 | scrollbar-color: var(--bgc) var(--primaryColor); 31 | scrollbar-width: auto; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/store/langContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useLocalStorage } from "usehooks-ts"; 3 | import i18n from "../locale"; 4 | 5 | type langContextObj = { 6 | lang: string | null; 7 | toggleLanguage: (sLang: string) => void; 8 | }; 9 | 10 | const LangContext = React.createContext({ 11 | lang: "", 12 | toggleLanguage: (slang) => {}, 13 | }); 14 | 15 | export const LangContextProvider: React.FC = (props) => { 16 | const [lang, setLang] = useLocalStorage("language", "en"); 17 | 18 | useEffect(() => { 19 | i18n.changeLanguage(lang); 20 | }, [lang]); 21 | function toggleLanguage(sLang: string) { 22 | setLang(sLang); 23 | } 24 | 25 | const langValue: langContextObj = { 26 | lang, 27 | toggleLanguage, 28 | }; 29 | 30 | return ( 31 | 32 | {props.children} 33 | 34 | ); 35 | }; 36 | 37 | export default LangContext; 38 | -------------------------------------------------------------------------------- /src/store/themeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useLocalStorage } from "usehooks-ts"; 3 | 4 | type themeType = { 5 | theme: string; 6 | toggleTheme: () => void; 7 | }; 8 | 9 | const ThemeContext = React.createContext({ 10 | theme: "", 11 | toggleTheme: () => {}, 12 | }); 13 | 14 | export const ThemeContextProvider: React.FC = (props) => { 15 | const [theme, setTheme] = useLocalStorage("theme", "light"); 16 | 17 | useEffect(() => { 18 | document.documentElement.setAttribute("theme", theme); 19 | }, [theme]); 20 | 21 | function toggleTheme() { 22 | setTheme((prev) => { 23 | return prev === "light" ? "dark" : "light"; 24 | }); 25 | } 26 | 27 | const themeValue: themeType = { 28 | theme, 29 | toggleTheme, 30 | }; 31 | return ( 32 | 33 | {props.children} 34 | 35 | ); 36 | }; 37 | 38 | export default ThemeContext; 39 | -------------------------------------------------------------------------------- /src/components/tables/DashboardTables.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | 3 | .table { 4 | @include flex(stretch, flex-start); 5 | flex-wrap: wrap; 6 | margin: 3rem 0; 7 | font-size: 14px; 8 | margin-left: -2rem; 9 | margin-right: -2rem; 10 | 11 | &__child { 12 | padding: 0 1rem; 13 | } 14 | &__top__customers { 15 | min-width: 550px; 16 | flex: 2; 17 | @media screen and (max-width: 1024px) { 18 | min-width: 400px; 19 | } 20 | @media screen and (max-width: 425px) { 21 | min-width: 246px; 22 | } 23 | } 24 | &__latest__orders { 25 | min-width: 650px; 26 | flex: 3; 27 | @media screen and (max-width: 1024px) { 28 | min-width: 400px; 29 | } 30 | @media screen and (max-width: 425px) { 31 | min-width: 246px; 32 | } 33 | } 34 | } 35 | 36 | .table__title { 37 | @include flex(center, space-between); 38 | padding: 0 1rem; 39 | & a { 40 | margin-bottom: 1rem; 41 | color: rgb(0, 132, 255); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/scss/_flex-grid.scss: -------------------------------------------------------------------------------- 1 | @use './abstracts/' as *; 2 | @use 'sass:math'; 3 | 4 | .row { 5 | display: flex; 6 | flex-wrap: wrap; 7 | margin: 0 math.div($spacing, 2) * -1; 8 | 9 | @include tablet { 10 | margin: 0 math.div($mobileSpacing, 2) * -1; 11 | } 12 | } 13 | 14 | [class*="col-"] { 15 | padding: 0 math.div($spacing, 2); 16 | 17 | @include tablet { 18 | padding: 0 math.div($mobileSpacing, 2); 19 | } 20 | } 21 | 22 | @for $i from 1 through 12 { 23 | $v: math.div($i, 12) * 100; 24 | .col-#{$i} { 25 | width: $v * 1%; 26 | } 27 | } 28 | 29 | // medium screen 30 | 31 | @include tablet { 32 | @for $i from 1 through 12 { 33 | $v: math.div($i, 12) * 100; 34 | .col-md-#{$i} { 35 | width: $v * 1%; 36 | } 37 | } 38 | 39 | .hide-md { 40 | display: none; 41 | } 42 | } 43 | 44 | // small screen 45 | 46 | @include mobile { 47 | @for $i from 1 through 12 { 48 | $v: math.div($i, 12) * 100; 49 | .col-sm-#{$i} { 50 | width: $v * 1%; 51 | } 52 | } 53 | 54 | .hide-sm { 55 | display: none; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/topnav/TopNav.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | 3 | .topNav { 4 | display: flex; 5 | flex-wrap: wrap; 6 | padding-bottom: 1rem; 7 | border-bottom: 3px solid gainsboro; 8 | margin-bottom: 4rem; 9 | &_left { 10 | @include flex(center, flex-start); 11 | flex-wrap: wrap; 12 | flex: 1; 13 | 14 | &_menu_icon { 15 | cursor: pointer; 16 | @include tablet { 17 | display: none; 18 | } 19 | } 20 | &_menu { 21 | display: none; 22 | @include tablet { 23 | display: block; 24 | } 25 | } 26 | } 27 | 28 | &_right { 29 | flex: 1; 30 | } 31 | } 32 | .search_desktop_wrapper { 33 | flex: 1; 34 | @media screen and (max-width: 975px) { 35 | display: none; 36 | } 37 | } 38 | .search_tablet_wrapper { 39 | flex: 1 1 500px; 40 | display: none; 41 | @media screen and (max-width: 975px) { 42 | display: block; 43 | margin: 0.5rem 0 0; 44 | } 45 | } 46 | 47 | html[dir="rtl"] { 48 | .topNav_left_menu_icon { 49 | transform: rotate(180deg); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/config/sidebarNav.ts: -------------------------------------------------------------------------------- 1 | const sidebarNav = [ 2 | { 3 | link: "/", 4 | section: "dashboard", 5 | icon: "lucide:layout-dashboard", //width:"20" 6 | text: "Dashboard", 7 | }, 8 | { 9 | link: "/products", 10 | section: "products", 11 | icon: "icon-park-outline:ad-product", 12 | text: "Products", 13 | }, 14 | { 15 | link: "/customers", 16 | section: "customers", 17 | icon: "ph:users-bold", 18 | text: "Customers", 19 | }, 20 | { 21 | link: "/orders", 22 | section: "orders", 23 | icon: "icon-park-outline:transaction-order", 24 | text: "Orders", 25 | }, 26 | { 27 | link: "/analytics", 28 | section: "analytics", 29 | icon: "carbon:analytics", 30 | text: "Analytics", 31 | }, 32 | { 33 | link: "/discount", 34 | section: "discount", 35 | icon: "nimbus:discount-circle", 36 | text: "Discount", 37 | }, 38 | { 39 | link: "/inventory", 40 | section: "inventory", 41 | icon: "ic:round-inventory", 42 | text: "Inventory", 43 | }, 44 | ]; 45 | 46 | export default sidebarNav; 47 | -------------------------------------------------------------------------------- /src/components/chart/LineChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Tooltip, 9 | Legend, 10 | } from "chart.js"; 11 | import { Line } from "react-chartjs-2"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | PointElement, 17 | LineElement, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | export const options = { 23 | responsive: true, 24 | plugins: { 25 | legend: { 26 | // rtl: true, 27 | // textDirection: "rtl", 28 | labels: { 29 | font: { 30 | size: 20, 31 | color: "#fff", 32 | }, 33 | }, 34 | }, 35 | }, 36 | }; 37 | 38 | interface Props { 39 | labels: string[]; 40 | datasets: { 41 | label: string; 42 | data: number[]; 43 | borderColor: string; 44 | backgroundColor: string; 45 | }[]; 46 | } 47 | const LineChart: React.FC<{ chartData: Props }> = (props) => { 48 | return ; 49 | }; 50 | 51 | export default LineChart; 52 | -------------------------------------------------------------------------------- /src/components/summary/SummaryBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import { IsummData as Props } from "../../interfaces/IsummData"; 4 | import { Icon } from "@iconify/react"; 5 | import Card from "../UI/card/Card"; 6 | import classes from "./SummaryBox.module.scss"; 7 | const SummaryBox: React.FC<{ item: Props }> = (props) => { 8 | const { t } = useTranslation(); 9 | return ( 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |

{t(props.item.text)}

18 |
19 |

{t(props.item.amount)}

20 | {t(props.item.currency)} 21 |
22 |
23 |
24 |
25 |
26 | ); 27 | }; 28 | 29 | export default SummaryBox; 30 | -------------------------------------------------------------------------------- /src/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | 4 | import Sidebar from "../components/sidebar/Sidebar"; 5 | import TopNav from "../components/topnav/TopNav"; 6 | import sidebarNav from "../config/sidebarNav"; 7 | 8 | import SidebarContext from "../store/sidebarContext"; 9 | import classes from "./MainLayout.module.scss"; 10 | 11 | const MainLayout = () => { 12 | const sidebarCtx = useContext(SidebarContext); 13 | 14 | useEffect(() => { 15 | if (document.body.classList.contains("sidebar__open")) 16 | document.body.classList.remove("sidebar__open"); 17 | }, []); 18 | 19 | return ( 20 |
21 | 22 |
23 |
28 | 29 | 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default MainLayout; 37 | -------------------------------------------------------------------------------- /src/components/UI/dropdown/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import classes from "./Dropdown.module.scss"; 4 | 5 | type TOptionItem = { 6 | label: string; 7 | value: string; 8 | }; 9 | 10 | interface Props { 11 | dropdownData: TOptionItem[]; 12 | onChange: (e: React.ChangeEvent) => void; 13 | } 14 | const Dropdown: React.FC = (props) => { 15 | // const selectRef = useRef(null); 16 | 17 | const { t } = useTranslation(); 18 | 19 | return ( 20 |
21 | 22 | 34 |
35 | ); 36 | }; 37 | 38 | export default Dropdown; 39 | -------------------------------------------------------------------------------- /src/interfaces/Itable.ts: -------------------------------------------------------------------------------- 1 | export interface ItopCustomers extends Object { 2 | username: string; 3 | order: number; 4 | price: string; 5 | } 6 | 7 | export type TlatestTransactions = { 8 | orderId: string; 9 | customer: string; 10 | totalPrice: string; 11 | date: string; 12 | status: string; 13 | }; 14 | 15 | export interface IcustomersTable { 16 | ID: number | string; 17 | userName: string; 18 | avatar: string; 19 | email: string; 20 | phoneNumber: string; 21 | totalOrders: number; 22 | totalSpend: string; 23 | location: string; 24 | } 25 | 26 | export interface IProductsTable { 27 | ID: number | string; 28 | pic: string; 29 | product: string; 30 | inventory: number; 31 | price: string; 32 | category: string; 33 | } 34 | 35 | export type complex = 36 | | ItopCustomers 37 | | TlatestTransactions 38 | | IcustomersTable 39 | | IProductsTable; 40 | 41 | export interface Itable { 42 | limit?: number; 43 | selectedCategory?: string; 44 | headData: string[]; 45 | bodyData: ( 46 | | ItopCustomers 47 | | TlatestTransactions 48 | | IcustomersTable 49 | | IProductsTable 50 | )[]; 51 | } 52 | -------------------------------------------------------------------------------- /src/layout/MainLayout.module.scss: -------------------------------------------------------------------------------- 1 | @use "../scss/abstracts/" as *; 2 | 3 | .container { 4 | display: flex; 5 | } 6 | 7 | .main { 8 | background-color: var(--card-bgc); 9 | overflow-x: hidden; 10 | flex-grow: 1; 11 | 12 | &__content { 13 | min-height: 100vh; 14 | padding: 1rem 2.5rem 1rem 17rem; 15 | transition: all 0.5s ease-in-out; 16 | @include tablet { 17 | padding: 1rem; 18 | } 19 | &.close_sidebar { 20 | padding: 1rem 2.5rem 1rem 6rem; 21 | @include tablet { 22 | padding: 1rem; 23 | } 24 | } 25 | 26 | @include tablet { 27 | // padding: 0.8rem 2rem; 28 | position: relative; 29 | background-color: var(--card-bgc); 30 | z-index: 100; 31 | } 32 | 33 | @include mobile { 34 | padding: 1rem; 35 | } 36 | } 37 | } 38 | 39 | html[dir="rtl"] { 40 | .main__content { 41 | padding: 1rem 17rem 1rem 2.5rem; 42 | &.close_sidebar { 43 | padding: 1rem 6rem 1rem 2rem; 44 | @include tablet { 45 | padding: 0.8rem 2rem 0.8rem 2rem; 46 | } 47 | } 48 | @include tablet { 49 | padding: 0.8rem 2rem; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/chart/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | } from "chart.js"; 11 | import { Bar } from "react-chartjs-2"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | Title, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | interface IChartData { 23 | labels: string[]; 24 | datasets: { 25 | label: string; 26 | data: number[]; 27 | backgroundColor: string; 28 | }[]; 29 | } 30 | 31 | const BarChart: React.FC<{ chartData: IChartData; chartTitle: string }> = ( 32 | props 33 | ) => { 34 | const { chartTitle } = props; 35 | const options = useMemo( 36 | () => ({ 37 | responsive: true, 38 | plugins: { 39 | legend: { 40 | position: "center" as const, 41 | }, 42 | title: { 43 | display: true, 44 | text: chartTitle, 45 | }, 46 | }, 47 | }), 48 | [chartTitle] 49 | ); 50 | return ; 51 | }; 52 | 53 | export default BarChart; 54 | -------------------------------------------------------------------------------- /src/components/summary/Summary.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import SummaryBox from "./SummaryBox"; 4 | import { useTranslation } from "react-i18next"; 5 | import classes from "./Summary.module.scss"; 6 | import { IsummData } from "../../interfaces/IsummData"; 7 | 8 | const summaryData: IsummData[] = [ 9 | { 10 | icon: "akar-icons:shopping-bag", 11 | text: "thisMonthSales", 12 | amount: "salesAmount", 13 | currency: "currency", 14 | }, 15 | { 16 | icon: "icon-park-outline:transaction-order", 17 | text: "thisMonthOrders", 18 | amount: "orderAmount", 19 | currency: "", 20 | }, 21 | { 22 | icon: "jam:coin", 23 | text: "thisMonthRevenue", 24 | amount: "revenueAmount", 25 | currency: "currency", 26 | }, 27 | ]; 28 | 29 | function Summary() { 30 | const { t } = useTranslation(); 31 | return ( 32 |
33 |

{t("summary")}

34 |
35 | {summaryData.map((item) => ( 36 | 37 | ))} 38 |
39 |
40 | ); 41 | } 42 | 43 | export default Summary; 44 | -------------------------------------------------------------------------------- /src/pages/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Button from "../components/UI/button/Button"; 4 | import { useTranslation } from "react-i18next"; 5 | import langContextObj from "../store/langContext"; 6 | 7 | function NotFound() { 8 | const { t } = useTranslation(); 9 | const langCtx = useContext(langContextObj); 10 | return ( 11 |
18 |

23 | {t("notFoundMsg")} 24 |

25 | 26 | 27 | 28 |
29 | 404 page 36 |
37 |
38 | ); 39 | } 40 | 41 | export default NotFound; 42 | -------------------------------------------------------------------------------- /src/components/topnav/searchBox/SearchBox.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .searchBox { 4 | @include flex(center, center); 5 | 6 | border: 1.5px solid rgb(187, 187, 187); 7 | border-radius: 100vw; 8 | padding: 0.2rem 0.5rem; 9 | margin-left: 2rem; 10 | 11 | @media screen and (max-width: 975px) { 12 | margin-left: 0; 13 | } 14 | &_input { 15 | border: none; 16 | outline: none; 17 | background-color: inherit; 18 | margin-left: 0.5rem; 19 | padding: 0.3rem; 20 | 21 | flex: 1; 22 | 23 | &:-webkit-autofill, 24 | &:-webkit-autofill:hover, 25 | &:-webkit-autofill:focus, 26 | &:-webkit-autofill:active { 27 | -webkit-box-shadow: 0 0 0 30px var(--card-bgc) inset !important; 28 | box-shadow: 0 0 0 30px var(--card-bgc) inset !important; 29 | } 30 | } 31 | @media screen and (max-width: 950px) { 32 | margin-left: 0; 33 | } 34 | } 35 | 36 | html[dir="rtl"] { 37 | .searchBox { 38 | margin-left: 0; 39 | margin-right: 2rem; 40 | &_input { 41 | margin-left: 0rem; 42 | margin-right: 0.5rem; 43 | } 44 | 45 | @media screen and (max-width: 950px) { 46 | margin-right: 0; 47 | margin-top: 1rem; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/Customers.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import useFetch from "../hook/useFetch"; 4 | import CustomTable from "../components/tables/customTable/CustomTable"; 5 | import { IcustomersTable } from "../interfaces/Itable"; 6 | import { customers, customersHeader } from "../constants/tables"; 7 | import LoadingSpinner from "../components/UI/loadingSpinner/LoadingSpinner"; 8 | const url = 9 | "https://admin-panel-79c71-default-rtdb.europe-west1.firebasedatabase.app/customers.json"; 10 | function Customers() { 11 | const { t } = useTranslation(); 12 | const { data, error, status } = useFetch(url); 13 | let customerTable; 14 | 15 | if (status === "loading") { 16 | customerTable = ; 17 | } 18 | 19 | if (error) { 20 | customerTable = ( 21 | 22 | ); 23 | } 24 | 25 | if (status === "fetched" && data) { 26 | customerTable = ( 27 | 28 | ); 29 | } 30 | 31 | return ( 32 |
33 |

{t("customers")}

34 | {customerTable} 35 |
36 | ); 37 | } 38 | 39 | export default Customers; 40 | -------------------------------------------------------------------------------- /src/components/tables/DashboardTables.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import classes from "./DashboardTables.module.scss"; 3 | import { useTranslation } from "react-i18next"; 4 | import CustomTable from "./customTable/CustomTable"; 5 | import data from "../../constants/data"; 6 | 7 | const Table = () => { 8 | const { t } = useTranslation(); 9 | return ( 10 |
11 |
14 |
15 |

{t("topCustomers")}

16 | {t("viewAll")} 17 |
18 | 22 |
23 |
26 |
27 |

{t("latestTransaction")}

28 | {t("viewAll")} 29 |
30 | 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default Table; 40 | -------------------------------------------------------------------------------- /src/assets/images/bag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | -------------------------------------------------------------------------------- /src/pages/ProductEdit.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import useFetch from "../hook/useFetch"; 4 | import { useParams } from "react-router-dom"; 5 | import EditProduct from "../components/edit/editProduct/EditProduct"; 6 | import { IProductsTable } from "../interfaces/Itable"; 7 | import { products } from "../constants/tables"; 8 | import LoadingSpinner from "../components/UI/loadingSpinner/LoadingSpinner"; 9 | 10 | const url = 11 | "https://admin-panel-79c71-default-rtdb.europe-west1.firebasedatabase.app/products"; 12 | function ProductEdit() { 13 | const { t } = useTranslation(); 14 | const params = useParams(); 15 | let { productId } = params; 16 | 17 | let productInfo: IProductsTable = products.filter( 18 | (item) => item.ID.toString() === productId 19 | )[0]; 20 | 21 | let productEdit; 22 | 23 | const { data, error, status } = useFetch( 24 | `${url}/${productId}.json` 25 | ); 26 | 27 | if (status === "loading") { 28 | productEdit = ; 29 | } 30 | 31 | if (error) { 32 | productEdit = ; 33 | } 34 | 35 | if (status === "fetched" && data) { 36 | productEdit = ; 37 | } 38 | 39 | return ( 40 |
41 |

{t("editProduct")}

42 | {productEdit} 43 |
44 | ); 45 | } 46 | 47 | export default ProductEdit; 48 | -------------------------------------------------------------------------------- /src/components/UI/modal/Modal.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .backdrop { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100vh; 9 | z-index: 10; 10 | background: rgba(0, 0, 0, 0.75); 11 | } 12 | 13 | .modal { 14 | position: fixed; 15 | top: 30vh; 16 | left: 30%; 17 | width: 50%; 18 | min-width: 500px; 19 | max-width: 700px; 20 | z-index: 100; 21 | overflow: auto; 22 | border-radius: $largeBorderRadius; 23 | background-color: var(--bgc); 24 | } 25 | 26 | .header { 27 | background: var(--primaryColor); 28 | padding: 1rem; 29 | } 30 | 31 | .header h3 { 32 | margin: 0; 33 | color: white; 34 | } 35 | 36 | .content { 37 | padding: 1rem; 38 | } 39 | 40 | .actions { 41 | padding: 1rem; 42 | display: flex; 43 | justify-content: flex-end; 44 | 45 | .delete { 46 | font: inherit; 47 | border-radius: $smallBorderRadius; 48 | padding: 0.8rem 3rem; 49 | cursor: pointer; 50 | margin: 0 0.5rem; 51 | background-color: #9d0e0e; 52 | border: none; 53 | color: gainsboro; 54 | 55 | &:hover, 56 | &:active { 57 | background-color: darken(#9d0e0e, 10%); 58 | } 59 | 60 | &:focus { 61 | outline: none; 62 | } 63 | } 64 | } 65 | 66 | @media screen and (max-width: 768px) { 67 | .backdrop { 68 | z-index: 1000; 69 | } 70 | .modal { 71 | left: calc(50% - 11.5rem); 72 | width: 23rem; 73 | min-width: auto; 74 | z-index: 1000; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/scss/abstracts/_variables.scss: -------------------------------------------------------------------------------- 1 | /* ****Fonts**** */ 2 | 3 | //Body size 4 | $fontSizeBodySmall: 12px; //line-height:16px; letter-spacing:0.4%; 5 | $fontSizeBodyMedium: 14px; //line-height:20px; letter-spacing:0.25%; 6 | $fontSizeBodyLarge: 16px; //line-height:24px; letter-spacing:0.15%; 7 | //Headline size 8 | $fontSizeHeadlineSmall: 24px; //line-height:32px; letter-spacing:0; 9 | $fontSizeHeadlineMedium: 45px; //lineHeight:36px; letter-spacing:0; 10 | $fontSizeHeadlineLarge: 57px; //line-height:40px letter-spacing:0; 11 | //Display size 12 | $fontSizeDisplaySmall: 64px; //line-height:44px; letter-spacing:0; 13 | //helper font size 14 | $fontSizeTableStatus: 9px; //line-height:16px; letter-spacing:0.4%; 15 | 16 | /* ****Shadows**** */ 17 | $mainBoxShadow: 0 6px 30px 5px rgba(0, 0, 0, 0.12); 18 | $secondBoxShadow: -3px 6px 30px 5px rgba(0, 0, 0, 0.3); 19 | 20 | /* ****Border radius**** */ 21 | $smallBorderRadius: 8px; //for buttons 22 | $mediumBorderRadius: 12px; //for boxes 23 | $largeBorderRadius: 20px; //for tables and main section in tablet and mobile. 24 | 25 | /* ****icon size**** */ 26 | $smallIconSize: 20px; 27 | $largeIconSize: 48px; 28 | 29 | /* **** Device width **** */ 30 | //This design is mobile first 31 | $mobile-width: 576px; 32 | $tablet-width: 768px; 33 | $desktop-width: 992px; 34 | $largeDesktop-width: 1200px; 35 | 36 | /* **** helper variable **** */ 37 | $sideBarOpenWidth: 230px; 38 | $sideBarCloseWidth: 75px; 39 | $spacing: 60px; 40 | $mobileSpacing: 16px; 41 | $sideBarLogoHeight: 171px; 42 | -------------------------------------------------------------------------------- /src/pages/CustomerEdit.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import useFetch from "../hook/useFetch"; 4 | import { useParams } from "react-router-dom"; 5 | import EditCustomer from "../components/edit/editCustomer/EditCustomer"; 6 | import { IcustomersTable } from "../interfaces/Itable"; 7 | import { customers } from "../constants/tables"; 8 | import LoadingSpinner from "../components/UI/loadingSpinner/LoadingSpinner"; 9 | const url = 10 | "https://admin-panel-79c71-default-rtdb.europe-west1.firebasedatabase.app/customers"; 11 | function CustomerEdit() { 12 | const { t } = useTranslation(); 13 | const params = useParams(); 14 | let { customerId } = params; 15 | 16 | /* fallback in case of time limit to test firebase database will over */ 17 | let customerInfo: IcustomersTable = customers.filter( 18 | (item) => item.ID.toString() === customerId 19 | )[0]; 20 | 21 | let customerEdit; 22 | 23 | const { data, error, status } = useFetch( 24 | `${url}/${customerId}.json` 25 | ); 26 | 27 | if (status === "loading") { 28 | customerEdit = ; 29 | } 30 | 31 | if (error) { 32 | customerEdit = ; 33 | } 34 | 35 | if (status === "fetched" && data) { 36 | customerEdit = ; 37 | } 38 | 39 | return ( 40 |
41 |

{t("editCustomer")}

42 | {customerEdit} 43 |
44 | ); 45 | } 46 | 47 | export default CustomerEdit; 48 | -------------------------------------------------------------------------------- /src/components/topnav/TopNav.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Icon } from "@iconify/react"; 3 | import { useWindowSize } from "usehooks-ts"; 4 | import SearchBox from "./searchBox/SearchBox"; 5 | import TopNavRightBox from "./rightBox/TopNavRightBox"; 6 | import SidebarContext from "../../store/sidebarContext"; 7 | 8 | import classes from "./TopNav.module.scss"; 9 | 10 | function TopNav() { 11 | const sideOpenCtx = useContext(SidebarContext); 12 | const { width } = useWindowSize(); 13 | 14 | function openSidebarHandler() { 15 | sideOpenCtx.toggleSidebar(); 16 | if (width <= 768) document.body.classList.toggle("sidebar__open"); 17 | } 18 | 19 | return ( 20 |
21 |
22 |
26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | ); 48 | } 49 | 50 | export default TopNav; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://zahramirzaei.github.io", 3 | "name": "admin-panel", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@faker-js/faker": "^6.1.2", 8 | "@testing-library/jest-dom": "^5.14.1", 9 | "@testing-library/react": "^12.0.0", 10 | "@testing-library/user-event": "^13.2.1", 11 | "@types/jest": "^27.0.1", 12 | "@types/node": "^16.7.13", 13 | "@types/react": "^17.0.20", 14 | "@types/react-dom": "^17.0.9", 15 | "@types/react-router-dom": "^5.3.3", 16 | "chart.js": "^3.7.1", 17 | "i18next": "^21.6.14", 18 | "react": "^17.0.2", 19 | "react-chartjs-2": "^4.0.1", 20 | "react-dom": "^17.0.2", 21 | "react-i18next": "^11.15.7", 22 | "react-router-dom": "^6.2.2", 23 | "react-scripts": "5.0.0", 24 | "sass": "^1.49.9", 25 | "serve": "^13.0.2", 26 | "typescript": "^4.4.2", 27 | "usehooks-ts": "^2.4.2", 28 | "web-vitals": "^2.1.0" 29 | }, 30 | "scripts": { 31 | "predeploy": "npm run build", 32 | "deploy": "gh-pages -d build", 33 | "start": "react-scripts start", 34 | "build": "react-scripts build", 35 | "test": "react-scripts test", 36 | "eject": "react-scripts eject" 37 | }, 38 | "eslintConfig": { 39 | "extends": [ 40 | "react-app", 41 | "react-app/jest" 42 | ] 43 | }, 44 | "browserslist": { 45 | "production": [ 46 | ">0.2%", 47 | "not dead", 48 | "not op_mini all" 49 | ], 50 | "development": [ 51 | "last 1 chrome version", 52 | "last 1 firefox version", 53 | "last 1 safari version" 54 | ] 55 | }, 56 | "devDependencies": { 57 | "@iconify/react": "^3.1.4", 58 | "gh-pages": "^3.2.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/UI/input/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useImperativeHandle, useRef, useState } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import classes from "./Input.module.scss"; 4 | 5 | interface Props { 6 | id: string; 7 | type: string; 8 | minLength?: number; 9 | maxLength?: number; 10 | placeholder?: string; 11 | classes?: string; 12 | value?: string; 13 | ref?: HTMLInputElement; 14 | readonly?: boolean; 15 | autocomplete?: string; 16 | } 17 | 18 | interface IImperativeHandler { 19 | focus: () => void; 20 | value?: string; 21 | } 22 | const Input = React.forwardRef((props, ref) => { 23 | const inputRef = useRef(null); 24 | const [value, setValue] = useState(props.value || ""); 25 | 26 | function inputChangeHandler(e: React.FormEvent) { 27 | setValue(e.currentTarget.value); 28 | } 29 | 30 | function inputFocused() { 31 | inputRef.current?.focus(); 32 | inputRef.current?.setAttribute("style", "border:2px solid red"); 33 | } 34 | 35 | useImperativeHandle(ref, () => { 36 | return { 37 | focus: inputFocused, 38 | value: value, 39 | }; 40 | }); 41 | const { t } = useTranslation(); 42 | return ( 43 |
44 | 45 | 57 |
58 | ); 59 | }); 60 | 61 | export default Input; 62 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React Admin Panel 28 | 29 | 30 | 31 |
32 |
33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 3 | 4 | import MainLayout from "./layout/MainLayout"; 5 | import AuthLayout from "./layout/AuthLayout"; 6 | import LoadingSpinner from "./components/UI/loadingSpinner/LoadingSpinner"; 7 | import "./scss/App.scss"; 8 | 9 | const Dashboard = React.lazy(() => import("./pages/Dashboard")); 10 | const Customers = React.lazy(() => import("./pages/Customers")); 11 | const CustomerEdit = React.lazy(() => import("./pages/CustomerEdit")); 12 | const Products = React.lazy(() => import("./pages/Products")); 13 | const ProductEdit = React.lazy(() => import("./pages/ProductEdit")); 14 | const NotFound = React.lazy(() => import("./pages/NotFound")); 15 | const BlankPage = React.lazy(() => import("./pages/BlankPage")); 16 | const Login = React.lazy(() => import("./pages/Login")); 17 | 18 | function App() { 19 | return ( 20 | 21 | }> 22 | 23 | }> 24 | }> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | 35 | 36 | } /> 37 | } /> 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default App; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Admin Panel 2 | 3 | React Admin Panel developed and build with **React (v17.0.2)**, **TypeScript (v4.4.2)** and **React Router v6**. For Styles, is used **SCSS**. 4 | I am excited to share this dashboard with you and look forward to hearing your feedback [**my Linkedin post**](https://www.linkedin.com/posts/zahramirzaei_responsive-multilanguage-multitheme-activity-6920075757318725634-qRks?utm_source=linkedin_share&utm_medium=member_desktop_web) 5 | 6 | # Demo 7 | 8 | Visit: [Project React Admin Panel demo](https://admin-panel-portfolio.netlify.app/) 9 | 10 | ![Figma admin panel sketch](src/assets/images/figma-sketch-v3.png) 11 | 12 | # Features 13 | 14 | * Responsive layout 15 | * Multi language 16 | * Multi theme 17 | * Charts(cahrtjs) 18 | * React Hooks 19 | * React Context 20 | * React Router v6 21 | * SCSS Modular 22 | * iconify 23 | 24 | # Pages 25 | 26 | * Login 27 | * Dashboard 28 | * Products 29 | * Edit Product 30 | * Customers 31 | * Edit Customers 32 | * 404 Error(not found) 33 | 34 | 35 | # Getting Started 36 | 37 | **1. Clone from Github** 38 | 39 | `git clone https://github.com/ZahraMirzaei/admin-panel.git` 40 | 41 | **2. Run `npm install` or `yarn install`** 42 | 43 | This will install both run-time project dependencies and developer tools listed in package.json file. 44 | 45 | **3. Run `npm start` or `yarn start`** 46 | 47 | Runs the app in the development mode. 48 | 49 | Open http://localhost:3000 to view it in the browser. Whenever you modify any of the source files inside the /src folder, the module bundler (Webpack) will recompile the app on the fly and refresh all the connected browsers. 50 | 51 | **4. Run `npm build` or `yarn build`** 52 | 53 | Builds the app for production to the build folder. It correctly bundles React in production mode and optimizes the build for the best performance. 54 | 55 | The build is minified and the filenames include the hashes. Your app is ready to be deployed! 56 | 57 | 58 | # Would you mind support me? 59 | 60 | * Star GitHub repo 61 | * Create pull requests, submit bugs, suggest new features or documentation updates. 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/components/UI/modal/Modal.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDom from "react-dom"; 3 | import { useTranslation } from "react-i18next"; 4 | import classes from "./Modal.module.scss"; 5 | import Card from "../card/Card"; 6 | import Button from "../button/Button"; 7 | 8 | interface IBackdrop { 9 | onConfirm: () => void; 10 | } 11 | const Backdrop: React.FC = (props) => { 12 | return
; 13 | }; 14 | 15 | // interface IModalOverlay { 16 | // title: string; 17 | // message: string; 18 | // } 19 | 20 | interface IModal { 21 | title: string; 22 | message: string; 23 | onConfirm: () => void; 24 | } 25 | 26 | const ModalOverlay: React.FC = (props) => { 27 | const { t } = useTranslation(); 28 | 29 | return ( 30 | 31 |
32 |
33 |

{props.title}

34 |
35 |
36 |

{props.message}

37 |
38 |
39 | 42 | 45 |
46 |
47 |
48 | ); 49 | }; 50 | 51 | const Modal: React.FC = (props) => { 52 | const backdropRoot = document.getElementById("backdrop-root") as HTMLElement; 53 | const modalOverlay = document.getElementById("overlay-root") as HTMLElement; 54 | return ( 55 | <> 56 | {ReactDom.createPortal( 57 | , 58 | backdropRoot 59 | )} 60 | {ReactDom.createPortal( 61 | , 66 | modalOverlay 67 | )} 68 | 69 | ); 70 | }; 71 | 72 | export default Modal; 73 | -------------------------------------------------------------------------------- /src/components/login/Login.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | 3 | html { 4 | background-color: var(--bgc); 5 | } 6 | 7 | .container { 8 | @include flex(); 9 | min-height: 100vh; 10 | background-color: var(--bgc); 11 | .keyPic { 12 | width: 800px; 13 | min-width: 600px; 14 | order: 2; 15 | @media screen and (max-width: 992px) { 16 | display: none; 17 | } 18 | } 19 | } 20 | 21 | .loginBox { 22 | @include flex(); 23 | flex-direction: column; 24 | width: 400px; 25 | padding: 0 2rem 4rem; 26 | background-color: #f9f9fa; 27 | color: #424750; 28 | border-radius: $mediumBorderRadius; 29 | box-shadow: $mainBoxShadow; 30 | 31 | .logo { 32 | margin-top: -2rem; 33 | width: 200px; 34 | } 35 | 36 | .title { 37 | font-size: $fontSizeHeadlineMedium; 38 | line-height: 50px; 39 | letter-spacing: 0; 40 | font-weight: 400; 41 | margin: -2rem 0 4rem; 42 | } 43 | 44 | & input, 45 | & label, 46 | & a { 47 | background-color: #f9f9fa !important; 48 | color: #424750; 49 | } 50 | 51 | .errorMessage { 52 | position: relative; 53 | font-size: 14px; 54 | color: red; 55 | top: -25px; 56 | display: none; 57 | opacity: 0; 58 | transition: opacity 0.4s ease-in-out; 59 | } 60 | 61 | button { 62 | display: block; 63 | width: 100%; 64 | margin-left: 0; 65 | margin-right: 0; 66 | } 67 | 68 | .forgat_pass { 69 | margin: 1rem 0; 70 | display: inline-block; 71 | color: gray; 72 | font-size: 14px; 73 | } 74 | 75 | .checkbox { 76 | @include flex(center, flex-start); 77 | 78 | input { 79 | display: inline-block; 80 | width: 20px; 81 | height: 20px; 82 | padding: 6px; 83 | border-radius: 8px; 84 | } 85 | 86 | label { 87 | margin: 0 0.5rem; 88 | } 89 | } 90 | } 91 | 92 | .rtl { 93 | font-family: iranyekan, "IRANSans", "Tahoma"; 94 | direction: rtl; 95 | text-align: right; 96 | 97 | label { 98 | left: auto; 99 | right: 10px; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/topnav/rightBox/langBox/LangBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useState, 3 | useEffect, 4 | useRef, 5 | useCallback, 6 | useContext, 7 | } from "react"; 8 | import { Icon } from "@iconify/react"; 9 | import { useOnClickOutside } from "usehooks-ts"; 10 | import LangContext from "../../../../store/langContext"; 11 | 12 | import classes from "./LangBox.module.scss"; 13 | 14 | function LangBox() { 15 | const [showLangBox, setShowLangBox] = useState(false); 16 | const langBoxRef = useRef(null); 17 | const langCtx = useContext(LangContext); 18 | const lang = langCtx.lang; 19 | 20 | const showBoxHandler = function toDo() { 21 | setShowLangBox((prev) => !prev); 22 | }; 23 | useEffect(() => { 24 | document.documentElement.dir = lang === "en" ? "ltr" : "rtl"; 25 | document.documentElement.lang = lang === "en" ? "en" : "fa"; 26 | }, [lang]); 27 | const checkIfClickedOutside = useCallback(() => { 28 | // If the menu is open and the clicked target is not within the menu, 29 | // then close the menu 30 | if (showLangBox && langBoxRef.current) { 31 | setShowLangBox(false); 32 | } 33 | }, [showLangBox]); 34 | 35 | //custom hook - when click outside of langbox, it will close. 36 | useOnClickOutside(langBoxRef, checkIfClickedOutside); 37 | 38 | return ( 39 |
40 |
41 | 42 | 43 |
{lang}
44 | 45 | 46 |
47 |
50 |
{ 52 | langCtx.toggleLanguage("en"); 53 | showBoxHandler(); 54 | }} 55 | > 56 | English (en) 57 |
58 |
{ 60 | langCtx.toggleLanguage("fa"); 61 | showBoxHandler(); 62 | }} 63 | > 64 | Farsi (fa) 65 |
66 |
67 |
68 | ); 69 | } 70 | 71 | export default LangBox; 72 | -------------------------------------------------------------------------------- /src/assets/images/digikala.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/hook/useFetch.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer, useRef } from "react"; 2 | 3 | interface State { 4 | data?: T; 5 | error?: Error; 6 | status?: string; 7 | } 8 | 9 | type Cache = { [url: string]: T }; 10 | 11 | type Action = 12 | | { type: "loading" } 13 | | { type: "fetched"; payload: T } 14 | | { type: "error"; payload: Error }; 15 | 16 | function useFetch(url?: string, options?: RequestInit): State { 17 | const cache = useRef>({}); 18 | 19 | const cancelRequest = useRef(false); 20 | 21 | const initialState: State = { 22 | error: undefined, 23 | data: undefined, 24 | status: "", 25 | }; 26 | 27 | const fetchReducer = (state: State, action: Action): State => { 28 | switch (action.type) { 29 | case "loading": 30 | return { ...initialState, status: action.type }; 31 | case "fetched": 32 | return { ...initialState, data: action.payload, status: action.type }; 33 | case "error": 34 | return { ...initialState, error: action.payload, status: action.type }; 35 | default: 36 | return state; 37 | } 38 | }; 39 | 40 | const [state, dispatch] = useReducer(fetchReducer, initialState); 41 | useEffect(() => { 42 | if (!url) return; 43 | 44 | const fetchData = async () => { 45 | dispatch({ type: "loading" }); 46 | 47 | if (cache.current[url]) { 48 | dispatch({ type: "fetched", payload: cache.current[url] }); 49 | return; 50 | } 51 | 52 | try { 53 | const response = await fetch(url, options); 54 | if (!response.ok) { 55 | throw new Error(response.statusText); 56 | } 57 | if (response.ok && response.status !== 200) { 58 | throw new Error("302 error happen. Maybe you forgat .json"); 59 | } 60 | 61 | const data = (await response.json()) as T; 62 | cache.current[url] = data; 63 | if (cancelRequest.current) return; 64 | 65 | dispatch({ type: "fetched", payload: data }); 66 | } catch (error) { 67 | if (cancelRequest.current) return; 68 | 69 | dispatch({ type: "error", payload: error as Error }); 70 | } 71 | }; 72 | 73 | void fetchData(); 74 | 75 | return () => { 76 | cancelRequest.current = true; 77 | }; 78 | // eslint-disable-next-line react-hooks/exhaustive-deps 79 | }, [url]); 80 | 81 | return state; 82 | } 83 | 84 | export default useFetch; 85 | -------------------------------------------------------------------------------- /src/components/chart/Chart.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | 3 | .chart { 4 | margin-bottom: 2rem; 5 | &__wrapper { 6 | display: flex; 7 | overflow: auto; 8 | width: 100%; 9 | min-width: 500px; 10 | } 11 | } 12 | 13 | .charts__container { 14 | @include flex(stretch, center); 15 | flex-wrap: wrap; 16 | 17 | & > div:nth-child(2) { 18 | flex: 4 1 800px; 19 | align-items: center; 20 | margin-left: 1rem; 21 | margin-right: 0; 22 | 23 | @media screen and (max-width: 1610px) { 24 | margin-left: 0; 25 | } 26 | } 27 | } 28 | 29 | .charts__wrapper { 30 | @include flex(); 31 | flex-direction: column; 32 | min-width: 400px; 33 | flex: 3 1 500px; 34 | 35 | @media screen and (max-width: 1625px) { 36 | flex-direction: row; 37 | flex-wrap: wrap; 38 | margin-bottom: 0.5rem; 39 | 40 | & div:first-child { 41 | margin-right: 1rem; 42 | @media screen and (max-width: 1110px) { 43 | margin-right: 0; 44 | } 45 | } 46 | } 47 | 48 | @include mobile { 49 | min-width: 250px; 50 | } 51 | @media screen and (max-width: 310px) { 52 | min-width: 150px; 53 | } 54 | 55 | & > div { 56 | width: 100%; 57 | min-width: 400px; 58 | margin: 0.5rem 1rem 0.5rem 0; 59 | padding-left: 1rem; 60 | padding-right: 1rem; 61 | @media screen and (max-width: 1625px) { 62 | margin: 0.5rem 0 0.5rem 0; 63 | } 64 | 65 | @include mobile { 66 | min-width: 270px; 67 | margin: 0.5rem 0 !important; 68 | } 69 | 70 | @media screen and (max-width: 310px) { 71 | min-width: 150px; 72 | } 73 | } 74 | } 75 | 76 | html[dir="rtl"] { 77 | .charts__container { 78 | & > div:nth-child(2) { 79 | margin-left: 0; 80 | margin-right: 1rem; 81 | @media screen and (max-width: 1610px) { 82 | margin-right: 0; 83 | } 84 | } 85 | } 86 | 87 | .charts__wrapper { 88 | & div:first-child { 89 | margin-left: 1rem; 90 | @media screen and (max-width: 1112px) { 91 | margin-left: 0; 92 | } 93 | } 94 | & > div { 95 | margin: 0.5rem 0 0.5rem 1rem; 96 | 97 | @media screen and (max-width: 1625px) { 98 | margin: 0.5rem 0 0.5rem 0; 99 | } 100 | 101 | @include mobile { 102 | margin: 0.5rem 0 !important; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/components/chart/Chart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { faker } from "@faker-js/faker"; 3 | 4 | import { useTranslation } from "react-i18next"; 5 | import LineChart from "./LineChart"; 6 | import BarChart from "./BarChart"; 7 | import classes from "./Chart.module.scss"; 8 | import data from "../../constants/data"; 9 | import Card from "../UI/card/Card"; 10 | 11 | const SaleChart = () => { 12 | const { t } = useTranslation(); 13 | const labels = data.revenueByMonths.labels.map((month) => t(month)); 14 | const [userData] = useState({ 15 | labels, 16 | datasets: [ 17 | { 18 | label: t("summaryOfSale"), 19 | data: data.revenueByMonths.data, 20 | borderColor: "#ee384e", 21 | backgroundColor: "#3c4b6d", 22 | }, 23 | ], 24 | }); 25 | 26 | const [orderData] = useState({ 27 | labels, 28 | datasets: [ 29 | { 30 | label: t("summaryOfOrders"), 31 | data: labels.map(() => faker.datatype.number({ min: 0, max: 1000 })), 32 | backgroundColor: "rgba(255, 99, 132, 0.5)", 33 | }, 34 | ], 35 | }); 36 | 37 | const [revenueData] = useState({ 38 | labels, 39 | datasets: [ 40 | { 41 | label: t("summaryOfRevenue"), 42 | data: labels.map(() => faker.datatype.number({ min: 0, max: 1000 })), 43 | backgroundColor: "rgba(255, 99, 132, 0.5)", 44 | }, 45 | ], 46 | }); 47 | 48 | return ( 49 |
50 |

{t("quickAnalysis")}

51 |
52 |
53 | 54 |
55 | 59 |
60 |
61 | 62 |
63 | 67 |
68 |
69 |
70 | 71 |
72 | 73 |
74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default SaleChart; 81 | -------------------------------------------------------------------------------- /src/constants/data.ts: -------------------------------------------------------------------------------- 1 | import images from "./images"; 2 | 3 | const data = { 4 | user: { 5 | name: "Zahra Mirzaei", 6 | img: images.avt, 7 | }, 8 | summary: [ 9 | { 10 | title: "Sales", 11 | value: "$5.340", 12 | icon: "akar-icons:shopping-bag", 13 | }, 14 | { 15 | title: "Orders", 16 | value: "1760", 17 | icon: "icon-park-outline:transaction-order", //width:48px; 18 | }, 19 | { 20 | title: "Revenue", 21 | value: "$2.530", 22 | icon: "bi:coin", 23 | }, 24 | ], 25 | revenueByMonths: { 26 | labels: [ 27 | "Jan", 28 | "Feb", 29 | "Mar", 30 | "Apr", 31 | "May", 32 | "Jun", 33 | "July", 34 | "Aug", 35 | "Sep", 36 | "Oct", 37 | "Nov", 38 | "Dec", 39 | ], 40 | data: [250, 200, 300, 280, 100, 220, 310, 190, 200, 120, 250, 350], 41 | }, 42 | topCustomers: { 43 | head: ["customer", "totalOrders", "totalSpending"], 44 | body: [ 45 | { 46 | username: "john doe", 47 | order: 490, 48 | price: "$15,870", 49 | }, 50 | { 51 | username: "frank iva", 52 | order: 250, 53 | price: "$12,251", 54 | }, 55 | { 56 | username: "anthony baker", 57 | order: 120, 58 | price: "$10,840", 59 | }, 60 | { 61 | username: "frank iva", 62 | order: 110, 63 | price: "$9,251", 64 | }, 65 | { 66 | username: "anthony baker", 67 | order: 80, 68 | price: "$8,840", 69 | }, 70 | ], 71 | }, 72 | latestOrders: { 73 | header: ["orderID", "customer", "totalPrice", "date", "status"], 74 | body: [ 75 | { 76 | orderId: "#OD1711", 77 | customer: "john doe", 78 | totalPrice: "$900", 79 | date: "17 Jun 2022", 80 | status: "approved", 81 | }, 82 | { 83 | orderId: "#OD1712", 84 | customer: "frank iva", 85 | totalPrice: "$400", 86 | date: "1 Jun 2022", 87 | status: "pending", 88 | }, 89 | { 90 | orderId: "#OD1713", 91 | customer: "anthony baker", 92 | totalPrice: "$200", 93 | date: "27 Jun 2021", 94 | status: "approved", 95 | }, 96 | { 97 | orderId: "#OD1712", 98 | customer: "frank iva", 99 | totalPrice: "$400", 100 | date: "1 Jun 2022", 101 | status: "rejected", 102 | }, 103 | { 104 | orderId: "#OD1713", 105 | customer: "anthony baker", 106 | totalPrice: "$200", 107 | date: "27 Jun 2022", 108 | status: "approved", 109 | }, 110 | ], 111 | }, 112 | }; 113 | 114 | export default data; 115 | -------------------------------------------------------------------------------- /src/components/login/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef } from "react"; 2 | 3 | import LoginContext from "../../store/loginContext"; 4 | import langContextObj from "../../store/langContext"; 5 | import { images } from "../../constants"; 6 | import Input from "../UI/input/Input"; 7 | import Button from "../UI/button/Button"; 8 | import { useTranslation } from "react-i18next"; 9 | import classes from "./Login.module.scss"; 10 | import { Link, useNavigate } from "react-router-dom"; 11 | 12 | function LoginBox() { 13 | const loginCtx = useContext(LoginContext); 14 | const langCtx = useContext(langContextObj); 15 | const userNameRef = useRef(null); 16 | const errorMessageRef = useRef(null); 17 | const navigate = useNavigate(); 18 | const { t } = useTranslation(); 19 | 20 | let isValid = true; 21 | function loginHandler(e: React.FormEvent) { 22 | e.preventDefault(); 23 | isValid = userNameRef.current?.value === "admin"; 24 | if (userNameRef.current) { 25 | if (isValid) { 26 | loginCtx.toggleLogin(); 27 | navigate("/"); 28 | } else { 29 | userNameRef.current.focus(); 30 | errorMessageRef.current?.setAttribute( 31 | "style", 32 | "display: inline-block;opacity: 1" 33 | ); 34 | } 35 | } 36 | } 37 | 38 | return ( 39 |
44 |
45 |
46 | digikala 47 |
48 |

{t("loginPage")}

49 |
50 | 56 | 57 | {t("errorMessage")} 58 | 59 | 65 | 66 | 67 | {t("forgetPass")} 68 | 69 |
70 | 71 | 72 |
73 |
74 |
75 | 76 |
77 | illustrator key 81 |
82 |
83 | ); 84 | } 85 | 86 | export default LoginBox; 87 | -------------------------------------------------------------------------------- /src/components/sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import { Link, useLocation } from "react-router-dom"; 3 | import { useWindowSize } from "usehooks-ts"; 4 | import { useTranslation } from "react-i18next"; 5 | import { images } from "../../constants"; 6 | import sidebarNav from "../../config/sidebarNav"; 7 | import SidebarContext from "../../store/sidebarContext"; 8 | import LoginContext from "../../store/loginContext"; 9 | import { Icon } from "@iconify/react"; 10 | import classes from "./Sidebar.module.scss"; 11 | 12 | function Sidebar() { 13 | const [activeIndex, setActiveIndex] = useState(0); 14 | const { width } = useWindowSize(); 15 | const location = useLocation(); 16 | const sidebarCtx = useContext(SidebarContext); 17 | const loginCtx = useContext(LoginContext); 18 | const { t } = useTranslation(); 19 | 20 | function openSidebarHandler() { 21 | //for width>768(tablet size) if sidebar was open in width<768 was opened too. 22 | //just in case of tablet size and smaller then, sidebar__open can added. 23 | if (width <= 768) document.body.classList.toggle("sidebar__open"); 24 | } 25 | 26 | function logoutHandler() { 27 | openSidebarHandler(); 28 | loginCtx.toggleLogin(); 29 | } 30 | 31 | useEffect(() => { 32 | const curPath = window.location.pathname.split("/")[1]; 33 | const activeItem = sidebarNav.findIndex((item) => item.section === curPath); 34 | 35 | setActiveIndex(curPath.length === 0 ? 0 : activeItem); 36 | }, [location]); 37 | 38 | return ( 39 |
44 |
45 | digikala 46 |
47 |
48 | {sidebarNav.map((nav, index) => ( 49 | 57 |
58 | 59 |
60 |
61 | {t(nav.section)} 62 |
63 | 64 | ))} 65 |
66 | 67 |
68 | 73 |
74 | 75 |
76 |
{t("logout")}
77 | 78 |
79 |
80 | ); 81 | } 82 | 83 | export default Sidebar; 84 | -------------------------------------------------------------------------------- /src/pages/Products.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import useFetch from "../hook/useFetch"; 4 | import CustomTable from "../components/tables/customTable/CustomTable"; 5 | import { IProductsTable } from "../interfaces/Itable"; 6 | import { products, productsHeader } from "../constants/tables"; 7 | import LoadingSpinner from "../components/UI/loadingSpinner/LoadingSpinner"; 8 | import Dropdown from "../components/UI/dropdown/Dropdown"; 9 | 10 | const url = 11 | "https://admin-panel-79c71-default-rtdb.europe-west1.firebasedatabase.app/products.json"; 12 | 13 | const dropdownOptions = [ 14 | { label: "all", value: "all" }, 15 | { label: "digital", value: "digital" }, 16 | { label: "clothing", value: "clothing" }, 17 | { label: "beauty", value: "beauty" }, 18 | ]; 19 | function Products() { 20 | const { t } = useTranslation(); 21 | const [selected, setSelected] = useState(dropdownOptions[0].value); 22 | const { data, error, status } = useFetch(url); 23 | let productsTable; 24 | let tableData: IProductsTable[] | undefined; 25 | 26 | function selectedChangeHandler(e: React.ChangeEvent) { 27 | setSelected(() => e.target.value); 28 | } 29 | 30 | if (status === "loading") { 31 | productsTable = ; 32 | } 33 | 34 | if (error) { 35 | //if fetch has error: 36 | //select data from local file ("../constants/tables.ts") 37 | switch (selected) { 38 | case "digital": 39 | tableData = products?.filter((item) => item.category === selected); 40 | break; 41 | case "clothing": 42 | tableData = products?.filter((item) => item.category === selected); 43 | break; 44 | case "beauty": 45 | tableData = products?.filter((item) => item.category === selected); 46 | break; 47 | default: 48 | tableData = products; 49 | } 50 | 51 | productsTable = ( 52 | 53 | ); 54 | } 55 | 56 | if (status === "fetched" && data) { 57 | switch (selected) { 58 | case "digital": 59 | tableData = data?.filter((item) => item.category === selected); 60 | break; 61 | case "clothing": 62 | tableData = data?.filter((item) => item.category === selected); 63 | break; 64 | case "beauty": 65 | tableData = data?.filter((item) => item.category === selected); 66 | break; 67 | default: 68 | tableData = data; 69 | } 70 | 71 | productsTable = ( 72 | 78 | ); 79 | } 80 | 81 | return ( 82 |
83 |

{t("products")}

84 | {/* */} 88 | {productsTable} 89 |
90 | ); 91 | } 92 | 93 | export default Products; 94 | -------------------------------------------------------------------------------- /src/components/sidebar/Sidebar.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../scss/abstracts/" as *; 2 | @use "sass:math"; 3 | 4 | .sidebar { 5 | @include flex(stretch, center); 6 | flex-direction: column; 7 | background-color: var(--bgc); 8 | color: var(--text-color) !important; 9 | // border: 1px solid blue; 10 | width: $sideBarOpenWidth; 11 | height: 100vh; 12 | position: fixed; 13 | top: 0; 14 | transition: all 0.5s ease-in-out; 15 | z-index: 200; 16 | overflow-y: auto; 17 | overflow-x: hidden; 18 | 19 | &.sidebar_close { 20 | width: 50px; 21 | @include tablet { 22 | width: 100%; 23 | } 24 | } 25 | 26 | @include tablet { 27 | width: 100vw; 28 | z-index: 1; 29 | background-color: var(--secondaryColor); 30 | color: #ebebeb !important; 31 | } 32 | 33 | &__logo { 34 | // @include flex(); 35 | // width: $sideBarLogoHeight; 36 | text-align: center; 37 | padding: 0 12px 2rem; 38 | 39 | img { 40 | --size: 7.5rem; 41 | width: var(--size); 42 | height: var(--size); 43 | } 44 | 45 | @include tablet { 46 | padding-top: 1rem; 47 | } 48 | @include mobile { 49 | padding-top: 0.5rem; 50 | } 51 | } 52 | 53 | &__menu { 54 | @include flex(stretch, flex-start); 55 | flex-direction: column; 56 | flex-grow: 1; 57 | 58 | &__item { 59 | @include flex(center, flex-start); 60 | position: relative; 61 | margin-bottom: 2rem; 62 | padding-left: 16px; 63 | transition: all 0.3s ease-in-out; 64 | 65 | &:hover { 66 | color: var(--primaryColor); 67 | } 68 | 69 | &.active { 70 | font-weight: 700; 71 | color: var(--primaryColor); 72 | padding-left: 20px; 73 | 74 | &::before { 75 | content: ""; 76 | width: 4px; 77 | height: 100%; 78 | background-color: var(--primaryColor); 79 | border-top-right-radius: 3px; 80 | border-bottom-right-radius: 3px; 81 | position: absolute; 82 | left: 0; 83 | top: 0; 84 | } 85 | } 86 | 87 | &__icon { 88 | @include flex(center, center); 89 | margin-right: 1rem; 90 | 91 | svg { 92 | width: $smallIconSize; 93 | height: $smallIconSize; 94 | } 95 | } 96 | &__txt { 97 | white-space: nowrap; 98 | } 99 | } 100 | } 101 | } 102 | 103 | .logout { 104 | align-self: flex-end; 105 | } 106 | 107 | html[dir="rtl"] { 108 | .sidebar { 109 | &__menu { 110 | &__item { 111 | padding-left: 0; 112 | // padding-right: 16px; 113 | // font-weight: 500; 114 | font-size: 18px; 115 | // margin-bottom: 1.5rem; 116 | &.active { 117 | padding-right: 4px; 118 | font-weight: 700; 119 | &::before { 120 | border-top-right-radius: 0; 121 | border-bottom-right-radius: 0; 122 | border-top-left-radius: 3px; 123 | border-bottom-left-radius: 3px; 124 | right: 0; 125 | } 126 | } 127 | 128 | &__icon { 129 | margin-left: 1rem; 130 | } 131 | } 132 | } 133 | &.sidebar_close .sidebar__logo { 134 | direction: ltr; 135 | width: 46px; 136 | margin-right: 10px; 137 | overflow: hidden; 138 | @include tablet { 139 | direction: rtl; 140 | width: auto; 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/components/tables/customTable/CustomTable.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .container { 4 | padding: 0 1rem 2rem; 5 | } 6 | 7 | .wrapper { 8 | @include flex(); 9 | flex-direction: column; 10 | flex: 1; 11 | } 12 | 13 | .table__wrapper { 14 | @include flex(center, flex-start); 15 | width: 100%; 16 | min-width: 400px; 17 | flex: 1; 18 | margin: 0; 19 | } 20 | 21 | .table { 22 | width: 100%; 23 | min-width: 300px; 24 | border-spacing: 0; 25 | } 26 | 27 | .largeTable { 28 | border-spacing: 0; 29 | min-width: 900px; 30 | width: 100%; 31 | } 32 | 33 | th { 34 | background-color: var(--primaryColor); 35 | color: var(--card-bgc); 36 | text-align: left; 37 | white-space: wrap; 38 | &:first-child { 39 | border-top-left-radius: $largeBorderRadius; 40 | } 41 | &:last-child { 42 | border-top-right-radius: $largeBorderRadius; 43 | } 44 | } 45 | 46 | tr:nth-child(odd) { 47 | background-color: var(--card-bgc); 48 | } 49 | 50 | th, 51 | td { 52 | padding: 0.8rem 1rem; 53 | white-space: nowrap; 54 | } 55 | 56 | .userName, 57 | .product_name { 58 | @include flex(center, flex-start); 59 | } 60 | 61 | .avatar, 62 | .product_img { 63 | width: 30px; 64 | height: 30px; 65 | object-fit: cover; 66 | border-radius: 50%; 67 | display: inline-block; 68 | margin: 0 0.5rem; 69 | } 70 | 71 | .actions { 72 | width: 20px; 73 | text-align: center; 74 | position: relative; 75 | &__box { 76 | // display: none; 77 | @include flex(center, space-between); 78 | position: absolute; 79 | top: 20%; 80 | right: 53%; 81 | background-color: var(--bgc); 82 | box-shadow: $mainBoxShadow; 83 | border-radius: $smallBorderRadius; 84 | width: 100px; 85 | padding: 0.4rem 1.2rem 0; 86 | transform: scaleX(0); 87 | transform-origin: right; 88 | transition: all 0.3s ease-in-out; 89 | 90 | & svg { 91 | transition: all 0.1s ease-in-out; 92 | 93 | &:hover, 94 | &:active, 95 | &:visited { 96 | -webkit-filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3)); 97 | filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3)); 98 | } 99 | } 100 | } 101 | 102 | &:hover &__box, 103 | &:active &__box { 104 | transform: scaleX(1); 105 | } 106 | 107 | &__edit { 108 | color: var(--approved-textColor); 109 | cursor: pointer; 110 | } 111 | 112 | &__delete { 113 | color: var(--rejected-textColor); 114 | cursor: pointer; 115 | } 116 | } 117 | 118 | .table__pagination { 119 | @include flex(center, flex-end); 120 | width: 100%; 121 | margin-top: 20px; 122 | 123 | &_item ~ &_item { 124 | margin-left: 10px; 125 | } 126 | 127 | &_item { 128 | @include flex(); 129 | width: 30px; 130 | height: 30px; 131 | border-radius: 50%; 132 | cursor: pointer; 133 | transition: all 0.3s ease-in-out; 134 | } 135 | 136 | &_item.active, 137 | &_item.active:hover { 138 | background-color: var(--primaryColor); 139 | color: #ebebeb; 140 | font-weight: 600; 141 | } 142 | 143 | &_item:hover { 144 | color: var(--primaryColor); 145 | font-weight: 600; 146 | } 147 | } 148 | 149 | html[dir="rtl"] { 150 | th { 151 | text-align: right; 152 | &:first-child { 153 | border-top-right-radius: $largeBorderRadius; 154 | border-top-left-radius: 0; 155 | } 156 | &:last-child { 157 | border-top-left-radius: $largeBorderRadius; 158 | border-top-right-radius: 0; 159 | } 160 | } 161 | .actions__box { 162 | right: auto; 163 | left: 53%; 164 | transform-origin: left; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/components/edit/editCustomer/EditCustomer.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .edit__container { 4 | @include flex(); 5 | flex-wrap: wrap; 6 | } 7 | 8 | .edit__left, 9 | .edit__right { 10 | flex: 1; 11 | min-width: 400px; 12 | max-width: 600px; 13 | margin: 0 1rem; 14 | @media screen and (max-width: 1210px) { 15 | margin-top: 2rem; 16 | } 17 | @media screen and (max-width: 410px) { 18 | min-width: 350px; 19 | } 20 | @media screen and (max-width: 290px) { 21 | min-width: 250px; 22 | } 23 | } 24 | 25 | .edit__right { 26 | position: relative; 27 | 28 | .img_wrapper { 29 | position: absolute; 30 | top: 1rem; 31 | right: 1rem; 32 | 33 | @media screen and (max-width: 410px) { 34 | top: 5rem; 35 | right: auto; 36 | left: 0; 37 | } 38 | } 39 | 40 | .upload_icon { 41 | @include flex(); 42 | position: absolute; 43 | top: 0; 44 | bottom: 0; 45 | left: 0; 46 | right: 0; 47 | font-size: 4rem; 48 | color: #ebebeb; 49 | background-color: rgba(0, 0, 0, 0.4); 50 | opacity: 0; 51 | transition: opacity 0.3s ease-in-out; 52 | @include mobile { 53 | opacity: 1; 54 | } 55 | svg { 56 | cursor: pointer; 57 | } 58 | } 59 | 60 | .img_wrapper:hover .upload_icon, 61 | .img_wrapper:active .upload_icon { 62 | opacity: 1; 63 | } 64 | 65 | .file_input_control { 66 | .file_input { 67 | position: absolute; 68 | top: 35%; 69 | opacity: 0; 70 | padding: 2rem 0; 71 | } 72 | } 73 | } 74 | 75 | .img_wrapper { 76 | @include flex(stretch, center); 77 | align-self: center; 78 | margin-bottom: 2rem; 79 | width: 200px; 80 | height: 200px; 81 | border-radius: 50%; 82 | overflow: hidden; 83 | box-shadow: $mainBoxShadow; 84 | 85 | @media screen and (max-width: 410px) { 86 | width: 150px; 87 | height: 150px; 88 | align-self: flex-start; 89 | } 90 | } 91 | 92 | .avatar { 93 | object-fit: cover; 94 | } 95 | 96 | .account { 97 | @include flex(baseline, center); 98 | flex-direction: column; 99 | flex: 1; 100 | margin: 3rem; 101 | @media screen and (max-width: 1250px) { 102 | margin: 3rem 0.5rem; 103 | } 104 | 105 | @media screen and (max-width: 410px) { 106 | position: relative; 107 | } 108 | &__info { 109 | color: lightslategray; 110 | margin-bottom: 2rem; 111 | 112 | & > div { 113 | @include flex(center, flex-start); 114 | color: var(--text-color); 115 | margin: 0.5rem 1rem; 116 | 117 | & > div { 118 | margin: 0.5rem 1rem; 119 | } 120 | } 121 | } 122 | 123 | &__contact__phone div { 124 | direction: ltr; 125 | } 126 | 127 | &__contact__email svg { 128 | margin: 0 0.2rem; 129 | } 130 | 131 | .subTitle { 132 | @include flex(); 133 | font-size: 36px; 134 | font-weight: 400; 135 | margin-top: 2rem; 136 | margin-bottom: 7rem; 137 | 138 | @media screen and (max-width: 410px) { 139 | margin-bottom: 14rem; 140 | } 141 | } 142 | } 143 | 144 | .btn__wrapper { 145 | @include flex(center, space-between); 146 | flex-wrap: wrap; 147 | 148 | & a { 149 | flex: 1; 150 | margin: 0.5rem 0; 151 | } 152 | & button { 153 | width: 90%; 154 | 155 | @include tablet { 156 | margin: 0; 157 | } 158 | } 159 | } 160 | 161 | html[dir="rtl"] { 162 | input[type="tel"] { 163 | text-align: right; 164 | direction: ltr; 165 | } 166 | 167 | .edit__right { 168 | .img_wrapper { 169 | right: auto; 170 | left: 1rem; 171 | 172 | @media screen and (max-width: 410px) { 173 | right: 0; 174 | left: auto; 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/assets/fonts/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(fontiran.css); /* لینک فایلی که وظیفه بارگذاری فونت ها را برعهده دارد */ 2 | body { 3 | font-family: iranyekan !important; 4 | direction: rtl; 5 | background-color: #cdcdcd; 6 | margin: 0; 7 | } 8 | h1, h2, h3, h4, h5, h6,input, textarea { 9 | font-family: iranyekan !important; 10 | } 11 | h1 { 12 | font-weight: bold; 13 | } 14 | .wrapper { 15 | max-width: 900px; 16 | margin: 0 auto; 17 | } 18 | .ltr { 19 | direction: ltr; 20 | } 21 | .text-right { 22 | text-align: right; 23 | } 24 | .text-center { 25 | text-align: center; 26 | } 27 | .text-left { 28 | text-align: left; 29 | } 30 | .text-small { 31 | font-size: 0.8em; 32 | } 33 | .text-xsmall { 34 | font-size: 0.6em; 35 | } 36 | .text-large { 37 | font-size: 1.2em; 38 | } 39 | .text-xlarge { 40 | font-size: 1.4em; 41 | } 42 | .text-underline { 43 | text-decoration:underline; 44 | } 45 | .text-thin { 46 | font-weight: 100; 47 | } 48 | .text-light { 49 | font-weight: 300; 50 | } 51 | .text-regular { 52 | font-weight: normal; 53 | } 54 | .text-medium { 55 | font-weight: 500; 56 | } 57 | .text-bold { 58 | font-weight: bold; 59 | } 60 | .text-extrabold { 61 | font-weight: 800; 62 | } 63 | .text-black { 64 | font-weight: 850; 65 | } 66 | .text-extrablack { 67 | font-weight: 900; 68 | } 69 | blockquote { 70 | font-weight: 500; 71 | padding: 10px; 72 | border: 1px dashed #666666; 73 | } 74 | 75 | .mainbox { 76 | width: 100%; 77 | background-color: #EFEFEF; 78 | display: table; 79 | margin-bottom: 30px; 80 | border-right: 8px solid #CCFF33; 81 | } 82 | 83 | .mainboxnegativ { 84 | width: 100%; 85 | background-color: #000000; 86 | display: table; 87 | margin-bottom: 30px; 88 | border-right: 8px solid #CCFF33; 89 | color: #F9F9F9; 90 | } 91 | 92 | .mainbox2 { 93 | font-size: 1em; 94 | width: 90%; 95 | padding-right: 20px; 96 | padding-top: 10px; 97 | padding-bottom: 10px; 98 | } 99 | 100 | .mainbox3 { 101 | width: 100%; 102 | background-color: #DFDFDF; 103 | display: table; 104 | margin-bottom: 30px; 105 | border-right: 8px solid #bd70ff; 106 | } 107 | 108 | .mainbox2negativ { 109 | font-size: 1em; 110 | color: #F9F9F9; 111 | background-color: #000000; 112 | padding-right: 20px; 113 | } 114 | 115 | 116 | .farsiparagraph { 117 | font-size: 1em; 118 | width: 47%; 119 | float:right; 120 | padding-right: 20px; 121 | padding-top: 10px; 122 | padding-bottom: 10px; 123 | 124 | } 125 | 126 | .farsiparagraph_negativ { 127 | font-size: 1em; 128 | color: #F9F9F9; 129 | background-color: #000000; 130 | width: 47%; 131 | float:right; 132 | padding-right: 20px; 133 | padding-top: 10px; 134 | padding-bottom: 10px; 135 | 136 | } 137 | 138 | 139 | .englishparagraph { 140 | font-size: 1em; 141 | width: 47%; 142 | float: left; 143 | direction:ltr; 144 | padding-left: 20px; 145 | padding-top: 10px; 146 | padding-bottom: 10px; 147 | 148 | 149 | } 150 | 151 | .englishparagraph_negativ { 152 | font-size: 1em; 153 | color: #F9F9F9; 154 | background-color: #000000; 155 | width: 47%; 156 | float: left; 157 | direction:ltr; 158 | padding-left: 20px; 159 | padding-top: 10px; 160 | padding-bottom: 10px; 161 | 162 | 163 | } 164 | .rightbox { 165 | width: 60%; 166 | padding-right: 20px; 167 | padding-left: 5px; 168 | float: right; 169 | margin-left: 10px; 170 | margin-bottom: 0px; 171 | min-width: 0px; 172 | background-color: #F7F7F7; 173 | 174 | } 175 | 176 | .titelbox { 177 | width: 60%; 178 | padding-right: 25px; 179 | padding-left: 0px; 180 | float: right; 181 | margin-left: 10px; 182 | margin-bottom: 0px; 183 | min-width: 0px; 184 | background-color: #d5d5d5; 185 | color: #4B4B4B; 186 | } 187 | 188 | 189 | .lefttbox { 190 | 191 | padding-right: 20px; 192 | padding-left: 4px; 193 | float: right; 194 | margin-bottom: 10px; 195 | min-width: 0px; 196 | } 197 | 198 | .alphabet { 199 | width: 35%; 200 | float: left; 201 | font-size: 21em; 202 | text-align: center; 203 | font-weight: 500; 204 | color: #999999; 205 | } 206 | 207 | .alphabet2 { 208 | width: 35%; 209 | float: left; 210 | direction: ltr; 211 | font-size: 1.6em; 212 | text-align: left; 213 | font-weight: 500; 214 | color: #333333; 215 | margin-top: 100px; 216 | } 217 | .footer { 218 | font-weight: 300; 219 | font-size: 0.7em; 220 | text-align: center; 221 | direction: ltr; 222 | margin-bottom: 0px; 223 | padding-bottom: 0px; 224 | } 225 | -------------------------------------------------------------------------------- /src/assets/fonts/css/fontiran.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Name: IRANYekan Font 4 | * Version: 3.0 5 | * Author: Moslem Ebrahimi (moslemebrahimi.com) 6 | * Created on: Dec 20, 2018 7 | * Updated on: Dec 20, 2018 8 | * Website: http://fontiran.com 9 | * Copyright: Commercial/Proprietary Software 10 | -------------------------------------------------------------------------------------- 11 | فونت ایران یکان یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید 12 | -------------------------------------------------------------------------------------- 13 | IRANYekan fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com 14 | -------------------------------------------------------------------------------------- 15 | This set of fonts are used in this project under the license: (.....) 16 | -------------------------------------------------------------------------------------- 17 | * 18 | **/ 19 | @font-face { 20 | font-family: iranyekan; 21 | font-style: normal; 22 | font-weight: bold; 23 | src: url('../fonts/eot/iranyekanwebbold.eot'); 24 | src: url('../fonts/eot/iranyekanwebbold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 25 | url('../fonts/woff/iranyekanwebbold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 26 | url('../fonts/ttf/iranyekanwebbold.ttf') format('truetype'); 27 | } 28 | 29 | @font-face { 30 | font-family: iranyekan; 31 | font-style: normal; 32 | font-weight: 100; 33 | src: url('../fonts/eot/iranyekanwebthin.eot'); 34 | src: url('../fonts/eot/iranyekanwebthin.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 35 | url('../fonts/woff/iranyekanwebthin.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 36 | url('../fonts/ttf/iranyekanwebthin.ttf') format('truetype'); 37 | } 38 | 39 | @font-face { 40 | font-family: iranyekan; 41 | font-style: normal; 42 | font-weight: 300; 43 | src: url('../fonts/eot/iranyekanweblight.eot'); 44 | src: url('../fonts/eot/iranyekanweblight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 45 | url('../fonts/woff/iranyekanweblight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 46 | url('../fonts/ttf/iranyekanweblight.ttf') format('truetype'); 47 | } 48 | 49 | @font-face { 50 | font-family: iranyekan; 51 | font-style: normal; 52 | font-weight: normal; 53 | src: url('../fonts/eot/iranyekanwebregular.eot'); 54 | src: url('../fonts/eot/iranyekanwebregular.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 55 | url('../fonts/woff/iranyekanwebregular.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 56 | url('../fonts/ttf/iranyekanwebregular.ttf') format('truetype'); 57 | } 58 | 59 | @font-face { 60 | font-family: iranyekan; 61 | font-style: normal; 62 | font-weight: 500; 63 | src: url('../fonts/eot/iranyekanwebmedium.eot'); 64 | src: url('../fonts/eot/iranyekanwebmedium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 65 | url('../fonts/woff/iranyekanwebmedium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 66 | url('../fonts/ttf/iranyekanwebmedium.ttf') format('truetype'); 67 | } 68 | 69 | @font-face { 70 | font-family: iranyekan; 71 | font-style: normal; 72 | font-weight: 800; 73 | src: url('../fonts/eot/iranyekanwebextrabold.eot'); 74 | src: url('../fonts/eot/iranyekanwebextrabold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 75 | url('../fonts/woff/iranyekanwebextrabold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 76 | url('../fonts/ttf/iranyekanwebextrabold.ttf') format('truetype'); 77 | } 78 | 79 | @font-face { 80 | font-family: iranyekan; 81 | font-style: normal; 82 | font-weight: 850; 83 | src: url('../fonts/eot/iranyekanwebblack.eot'); 84 | src: url('../fonts/eot/iranyekanwebblack.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 85 | url('../fonts/woff/iranyekanwebblack.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 86 | url('../fonts/ttf/iranyekanwebblack.ttf') format('truetype'); 87 | } 88 | 89 | @font-face { 90 | font-family: iranyekan; 91 | font-style: normal; 92 | font-weight: 900; 93 | src: url('../fonts/eot/iranyekanwebextrablack.eot'); 94 | src: url('../fonts/eot/iranyekanwebextrablack.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ 95 | url('../fonts/woff/iranyekanwebextrablack.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ 96 | url('../fonts/ttf/iranyekanwebextrablack.ttf') format('truetype'); 97 | } -------------------------------------------------------------------------------- /src/components/edit/editProduct/EditProduct.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Card from "../../UI/card/Card"; 3 | import { Link } from "react-router-dom"; 4 | import { useTranslation } from "react-i18next"; 5 | import { IProductsTable as Props } from "../../../interfaces/Itable"; 6 | import classes from "./EditProduct.module.scss"; 7 | import { Icon } from "@iconify/react"; 8 | import Button from "../../UI/button/Button"; 9 | import Input from "../../UI/input/Input"; 10 | const EditProduct: React.FC<{ product?: Props }> = (props) => { 11 | const { t } = useTranslation(); 12 | return ( 13 |
14 |
15 | 16 |
17 | product pic 22 |
23 |
24 |
25 |
{t("proName")}
26 |
27 | {t(`${props.product?.product}`)} 28 |
29 |
30 |
31 |
{t("category")}
32 |
33 | {t(`${props.product?.category}`)} 34 |
35 |
36 |
37 |
{t("price")}
38 |
39 | {t(`${props.product?.price}`)} 40 |
41 |
42 |
43 |
{t("inventoryCount")}
44 |
45 | {t(`${props.product?.inventory}`)} 46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 | 54 |
55 |

56 | 57 | {t("edit")} 58 |

59 |
60 |
61 | 62 |
63 |
64 | 71 |
72 | product pic 77 |
78 |
{ 80 | e.preventDefault(); 81 | }} 82 | > 83 | 88 | 93 | 98 | 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 |
112 |
113 |
114 |
115 |
116 | ); 117 | }; 118 | 119 | export default EditProduct; 120 | -------------------------------------------------------------------------------- /src/components/edit/editCustomer/EditCustomer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Card from "../../UI/card/Card"; 3 | import { Link } from "react-router-dom"; 4 | import { useTranslation } from "react-i18next"; 5 | import classes from "./EditCustomer.module.scss"; 6 | import { IcustomersTable } from "../../../interfaces/Itable"; 7 | import { Icon } from "@iconify/react"; 8 | import Button from "../../UI/button/Button"; 9 | import Input from "../../UI/input/Input"; 10 | 11 | const EditCustomer: React.FC<{ customer?: IcustomersTable }> = (props) => { 12 | const { t } = useTranslation(); 13 | return ( 14 |
15 |
16 | 17 |
18 |
19 | customer avatar 24 |
25 |
26 |

{t("AccountDetails")}

27 |
28 | 29 |
{props.customer?.userName}
30 |
31 |
32 |
33 |

{t("contacts")}

34 |
35 | 36 |
{props.customer?.phoneNumber}
37 |
38 |
39 | 40 |
{props.customer?.email}
41 |
42 |
43 | 44 |
{props.customer?.location}
45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 |

55 | 56 | {t("edit")} 57 |

58 |
59 |
60 | 61 |
62 |
63 | 70 |
71 | customer avatar 76 |
77 |
{ 79 | e.preventDefault(); 80 | }} 81 | > 82 | 87 | 94 | 99 | 105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 | ); 119 | }; 120 | 121 | export default EditCustomer; 122 | -------------------------------------------------------------------------------- /src/components/edit/editProduct/EditProduct.module.scss: -------------------------------------------------------------------------------- 1 | @use "../../../scss/abstracts/" as *; 2 | 3 | .edit__container { 4 | @include flex(); 5 | flex-wrap: wrap; 6 | } 7 | 8 | .edit__left, 9 | .edit__right { 10 | flex: 1; 11 | min-width: 450px; 12 | max-width: 600px; 13 | margin: 1rem; 14 | @media screen and (max-width: 1210px) { 15 | margin-top: 2rem; 16 | } 17 | @media screen and (max-width: 550px) { 18 | min-width: 350px; 19 | } 20 | @media screen and (max-width: 330px) { 21 | min-width: 260px; 22 | } 23 | } 24 | 25 | .img_wrapper { 26 | @include flex(stretch, center); 27 | align-self: center; 28 | width: 200px; 29 | height: 200px; 30 | border-radius: 50%; 31 | overflow: hidden; 32 | box-shadow: $mainBoxShadow; 33 | 34 | @media screen and (max-width: 576px) { 35 | width: 150px; 36 | height: 150px; 37 | } 38 | } 39 | 40 | .edit__left { 41 | & .img_wrapper { 42 | margin-top: 2rem; 43 | margin-right: 1.5rem; 44 | } 45 | & > div:first-child { 46 | flex-direction: column; 47 | } 48 | } 49 | 50 | .edit__right { 51 | position: relative; 52 | // min-width: 450px; 53 | @media screen and (max-width: 410px) { 54 | & > div:first-child { 55 | padding: 1rem 0; 56 | } 57 | } 58 | 59 | .img_wrapper { 60 | position: absolute; 61 | top: -3rem; 62 | right: -3.5rem; 63 | 64 | @media screen and (max-width: 576px) { 65 | right: 0; 66 | top: 0; 67 | } 68 | @media screen and (max-width: 410px) { 69 | top: 6rem; 70 | right: auto; 71 | left: 5rem; 72 | } 73 | } 74 | 75 | .upload_icon { 76 | @include flex(); 77 | position: absolute; 78 | top: 0; 79 | bottom: 0; 80 | left: 0; 81 | right: 0; 82 | font-size: 4rem; 83 | color: #ebebeb; 84 | background-color: rgba(0, 0, 0, 0.4); 85 | opacity: 0; 86 | transition: opacity 0.3s ease-in-out; 87 | svg { 88 | cursor: pointer; 89 | } 90 | 91 | @include tablet { 92 | opacity: 1; 93 | } 94 | } 95 | 96 | .img_wrapper:hover .upload_icon, 97 | .img_wrapper:active .upload_icon { 98 | opacity: 1; 99 | } 100 | 101 | .file_input_control { 102 | .file_input { 103 | position: absolute; 104 | top: 35%; 105 | opacity: 0; 106 | padding: 2rem 0; 107 | } 108 | } 109 | } 110 | 111 | .pic { 112 | object-fit: cover; 113 | } 114 | 115 | .product__edit { 116 | position: relative; 117 | margin: 3rem; 118 | width: 100%; 119 | 120 | @include mobile { 121 | margin: 3rem 0.5rem; 122 | } 123 | } 124 | 125 | .product__info { 126 | @include flex(); 127 | flex-direction: column; 128 | margin-top: 2rem; 129 | margin-bottom: 3rem; 130 | flex: 1; 131 | 132 | & > div { 133 | margin: 1rem; 134 | width: 100%; 135 | & > div { 136 | display: inline-block; 137 | width: 45%; 138 | @media screen and (max-width: 810px) { 139 | width: 100%; 140 | text-align: center; 141 | } 142 | } 143 | } 144 | } 145 | 146 | .title { 147 | color: gray; 148 | text-align: right; 149 | @media screen and (max-width: 810px) { 150 | text-align: center; 151 | white-space: nowrap; 152 | } 153 | } 154 | .value { 155 | margin-left: 2rem; 156 | @media screen and (max-width: 810px) { 157 | margin-left: 0; 158 | margin-top: 0.7rem; 159 | white-space: nowrap; 160 | } 161 | } 162 | 163 | .subTitle { 164 | @include flex(center, flex-start); 165 | font-size: 36px; 166 | font-weight: 400; 167 | margin-top: 2rem; 168 | margin-bottom: 7rem; 169 | 170 | @media screen and (max-width: 410px) { 171 | margin-bottom: 14rem; 172 | } 173 | } 174 | 175 | .btn__wrapper { 176 | @include flex(center, space-between); 177 | flex-wrap: wrap; 178 | 179 | & a { 180 | flex: 1; 181 | margin: 0.5rem 0; 182 | } 183 | & button { 184 | width: 90%; 185 | 186 | @include tablet { 187 | margin: 0; 188 | } 189 | } 190 | } 191 | 192 | html[dir="rtl"] { 193 | .title { 194 | text-align: left; 195 | @media screen and (max-width: 810px) { 196 | text-align: center; 197 | } 198 | } 199 | .value { 200 | margin-left: 0; 201 | margin-right: 2rem; 202 | @media screen and (max-width: 810px) { 203 | margin-right: 0; 204 | } 205 | } 206 | .edit__left { 207 | & .img_wrapper { 208 | margin-left: 1.5rem; 209 | } 210 | } 211 | .edit__right { 212 | .img_wrapper { 213 | right: auto; 214 | left: -3.5rem; 215 | @media screen and (max-width: 576px) { 216 | top: 0; 217 | left: 0; 218 | width: 150px; 219 | height: 150px; 220 | } 221 | @media screen and (max-width: 410px) { 222 | top: 6rem; 223 | right: 6rem; 224 | left: auto; 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/scss/_global.scss: -------------------------------------------------------------------------------- 1 | @use "./abstracts/" as *; 2 | @import url(../assets/fonts/css/fontiran.css); 3 | 4 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;500&display=swap"); 5 | 6 | html { 7 | /* ****Colors**** */ 8 | --primaryColor: #ee384e; 9 | --secondaryColor: #3c4b6d; 10 | //light theme 11 | --bgc: #f2f2f2; 12 | --card-bgc: #f9f9fa; 13 | --text-color: #424750; 14 | //light theme status 15 | --approved-bgc: rgba(86, 240, 0, 0.3); 16 | --approved-textColor: #246500; 17 | --pending-bgc: rgba(45, 204, 255, 0.3); 18 | --pending-textColor: #086785; 19 | --rejected-bgc: rgba(255, 56, 56, 0.3); 20 | --rejected-textColor: #9d0e0e; 21 | 22 | /* ****Font family**** */ 23 | --fontFamily: "Poppins", "Roboto", sans-serif; 24 | } 25 | html[theme="dark"] { 26 | //dark theme 27 | --bgc: #283036; 28 | --card-bgc: #1e2529; 29 | --text-color: #ebebeb; 30 | 31 | //light theme status 32 | --approved-bgc: rgba(66, 171, 8, 0.3); 33 | --approved-textColor: #d0ffb6; 34 | --pending-bgc: rgba(45, 204, 255, 0.3); 35 | --pending-textColor: #a5e9ff; 36 | --rejected-bgc: rgba(255, 56, 56, 0.3); 37 | --rejected-textColor: #f2bebe; 38 | } 39 | 40 | html[dir="rtl"] { 41 | --fontFamily: iranyekan, "IRANSans", "Tahoma"; 42 | } 43 | 44 | //reset 45 | *, 46 | *::before, 47 | *::after { 48 | margin: 0; 49 | padding: 0; 50 | box-sizing: border-box; 51 | } 52 | 53 | img { 54 | display: block; 55 | width: 100%; 56 | } 57 | 58 | ol, 59 | ul { 60 | list-style: none; 61 | } 62 | 63 | a { 64 | text-decoration: none; 65 | color: unset; 66 | } 67 | 68 | input, 69 | select { 70 | font-family: inherit; 71 | color: inherit; 72 | font-size: inherit; 73 | &:focus { 74 | background-color: transparent; 75 | } 76 | } 77 | 78 | input:-webkit-autofill, 79 | input:-webkit-autofill:hover, 80 | input:-webkit-autofill:focus, 81 | textarea:-webkit-autofill, 82 | textarea:-webkit-autofill:hover, 83 | textarea:-webkit-autofill:focus, 84 | select:-webkit-autofill, 85 | select:-webkit-autofill:hover, 86 | select:-webkit-autofill:focus { 87 | // border: 1px solid green; 88 | // -webkit-text-fill-color: green; 89 | -webkit-box-shadow: 0 0 0px 1000px transparent inset; 90 | box-shadow: 0 0 0px 1000px transparent inset; 91 | transition: background-color 5000s ease-in-out 0s; 92 | } 93 | 94 | ::placeholder { 95 | color: #b9b9b9; 96 | } 97 | 98 | input[type=file], /* FF, IE7+, chrome (except button) */ 99 | input[type=file]::-webkit-file-upload-button { 100 | /* chromes and blink button */ 101 | cursor: pointer; 102 | } 103 | 104 | //general styles 105 | body { 106 | background-color: var(--bgc); 107 | color: var(--text-color); 108 | font-family: var(--fontFamily); 109 | font-size: $fontSizeBodyLarge; 110 | line-height: 1.5em; 111 | overflow-x: hidden; 112 | @include tablet { 113 | background-color: var(--secondaryColor); 114 | } 115 | } 116 | 117 | .title { 118 | font-size: $fontSizeHeadlineSmall; 119 | font-weight: 700; 120 | line-height: 2em; 121 | margin-bottom: 2rem; 122 | } 123 | 124 | .subTitle { 125 | font-size: 18px; 126 | margin-bottom: 1rem; 127 | } 128 | 129 | .status { 130 | display: inline-block; 131 | padding: 2px 10px; 132 | text-align: center; 133 | font-size: 10px; 134 | border-radius: $largeBorderRadius; 135 | } 136 | .approved { 137 | background-color: var(--approved-bgc); 138 | color: var(--approved-textColor); 139 | } 140 | 141 | .pending { 142 | background-color: var(--pending-bgc); 143 | color: var(--pending-textColor); 144 | } 145 | 146 | .rejected { 147 | background-color: var(--rejected-bgc); 148 | color: var(--rejected-textColor); 149 | } 150 | 151 | .ltr { 152 | direction: ltr; 153 | text-align: left; 154 | } 155 | 156 | .rtl { 157 | font-family: iranyekan, "IRANSans", "Tahoma"; 158 | direction: rtl; 159 | text-align: right; 160 | } 161 | 162 | .error { 163 | color: var(--rejected-textColor); 164 | } 165 | 166 | @include tablet { 167 | .topNav_left_menu_open { 168 | display: block; 169 | } 170 | 171 | .topNav_left_menu_close { 172 | display: none; 173 | } 174 | } 175 | 176 | @include tablet { 177 | .sidebar__open .main_wrapper { 178 | height: 100vh; 179 | overflow: hidden; 180 | border-top-left-radius: 30px; 181 | border-bottom-left-radius: 30px; 182 | transform: scale(0.9) translateX(45%); 183 | transition: all 0.5s ease-in-out; 184 | box-shadow: $secondBoxShadow; 185 | } 186 | 187 | .sidebar__open .topNav_left_menu_open { 188 | display: none; 189 | } 190 | 191 | .sidebar__open .topNav_left_menu_close { 192 | display: block; 193 | } 194 | } 195 | 196 | @include mobile { 197 | .sidebar__open .main_wrapper { 198 | transform: scale(0.9) translateX(60%); 199 | } 200 | } 201 | 202 | #overlay-root > div { 203 | padding: 0; 204 | } 205 | 206 | .main_wrapper { 207 | transform: scale(1) translateX(0); 208 | // transition: all 0.5s ease-in-out; 209 | } 210 | 211 | //----------- not found Page ----------------- 212 | .notFound__container { 213 | font-family: var(--fontFamily); 214 | @include flex(); 215 | flex-direction: column; 216 | width: 100%; 217 | min-height: 100vh; 218 | @include tablet { 219 | background-color: var(--bgc); 220 | } 221 | } 222 | 223 | .notFound__container_rtl { 224 | font-family: iranyekan, "IRANSans", "Tahoma"; 225 | min-height: 100vh; 226 | @include flex(); 227 | flex-direction: column; 228 | @include tablet { 229 | background-color: var(--bgc); 230 | } 231 | } 232 | 233 | .notFound__title { 234 | font-size: 63px; 235 | margin: 5rem 0 3rem; 236 | font-family: inherit; 237 | line-height: 70px; 238 | &_rtl { 239 | direction: rtl; 240 | } 241 | 242 | @include tablet { 243 | font-size: 36px; 244 | margin: 3rem; 245 | line-height: 33px; 246 | } 247 | @include mobile { 248 | font-size: 24px; 249 | margin: 1rem; 250 | } 251 | } 252 | 253 | .notFound__img { 254 | max-width: 500px; 255 | margin: 0 auto; 256 | } 257 | 258 | //---------- end of not Found page 259 | 260 | html[dir="rtl"] { 261 | @include tablet { 262 | .sidebar__open .main_wrapper { 263 | border-top-right-radius: 30px; 264 | border-bottom-right-radius: 30px; 265 | transform: scale(0.9) translateX(-45%); 266 | } 267 | } 268 | } 269 | 270 | //---------- scrollbar styles 271 | 272 | ::-webkit-scrollbar { 273 | width: 0.5rem; 274 | } 275 | 276 | ::-webkit-scrollbar-track { 277 | background-color: var(--bgc); 278 | } 279 | 280 | ::-webkit-scrollbar-thumb { 281 | background-color: var(--secondaryColor); 282 | border-radius: 100vw; 283 | } 284 | -------------------------------------------------------------------------------- /src/locale.tsx: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | 4 | i18n 5 | .use(initReactI18next) // passes i18n down to react-i18next 6 | .init({ 7 | // the translations 8 | // (tip move them in a JSON file and import them, 9 | // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui) 10 | resources: { 11 | en: { 12 | translation: { 13 | zahraMirzaei: "Zahra Mirzaei", 14 | admin: "admin", 15 | dashboard: "Dashboard", 16 | orders: "Orders", 17 | products: "Products", 18 | customers: "Customers", 19 | analytics: "Analytics", 20 | discount: "Discount", 21 | inventory: "Inventory", 22 | logout: "Logout", 23 | login: "Login", 24 | summary: "Summary", 25 | thisMonthSales: "This month Sales", 26 | thisMonthOrders: "This month Orders", 27 | thisMonthRevenue: "This month Revenue", 28 | quickAnalysis: "Quick Analysis", 29 | topCustomers: "Top Customers", 30 | latestTransaction: "Latest Transactions", 31 | customer: "Customer", 32 | totalSpending: "Total Spending", 33 | totalOrders: "Total Orders", 34 | orderID: "Order ID", 35 | totalPrice: "Total Price", 36 | date: "Date", 37 | status: "Status", 38 | approved: "Approved", 39 | pending: "Pending", 40 | rejected: "Rejected", 41 | viewAll: "View All", 42 | search: "Search", 43 | editCustomer: "Edit Customer", 44 | editProduct: "Edit Product", 45 | AccountDetails: "Account Details", 46 | contacts: "Contacts", 47 | edit: "Edit", 48 | userName: "User Name", 49 | pass: "Password", 50 | phoneNumber: "Phone Number", 51 | email: "Email", 52 | address: "Address", 53 | upload: "Upload", 54 | location: "Location", 55 | deleteCustomer: "Delete Customer", 56 | modalMessage: "Are you sure about delete this?", 57 | delete: "Delete", 58 | cancel: "Cancel", 59 | actions: "Actions", 60 | category: "Category", 61 | all: "All", 62 | clothing: "Clothing", 63 | digital: "Digital", 64 | beauty: "Beauty", 65 | product: "Product", 66 | price: "Price", 67 | proName: "Product Name", 68 | inventoryCount: "Inventory Count", 69 | loginPage: "Login Into Your Account", 70 | errorMessage: "Please enter 'admin' in User Name box", 71 | forgetPass: "Forget your password?", 72 | rememberMe: "Remember me", 73 | salesAmount: "5,340", 74 | orderAmount: "3000", 75 | revenueAmount: "2,500", 76 | currency: "$", 77 | summaryOfSale: "Chart Of Sale", 78 | summaryOfRevenue: "Chart Of Revenue", 79 | summaryOfOrders: "Chart Of Order", 80 | Jan: "Jan", 81 | Feb: "Feb", 82 | Mar: "Mar", 83 | Apr: "Apr", 84 | May: "May", 85 | Jun: "Jun", 86 | July: "July", 87 | Aug: "Aug", 88 | Sep: "Sep", 89 | Oct: "Oct", 90 | Nov: "Nov", 91 | Dec: "Dec", 92 | backToHome: "Back to Main Page", 93 | notFoundMsg: "Page Not Found!", 94 | }, 95 | }, 96 | fa: { 97 | translation: { 98 | zahraMirzaei: "زهرا میرزایی", 99 | admin: "مدیر", 100 | dashboard: "داشبورد", 101 | orders: "سفارشات", 102 | products: "محصولات", 103 | customers: "مشتریان", 104 | analytics: "آنالیزها", 105 | discount: "تخفیفات", 106 | inventory: "موجودی انبار", 107 | logout: "خروج", 108 | login: "ورود", 109 | summary: "خلاصه", 110 | thisMonthSales: "فروش این ماه", 111 | thisMonthOrders: "سفارشات این ماه", 112 | thisMonthRevenue: "درآمد این ماه", 113 | quickAnalysis: "بررسی سریع", 114 | topCustomers: "مشتریان برتر", 115 | latestTransaction: "آخرین تراکنشات", 116 | customer: "مشتری", 117 | totalSpending: "کل خرج‌کرد", 118 | totalOrders: "کل سفارشات", 119 | orderID: "ID سفارش", 120 | totalPrice: "هزینه کل", 121 | date: "تاریخ", 122 | status: "وضعیت", 123 | approved: "تأیید شده", 124 | pending: "در انتظار", 125 | rejected: "رد شده", 126 | viewAll: "دیدن همه", 127 | search: "جستجو", 128 | editCustomer: "ویرایش کاربر", 129 | editProduct: "ویرایش محصول", 130 | AccountDetails: "جزئیات حساب‌کاربری", 131 | contacts: "اطلاعات تماس", 132 | edit: "ویرایش", 133 | userName: "نام کاربری", 134 | pass: "رمز ورود", 135 | phoneNumber: "شماره تماس", 136 | email: "ایمیل", 137 | address: "آدرس", 138 | upload: "بارگزاری", 139 | location: "موقعیت مکان", 140 | actions: "عملیات", 141 | deleteCustomer: "حذف مشتری", 142 | modalMessage: "آیا درباره حذف مطمئن هستید؟", 143 | delete: "حذف", 144 | cancel: "صرف‌نظر", 145 | category: "دسته بندی", 146 | all: "همه", 147 | clothing: "پوشاک", 148 | digital: "دیجیتال", 149 | beauty: "زیبایی", 150 | product: "محصول", 151 | price: "قیمت", 152 | proName: "نام محصول", 153 | inventoryCount: "شمارش موجودی", 154 | loginPage: "به حساب کاربری خود وارد شوید", 155 | errorMessage: "لطفاً عبارت 'admin' را در کادر نام کاربری وارد کنید.", 156 | forgetPass: "رمز عبورتان را فراموش کرده‌اید؟", 157 | rememberMe: "مرا به خاطر بسپار", 158 | salesAmount: "۲۴,۰۰۰,۰۰۰", 159 | orderAmount: "۳۰۰۰", 160 | revenueAmount: "۱۲,۰۰۰,۰۰۰", 161 | currency: "تومان", 162 | summaryOfSale: "نمودار فروش", 163 | summaryOfRevenue: "نمودار سود", 164 | summaryOfOrders: "نمودار سفارش", 165 | Jan: "دی", 166 | Feb: "بهمن", 167 | Mar: "اسفند", 168 | Apr: "فروردین", 169 | May: "اردیبهشت", 170 | Jun: "خرداد", 171 | July: "تیر", 172 | Aug: "مرداد", 173 | Sep: "شهریور", 174 | Oct: "مهر", 175 | Nov: "آبان", 176 | Dec: "آذر", 177 | backToHome: "برگشت به صفحه اصلی", 178 | notFoundMsg: "صفحه مورد نظر یافت نشد!", 179 | }, 180 | }, 181 | }, 182 | lng: "en", // if you're using a language detector, do not define the lng option 183 | fallbackLng: "en", 184 | 185 | interpolation: { 186 | escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape 187 | }, 188 | }); 189 | 190 | export default i18n; 191 | -------------------------------------------------------------------------------- /src/components/tables/customTable/CustomTable.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { Itable as Props, complex } from "../../../interfaces/Itable"; 4 | import Card from "../../UI/card/Card"; 5 | import Badge from "../../UI/badge/Badge"; 6 | import Modal from "../../UI/modal/Modal"; 7 | import { useTranslation } from "react-i18next"; 8 | import { Icon } from "@iconify/react"; 9 | import classes from "./CustomTable.module.scss"; 10 | 11 | const CustomTable: React.FC = (props) => { 12 | const [showModal, setShowModal] = useState(false); 13 | function showModalHandler() { 14 | setShowModal((prev) => !prev); 15 | } 16 | function tableBody(item: complex, index: number) { 17 | /* type guard (in typescript) */ 18 | if ("username" in item) { 19 | //for implementing top customers 20 | return ( 21 | 22 | {item.username} 23 | {item.order} 24 | {item.price} 25 | 26 | ); 27 | } else if ("orderId" in item) { 28 | //for implementing latest transactions 29 | return ( 30 | 31 | {item.orderId} 32 | {item.customer} 33 | {item.totalPrice} 34 | {item.date} 35 | 36 | 37 | 38 | 39 | ); 40 | } else if ("email" in item) { 41 | //for implementing customers table 42 | return ( 43 | 44 | {item.ID} 45 | 46 | user avatar 51 | {item.userName} 52 | 53 | {item.email} 54 | {item.phoneNumber} 55 | {item.totalOrders} 56 | {item.totalSpend} 57 | {item.location} 58 | 59 | 60 |
61 |
65 | 66 |
67 |
68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 | ); 76 | } else if ("category" in item) { 77 | //for implementing products table 78 | return ( 79 | 80 | {item.ID} 81 | 82 | user avatar 87 | {item.product} 88 | 89 | {item.inventory} 90 | {item.price} 91 | {item.category} 92 | 93 | 94 |
95 |
99 | 100 |
101 |
102 | 103 | 104 | 105 |
106 |
107 | 108 | 109 | ); 110 | } 111 | } 112 | 113 | const initDataShow = () => { 114 | return props.limit && props.bodyData 115 | ? props.bodyData.slice(0, Number(props.limit)) 116 | : props.bodyData; 117 | }; 118 | 119 | const [dataShow, setDataShow] = useState(initDataShow); 120 | // const [selectedCategory, setSelectedCategory] = useState( 121 | // props.selectedCategory 122 | // ); 123 | 124 | // if (props.selectedCategory) { 125 | // if (selectedCategory !== props.selectedCategory) 126 | // setDataShow(props.bodyData); 127 | // } 128 | // setSelectedCategory(props.selectedCategory); 129 | 130 | let pages = 1; 131 | 132 | let range: number[] = []; 133 | 134 | if (props.limit !== undefined) { 135 | let page = Math.floor(props.bodyData.length / Number(props.limit)); 136 | pages = props.bodyData.length % Number(props.limit) === 0 ? page : page + 1; 137 | range = [...Array(pages).keys()]; 138 | } 139 | 140 | const [currPage, setCurrPage] = useState(0); 141 | 142 | const selectPage = (page: number) => { 143 | const start = Number(props.limit) * page; 144 | const end = start + Number(props.limit); 145 | 146 | setDataShow(props.bodyData?.slice(start, end)); 147 | 148 | setCurrPage(page); 149 | }; 150 | 151 | const { t } = useTranslation(); 152 | 153 | return ( 154 | <> 155 | {/* modal for delete customer and product case*/} 156 | {showModal ? ( 157 | 162 | ) : null} 163 | 164 |
165 | 166 |
167 |
168 | 171 | {props.headData ? ( 172 | 173 | 174 | {props.headData.map((item, index) => ( 175 | 176 | ))} 177 | 178 | 179 | ) : null} 180 | 181 | {dataShow.map((item, index) => tableBody(item, index))} 182 | 183 |
{t(item)}
184 |
185 | 186 | {pages > 1 ? ( 187 |
188 | {range.map((item, index) => ( 189 |
selectPage(index)} 195 | > 196 | {item + 1} 197 |
198 | ))} 199 |
200 | ) : null} 201 |
202 |
203 |
204 | 205 | ); 206 | }; 207 | 208 | export default CustomTable; 209 | -------------------------------------------------------------------------------- /src/constants/tables.ts: -------------------------------------------------------------------------------- 1 | export const customersHeader = [ 2 | "ID", 3 | "userName", 4 | "email", 5 | "phoneNumber", 6 | "totalOrders", 7 | "totalSpending", 8 | "location", 9 | "actions", 10 | ]; 11 | export const customers = [ 12 | { 13 | ID: 0, 14 | avatar: 15 | "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 16 | userName: "Zahra Mirzaei", 17 | email: "zahra_mirzaei@gmail.com", 18 | phoneNumber: "+98 918 123 45 67", 19 | totalOrders: 804, 20 | totalSpend: "$120.00", 21 | location: "UK", 22 | }, 23 | { 24 | ID: 1, 25 | avatar: 26 | "https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 27 | userName: "John Smith", 28 | email: "john_smith@gmail.com", 29 | phoneNumber: "+98 918 123 45 67", 30 | totalOrders: 230, 31 | totalSpend: "$120.00", 32 | location: "UK", 33 | }, 34 | { 35 | ID: 2, 36 | avatar: 37 | "https://images.unsplash.com/photo-1554151228-14d9def656e4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=386&q=80", 38 | userName: "Kyle Simpson", 39 | email: "Kyle_simpson@gmail.com", 40 | phoneNumber: "+98 918 123 45 67", 41 | totalOrders: 120, 42 | totalSpend: "$120.00", 43 | location: "USA", 44 | }, 45 | { 46 | ID: 3, 47 | avatar: 48 | "https://images.unsplash.com/photo-1504593811423-6dd665756598?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 49 | userName: " Maximilian Doe", 50 | email: "john_smith@gmail.com", 51 | phoneNumber: "+98 918 123 45 67", 52 | totalOrders: 15, 53 | totalSpend: "$120.00", 54 | location: "USA", 55 | }, 56 | { 57 | ID: 4, 58 | avatar: 59 | "https://images.unsplash.com/photo-1599566150163-29194dcaad36?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 60 | userName: "John Smith", 61 | email: "john_smith@gmail.com", 62 | phoneNumber: "+98 918 123 45 67", 63 | totalOrders: 270, 64 | totalSpend: "$120.00", 65 | location: "USA", 66 | }, 67 | { 68 | ID: 5, 69 | avatar: 70 | "https://images.unsplash.com/photo-1544005313-94ddf0286df2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=388&q=80", 71 | userName: "John Smith", 72 | email: "john_smith@gmail.com", 73 | phoneNumber: "+98 918 123 45 67", 74 | totalOrders: 310, 75 | totalSpend: "$120.00", 76 | location: "USA", 77 | }, 78 | { 79 | ID: 6, 80 | avatar: 81 | "https://images.unsplash.com/photo-1491349174775-aaafddd81942?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 82 | userName: "John Smith", 83 | email: "john_smith@gmail.com", 84 | phoneNumber: "+98 918 123 45 67", 85 | totalOrders: 804, 86 | totalSpend: "$120.00", 87 | location: "USA", 88 | }, 89 | { 90 | ID: 7, 91 | avatar: 92 | "https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 93 | userName: "Andrei Neagoie", 94 | email: "john_smith@gmail.com", 95 | phoneNumber: "+98 918 123 45 67", 96 | totalOrders: 803, 97 | totalSpend: "$120.00", 98 | location: "USA", 99 | }, 100 | { 101 | ID: 8, 102 | avatar: 103 | "https://images.unsplash.com/photo-1555952517-2e8e729e0b44?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80", 104 | userName: "John Smith", 105 | email: "john_smith@gmail.com", 106 | phoneNumber: "+98 918 123 45 67", 107 | totalOrders: 110, 108 | totalSpend: "$120.00", 109 | location: "USA", 110 | }, 111 | { 112 | ID: 9, 113 | avatar: 114 | "https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=853&q=80", 115 | userName: "John Smith", 116 | email: "john_smith@gmail.com", 117 | phoneNumber: "+98 918 123 45 67", 118 | totalOrders: 804, 119 | totalSpend: "$120.00", 120 | location: "USA", 121 | }, 122 | { 123 | ID: 10, 124 | avatar: 125 | "https://images.unsplash.com/photo-1544723795-3fb6469f5b39?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=389&q=80", 126 | userName: "Jason Bourne", 127 | email: "test@gmail.com", 128 | phoneNumber: "+98 918 123 45 67", 129 | totalOrders: 460, 130 | totalSpend: "$260.00", 131 | location: "UK", 132 | }, 133 | ]; 134 | 135 | export const productsHeader = [ 136 | "ID", 137 | "product", 138 | "inventory", 139 | "price", 140 | "category", 141 | "actions", 142 | ]; 143 | export const products = [ 144 | { 145 | ID: 0, 146 | pic: "https://images.unsplash.com/photo-1628773193539-ad29c647c071?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 147 | product: "Apple AirPods", 148 | inventory: 30, 149 | price: "$200.00", 150 | category: "digital", 151 | }, 152 | { 153 | ID: 1, 154 | pic: "https://images.unsplash.com/photo-1484704849700-f032a568e944?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 155 | product: "NUBWO G06", 156 | inventory: 28, 157 | price: "$120.00", 158 | category: "digital", 159 | }, 160 | { 161 | ID: 2, 162 | pic: "https://images.unsplash.com/photo-1607860087860-c46e865f6ab0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=426&q=80", 163 | product: "Hooded Sweatshirt", 164 | inventory: 10, 165 | price: "$10.00", 166 | category: "clothing", 167 | }, 168 | { 169 | ID: 3, 170 | pic: "https://images.unsplash.com/photo-1587829741301-dc798b83add3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=865&q=80", 171 | product: "Keyboard", 172 | inventory: 50, 173 | price: "$50.00", 174 | category: "digital", 175 | }, 176 | { 177 | ID: 4, 178 | pic: "https://images.unsplash.com/photo-1622470953794-aa9c70b0fb9d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 179 | product: "T-Shirt", 180 | inventory: 20, 181 | price: "$75.00", 182 | category: "clothing", 183 | }, 184 | { 185 | ID: 5, 186 | pic: "https://images.unsplash.com/photo-1527443224154-c4a3942d3acf?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 187 | product: "LED Monitor", 188 | inventory: 31, 189 | price: "$510.00", 190 | category: "digital", 191 | }, 192 | { 193 | ID: 6, 194 | pic: "https://images.unsplash.com/photo-1640025867572-f6b3a8410c81?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80", 195 | product: "Socks", 196 | inventory: 5, 197 | price: "$10.00", 198 | category: "clothing", 199 | }, 200 | { 201 | ID: 7, 202 | pic: "https://images.unsplash.com/photo-1615663245857-ac93bb7c39e7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=465&q=80", 203 | product: "Mouse", 204 | inventory: 3, 205 | price: "$16.00", 206 | category: "digital", 207 | }, 208 | { 209 | ID: 8, 210 | pic: "https://images.unsplash.com/photo-1625753783470-ec2ab4efeeec?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80", 211 | product: "Mielle", 212 | inventory: 36, 213 | price: "$170.00", 214 | category: "beauty", 215 | }, 216 | { 217 | ID: 9, 218 | pic: "https://images.unsplash.com/photo-1556306535-0f09a537f0a3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", 219 | product: "hat cap", 220 | inventory: 17, 221 | price: "$25.00", 222 | category: "clothing", 223 | }, 224 | { 225 | ID: 10, 226 | pic: "https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 227 | product: "CeraVe", 228 | inventory: 7, 229 | price: "$220.00", 230 | category: "beauty", 231 | }, 232 | { 233 | ID: 11, 234 | pic: "https://images.unsplash.com/photo-1620916566398-39f1143ab7be?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", 235 | product: "Neutrogena", 236 | inventory: 30, 237 | price: "$205.00", 238 | category: "beauty", 239 | }, 240 | ]; 241 | -------------------------------------------------------------------------------- /src/assets/images/D-digikala.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------