structure + sidebar

This commit is contained in:
hamid zarghami
2025-04-28 10:15:35 +03:30
parent 2ef02e0b6b
commit 53212445c6
25 changed files with 1144 additions and 19 deletions
+9 -1
View File
@@ -11,9 +11,17 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.1.4",
"i18next": "^25.0.1",
"iconsax-react": "^0.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.1.4"
"react-i18next": "^15.5.1",
"react-router-dom": "^7.5.2",
"react-spinners": "^0.17.0",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4",
"vite-tsconfig-paths": "^5.1.4",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
+253
View File
@@ -11,15 +11,39 @@ importers:
'@tailwindcss/vite':
specifier: ^4.1.4
version: 4.1.4(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
i18next:
specifier: ^25.0.1
version: 25.0.1(typescript@5.7.3)
iconsax-react:
specifier: ^0.0.8
version: 0.0.8(react@19.1.0)
react:
specifier: ^19.0.0
version: 19.1.0
react-dom:
specifier: ^19.0.0
version: 19.1.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.1(i18next@25.0.1(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3)
react-router-dom:
specifier: ^7.5.2
version: 7.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-spinners:
specifier: ^0.17.0
version: 0.17.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
tailwind-merge:
specifier: ^3.2.0
version: 3.2.0
tailwindcss:
specifier: ^4.1.4
version: 4.1.4
vite-tsconfig-paths:
specifier: ^5.1.4
version: 5.1.4(typescript@5.7.3)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@19.1.2)(react@19.1.0)
devDependencies:
'@eslint/js':
specifier: ^9.22.0
@@ -128,6 +152,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.27.0':
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.0':
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
engines: {node: '>=6.9.0'}
@@ -709,6 +737,10 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -877,6 +909,9 @@ packages:
resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==}
engines: {node: '>=18'}
globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -887,6 +922,22 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
i18next@25.0.1:
resolution: {integrity: sha512-8S8PyZbrymJZn3DaN70/34JYWNhsqrU6yA4MuzcygJBv+41dgNMocEA8h+kV1P7MCc1ll03lOTOIXE7mpNCicw==}
peerDependencies:
typescript: ^5
peerDependenciesMeta:
typescript:
optional: true
iconsax-react@0.0.8:
resolution: {integrity: sha512-l3dVk4zGtkkJHgvNYqAf0wDKqnKxXykee5/DoESGo2JvSYwaxajJUHSX2YrPRXSov8Hd8ClGFwJxCEaEjrFD1Q==}
peerDependencies:
react: '*'
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1022,6 +1073,10 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -1054,6 +1109,10 @@ packages:
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -1097,6 +1156,9 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -1109,14 +1171,59 @@ packages:
peerDependencies:
react: ^19.1.0
react-i18next@15.5.1:
resolution: {integrity: sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==}
peerDependencies:
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
typescript: ^5
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
typescript:
optional: true
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-refresh@0.17.0:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
react-router-dom@7.5.2:
resolution: {integrity: sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.5.2:
resolution: {integrity: sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react-spinners@0.17.0:
resolution: {integrity: sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==}
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -1145,6 +1252,9 @@ packages:
engines: {node: '>=10'}
hasBin: true
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -1165,6 +1275,9 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
tailwind-merge@3.2.0:
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
tailwindcss@4.1.4:
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
@@ -1186,6 +1299,19 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
tsconfck@3.1.5:
resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==}
engines: {node: ^18 || >=20}
hasBin: true
peerDependencies:
typescript: ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
turbo-stream@2.4.0:
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -1211,6 +1337,14 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
vite-tsconfig-paths@5.1.4:
resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
peerDependencies:
vite: '*'
peerDependenciesMeta:
vite:
optional: true
vite@6.3.3:
resolution: {integrity: sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -1251,6 +1385,10 @@ packages:
yaml:
optional: true
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -1267,6 +1405,24 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
'@ampproject/remapping@2.3.0':
@@ -1361,6 +1517,10 @@ snapshots:
'@babel/core': 7.26.10
'@babel/helper-plugin-utils': 7.26.5
'@babel/runtime@7.27.0':
dependencies:
regenerator-runtime: 0.14.1
'@babel/template@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
@@ -1851,6 +2011,8 @@ snapshots:
convert-source-map@2.0.0: {}
cookie@1.0.2: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -2044,12 +2206,29 @@ snapshots:
globals@16.0.0: {}
globrex@0.1.2: {}
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
has-flag@4.0.0: {}
html-parse-stringify@3.0.1:
dependencies:
void-elements: 3.1.0
i18next@25.0.1(typescript@5.7.3):
dependencies:
'@babel/runtime': 7.27.0
optionalDependencies:
typescript: 5.7.3
iconsax-react@0.0.8(react@19.1.0):
dependencies:
prop-types: 15.8.1
react: 19.1.0
ignore@5.3.2: {}
import-fresh@3.3.1:
@@ -2147,6 +2326,10 @@ snapshots:
lodash.merge@4.6.2: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -2174,6 +2357,8 @@ snapshots:
node-releases@2.0.19: {}
object-assign@4.1.1: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -2213,6 +2398,12 @@ snapshots:
prelude-ls@1.2.1: {}
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -2222,10 +2413,44 @@ snapshots:
react: 19.1.0
scheduler: 0.26.0
react-i18next@15.5.1(i18next@25.0.1(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3):
dependencies:
'@babel/runtime': 7.27.0
html-parse-stringify: 3.0.1
i18next: 25.0.1(typescript@5.7.3)
react: 19.1.0
optionalDependencies:
react-dom: 19.1.0(react@19.1.0)
typescript: 5.7.3
react-is@16.13.1: {}
react-refresh@0.17.0: {}
react-router-dom@7.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-router: 7.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-router@7.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
cookie: 1.0.2
react: 19.1.0
set-cookie-parser: 2.7.1
turbo-stream: 2.4.0
optionalDependencies:
react-dom: 19.1.0(react@19.1.0)
react-spinners@0.17.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react@19.1.0: {}
regenerator-runtime@0.14.1: {}
resolve-from@4.0.0: {}
reusify@1.1.0: {}
@@ -2266,6 +2491,8 @@ snapshots:
semver@7.7.1: {}
set-cookie-parser@2.7.1: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -2280,6 +2507,8 @@ snapshots:
dependencies:
has-flag: 4.0.0
tailwind-merge@3.2.0: {}
tailwindcss@4.1.4: {}
tapable@2.2.1: {}
@@ -2297,6 +2526,12 @@ snapshots:
dependencies:
typescript: 5.7.3
tsconfck@3.1.5(typescript@5.7.3):
optionalDependencies:
typescript: 5.7.3
turbo-stream@2.4.0: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -2323,6 +2558,17 @@ snapshots:
dependencies:
punycode: 2.3.1
vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)):
dependencies:
debug: 4.4.0
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.7.3)
optionalDependencies:
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
transitivePeerDependencies:
- supports-color
- typescript
vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2):
dependencies:
esbuild: 0.25.3
@@ -2336,6 +2582,8 @@ snapshots:
jiti: 2.4.2
lightningcss: 1.29.2
void-elements@3.1.0: {}
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -2345,3 +2593,8 @@ snapshots:
yallist@3.1.1: {}
yocto-queue@0.1.0: {}
zustand@5.0.3(@types/react@19.1.2)(react@19.1.0):
optionalDependencies:
'@types/react': 19.1.2
react: 19.1.0
+20 -1
View File
@@ -1,9 +1,28 @@
import { FC } from 'react'
import './assets/fonts/irancell/style.css'
import { BrowserRouter } from 'react-router-dom'
import Main from './shared/Main'
import i18next from 'i18next'
import FaJson from './langs/fa.json'
import { I18nextProvider } from 'react-i18next'
i18next.init({
interpolation: { escapeValue: false },
lng: 'fa',
defaultNS: 'global',
resources: {
fa: {
global: FaJson
}
}
})
const App: FC = () => {
return (
<div>App</div>
<BrowserRouter>
<I18nextProvider i18n={i18next}>
<Main />
</I18nextProvider>
</BrowserRouter>
)
}
+91
View File
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 961.6 222.5" style="enable-background:new 0 0 961.6 222.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1D1D1B;}
</style>
<g>
<g>
<g>
<g>
<path class="st0" d="M246,180.4h-8.7v24.3h-3.6v-24.3H225v-2.9h21V180.4z"/>
<path class="st0" d="M279.1,205c-2.7,0-5-0.9-6.7-2.7s-2.6-4.2-2.6-7.2v-0.6c0-2,0.4-3.8,1.1-5.4s1.8-2.8,3.2-3.7
s2.9-1.3,4.5-1.3c2.6,0,4.7,0.9,6.1,2.6c1.5,1.7,2.2,4.2,2.2,7.4v1.4h-13.7c0,2,0.6,3.6,1.7,4.8s2.5,1.8,4.2,1.8
c1.2,0,2.3-0.2,3.1-0.7s1.6-1.2,2.2-2l2.1,1.6C285,203.7,282.5,205,279.1,205z M278.7,186.9c-1.4,0-2.6,0.5-3.5,1.5
s-1.5,2.4-1.8,4.3h10.1v-0.3c-0.1-1.8-0.6-3.1-1.4-4.1C281.3,187.4,280.1,186.9,278.7,186.9z"/>
<path class="st0" d="M320.4,202.2c1.2,0,2.3-0.4,3.2-1.1c0.9-0.7,1.4-1.7,1.5-2.8h3.3c-0.1,1.2-0.5,2.3-1.2,3.3
s-1.7,1.9-2.9,2.5c-1.2,0.6-2.5,0.9-3.9,0.9c-2.8,0-4.9-0.9-6.6-2.8c-1.6-1.8-2.4-4.3-2.4-7.5v-0.6c0-2,0.4-3.7,1.1-5.2
s1.8-2.7,3.1-3.6c1.4-0.8,2.9-1.3,4.8-1.3c2.3,0,4.1,0.7,5.6,2c1.5,1.4,2.3,3.1,2.4,5.3h-3.3c-0.1-1.3-0.6-2.4-1.5-3.2
s-2-1.3-3.3-1.3c-1.8,0-3.1,0.6-4.1,1.9s-1.4,3.1-1.4,5.5v0.7c0,2.3,0.5,4.1,1.4,5.4C317.2,201.6,318.6,202.2,320.4,202.2z"/>
<path class="st0" d="M357,186.9c1.5-1.9,3.5-2.8,6-2.8c4.3,0,6.4,2.4,6.5,7.2v13.4H366v-13.4c0-1.5-0.3-2.5-1-3.2s-1.7-1-3.1-1
c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.4v-28.7h3.5V186.9z"/>
<path class="st0" d="M399.1,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M437.3,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.6,1.9-2.8,3.2-3.7s3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7c-1.4,0.9-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2
H437.3z M440.8,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2c1.8,0,3.1-0.7,4.2-2.1s1.6-3.3,1.6-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C441.3,190.3,440.8,192.3,440.8,194.8z"/>
<path class="st0" d="M485.1,204.7h-3.5V176h3.5V204.7z"/>
<path class="st0" d="M511,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.5,1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9c-1.7-1.9-2.5-4.4-2.5-7.6v-0.2
H511z M514.4,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C515,190.3,514.4,192.3,514.4,194.8z"/>
<path class="st0" d="M554.1,194.4c0-3.2,0.7-5.7,2.2-7.5s3.4-2.8,5.8-2.8c2.5,0,4.4,0.9,5.8,2.6l0.2-2.2h3.2v19.7
c0,2.6-0.8,4.7-2.3,6.2c-1.6,1.5-3.6,2.3-6.2,2.3c-1.5,0-2.9-0.3-4.3-0.9c-1.4-0.6-2.5-1.5-3.2-2.6l1.8-2.1
c1.5,1.8,3.3,2.7,5.4,2.7c1.7,0,3-0.5,3.9-1.4s1.4-2.3,1.4-4v-1.7c-1.4,1.6-3.3,2.4-5.7,2.4s-4.3-1-5.7-2.9
S554.1,197.7,554.1,194.4z M557.6,194.8c0,2.3,0.5,4.1,1.4,5.4c0.9,1.3,2.2,2,3.9,2c2.2,0,3.8-1,4.8-3V190
c-1.1-1.9-2.6-2.9-4.8-2.9c-1.7,0-3,0.7-3.9,2C558.1,190.3,557.6,192.2,557.6,194.8z"/>
<path class="st0" d="M604.3,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L604.3,199.6z"/>
<path class="st0" d="M688.3,196c-0.3,2.9-1.4,5.1-3.2,6.7c-1.8,1.6-4.2,2.3-7.1,2.3c-3.2,0-5.8-1.2-7.7-3.5s-2.9-5.4-2.9-9.2
v-2.6c0-2.5,0.5-4.8,1.4-6.7s2.2-3.4,3.8-4.4s3.6-1.5,5.8-1.5c2.9,0,5.2,0.8,6.9,2.4c1.7,1.6,2.8,3.8,3,6.7h-3.6
c-0.3-2.2-1-3.7-2-4.7s-2.5-1.5-4.4-1.5c-2.3,0-4.1,0.8-5.4,2.5c-1.3,1.7-2,4.1-2,7.2v2.6c0,3,0.6,5.3,1.8,7s3,2.6,5.2,2.6
c2,0,3.5-0.5,4.6-1.4s1.8-2.5,2.1-4.7h3.7V196z"/>
<path class="st0" d="M713,194.4c0-2,0.4-3.8,1.2-5.3s1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9s2.5,4.4,2.5,7.6
v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2H713z M716.5,194.8
c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4s-2.5-2.1-4.2-2.1
s-3.1,0.7-4.1,2C717,190.3,716.5,192.3,716.5,194.8z"/>
<path class="st0" d="M760.3,184.5l0.1,2.2c1.5-1.7,3.5-2.6,6-2.6c2.8,0,4.8,1.1,5.8,3.2c0.7-1,1.5-1.8,2.6-2.4
c1.1-0.6,2.4-0.9,3.8-0.9c4.4,0,6.7,2.3,6.8,7v13.5h-3.5v-13.3c0-1.4-0.3-2.5-1-3.2s-1.8-1.1-3.3-1.1c-1.3,0-2.3,0.4-3.2,1.1
c-0.8,0.8-1.3,1.8-1.5,3.1v13.4h-3.5v-13.2c0-2.9-1.4-4.4-4.3-4.4c-2.3,0-3.8,1-4.7,2.9v14.8h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M828.9,194.8c0,3.1-0.7,5.6-2.1,7.4c-1.4,1.9-3.3,2.8-5.7,2.8c-2.5,0-4.4-0.8-5.8-2.3v9.7h-3.5v-28h3.2
l0.2,2.2c1.4-1.7,3.4-2.6,5.9-2.6c2.4,0,4.3,0.9,5.8,2.7c1.4,1.8,2.1,4.4,2.1,7.6v0.5H828.9z M825.5,194.4
c0-2.3-0.5-4.1-1.5-5.4c-1-1.3-2.3-2-4-2c-2.1,0-3.7,0.9-4.7,2.8v9.7c1,1.8,2.6,2.8,4.7,2.8c1.7,0,3-0.7,4-2
S825.5,197,825.5,194.4z"/>
<path class="st0" d="M867,204.7c-0.2-0.4-0.4-1.1-0.5-2.1c-1.6,1.7-3.5,2.5-5.8,2.5c-2,0-3.6-0.6-4.9-1.7s-1.9-2.6-1.9-4.3
c0-2.1,0.8-3.7,2.4-4.9c1.6-1.2,3.9-1.7,6.8-1.7h3.4v-1.7c0-1.2-0.4-2.2-1.1-2.9c-0.7-0.7-1.8-1.1-3.2-1.1
c-1.2,0-2.3,0.3-3.1,0.9c-0.8,0.6-1.3,1.4-1.3,2.3h-3.5c0-1,0.4-2,1.1-2.9c0.7-0.9,1.7-1.7,2.9-2.2s2.6-0.8,4-0.8
c2.3,0,4.2,0.6,5.5,1.7c1.3,1.2,2,2.8,2.1,4.8v9.3c0,1.9,0.2,3.3,0.7,4.4v0.3H867V204.7z M861.3,202c1.1,0,2.1-0.3,3.1-0.8
c1-0.6,1.7-1.3,2.1-2.2v-4.1h-2.7c-4.2,0-6.4,1.2-6.4,3.7c0,1.1,0.4,1.9,1.1,2.5C859.2,201.7,860.2,202,861.3,202z"/>
<path class="st0" d="M899.9,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M945.6,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L945.6,199.6z"/>
</g>
</g>
<path class="st0" d="M221.6,148.7V44.1h29.5c9.1,0,17.1,2,24.1,6s12.4,9.7,16.2,17.2c3.8,7.4,5.7,15.9,5.8,25.6v6.7
c0,9.9-1.9,18.5-5.7,25.9c-3.8,7.4-9.2,13.1-16.3,17.1c-7.1,4-15.3,6-24.7,6.1L221.6,148.7L221.6,148.7z M235.4,55.4v82h14.5
c10.6,0,18.9-3.3,24.8-9.9c5.9-6.6,8.9-16,8.9-28.3v-6.1c0-11.9-2.8-21.1-8.4-27.7s-13.5-9.9-23.7-10L235.4,55.4L235.4,55.4z"/>
<path class="st0" d="M627.1,148.7h-13.9l-52.7-80.6v80.6h-13.9V44.1h13.9l52.8,81v-81H627v104.6H627.1z"/>
<path class="st0" d="M900.9,100.1l-12.8,13.3v35.3h-13.8V44.1h13.8v51.7l46.5-51.7h16.7l-41.2,46.2l44.4,58.4H938L900.9,100.1z"/>
<polygon class="st0" points="423.2,61.1 455.1,148.7 469.2,148.7 429.2,44.1 417.1,44.1 377.2,148.7 391.4,148.7 "/>
<polygon class="st0" points="750.6,61.1 782.6,148.7 796.7,148.7 756.7,44.1 744.6,44.1 704.7,148.7 718.9,148.7 "/>
</g>
<path class="st0" d="M132.1,140.4c0,0-26.1,25.2-48,46.5v28.9c-2.2,1.7,77.6-75.4,77.6-75.4L84.1,59v31L132.1,140.4z"/>
<g>
<polygon class="st0" points="83.7,157.5 6.5,75.1 7.5,74.2 84.7,156.6 "/>
<polygon class="st0" points="83.7,149.8 13.6,75.2 14.6,74.3 84.6,148.9 "/>
<polygon class="st0" points="83.7,142.1 20.7,75.4 21.7,74.4 84.6,141.1 "/>
<polygon class="st0" points="83.6,134.3 27.8,75.5 28.7,74.6 84.6,133.4 "/>
<polygon class="st0" points="83.6,126.6 34.9,75.6 35.8,74.7 84.6,125.7 "/>
</g>
<g>
<polygon class="st0" points="8,75.5 7.1,74.5 83.7,0 84.6,0.9 "/>
<polygon class="st0" points="15.2,75.5 14.3,74.6 83.6,7 84.5,7.9 "/>
<polygon class="st0" points="22.4,75.6 21.4,74.7 83.6,14 84.5,14.9 "/>
<polygon class="st0" points="29.5,75.6 28.6,74.7 83.5,21 84.4,22 "/>
<polygon class="st0" points="36.7,75.7 35.7,74.8 83.5,28 84.4,29 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

