base structure
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
VITE_TOKEN_NAME = 'dpage-editor-t'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dpage-editor-t'
|
||||
Generated
+193
-6
@@ -9,10 +9,15 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"i18next": "^25.6.2",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-i18next": "^16.3.0",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"tailwindcss": "^4.1.17"
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
@@ -265,6 +270,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -1644,7 +1658,7 @@
|
||||
"version": "19.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2214,7 +2228,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -2711,6 +2725,59 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.6.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.2.tgz",
|
||||
"integrity": "sha512-0GawNyVUw0yvJoOEBq1VHMAsqdM23XrHkMtl2gKEjviJQSLVXsrPqsoYAxBEugW5AB96I2pZkwRxyl8WZVoWdw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/iconsax-react": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/iconsax-react/-/iconsax-react-0.0.8.tgz",
|
||||
"integrity": "sha512-l3dVk4zGtkkJHgvNYqAf0wDKqnKxXykee5/DoESGo2JvSYwaxajJUHSX2YrPRXSov8Hd8ClGFwJxCEaEjrFD1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -2801,7 +2868,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -3160,6 +3226,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -3255,6 +3333,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -3395,6 +3482,17 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -3431,7 +3529,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3449,6 +3546,39 @@
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.0.tgz",
|
||||
"integrity": "sha512-XGYIVU6gCOL4UQsfp87WbbvBc2WvgdkEDI8r4TwACzFg1bXY8pd1d9Cw6u9WJ2soTKHKaF1xQEyWA3/dUvtAGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 25.6.2",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
||||
@@ -3663,6 +3793,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
|
||||
@@ -3771,7 +3911,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
@@ -3854,6 +3994,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
@@ -3959,6 +4108,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -4004,6 +4162,35 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
||||
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
||||
"license": "MIT",
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -11,10 +11,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"i18next": "^25.6.2",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-i18next": "^16.3.0",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"tailwindcss": "^4.1.17"
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
||||
+17
-1
@@ -1,11 +1,27 @@
|
||||
import { type FC } from 'react'
|
||||
import './assets/fonts/irancell/style.css'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import MainRouter from './router/MainRouter'
|
||||
import i18next from 'i18next'
|
||||
import FaJson from '@/langs/fa.json'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
|
||||
i18next.init({
|
||||
interpolation: { escapeValue: false },
|
||||
lng: 'fa',
|
||||
resources: {
|
||||
fa: {
|
||||
global: FaJson
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const App: FC = () => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<MainRouter />
|
||||
</I18nextProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg width="39" height="54" viewBox="0 0 39 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.1559 34.8412C31.1559 34.8412 24.6651 41.094 19.2416 46.3804V53.5924C18.6935 54.0179 38.4905 34.8845 38.4905 34.8845L19.2416 14.6909V22.3573L31.1559 34.8412Z" fill="#1D1D1B"/>
|
||||
<path d="M19.1479 39.0818L0 18.643L0.237992 18.405L19.3931 38.8583L19.1479 39.0818Z" fill="#1D1D1B"/>
|
||||
<path d="M19.1407 37.1635L1.75977 18.6719L1.99776 18.4412L19.3787 36.9399L19.1407 37.1635Z" fill="#1D1D1B"/>
|
||||
<path d="M19.1334 35.2523L3.52661 18.708L3.7574 18.47L19.3786 35.0215L19.1334 35.2523Z" fill="#1D1D1B"/>
|
||||
<path d="M19.1262 33.3337L5.28638 18.7366L5.50995 18.5059L19.3714 33.1029L19.1262 33.3337Z" fill="#1D1D1B"/>
|
||||
<path d="M19.1191 31.4155L7.05347 18.7729L7.26982 18.5349L19.3571 31.1848L19.1191 31.4155Z" fill="#1D1D1B"/>
|
||||
<path d="M0.375071 18.7368L0.144287 18.4988L19.1407 0L19.3715 0.237996L0.375071 18.7368Z" fill="#1D1D1B"/>
|
||||
<path d="M2.15645 18.7442L1.91846 18.5134L19.1263 1.73828L19.3571 1.97628L2.15645 18.7442Z" fill="#1D1D1B"/>
|
||||
<path d="M3.93049 18.7587L3.69971 18.5279L19.1117 3.48364L19.3425 3.71443L3.93049 18.7587Z" fill="#1D1D1B"/>
|
||||
<path d="M5.71187 18.7658L5.47388 18.5422L19.0973 5.22168L19.3281 5.45968L5.71187 18.7658Z" fill="#1D1D1B"/>
|
||||
<path d="M7.49326 18.7799L7.24805 18.5564L19.0901 6.95947L19.3137 7.19747L7.49326 18.7799Z" fill="#1D1D1B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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 |
@@ -0,0 +1,46 @@
|
||||
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'
|
||||
import { clx } from '@/helpers/utils'
|
||||
|
||||
type ButtonProps = {
|
||||
variant?: 'primary' | 'secondary' | 'outline'
|
||||
leftIcon?: ReactNode
|
||||
rightIcon?: ReactNode
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement>
|
||||
|
||||
const variantClasses: Record<NonNullable<ButtonProps['variant']>, string> = {
|
||||
primary: 'bg-black text-white hover:bg-black/90',
|
||||
secondary: 'bg-white text-black border border-black hover:bg-black/5',
|
||||
outline: 'border border-black text-black hover:bg-black/5'
|
||||
}
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
variant = 'primary',
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={clx(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-black/10 disabled:pointer-events-none disabled:opacity-50',
|
||||
variantClasses[variant],
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{leftIcon ? <span className='flex items-center'>{leftIcon}</span> : null}
|
||||
{children}
|
||||
{rightIcon ? <span className='flex items-center'>{rightIcon}</span> : null}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
Button.displayName = 'Button'
|
||||
|
||||
export default Button
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { type FC } from 'react'
|
||||
|
||||
import { clx } from '@/helpers/utils'
|
||||
|
||||
type DividerProps = {
|
||||
label?: string
|
||||
className?: string
|
||||
lineClassName?: string
|
||||
labelClassName?: string
|
||||
}
|
||||
|
||||
const Divider: FC<DividerProps> = ({ label, className, lineClassName, labelClassName }) => {
|
||||
return (
|
||||
<div className={clx('flex items-center gap-4', className)}>
|
||||
<span className={clx('flex-1 border-t border-dashed border-neutral-200', lineClassName)} />
|
||||
{label ? (
|
||||
<span className={clx('text-xs text-neutral-500', labelClassName)}>
|
||||
{label}
|
||||
</span>
|
||||
) : null}
|
||||
<span className={clx('flex-1 border-t border-dashed border-neutral-200', lineClassName)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Divider
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { type 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
|
||||
@@ -0,0 +1,124 @@
|
||||
import { type FC, type InputHTMLAttributes, useEffect, useState } from 'react'
|
||||
import { clx } from '../helpers/utils';
|
||||
import { Eye, EyeSlash, 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' &&
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setShowPassword((oldValue) => !oldValue)}
|
||||
className='absolute top-0 bottom-0 left-3 my-auto flex h-5 w-5 cursor-pointer items-center justify-center text-black'
|
||||
>
|
||||
{
|
||||
showPassword
|
||||
? <EyeSlash color='#8C90A3' size={20} />
|
||||
: <Eye color='#8C90A3' size={20} />
|
||||
}
|
||||
</button>
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
@@ -0,0 +1,4 @@
|
||||
export const Paths = {
|
||||
home: "/",
|
||||
editor: "/editor",
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
export const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat("fa-IR", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
export const formatDateShort = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat("fa-IR", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
export const formatPrice = (price: number): string => {
|
||||
return new Intl.NumberFormat("fa-IR", {
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(price);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
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[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export interface IGeneralError {
|
||||
status: number;
|
||||
success: boolean;
|
||||
error: {
|
||||
message: string;
|
||||
details: string[];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { IGeneralError } from "./types";
|
||||
|
||||
/**
|
||||
* 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(" "));
|
||||
};
|
||||
|
||||
export const extractErrorMessage = (
|
||||
error: Error | unknown,
|
||||
fallbackMessage: string = "خطایی رخ داده است"
|
||||
): string => {
|
||||
try {
|
||||
const axiosError = error as { response?: { data: IGeneralError } };
|
||||
return axiosError?.response?.data?.error?.details?.[0] || fallbackMessage;
|
||||
} catch {
|
||||
return fallbackMessage;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"sidebar": {
|
||||
"menu": "منو",
|
||||
"home_page": "صفحه اصلی"
|
||||
},
|
||||
"header": {
|
||||
"search": "جستجو"
|
||||
},
|
||||
"view": {
|
||||
"title": "نمایش کاتالوگ",
|
||||
"download_pdf": "دانلود PDF",
|
||||
"sidebar": {
|
||||
"title": "صفحات کاتالوگ",
|
||||
"search_placeholder": "جستجوی صفحه",
|
||||
"items": {
|
||||
"cover": "صفحه جلد",
|
||||
"details": "جزئیات محصول",
|
||||
"gallery": "گالری تصاویر"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"add_page": "افزودن صفحه جدید"
|
||||
},
|
||||
"main": {
|
||||
"upload_hint": "برای جایگزینی فایل جدید، PDF خود را آپلود کنید.",
|
||||
"empty_state": "برای مشاهده محتوا، یکی از صفحات سایدبار را انتخاب کنید."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import EditorSidebar from './components/EditorSidebar'
|
||||
|
||||
const Editor = () => {
|
||||
return (
|
||||
<div>
|
||||
<EditorSidebar />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Editor
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
import Logo from "@/assets/images/logo.svg";
|
||||
import { clx } from "@/helpers/utils";
|
||||
import { useSharedStore } from "@/shared/store/sharedStore";
|
||||
|
||||
const EditorSidebar = () => {
|
||||
const { openSidebar, setOpenSidebar } = useSharedStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{openSidebar ? (
|
||||
<div className="fixed inset-0 z-10 bg-black/50" onClick={() => setOpenSidebar(false)} />
|
||||
) : null}
|
||||
<aside
|
||||
className={clx(
|
||||
"fixed right-0 top-0 bottom-0 flex translate-x-[420px] w-[350px] flex-col bg-white px-8 py-10 opacity-0 transition-all ease-in-out xl:visible xl:right-4 xl:top-4 xl:bottom-4 xl:translate-x-0 xl:rounded-[32px] xl:bg-white xl:px-8 xl:py-10 xl:opacity-100",
|
||||
openSidebar && "z-40 translate-x-0 opacity-100",
|
||||
)}
|
||||
>
|
||||
<div className="mb-10 flex items-center justify-center">
|
||||
<img src={Logo} alt="لوگو دانک" className="h-9" />
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorSidebar;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<div>Home</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -0,0 +1,42 @@
|
||||
import SideBar from '@/shared/SideBar'
|
||||
import Header from '@/shared/Header'
|
||||
import { Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { useSharedStore } from '@/shared/store/sharedStore'
|
||||
import Home from '@/pages/home/Home'
|
||||
import Editor from '@/pages/editor/Editor'
|
||||
|
||||
const MainRouter = () => {
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
const location = useLocation()
|
||||
const hiddenSideBarRoutes = [Paths.editor]
|
||||
const shouldRenderSideBar = !hiddenSideBarRoutes.includes(location.pathname)
|
||||
const routeHasLocalSidebar = location.pathname === Paths.editor
|
||||
const hasSidebarSpace = shouldRenderSideBar || routeHasLocalSidebar
|
||||
const headerSidebarSize = routeHasLocalSidebar ? 'wide' : 'default'
|
||||
|
||||
return (
|
||||
<div className='flex min-h-full flex-col overflow-hidden p-4'>
|
||||
{shouldRenderSideBar ? <SideBar /> : null}
|
||||
<Header hasMainSidebar={hasSidebarSpace} sidebarSize={headerSidebarSize} />
|
||||
<div className={clx(
|
||||
'mt-[68px] flex flex-1 flex-col xl:mt-[81px]',
|
||||
shouldRenderSideBar && 'xl:ms-[269px]',
|
||||
shouldRenderSideBar && hasSubMenu && 'xl:ms-[305px]',
|
||||
routeHasLocalSidebar && 'xl:mr-[374px]',
|
||||
)}>
|
||||
<div className='flex-1 flex flex-col overflow-auto w-full max-h-[calc(100vh-113px)]'>
|
||||
<div className='flex-1 h-full flex'>
|
||||
<Routes>
|
||||
<Route path={Paths.home} element={<Home />} />
|
||||
<Route path={Paths.editor} element={<Editor />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainRouter
|
||||
@@ -0,0 +1,130 @@
|
||||
import { type FC, useEffect } from 'react'
|
||||
import Input from '@/components/Input'
|
||||
// import { ArrowDown2, CloseCircle, HambergerMenu, Logout, Wallet } from 'iconsax-react'
|
||||
// import AvatarImage from '../assets/images/avatar_image.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useSharedStore } from '@/shared/store/sharedStore'
|
||||
import { HambergerMenu } from 'iconsax-react'
|
||||
import { clx } from '@/helpers/utils'
|
||||
|
||||
type SidebarSize = 'default' | 'wide'
|
||||
|
||||
type HeaderProps = {
|
||||
hasMainSidebar?: boolean
|
||||
sidebarSize?: SidebarSize
|
||||
}
|
||||
|
||||
const sidebarSizeClasses: Record<SidebarSize, { base: string; withSub: string }> = {
|
||||
default: {
|
||||
base: 'xl:right-[285px] xl:w-[calc(100%-305px)]',
|
||||
withSub: 'xl:right-[320px] xl:w-[calc(100%-340px)]',
|
||||
},
|
||||
wide: {
|
||||
base: 'xl:right-[390px] xl:w-[calc(100%-406px)]',
|
||||
withSub: 'xl:right-[425px] xl:w-[calc(100%-441px)]',
|
||||
},
|
||||
}
|
||||
|
||||
const Header: FC<HeaderProps> = ({ hasMainSidebar = true, sidebarSize = 'default' }) => {
|
||||
|
||||
// const location = useLocation();
|
||||
// const [popoverKey, setPopoverKey] = useState(0);
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar, hasSubMenu, setSearch } = useSharedStore()
|
||||
const location = useLocation()
|
||||
|
||||
// useEffect(() => {
|
||||
// setPopoverKey((prevKey) => prevKey + 1);
|
||||
// }, [location.pathname]);
|
||||
|
||||
// const handleLogout = () => {
|
||||
// removeToken()
|
||||
// removeRefreshToken()
|
||||
// window.location.href = Pages.auth.login
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
setSearch('')
|
||||
}, [location.pathname])
|
||||
|
||||
return (
|
||||
<div className={clx(
|
||||
'fixed z-10 right-4 left-4 top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px]',
|
||||
hasMainSidebar && (
|
||||
hasSubMenu
|
||||
? sidebarSizeClasses[sidebarSize].withSub
|
||||
: sidebarSizeClasses[sidebarSize].base
|
||||
)
|
||||
)}>
|
||||
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('header.search')}
|
||||
onChangeSearchFinal={(value) => setSearch(value)}
|
||||
/>
|
||||
</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 className='xl:hidden' to={Paths.wallet}>
|
||||
<Wallet className='xl:size-[18px] size-[17px]' color='#da2129' />
|
||||
</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='#da2129' />
|
||||
</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='px-6 mt-2'>
|
||||
<div onClick={() => handleLogout()} className='flex gap-2 items-center cursor-pointer'>
|
||||
<Logout size={20} color='#da2129' />
|
||||
<div>
|
||||
{t('sidebar.logout')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
)
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
@@ -0,0 +1,110 @@
|
||||
import { type FC, useEffect } from 'react'
|
||||
import LogoImage from '../assets/images/logo.svg'
|
||||
import LogoSmall from '../assets/images/logo-small.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Home2,
|
||||
Logout,
|
||||
} from 'iconsax-react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { Paths } from '../config/Paths'
|
||||
|
||||
|
||||
const SideBar: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { openSidebar, setOpenSidebar, hasSubMenu, setSubMenuName, setSubtMenu, subMenuName } = useSharedStore()
|
||||
const location = useLocation()
|
||||
|
||||
const isActive = (name: string) => {
|
||||
return window.location.href.includes(name)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const split = location.pathname.split('/')
|
||||
|
||||
if (split[1] === 'dashboard' || split[1] === 'products') {
|
||||
setSubMenuName(split[1])
|
||||
setSubtMenu(true)
|
||||
} else {
|
||||
setSubMenuName('')
|
||||
setSubtMenu(false)
|
||||
}
|
||||
}, [location.pathname, setSubMenuName, setSubtMenu])
|
||||
|
||||
const iconSizeSideBar = 20
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
openSidebar && <div className='fixed top-0 left-0 right-0 bottom-0 bg-black/50 bg-opacity-50 z-10' onClick={() => setOpenSidebar(false)} />
|
||||
}
|
||||
<div
|
||||
className={clx(
|
||||
'fixed xl:flex 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',
|
||||
hasSubMenu && 'w-24 !rounded-tl-none !rounded-bl-none'
|
||||
)}
|
||||
>
|
||||
<div className='flex justify-center'>
|
||||
{
|
||||
!hasSubMenu ?
|
||||
<img src={LogoImage} className='w-[140px]' />
|
||||
:
|
||||
<img src={LogoSmall} className='w-7' />
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col h-full overflow-y-auto no-scrollbar'>
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center'
|
||||
)}>
|
||||
{t('sidebar.menu')}
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Home2 variant={isActive(Paths.home) ? 'Bold' : 'Outline'} color={isActive(Paths.home) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.home_page')}
|
||||
isActive={isActive(Paths.home)}
|
||||
link={Paths.home}
|
||||
activeName={Paths.home}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col justify-end mt-14'>
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Logout variant={isActive('logout') ? 'Bold' : 'Outline'} color={isActive('logout') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title="خروج"
|
||||
isActive={isActive('logout')}
|
||||
link={`#`}
|
||||
isLogout
|
||||
activeName=''
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* منوی فرعی */}
|
||||
{
|
||||
hasSubMenu && (openSidebar || window.innerWidth > 1139) &&
|
||||
<div className='fixed xl:right-[112px] right-[96px] bg-white z-20 xl:top-4 xl:bottom-4 top-0 bottom-0 rounded-tl-[32px] rounded-bl-[32px] w-[190px] border-r border-border'>
|
||||
{
|
||||
subMenuName === 'dashboard' ? null
|
||||
// <DashboardSubMenu />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default SideBar
|
||||
@@ -0,0 +1,61 @@
|
||||
import { type FC, type ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
// import { useGetAdminPermissions } from '../pages/users/hooks/useUserData'
|
||||
|
||||
type Props = {
|
||||
icon: ReactNode,
|
||||
title: string,
|
||||
isActive: boolean,
|
||||
link: string,
|
||||
isLogout?: boolean,
|
||||
name?: string,
|
||||
activeName?: string
|
||||
}
|
||||
|
||||
const SideBarItem: FC<Props> = (props: Props) => {
|
||||
|
||||
const { hasSubMenu, setSubMenuName, setSubtMenu } = useSharedStore()
|
||||
// const { data: adminPermissions } = useGetAdminPermissions()
|
||||
const handleLogout = () => {
|
||||
// removeToken()
|
||||
// removeRefreshToken()
|
||||
// window.location.href = Pages.auth.login
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.name) {
|
||||
setSubMenuName(props.name)
|
||||
setSubtMenu(true)
|
||||
}
|
||||
}
|
||||
// if (props.activeName === '' || props.activeName && adminPermissions?.data?.permissions?.some((permission: { name: string }) => permission.name === props.activeName)) {
|
||||
if (true) {
|
||||
return (
|
||||
<Link onClick={props.isLogout ? handleLogout : handleClick} to={props.link} className={clx(
|
||||
'flex text-xs gap-9 mt-4',
|
||||
hasSubMenu && 'flex-col',
|
||||
)}>
|
||||
<div className={clx(
|
||||
'w-1 bg-primary h-6',
|
||||
!props.isActive && 'invisible',
|
||||
hasSubMenu && 'hidden',
|
||||
)}></div>
|
||||
<div className={clx(
|
||||
'flex gap-3 items-center',
|
||||
hasSubMenu && 'flex-col text-[10px]',
|
||||
)}>
|
||||
{props.icon}
|
||||
<div className={props.isActive ? 'text-primary' : ''}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SideBarItem
|
||||
@@ -0,0 +1,13 @@
|
||||
import { create } from "zustand";
|
||||
import { type SharedStoreType } from "../types/SharedTypes";
|
||||
|
||||
export const useSharedStore = create<SharedStoreType>((set) => ({
|
||||
openSidebar: false,
|
||||
setOpenSidebar: (value) => set({ openSidebar: value }),
|
||||
hasSubMenu: false,
|
||||
setSubtMenu: (value: boolean) => set({ hasSubMenu: value }),
|
||||
subMenuName: "",
|
||||
setSubMenuName: (value: string) => set({ subMenuName: value }),
|
||||
search: "",
|
||||
setSearch: (value: string) => set({ search: value }),
|
||||
}));
|
||||
@@ -0,0 +1,19 @@
|
||||
export type SharedStoreType = {
|
||||
openSidebar: boolean;
|
||||
setOpenSidebar: (value: boolean) => void;
|
||||
hasSubMenu: boolean;
|
||||
setSubtMenu: (value: boolean) => void;
|
||||
subMenuName: string;
|
||||
setSubMenuName: (value: string) => void;
|
||||
search: string;
|
||||
setSearch: (value: string) => void;
|
||||
};
|
||||
|
||||
export type PagerType = {
|
||||
page: number;
|
||||
limit: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean | null;
|
||||
nextPage: string | null;
|
||||
};
|
||||
@@ -15,6 +15,10 @@
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -12,4 +13,9 @@ export default defineConfig({
|
||||
}),
|
||||
tailwindcss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user