+40
View File
@@ -0,0 +1,40 @@
import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react'
import MoonLoader from "react-spinners/MoonLoader"
import { XOR } from '../helpers/types'
import { clx } from '../helpers/utils'
type Props = {
className?: string;
isLoading?: boolean,
percentage?: number,
} & ButtonHTMLAttributes<HTMLButtonElement> &
XOR<{ children: ReactNode }, { label: string }>;
const Button: FC<Props> = memo((props: Props) => {
const buttonClass = clx(
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full',
props.disabled && 'cursor-not-allowed opacity-60',
props.className
);
return (
<button {...props} className={`${buttonClass} ${props.className}`} >
{
props.isLoading ?
<div className='flex gap-2 items-center'>
{
!props.percentage ?
<MoonLoader size={20} color='#fff' />
:
<span>{props.percentage}%</span>
}
</div>
:
props.label || props.children
}
</button>
)
})
export default Button
+15
View File
@@ -0,0 +1,15 @@
import { FC } from 'react'
type Props = {
errorText: string
}
const Error: FC<Props> = (props: Props) => {
return (
<div className='mt-1.5 font-normal text-red-500 text-[11px]'>
{props.errorText}
</div>
)
}
export default Error
+115
View File
@@ -0,0 +1,115 @@
import { FC, InputHTMLAttributes, useEffect, useState } from 'react'
import { clx } from '../helpers/utils';
import { Eye, SearchNormal } from 'iconsax-react';
import Error from './Error';
type Variant = "floating_outlined" | "primary" | "search";
type Props = {
label?: string;
className?: string;
variant?: Variant;
error_text?: string;
onEnter?: () => void;
unit?: string;
seprator?: boolean;
isNotRequired?: boolean;
onChangeSearchFinal?: (value: string) => void;
} & InputHTMLAttributes<HTMLInputElement>
const formatNumber = (value: string | number): string => {
if (!value) return '';
const inputValue = String(value).replace(/,/g, '');
return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const Input: FC<Props> = (props: Props) => {
const [formattedValue, setFormattedValue] = useState<string>(
props.value ? formatNumber(props.value as string) : ''
);
const [showPassword, setShowPassword] = useState<boolean>(false)
const [search, setSearch] = useState<string>('')
const inputClass = clx(
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
props.readOnly && 'bg-gray-100 border-0 text-description',
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
props.className
);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (props.seprator) {
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
const formatted = formatNumber(inputValue);
// به‌روزرسانی مقدار قالب‌بندی‌شده
setFormattedValue(formatted);
// ارسال مقدار خام به `onChange` والد
props.onChange?.({
...event,
target: {
...event.target,
value: inputValue,
},
});
} else {
props.onChange?.(event);
}
};
useEffect(() => {
if (props.value) {
setFormattedValue(formatNumber(props.value as string));
} else {
setFormattedValue('');
}
}, [props.value])
useEffect(() => {
if (props.variant === 'search' && props.onChangeSearchFinal) {
const timeout = setTimeout(() => {
props.onChangeSearchFinal?.(search)
}, 1000)
return () => clearTimeout(timeout)
}
}, [search, props])
return (
<div className='w-full'>
<label className='text-sm'>
{props.label}
</label>
<div className='w-full relative mt-1'>
<input {...props} onChange={(e) => {
setSearch(e.target.value)
handleInputChange(e)
}} value={props.seprator ? formattedValue : props.value} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
{
props.type === 'password' &&
<Eye onClick={() => setShowPassword((oldValue) => !oldValue)} size={20} color='#8C90A3' className='absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
// <img onClick={() => setShowPassword((oldValue) => !oldValue)} src={EyeIcon} className='w-5 absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
}
{
props.variant === 'search' &&
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
}
{
props.error_text &&
<Error
errorText={props.error_text}
/>
}
</div>
</div>
)
}
export default Input
+20
View File
@@ -0,0 +1,20 @@
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
export type XOR<T, U> = T | U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U;
export type ItemsSelectType = {
value: string;
label: string;
};
export type ErrorType = Error & {
response?: {
data?: {
error: {
message: string[];
};
};
};
};
+11
View File
@@ -0,0 +1,11 @@
import { twMerge } from "tailwind-merge";
/**
* Concatenates truthy classes into a space-separated string.
*
* @param classes - The classes to concatenate.
* @returns The concatenated classes.
*/
export const clx = (...classes: (string | boolean | undefined)[]): string => {
return twMerge(classes.filter(Boolean).join(" "));
};
+6
View File
@@ -40,3 +40,9 @@ input::placeholder,
textarea::placeholder {
color: #888888;
}
@theme {
--color-primary: #000000;
--color-secondary: #eaecf4;
--color-description: #888888;
}
+20
View File
@@ -0,0 +1,20 @@
{
"sidebar": {
"menu": "منو",
"received": "دریافتی ها",
"sent": "ارسال شده ها",
"draft": "پیش نویس ها",
"archive": "آرشیو شده ها",
"profile": "پروفایل",
"trash": "سطل زباله",
"other": "سایر",
"favorite": "نشان شده ها",
"spam": "هرزنامه ها",
"setting": "تنظیمات",
"logout": "خروج",
"new_message": "پیام جدید"
},
"header": {
"search": "جستجو"
}
}
+9
View File
@@ -0,0 +1,9 @@
import { FC } from 'react'
const Home: FC = () => {
return (
<div>home page</div>
)
}
export default Home
+13
View File
@@ -0,0 +1,13 @@
import { FC } from 'react'
import { Routes, Route } from 'react-router-dom'
import Home from '@/pages/home/Home'
const AppRouter: FC = () => {
return (
<Routes>
<Route path='/' element={<Home />} />
</Routes>
)
}
export default AppRouter
+86
View File
@@ -0,0 +1,86 @@
import { Element3, Element4, Home2, Messages3, NotificationStatus } from 'iconsax-react'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { Pages } from '../config/Pages'
import { Link, useLocation } from 'react-router-dom'
const Footer: FC = () => {
const { t } = useTranslation('global')
const location = useLocation()
const isActive = (path: string) => location.pathname === path
return (
<div className='xl:hidden'>
<div className='h-[60px] '></div>
<div className='fixed z-10 bottom-2 right-3 left-3 bg-white h-[60px] rounded-2xl flex justify-between items-center px-3'>
<Link to={Pages.services.mine}>
<div className={`text-description w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.dashboard) ? 'text-black' : ''}`}>
<Element4
className='size-5'
color={isActive(Pages.services.mine) ? 'black' : '#8C90A3'}
variant={isActive(Pages.services.mine) ? 'Bold' : 'Linear'}
/>
<div className={`text-[10px] ${isActive(Pages.services.mine) ? 'text-black' : ''}`}>
{t('footer.my_services')}
</div>
</div>
</Link>
<Link to={Pages.services.other}>
<div className={`text-description w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.services.other) ? 'text-black' : ''}`}>
<Element3
className='size-5'
color={isActive(Pages.services.other) ? 'black' : '#8C90A3'}
variant={isActive(Pages.services.other) ? 'Bold' : 'Linear'}
/>
<div className={`text-[10px] ${isActive(Pages.services.other) ? 'text-black' : ''}`}>
{t('footer.other_services')}
</div>
</div>
</Link>
<Link to={Pages.dashboard}>
<div className={`text-description w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.services.mine) ? 'text-black' : ''}`}>
<div className='bg-white p-1 rounded-full -mt-7'>
<div className={`bg-black flex justify-center items-center size-10 rounded-full ${isActive(Pages.services.mine) ? 'bg-black' : ''}`}>
<Home2
className='size-5'
color={isActive(Pages.dashboard) ? 'white' : 'white'}
variant={isActive(Pages.dashboard) ? 'Bold' : 'Linear'}
/>
</div>
</div>
<div className={`text-[10px] ${isActive(Pages.services.mine) ? 'text-black' : ''}`}>
{t('footer.home')}
</div>
</div>
</Link>
<Link to={Pages.announcement.list}>
<div className={`text-description w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.announcement.list) ? 'text-black' : ''}`}>
<NotificationStatus
className='size-5'
color={isActive(Pages.announcement.list) ? 'black' : '#8C90A3'}
variant={isActive(Pages.announcement.list) ? 'Bold' : 'Linear'}
/>
<div className={`text-[10px] ${isActive(Pages.announcement.list) ? 'text-black' : ''}`}>
{t('footer.announcements')}
</div>
</div>
</Link>
<Link to={Pages.ticket.list}>
<div className={`text-description w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.ticket.list) ? 'text-black' : ''}`}>
<Messages3
className='size-5'
color={isActive(Pages.ticket.list) ? 'black' : '#8C90A3'}
variant={isActive(Pages.ticket.list) ? 'Bold' : 'Linear'}
/>
<div className={`text-[10px] ${isActive(Pages.ticket.list) ? 'text-black' : ''}`}>
{t('footer.tickets')}
</div>
</div>
</Link>
</div>
</div>
)
}
export default Footer
+173
View File
@@ -0,0 +1,173 @@
import { FC, useEffect, useState } from 'react'
import Input from '../components/Input'
import { Element3, HambergerMenu, Wallet } from 'iconsax-react'
import { useTranslation } from 'react-i18next'
import { Link, useLocation } from 'react-router-dom'
import { Paths } from '@/utils/Paths'
// import Notifications from '../pages/notification/Notification'
import { useSharedStore } from './store/sharedStore'
// import { useGetProfile } from '../pages/profile/hooks/useProfileData'
// import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
// import { useGetWalletBalance } from '../pages/wallet/hooks/useWalletData'
// import { NumberFormat } from '../config/func'
const Header: FC = () => {
const location = useLocation();
const [popoverKey, setPopoverKey] = useState(0);
const { t } = useTranslation('global')
const { setOpenSidebar, openSidebar } = useSharedStore()
// const { data } = useGetProfile()
// const getWalletBalance = useGetWalletBalance()
console.log(popoverKey);
useEffect(() => {
setPopoverKey((prevKey) => prevKey + 1);
}, [location.pathname]);
return (
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
<div className='min-w-[270px] hidden xl:block'>
<Input
variant='search'
placeholder={t('header.search')}
/>
</div>
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
<HambergerMenu size={24} color='black' />
</div>
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
<div className='flex xl:gap-6 gap-4 items-center'>
<Link to={Paths.home}>
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
</Link>
<Link className='xl:hidden' to={Paths.home}>
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
</Link>
<Link className='hidden xl:block' to={Paths.home}>
<div className='flex items-center h-8 pl-2 rounded-full bg-[#EEF0F7]'>
<div className='px-3 text-xs'>
{/* {NumberFormat(getWalletBalance.data?.data?.balance) + ' ' + t('toman')} */}
</div>
<div className='size-[26px] flex justify-center items-center bg-white rounded-xl'>
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
</div>
</div>
</Link>
{/* <Notifications /> */}
{/* {
data && (
<Popover className="relative" key={popoverKey}>
<PopoverButton >
<div className='flex gap-2 items-center mt-2.5'>
<div className='size-6 rounded-full bg-description overflow-hidden'>
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
</div>
<div className='xl:flex hidden gap-1 items-center'>
<div className='text-xs'>
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
</div>
<ArrowDown2 size={14} color='#8C90A3' />
</div>
</div>
</PopoverButton>
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
<div className='absolute xl:hidden top-6 left-6'>
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' />
</div>
<Link to={Pages.profile} className='flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4'>
<div className='size-14 rounded-full overflow-hidden'>
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
</div>
<div>
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
</div>
<div className='text-description'>
{data?.data?.user?.email}
</div>
</Link>
<div className='pb-6 px-6 border-b border-[#EAEDF5]'>
<div className='flex justify-between items-center'>
<SideBarItem
icon={<Wallet size={20} color='black' />}
title={t('header.wallet')}
link={Pages.wallet}
isActive
isWithoutLine
/>
<div className='flex xl:hidden items-center mt-4 h-8 pl-2 rounded-full bg-[#EEF0F7]'>
<div className='ps-3'>{t('home.balance')}</div>
<div className='px-3 text-xs'>
{NumberFormat(getWalletBalance.data?.data?.balance) + ' ' + t('toman')}
</div>
</div>
</div>
<SideBarItem
icon={<Card color={'black'} size={20} />}
title={t('sidebar.transactions')}
isActive
link={Pages.transactions}
isWithoutLine
/>
<SideBarItem
icon={<TicketDiscount color={'black'} size={20} />}
title={t('header.discounts')}
isActive
link={Pages.discounts}
isWithoutLine
/>
<SideBarItem
icon={<Receipt1 color={'black'} size={20} />}
title={t('header.financial_info')}
isActive
link={Pages.financial}
isWithoutLine
/>
<SideBarItem
icon={<ProfileCircle color={'black'} size={20} />}
title={t('header.profile')}
isActive
link={Pages.profile}
isWithoutLine
/>
<SideBarItem
icon={<Setting2 color={'black'} size={20} />}
title={t('header.setting')}
isActive
link={Pages.setting}
isWithoutLine
/>
</div>
<div className='px-6 -mt-[2px]'>
<SideBarItem
icon={<Logout color={'black'} size={20} />}
title={t('sidebar.logout')}
isActive
link={Pages.setting}
isWithoutLine
isLogout
/>
</div>
</PopoverPanel>
</Popover>
)
} */}
</div>
</div>
)
}
export default Header
+15
View File
@@ -0,0 +1,15 @@
import { FC } from 'react'
import SideBar from './SideBar'
import Header from './Header'
const Main: FC = () => {
return (
<div className='p-4 overflow-hidden'>
<SideBar />
<Header />
</div>
)
}
export default Main
+135
View File
@@ -0,0 +1,135 @@
import { FC } from 'react'
import LogoImage from '../assets/images/logo.svg'
import { Add, ArchiveTick, DirectInbox, DirectSend, Edit, InfoCircle, Logout, Setting2, Star, Trash } from 'iconsax-react'
import SideBarItem from './SideBarItem'
import { useLocation } from 'react-router-dom'
import { useSharedStore } from './store/sharedStore'
import { clx } from '../helpers/utils'
import { useTranslation } from 'react-i18next'
import { Paths } from '@/utils/Paths'
import Button from '@/components/Button'
const SideBar: FC = () => {
const { t } = useTranslation()
const { openSidebar, setOpenSidebar } = useSharedStore()
const location = useLocation()
const isActive = (path: string) => location.pathname === path
const iconSizeSideBar = 20
return (
<>
{
openSidebar && <div className='fixed top-0 left-0 right-0 bottom-0 bg-black bg-opacity-50 z-10' onClick={() => setOpenSidebar(false)} />
}
<div
className={clx(
'fixed xl:flex flex-1 translate-x-[400px] xl:translate-x-0 transition-all ease-in-out opacity-0 invisible xl:visible xl:opacity-100 xl:right-4 right-0 xl:top-4 top-0 xl:bottom-4 bottom-0 xl:rounded-[32px] w-[250px] bg-white flex-col py-12',
openSidebar && 'opacity-100 visible -translate-x-[0px] block z-40'
)}
>
<div className='flex justify-center'>
<img src={LogoImage} className='w-[140px]' />
</div>
<div className='flex justify-center mt-14'>
<Button
className='bg-secondary font-normal text-primary w-fit px-6'
>
<div className='flex gap-2'>
<Add size={20} color='black' />
<div>{t('sidebar.new_message')}</div>
</div>
</Button>
</div>
<div className='flex-1 flex flex-col h-full overflow-y-auto no-scrollbar'>
<div className='mt-10 px-12 text-[#C3C7DD] text-sm font-bold'>
{t('sidebar.menu')}
</div>
<div className='text-xs text-[#8C90A3]'>
<SideBarItem
icon={<DirectInbox variant={isActive(Paths.received) ? 'Bold' : 'Outline'} color={isActive(Paths.received) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.received')}
isActive={isActive(Paths.received)}
link={Paths.received}
/>
<SideBarItem
icon={<DirectSend variant={isActive(Paths.sent) ? 'Bold' : 'Outline'} color={isActive(Paths.sent) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.sent')}
isActive={isActive(Paths.sent)}
link={Paths.sent}
/>
<SideBarItem
icon={<Edit variant={isActive(Paths.draft) ? 'Bold' : 'Outline'} color={isActive(Paths.draft) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.draft')}
isActive={isActive(Paths.draft)}
link={Paths.draft}
/>
<SideBarItem
icon={<ArchiveTick variant={isActive(Paths.archive) ? 'Bold' : 'Outline'} color={isActive(Paths.archive) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.archive')}
isActive={isActive(Paths.archive)}
link={Paths.archive}
/>
<SideBarItem
icon={<Trash variant={isActive(Paths.trash) ? 'Bold' : 'Outline'} color={isActive(Paths.trash) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.trash')}
isActive={isActive(Paths.trash)}
link={Paths.trash}
/>
</div>
<div className='mt-10 px-12 text-[#C3C7DD] text-sm font-bold'>
{t('sidebar.other')}
</div>
<div className='text-xs text-[#8C90A3]'>
<SideBarItem
icon={<Star variant={isActive(Paths.favorite) ? 'Bold' : 'Outline'} color={isActive(Paths.favorite) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.favorite')}
isActive={isActive(Paths.favorite)}
link={Paths.favorite}
/>
<SideBarItem
icon={<InfoCircle variant={isActive(Paths.spam) ? 'Bold' : 'Outline'} color={isActive(Paths.spam) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.spam')}
isActive={isActive(Paths.spam)}
link={Paths.spam}
/>
</div>
<div className='flex-1 flex flex-col justify-end mt-14 pb-8'>
<div className='text-xs text-[#8C90A3]'>
<SideBarItem
icon={<Setting2 variant={isActive(Paths.setting) ? 'Bold' : 'Outline'} color={isActive(Paths.setting) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.setting')}
isActive={isActive(Paths.setting)}
link={Paths.setting}
/>
</div>
<div className='text-xs text-[#8C90A3]'>
<SideBarItem
icon={<Logout variant={isActive('logout') ? 'Bold' : 'Outline'} color={isActive('logout') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.logout')}
isActive={isActive('logout')}
link={`#`}
isLogout
/>
</div>
</div>
</div>
</div>
</>
)
}
export default SideBar
+43
View File
@@ -0,0 +1,43 @@
import { FC, ReactNode } from 'react'
import { Link } from 'react-router-dom'
import { clx } from '../helpers/utils'
type Props = {
icon: ReactNode,
title: string,
isActive: boolean,
link: string,
isLogout?: boolean,
isWithoutLine?: boolean
}
const SideBarItem: FC<Props> = (props: Props) => {
const handleLogout = async () => {
// await logout.mutateAsync()
// removeToken()
// removeRefreshToken()
// window.location.href = Pages.auth.login
}
return (
<Link onClick={props.isLogout ? handleLogout : undefined} to={props.link} className='flex text-xs gap-9 mt-4'>
{
!props.isWithoutLine &&
<div className={clx(
'w-1 bg-black h-6',
!props.isActive && 'invisible'
)}></div>
}
<div className='flex gap-2.5 items-center'>
{props.icon}
<div className={props.isActive ? 'text-black' : ''}>
{props.title}
</div>
</div>
</Link>
)
}
export default SideBarItem
+7
View File
@@ -0,0 +1,7 @@
import { create } from "zustand";
import { SharedStoreType } from "../types/SharedTypes";
export const useSharedStore = create<SharedStoreType>((set) => ({
openSidebar: false,
setOpenSidebar: (value) => set({ openSidebar: value }),
}));
+4
View File
@@ -0,0 +1,4 @@
export type SharedStoreType = {
openSidebar: boolean;
setOpenSidebar: (value: boolean) => void;
};
+11
View File
@@ -0,0 +1,11 @@
export const Paths = {
home: "/",
received: "/received",
sent: "/sent",
draft: "/draft",
archive: "/archive",
trash: "/trash",
favorite: "/favorite",
spam: "/spam",
setting: "/setting",
};
+15 -5
View File
@@ -3,10 +3,13 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
@@ -14,13 +17,20 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": ["src"]
"include": [
"src"
]
}
+14 -2
View File
@@ -1,7 +1,19 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
],
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
}
}
+13 -5
View File
@@ -2,23 +2,31 @@
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"lib": [
"ES2023"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": ["vite.config.ts"]
"include": [
"vite.config.ts"
]
}
+2 -1
View File
@@ -1,8 +1,9 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
plugins: [react(), tailwindcss(), tsconfigPaths()],
});