domain compelete api attachment
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
VITE_TOKEN_NAME = 'dsc_token'
|
||||||
|
VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
||||||
|
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
||||||
|
VITE_BASE_URL = 'http://192.168.1.134:4000'
|
||||||
|
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
|
||||||
|
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export default {
|
|
||||||
dmail: {
|
|
||||||
input: "./openapi.yaml", // مسیر فایل OpenAPI
|
|
||||||
output: {
|
|
||||||
mode: "tags-split", // یا 'single'
|
|
||||||
target: "./src/api", // خروجی فایلهای API
|
|
||||||
client: "react-query", // یا 'axios'
|
|
||||||
schemas: "./src/api/model", // خروجی مدلها
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -16,11 +16,15 @@
|
|||||||
"@react-three/drei": "^10.1.2",
|
"@react-three/drei": "^10.1.2",
|
||||||
"@react-three/fiber": "^9.1.2",
|
"@react-three/fiber": "^9.1.2",
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
|
"@tanstack/react-query": "^5.81.5",
|
||||||
|
"axios": "^1.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^25.0.1",
|
"i18next": "^25.0.1",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.511.0",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
@@ -44,6 +48,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/three": "^0.177.0",
|
"@types/three": "^0.177.0",
|
||||||
|
|||||||
Generated
+149
@@ -23,6 +23,12 @@ importers:
|
|||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.4
|
specifier: ^4.1.4
|
||||||
version: 4.1.7(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
version: 4.1.7(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||||
|
'@tanstack/react-query':
|
||||||
|
specifier: ^5.81.5
|
||||||
|
version: 5.81.5(react@19.1.0)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.10.0
|
||||||
|
version: 1.10.0
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -35,9 +41,15 @@ importers:
|
|||||||
iconsax-react:
|
iconsax-react:
|
||||||
specifier: ^0.0.8
|
specifier: ^0.0.8
|
||||||
version: 0.0.8(react@19.1.0)
|
version: 0.0.8(react@19.1.0)
|
||||||
|
js-cookie:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.511.0
|
specifier: ^0.511.0
|
||||||
version: 0.511.0(react@19.1.0)
|
version: 0.511.0(react@19.1.0)
|
||||||
|
moment-jalaali:
|
||||||
|
specifier: ^0.10.4
|
||||||
|
version: 0.10.4
|
||||||
quill:
|
quill:
|
||||||
specifier: ^2.0.3
|
specifier: ^2.0.3
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
@@ -102,6 +114,12 @@ importers:
|
|||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.22.0
|
specifier: ^9.22.0
|
||||||
version: 9.27.0
|
version: 9.27.0
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
|
'@types/moment-jalaali':
|
||||||
|
specifier: ^0.7.9
|
||||||
|
version: 0.7.9
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.0.10
|
specifier: ^19.0.10
|
||||||
version: 19.1.6
|
version: 19.1.6
|
||||||
@@ -1012,6 +1030,14 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.2.0 || ^6
|
vite: ^5.2.0 || ^6
|
||||||
|
|
||||||
|
'@tanstack/query-core@5.81.5':
|
||||||
|
resolution: {integrity: sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==}
|
||||||
|
|
||||||
|
'@tanstack/react-query@5.81.5':
|
||||||
|
resolution: {integrity: sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3':
|
'@tweenjs/tween.js@23.1.3':
|
||||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||||
|
|
||||||
@@ -1039,9 +1065,15 @@ packages:
|
|||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6':
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
|
'@types/moment-jalaali@0.7.9':
|
||||||
|
resolution: {integrity: sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==}
|
||||||
|
|
||||||
'@types/node@24.0.7':
|
'@types/node@24.0.7':
|
||||||
resolution: {integrity: sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==}
|
resolution: {integrity: sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==}
|
||||||
|
|
||||||
@@ -1219,6 +1251,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
attr-accept@2.2.5:
|
attr-accept@2.2.5:
|
||||||
resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
|
resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -1227,6 +1262,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
axios@1.10.0:
|
||||||
|
resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
@@ -1319,6 +1357,10 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
compare-versions@6.1.1:
|
compare-versions@6.1.1:
|
||||||
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
|
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
|
||||||
|
|
||||||
@@ -1380,6 +1422,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
dependency-graph@0.11.0:
|
dependency-graph@0.11.0:
|
||||||
resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==}
|
resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
@@ -1602,10 +1648,23 @@ packages:
|
|||||||
flatted@3.3.3:
|
flatted@3.3.3:
|
||||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||||
|
|
||||||
|
follow-redirects@1.15.9:
|
||||||
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
for-each@0.3.5:
|
for-each@0.3.5:
|
||||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
form-data@4.0.3:
|
||||||
|
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fs-extra@11.3.0:
|
fs-extra@11.3.0:
|
||||||
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
|
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
|
||||||
engines: {node: '>=14.14'}
|
engines: {node: '>=14.14'}
|
||||||
@@ -1897,10 +1956,17 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^19.0.0
|
react: ^19.0.0
|
||||||
|
|
||||||
|
jalaali-js@1.2.8:
|
||||||
|
resolution: {integrity: sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==}
|
||||||
|
|
||||||
jiti@2.4.2:
|
jiti@2.4.2:
|
||||||
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-cookie@3.0.5:
|
||||||
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -2132,6 +2198,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mimic-fn@2.1.0:
|
mimic-fn@2.1.0:
|
||||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2160,6 +2234,15 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
moment-jalaali@0.10.4:
|
||||||
|
resolution: {integrity: sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==}
|
||||||
|
|
||||||
|
moment-timezone@0.5.48:
|
||||||
|
resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==}
|
||||||
|
|
||||||
|
moment@2.30.1:
|
||||||
|
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -2325,6 +2408,9 @@ packages:
|
|||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
punycode.js@2.3.1:
|
punycode.js@2.3.1:
|
||||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -3945,6 +4031,13 @@ snapshots:
|
|||||||
tailwindcss: 4.1.7
|
tailwindcss: 4.1.7
|
||||||
vite: 6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
vite: 6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||||
|
|
||||||
|
'@tanstack/query-core@5.81.5': {}
|
||||||
|
|
||||||
|
'@tanstack/react-query@5.81.5(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/query-core': 5.81.5
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3': {}
|
'@tweenjs/tween.js@23.1.3': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
@@ -3980,8 +4073,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/moment-jalaali@0.7.9':
|
||||||
|
dependencies:
|
||||||
|
moment: 2.30.1
|
||||||
|
|
||||||
'@types/node@24.0.7':
|
'@types/node@24.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.8.0
|
undici-types: 7.8.0
|
||||||
@@ -4187,12 +4286,22 @@ snapshots:
|
|||||||
|
|
||||||
async-function@1.0.0: {}
|
async-function@1.0.0: {}
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
attr-accept@2.2.5: {}
|
attr-accept@2.2.5: {}
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
|
|
||||||
|
axios@1.10.0:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.9
|
||||||
|
form-data: 4.0.3
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
base64-js@1.5.1: {}
|
base64-js@1.5.1: {}
|
||||||
@@ -4286,6 +4395,10 @@ snapshots:
|
|||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
compare-versions@6.1.1: {}
|
compare-versions@6.1.1: {}
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
@@ -4351,6 +4464,8 @@ snapshots:
|
|||||||
has-property-descriptors: 1.0.2
|
has-property-descriptors: 1.0.2
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
dependency-graph@0.11.0: {}
|
dependency-graph@0.11.0: {}
|
||||||
|
|
||||||
detect-gpu@5.0.70:
|
detect-gpu@5.0.70:
|
||||||
@@ -4667,10 +4782,20 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.3.3: {}
|
flatted@3.3.3: {}
|
||||||
|
|
||||||
|
follow-redirects@1.15.9: {}
|
||||||
|
|
||||||
for-each@0.3.5:
|
for-each@0.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
|
|
||||||
|
form-data@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fs-extra@11.3.0:
|
fs-extra@11.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -4952,8 +5077,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
|
jalaali-js@1.2.8: {}
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
|
|
||||||
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
@@ -5137,6 +5266,12 @@ snapshots:
|
|||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
mimic-fn@2.1.0: {}
|
mimic-fn@2.1.0: {}
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
@@ -5159,6 +5294,18 @@ snapshots:
|
|||||||
|
|
||||||
mkdirp@3.0.1: {}
|
mkdirp@3.0.1: {}
|
||||||
|
|
||||||
|
moment-jalaali@0.10.4:
|
||||||
|
dependencies:
|
||||||
|
jalaali-js: 1.2.8
|
||||||
|
moment: 2.30.1
|
||||||
|
moment-timezone: 0.5.48
|
||||||
|
|
||||||
|
moment-timezone@0.5.48:
|
||||||
|
dependencies:
|
||||||
|
moment: 2.30.1
|
||||||
|
|
||||||
|
moment@2.30.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
@@ -5359,6 +5506,8 @@ snapshots:
|
|||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
punycode.js@2.3.1: {}
|
punycode.js@2.3.1: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|||||||
+27
-2
@@ -9,6 +9,9 @@ import i18next from 'i18next'
|
|||||||
import FaJson from './langs/fa.json'
|
import FaJson from './langs/fa.json'
|
||||||
import { I18nextProvider } from 'react-i18next'
|
import { I18nextProvider } from 'react-i18next'
|
||||||
import NewMessage from './components/newMessage/NewMessage';
|
import NewMessage from './components/newMessage/NewMessage';
|
||||||
|
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { IApiErrorRepsonse } from './types/error.types';
|
||||||
|
import ToastContainer from './components/Toast';
|
||||||
i18next.init({
|
i18next.init({
|
||||||
interpolation: { escapeValue: false },
|
interpolation: { escapeValue: false },
|
||||||
lng: 'fa',
|
lng: 'fa',
|
||||||
@@ -20,12 +23,34 @@ i18next.init({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
queryCache: new QueryCache({
|
||||||
|
onError: async (error: IApiErrorRepsonse) => {
|
||||||
|
if (error?.response?.statusCode === 401) {
|
||||||
|
console.log(401);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
retry: false
|
||||||
|
// staleTime: 86400000 // 1 day in milliseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<I18nextProvider i18n={i18next}>
|
<I18nextProvider i18n={i18next}>
|
||||||
<Main />
|
<QueryClientProvider client={queryClient}>
|
||||||
<NewMessage />
|
|
||||||
|
<Main />
|
||||||
|
<NewMessage />
|
||||||
|
<ToastContainer />
|
||||||
|
</QueryClientProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { FC, ReactNode } from 'react'
|
import { FC, ReactNode } from 'react'
|
||||||
import { clx } from '../helpers/utils'
|
import { clx } from '../helpers/utils'
|
||||||
|
import MoonLoader from "react-spinners/ClipLoader"
|
||||||
|
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -28,10 +30,8 @@ const Button: FC<ButtonProps> = (props) => {
|
|||||||
className={buttonClass}
|
className={buttonClass}
|
||||||
>
|
>
|
||||||
{props.loading ? (
|
{props.loading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<MoonLoader className='mt-1.5' size={15} color={props.variant === 'secondary' ? '#000' : '#fff'} />
|
||||||
<div className="w-3 h-3 md:w-4 md:h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
||||||
درحال بارگذاری...
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
props.children || props.label
|
props.children || props.label
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { CloseCircle, InfoCircle, TickCircle } from 'iconsax-react';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface Toast {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
type?: 'success' | 'error' | 'info';
|
||||||
|
isExiting?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let addToast: (toast: Toast) => void;
|
||||||
|
|
||||||
|
const ToastContainer: React.FC = () => {
|
||||||
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||||
|
|
||||||
|
// ثبت toast جدید
|
||||||
|
const showToast = (toast: Toast) => {
|
||||||
|
setToasts((prev) => [...prev, toast]);
|
||||||
|
|
||||||
|
// حذف خودکار بعد از ۳ ثانیه
|
||||||
|
setTimeout(() => {
|
||||||
|
setToasts((prev) => prev.map(t =>
|
||||||
|
t.id === toast.id ? { ...t, isExiting: true } : t
|
||||||
|
));
|
||||||
|
|
||||||
|
// حذف از DOM بعد از اتمام انیمیشن
|
||||||
|
setTimeout(() => {
|
||||||
|
setToasts((prev) => prev.filter((t) => t.id !== toast.id));
|
||||||
|
}, 300); // مدت زمان انیمیشن خروج
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تخصیص تابع نمایش toast به متغیر سراسری
|
||||||
|
useEffect(() => {
|
||||||
|
addToast = showToast;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-50 flex flex-col gap-2">
|
||||||
|
{toasts.map((toast) => (
|
||||||
|
<div
|
||||||
|
key={toast.id}
|
||||||
|
className={`px-4 flex items-center gap-2 backdrop-blur-2xl h-16 min-w-[300px] rounded-2xl shadow-md
|
||||||
|
${toast.isExiting ? 'animate-slide-out-right' : 'animate-slide-in-right'} ${toast.type === 'success'
|
||||||
|
? 'bg-white/70 text-black'
|
||||||
|
: toast.type === 'error'
|
||||||
|
? 'bg-white/70 text-black'
|
||||||
|
: 'bg-white/70 text-black'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
animationFillMode: 'forwards',
|
||||||
|
animationDuration: '0.3s',
|
||||||
|
animationTimingFunction: 'ease-out'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
toast.type === 'success' ? <TickCircle className='size-5' color='green' /> :
|
||||||
|
toast.type === 'error' ? <CloseCircle className='size-5' color='red' /> :
|
||||||
|
<InfoCircle className='size-5' color='blue' />
|
||||||
|
}
|
||||||
|
{toast.message}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toast = (message?: string, type: 'success' | 'error' | 'info' = 'info') => {
|
||||||
|
addToast({ id: Date.now().toString(), message: message || '', type });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToastContainer;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { getToken } from "./func";
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(async function (config) {
|
||||||
|
const token = getToken();
|
||||||
|
|
||||||
|
config.headers.Authorization = "Bearer " + token;
|
||||||
|
config.headers["x-business-id"] = localStorage.getItem(
|
||||||
|
import.meta.env.VITE_WORKSPACE_ID
|
||||||
|
);
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axiosInstance;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { getToken } from "./func";
|
||||||
|
|
||||||
|
const axiosDanak = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_DANAK_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosDanak.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosDanak.interceptors.request.use(async function (config) {
|
||||||
|
const token = getToken();
|
||||||
|
|
||||||
|
config.headers.Authorization = "Bearer " + token;
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axiosDanak;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import Cookie from "js-cookie";
|
||||||
|
|
||||||
|
export function isEmail(input: string): boolean {
|
||||||
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
|
return emailRegex.test(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberFormat(number: number): string {
|
||||||
|
return Intl.NumberFormat("fa-IR").format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToken = () => {
|
||||||
|
return Cookie.get(import.meta.env.VITE_TOKEN_NAME);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRefreshToken = () => {
|
||||||
|
return Cookie.get(import.meta.env.VITE_REFRESH_TOKEN_NAME);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isProduction = import.meta.env.MODE === "production";
|
||||||
|
const domain = isProduction ? ".danakcorp.com" : undefined;
|
||||||
|
|
||||||
|
export const setToken = async (token: string) => {
|
||||||
|
Cookie.set(import.meta.env.VITE_TOKEN_NAME, token, { domain });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setRefreshToken = async (refreshToken: string) => {
|
||||||
|
Cookie.set(import.meta.env.VITE_REFRESH_TOKEN_NAME, refreshToken, {
|
||||||
|
domain,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeToken = async () => {
|
||||||
|
Cookie.remove(import.meta.env.VITE_TOKEN_NAME, { domain, path: "/" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeRefreshToken = async () => {
|
||||||
|
Cookie.remove(import.meta.env.VITE_REFRESH_TOKEN_NAME, { domain, path: "/" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timeAgo = (date: string | Date): string => {
|
||||||
|
const now = new Date();
|
||||||
|
const past = new Date(date);
|
||||||
|
const diff = Math.floor((now.getTime() - past.getTime()) / 1000); // اختلاف بر حسب ثانیه
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ name: "سال", value: 60 * 60 * 24 * 365 },
|
||||||
|
{ name: "ماه", value: 60 * 60 * 24 * 30 },
|
||||||
|
{ name: "روز", value: 60 * 60 * 24 },
|
||||||
|
{ name: "ساعت", value: 60 * 60 },
|
||||||
|
{ name: "دقیقه", value: 60 },
|
||||||
|
{ name: "ثانیه", value: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const unit of units) {
|
||||||
|
const amount = Math.floor(diff / unit.value);
|
||||||
|
if (amount >= 1) return `${amount} ${unit.name} پیش`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "همین الان";
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import axios, { AxiosRequestConfig } from "axios";
|
||||||
|
import { getToken } from "./func";
|
||||||
|
|
||||||
|
const orvalAxiosInstance = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
orvalAxiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
orvalAxiosInstance.interceptors.request.use(async (config) => {
|
||||||
|
const token = getToken();
|
||||||
|
|
||||||
|
config.headers["Content-Type"] = "application/json";
|
||||||
|
config.headers["Accept"] = "application/json";
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const businessId = localStorage.getItem(import.meta.env.VITE_WORKSPACE_ID);
|
||||||
|
if (businessId) {
|
||||||
|
config.headers["x-business-id"] = businessId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const customAxiosInstance = <T = unknown>(
|
||||||
|
config: AxiosRequestConfig,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<T> => {
|
||||||
|
const source = axios.CancelToken.source();
|
||||||
|
const promise = orvalAxiosInstance({
|
||||||
|
...config,
|
||||||
|
...options,
|
||||||
|
cancelToken: source.token,
|
||||||
|
}).then(({ data }) => data);
|
||||||
|
|
||||||
|
// @ts-expect-error - Adding cancel method to promise
|
||||||
|
promise.cancel = () => {
|
||||||
|
source.cancel("Query was cancelled");
|
||||||
|
};
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
@@ -118,6 +118,21 @@
|
|||||||
"description_sign": "توضیحات امضا",
|
"description_sign": "توضیحات امضا",
|
||||||
"select_section": "لطفا یک بخش انتخاب کنید"
|
"select_section": "لطفا یک بخش انتخاب کنید"
|
||||||
},
|
},
|
||||||
|
"domain": {
|
||||||
|
"table": {
|
||||||
|
"record_type": "نوع رکورد",
|
||||||
|
"record_name": "نام رکورد",
|
||||||
|
"record_value": "مقدار رکورد",
|
||||||
|
"priority": "اولویت",
|
||||||
|
"ttl": "TTL",
|
||||||
|
"status": "وضعیت"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"verified": "تایید شده",
|
||||||
|
"pending": "در انتظار",
|
||||||
|
"failed": "ناموفق"
|
||||||
|
}
|
||||||
|
},
|
||||||
"select_file": "انتخاب فایل",
|
"select_file": "انتخاب فایل",
|
||||||
"upload": "آپلود",
|
"upload": "آپلود",
|
||||||
"new_message": {
|
"new_message": {
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"sidebar": {
|
|
||||||
"menu": "منو",
|
|
||||||
"received": "دریافتیها",
|
|
||||||
"sent": "ارسال شدهها",
|
|
||||||
"draft": "پیشنویسها",
|
|
||||||
"archive": "آرشیو",
|
|
||||||
"trash": "سطل زباله",
|
|
||||||
"other": "سایر",
|
|
||||||
"favorite": "مورد علاقه",
|
|
||||||
"spam": "اسپم",
|
|
||||||
"setting": "تنظیمات",
|
|
||||||
"logout": "خروج",
|
|
||||||
"new_message": "پیام جدید"
|
|
||||||
},
|
|
||||||
"new_message": {
|
|
||||||
"title": "پیام جدید",
|
|
||||||
"to": "به:",
|
|
||||||
"recipients_placeholder": "گیرندگان را وارد کنید",
|
|
||||||
"subject": "موضوع:",
|
|
||||||
"subject_placeholder": "موضوع پیام را وارد کنید",
|
|
||||||
"message_placeholder": "متن پیام خود را بنویسید...",
|
|
||||||
"send": "ارسال"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+73
-26
@@ -1,32 +1,79 @@
|
|||||||
import { FC } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import { useWorkspaces } from './hooks/useHomeData'
|
||||||
|
import { workspaceItem } from './types/HomeTypes'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
|
||||||
const Home: FC = () => {
|
const Home: FC = () => {
|
||||||
|
|
||||||
|
const getWorkspaces = useWorkspaces()
|
||||||
|
const [selectedWorkspace, setSelectedWorkspace] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedWorkspace(localStorage.getItem(import.meta.env.VITE_WORKSPACE_ID))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getWorkspaces.isError && getWorkspaces.error instanceof Error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const error = getWorkspaces.error as any;
|
||||||
|
if (error.response?.status === 403) {
|
||||||
|
localStorage.removeItem(import.meta.env.VITE_WORKSPACE_ID)
|
||||||
|
window.location.href = 'https://console.danakcorp.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [getWorkspaces.isError, getWorkspaces.data])
|
||||||
|
|
||||||
|
|
||||||
|
const handleChangeWorkspace = (id: string) => {
|
||||||
|
localStorage.setItem(import.meta.env.VITE_WORKSPACE_ID, id)
|
||||||
|
setSelectedWorkspace(id)
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
<div className='w-full flex gap-6'>
|
||||||
<div className="text-center py-12 md:py-20">
|
<div className='flex-1'>
|
||||||
<h1 className="text-2xl md:text-3xl xl:text-4xl font-bold text-gray-800 mb-4">
|
|
||||||
خوش آمدید به سیستم مدیریت ایمیل
|
<div className='mt-8 flex-wrap text-sm flex gap-6 items-center'>
|
||||||
</h1>
|
{
|
||||||
<p className="text-sm md:text-base text-gray-600 mb-8 max-w-2xl mx-auto">
|
getWorkspaces.data?.data?.workspaces?.map((item: workspaceItem) => {
|
||||||
از منوی کناری میتوانید به بخشهای مختلف سیستم دسترسی داشته باشید
|
return (
|
||||||
</p>
|
<div className={`p-6 min-w-[40%] xl:min-w-[15%] flex flex-col items-center xl:items-start flex-1 bg-white rounded-3xl cursor-pointer transition-all duration-300 hover:shadow-lg ${selectedWorkspace === item.id ? 'border border-black' : ''}`}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 md:gap-6 mt-8 md:mt-12">
|
onClick={() => handleChangeWorkspace(item.id)}
|
||||||
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
>
|
||||||
<h3 className="text-lg md:text-xl font-semibold mb-2">پیامهای دریافتی</h3>
|
<div className='mt-5'>
|
||||||
<p className="text-xs md:text-sm text-gray-600">مدیریت ایمیلهای واردشده</p>
|
{item.businessName}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
|
||||||
<h3 className="text-lg md:text-xl font-semibold mb-2">پیامهای ارسالی</h3>
|
<div className='mt-5'>
|
||||||
<p className="text-xs md:text-sm text-gray-600">مشاهده ایمیلهای ارسال شده</p>
|
{item.plan?.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
|
||||||
<h3 className="text-lg md:text-xl font-semibold mb-2">پیشنویسها</h3>
|
<div className='flex justify-between items-center mt-5 w-full'>
|
||||||
<p className="text-xs md:text-sm text-gray-600">ادامه نوشتن ایمیلهای ناتمام</p>
|
<div className=' flex gap-1'>
|
||||||
</div>
|
<div>تاریخ پایان: </div>
|
||||||
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
<div className='text-description text-sm'>{moment(item?.endDate).format('jYYYY/jMM/jDD')}</div>
|
||||||
<h3 className="text-lg md:text-xl font-semibold mb-2">تنظیمات</h3>
|
</div>
|
||||||
<p className="text-xs md:text-sm text-gray-600">پیکربندی سیستم و حساب کاربری</p>
|
|
||||||
</div>
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-5 flex justify-end w-full'>
|
||||||
|
|
||||||
|
<a onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}} target='_blank' href={`${import.meta.env.VITE_DZONE_URL}/${item.slug}`}>
|
||||||
|
{`${import.meta.env.VITE_DZONE_URL}/${item.slug}`}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className='px-6 min-w-[40%] xl:min-w-[15%] flex flex-col items-center xl:items-start flex-1 h-[178px]'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import { Add } from 'iconsax-react'
|
||||||
|
import { FC, useState } from 'react'
|
||||||
|
import DefaulModal from '../../../components/DefaulModal'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Select from '../../../components/Select'
|
||||||
|
import SwitchComponent from '../../../components/Switch'
|
||||||
|
|
||||||
|
const BoxNewAccessbility: FC = () => {
|
||||||
|
|
||||||
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="flex-1 min-w-[40%] xl:min-w-[20%] flex justify-center items-center h-[160px]"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: -1,
|
||||||
|
borderRadius: '22px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
fill="none"
|
||||||
|
// rx="22"
|
||||||
|
ry="22"
|
||||||
|
stroke="#8C90A3"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeDasharray="6,14"
|
||||||
|
// strokeDashoffset="25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div style={{ zIndex: 1 }}>
|
||||||
|
<Add size={38} color="#8C90A3" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DefaulModal
|
||||||
|
open={open}
|
||||||
|
close={() => setOpen(false)}
|
||||||
|
isHeader={true}
|
||||||
|
title_header={t('home.create_new_access')}
|
||||||
|
>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='flex items-center gap-6'>
|
||||||
|
<div className='min-w-[210px]'>
|
||||||
|
<Input
|
||||||
|
variant='search'
|
||||||
|
className='bg-opacity-25 bg-white'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
items={[
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
]}
|
||||||
|
placeholder={t('all')}
|
||||||
|
className='bg-opacity-25 bg-white w-[100px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 flex flex-wrap gap-6'>
|
||||||
|
<div className='flex-1 xl:min-w-[40%] min-w-full flex justify-between items-center border-b border-[#8C90A3] border-opacity-30 py-2'>
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<div className='size-10 rounded-xl bg-red-300'></div>
|
||||||
|
<div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
دی منو
|
||||||
|
</div>
|
||||||
|
<div className='text-xs text-description'>
|
||||||
|
منو رستوران
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SwitchComponent
|
||||||
|
active
|
||||||
|
onChange={() => { }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex-1 xl:min-w-[40%] min-w-full flex justify-between items-center border-b border-[#8C90A3] border-opacity-30 py-2'>
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<div className='size-10 rounded-xl bg-blue-300'></div>
|
||||||
|
<div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
دی منو
|
||||||
|
</div>
|
||||||
|
<div className='text-xs text-description'>
|
||||||
|
منو رستوران
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SwitchComponent
|
||||||
|
active
|
||||||
|
onChange={() => { }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex-1 xl:min-w-[40%] min-w-full flex justify-between items-center border-b border-[#8C90A3] border-opacity-30 py-2'>
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<div className='size-10 rounded-xl bg-green-300'></div>
|
||||||
|
<div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
دی منو
|
||||||
|
</div>
|
||||||
|
<div className='text-xs text-description'>
|
||||||
|
منو رستوران
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SwitchComponent
|
||||||
|
active
|
||||||
|
onChange={() => { }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex-1 min-w-[40%]'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BoxNewAccessbility
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ArrowLeft } from 'iconsax-react'
|
||||||
|
import CoverImage from '../../../assets/images/banner.png'
|
||||||
|
|
||||||
|
const DanakLearning: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='bg-white w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div>
|
||||||
|
{t('home.danak_learning')}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2 items-center text-[10px]'>
|
||||||
|
<div>{t('home.see_all')}</div>
|
||||||
|
<ArrowLeft size={12} color='black' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-7'>
|
||||||
|
<div className='flex gap-3'>
|
||||||
|
<div>
|
||||||
|
<img src={CoverImage} alt='danak-learning-1' className='w-36 h-24 object-cover rounded-2xl' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='text-xs'>
|
||||||
|
<div className='leading-5'>
|
||||||
|
لورم ایپسوم متن ساختگی با تولید سادگی
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-1 text-[11px] mt-3 text-description'>
|
||||||
|
<div>دیزاین ,</div>
|
||||||
|
<div>۲۴ دقیقه</div>
|
||||||
|
</div>
|
||||||
|
<div className='text-description text-[11px] mt-2'>
|
||||||
|
آذر ماه 1403
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DanakLearning
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { FC, ReactNode } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string
|
||||||
|
icon: ReactNode,
|
||||||
|
color: string,
|
||||||
|
count: number,
|
||||||
|
description: string,
|
||||||
|
to?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemDashboard: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to={props.to ? props.to : ''} className='p-6 min-w-[40%] xl:min-w-[15%] flex flex-col items-center xl:items-start flex-1 h-[178px] bg-white rounded-3xl'>
|
||||||
|
<div className='size-10 rounded-full bg-[#EEF0F7] flex justify-center items-center'>
|
||||||
|
{props.icon}
|
||||||
|
</div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
{props.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-5 text-xs text-description flex gap-1.5 items-center'>
|
||||||
|
<div style={{ background: props.color }} className='size-1.5 rounded-full'></div>
|
||||||
|
<div className='flex gap-0.5'>
|
||||||
|
<div>{props.count}</div>
|
||||||
|
<div className='whitespace-nowrap'>
|
||||||
|
{props.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ItemDashboard
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/HomeService";
|
||||||
|
|
||||||
|
export const useWorkspaces = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["workspaces"],
|
||||||
|
queryFn: api.getWorkspaces,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import danakAxios from "../../../config/axiosDanak";
|
||||||
|
|
||||||
|
export const getWorkspaces = async () => {
|
||||||
|
const { data } = await danakAxios.get(
|
||||||
|
`/subscriptions/workspaces/${import.meta.env.VITE_SERVICE_ID}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { UserItemType } from "../../users/types/UserTypes";
|
||||||
|
|
||||||
|
export type workspaceItem = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
plan: {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
duration: number;
|
||||||
|
price: number;
|
||||||
|
originalPrice: number;
|
||||||
|
isActive: boolean;
|
||||||
|
service: {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
isDanakSuggest: boolean;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
createDate: string;
|
||||||
|
userCount: number;
|
||||||
|
serviceLanguage: string;
|
||||||
|
softwareLanguage: string;
|
||||||
|
metaDescription: string;
|
||||||
|
isActive: boolean;
|
||||||
|
showInSlider: boolean;
|
||||||
|
link: string;
|
||||||
|
icon: string;
|
||||||
|
coverUrl: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
};
|
||||||
|
deletedAt: string | null;
|
||||||
|
};
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
businessName: string;
|
||||||
|
businessPhone: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
status: string;
|
||||||
|
staff: UserItemType[];
|
||||||
|
};
|
||||||
@@ -12,7 +12,7 @@ import Signture from './signture/Signture'
|
|||||||
|
|
||||||
const Setting: FC = () => {
|
const Setting: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [activeTab, setActiveTab] = useState<SettingTabEnum>(SettingTabEnum.SETTING_PERSONALITY)
|
const [activeTab, setActiveTab] = useState<SettingTabEnum>(SettingTabEnum.SETTING_DOMAIN)
|
||||||
|
|
||||||
const renderActiveTab = () => {
|
const renderActiveTab = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
|
|||||||
+118
-109
@@ -1,145 +1,154 @@
|
|||||||
import Button from '@/components/Button'
|
|
||||||
import Input from '@/components/Input'
|
|
||||||
import Table from '@/components/Table'
|
import Table from '@/components/Table'
|
||||||
import { FC } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle, Copy, CloseCircle, Clock, TickSquare } from 'iconsax-react'
|
||||||
import { Copy } from 'iconsax-react'
|
import CreateDomain from './components/CreateDomain'
|
||||||
|
import { useGetDnsRecords } from './hooks/useDomainData'
|
||||||
interface DNSRecord extends Record<string, unknown> {
|
import { DnsRecordType } from './types/Types'
|
||||||
id: number
|
|
||||||
status: 'verified' | 'pending'
|
|
||||||
type: string
|
|
||||||
name: string
|
|
||||||
value: string
|
|
||||||
number: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const Domain: FC = () => {
|
const Domain: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [disabled, setDisabled] = useState(false)
|
||||||
|
const { data, isLoading } = useGetDnsRecords(disabled)
|
||||||
|
const [copiedField, setCopiedField] = useState<string | null>(null)
|
||||||
|
|
||||||
// نمونه دادههای DNS records طبق تصویر
|
useEffect(() => {
|
||||||
const dnsRecords: DNSRecord[] = [
|
if (data?.data?.overallStatus?.isVerified && !disabled) {
|
||||||
{
|
setDisabled(true)
|
||||||
id: 1,
|
|
||||||
status: 'verified',
|
|
||||||
type: 'TXT',
|
|
||||||
name: 'example.com',
|
|
||||||
value: 'v=spf1 include:_spf.example.com ~all',
|
|
||||||
number: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
status: 'verified',
|
|
||||||
type: 'MX | Priority:10',
|
|
||||||
name: 'example.com',
|
|
||||||
value: '10 mail.example.com',
|
|
||||||
number: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
status: 'verified',
|
|
||||||
type: 'Cname | No CDN',
|
|
||||||
name: 'example.com',
|
|
||||||
value: 'v=spf1 include:_spf.example.com ~all',
|
|
||||||
number: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
status: 'verified',
|
|
||||||
type: 'TXT',
|
|
||||||
name: 'example.com',
|
|
||||||
value: 'v=spf1 include:_spf.example.com ~all',
|
|
||||||
number: 1
|
|
||||||
}
|
}
|
||||||
]
|
}, [data])
|
||||||
|
|
||||||
|
const getStatusIcon = (status: DnsRecordType['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'VERIFIED':
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TickCircle size={20} color="#22C55E" variant="Bold" />
|
||||||
|
<span className="text-green-500 text-xs">{t('domain.status.verified')}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
case 'PENDING':
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Clock size={20} color="#F59E0B" variant="Bold" />
|
||||||
|
<span className="text-yellow-500 text-xs">{t('domain.status.pending')}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
case 'FAILED':
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CloseCircle size={20} color="#EF4444" variant="Bold" />
|
||||||
|
<span className="text-red-500 text-xs">{t('domain.status.failed')}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyToClipboard = (text: string, fieldKey: string) => {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
setCopiedField(fieldKey)
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopiedField(null)
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'شماره',
|
title: t('domain.table.record_type'),
|
||||||
key: 'number',
|
key: 'type',
|
||||||
width: '80px',
|
width: '100px',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'مقدار رکورد',
|
title: t('domain.table.record_name'),
|
||||||
key: 'value',
|
|
||||||
render: (record: DNSRecord) => (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
|
|
||||||
<span className="truncate max-w-[300px]">{record.value}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'نام رکورد',
|
|
||||||
key: 'name',
|
key: 'name',
|
||||||
render: (record: DNSRecord) => (
|
render: (record: DnsRecordType) => {
|
||||||
<div className="flex items-center gap-2">
|
const fieldKey = `name-${record.id}`
|
||||||
<button className="text-blue-500 hover:text-blue-700">
|
const isCopied = copiedField === fieldKey
|
||||||
<Copy size={16} color='#0038FF' />
|
|
||||||
</button>
|
return (
|
||||||
<span>{record.name}</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<button
|
||||||
|
onClick={() => copyToClipboard(record.name, fieldKey)}
|
||||||
|
className={`transition-all duration-200 ${isCopied ? 'text-green-500' : 'text-blue-500 hover:text-blue-700'}`}
|
||||||
|
>
|
||||||
|
{isCopied ? (
|
||||||
|
<TickSquare size={16} color='#22C55E' variant="Bold" />
|
||||||
|
) : (
|
||||||
|
<Copy size={16} color='#0038FF' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<span className="truncate max-w-[200px] dltr">{record.name}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('domain.table.record_value'),
|
||||||
|
key: 'value',
|
||||||
|
render: (record: DnsRecordType) => {
|
||||||
|
const fieldKey = `value-${record.id}`
|
||||||
|
const isCopied = copiedField === fieldKey
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => copyToClipboard(record.value, fieldKey)}
|
||||||
|
className={`transition-all duration-200 ${isCopied ? 'text-green-500' : 'text-blue-500 hover:text-blue-700'}`}
|
||||||
|
>
|
||||||
|
{isCopied ? (
|
||||||
|
<TickSquare size={16} color='#22C55E' variant="Bold" />
|
||||||
|
) : (
|
||||||
|
<Copy size={16} color='#0038FF' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<span className="truncate max-w-[300px] dltr">{record.value}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('domain.table.priority'),
|
||||||
|
key: 'priority',
|
||||||
|
width: '80px',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (record: DnsRecordType) => (
|
||||||
|
<span>{record.priority || '-'}</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'نوع رکورد',
|
title: t('domain.table.ttl'),
|
||||||
key: 'type',
|
key: 'ttl',
|
||||||
|
width: '80px',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (record: DnsRecordType) => (
|
||||||
|
<span>{record.ttl}</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'وضعیت',
|
title: t('domain.table.status'),
|
||||||
key: 'status',
|
key: 'status',
|
||||||
render: (record: DNSRecord) => (
|
render: (record: DnsRecordType) => getStatusIcon(record.status)
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{record.status === 'verified' && (
|
|
||||||
<>
|
|
||||||
<TickCircle size={20} color="#22C55E" variant="Bold" />
|
|
||||||
<span className="text-green-500 text-xs">ثبت شده</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const dnsRecords = data?.data?.dnsRecords || []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='flex xl:flex-row gap-4 flex-col justify-between mt-9'>
|
<CreateDomain />
|
||||||
<div>
|
|
||||||
<div className='xl:text-lg'>
|
|
||||||
{t('setting.record_dns')}
|
|
||||||
</div>
|
|
||||||
<p className='mt-2 xl:text-sm text-xs font-extralight'>
|
|
||||||
{t('setting.record_dns_description')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<Input
|
|
||||||
placeholder={t('setting.your_domain')}
|
|
||||||
className='xl:w-[300px]'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant='secondary'
|
|
||||||
className='w-fit xl:px-14 px-8 border border-black'
|
|
||||||
label={t('setting.submit')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div className='mt-9'>
|
<div className='mt-9'>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={dnsRecords}
|
data={dnsRecords}
|
||||||
className="!mt-0"
|
className="!mt-0"
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import Button from '@/components/Button'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import { toast } from '@/components/Toast'
|
||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useCreateDomain, useGetDomains } from '../hooks/useDomainData'
|
||||||
|
import { ErrorType } from '@/helpers/types'
|
||||||
|
|
||||||
|
const CreateDomain: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { mutate: createDomain, isPending } = useCreateDomain()
|
||||||
|
const { data: domains } = useGetDomains()
|
||||||
|
const [domain, setDomain] = useState('')
|
||||||
|
const [isVerified, setIsVerified] = useState<boolean>(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (domains?.data?.domain) {
|
||||||
|
setDomain(domains?.data?.domain?.name)
|
||||||
|
if (domains?.data?.domain?.isVerified) {
|
||||||
|
setIsVerified(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [domains])
|
||||||
|
|
||||||
|
|
||||||
|
const handleCreateDomain = () => {
|
||||||
|
createDomain({
|
||||||
|
name: domain,
|
||||||
|
notes: domain
|
||||||
|
}, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast(data.message, 'success')
|
||||||
|
setDomain('')
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='flex xl:flex-row gap-4 flex-col justify-between mt-9 items-end'>
|
||||||
|
<div>
|
||||||
|
<div className='xl:text-lg'>
|
||||||
|
{t('setting.record_dns')}
|
||||||
|
</div>
|
||||||
|
<p className='mt-2 xl:text-sm text-xs font-extralight'>
|
||||||
|
{t('setting.record_dns_description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<Input
|
||||||
|
placeholder={t('setting.your_domain')}
|
||||||
|
className='xl:w-[300px]'
|
||||||
|
value={domain}
|
||||||
|
onChange={(e) => setDomain(e.target.value)}
|
||||||
|
readOnly={isVerified}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
!isVerified &&
|
||||||
|
<Button
|
||||||
|
variant='secondary'
|
||||||
|
className='w-fit xl:px-14 px-8 border border-black'
|
||||||
|
label={t('setting.submit')}
|
||||||
|
onClick={handleCreateDomain}
|
||||||
|
loading={isPending}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateDomain
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import { CreateDomainType } from "../types/Types";
|
||||||
|
import * as api from "../service/DomainService";
|
||||||
|
|
||||||
|
export const useCreateDomain = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: CreateDomainType) => api.createDomain(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetDomains = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["domains"],
|
||||||
|
queryFn: () => api.getDomains(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetDnsRecords = (disabled?: boolean) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["dns-records"],
|
||||||
|
queryFn: () => api.getDnsRecords(),
|
||||||
|
refetchInterval: 3000,
|
||||||
|
enabled: !disabled,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVerifyDnsRecord = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["verify-dns-record"],
|
||||||
|
queryFn: () => api.verifyDnsRecord(),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from "@/config/axios";
|
||||||
|
import { CreateDomainType } from "../types/Types";
|
||||||
|
|
||||||
|
export const createDomain = async (params: CreateDomainType) => {
|
||||||
|
const { data } = await axios.post(`/domains`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDomains = async () => {
|
||||||
|
const { data } = await axios.get(`/domains`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDnsRecords = async () => {
|
||||||
|
const { data } = await axios.get(`/domains/dns-records`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyDnsRecord = async () => {
|
||||||
|
const { data } = await axios.get(`/domains/verify-dns`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { IResponse } from "@/types/response.types";
|
||||||
|
|
||||||
|
export type CreateDomainType = {
|
||||||
|
name: string;
|
||||||
|
notes?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DnsRecordType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
wildduckId: string | null;
|
||||||
|
name: string;
|
||||||
|
type: "MX" | "SPF" | "DMARC" | "DKIM" | "TXT" | "A" | "AAAA" | "CNAME";
|
||||||
|
value: string;
|
||||||
|
ttl: number;
|
||||||
|
priority: number | null;
|
||||||
|
status: "PENDING" | "VERIFIED" | "FAILED";
|
||||||
|
isRequired: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
lastVerifiedAt: string | null;
|
||||||
|
lastCheckedAt: string | null;
|
||||||
|
description: string;
|
||||||
|
errorMessage: string | null;
|
||||||
|
verificationAttempts: number;
|
||||||
|
nextVerificationAt: string | null;
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DnsRecordResponseType = IResponse<{
|
||||||
|
dnsRecords: DnsRecordType[];
|
||||||
|
}>;
|
||||||
@@ -8,10 +8,11 @@ import DraftList from '@/pages/draft/List'
|
|||||||
import ArchiveList from '@/pages/archive/List'
|
import ArchiveList from '@/pages/archive/List'
|
||||||
import TrashList from '@/pages/Trash/List'
|
import TrashList from '@/pages/Trash/List'
|
||||||
import DetailEmail from '@/pages/received/Detail'
|
import DetailEmail from '@/pages/received/Detail'
|
||||||
|
import Home from '@/pages/home/Home'
|
||||||
const AppRouter: FC = () => {
|
const AppRouter: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<ReceivedList />} />
|
<Route path='/' element={<Home />} />
|
||||||
<Route path={Paths.received} element={<ReceivedList />} />
|
<Route path={Paths.received} element={<ReceivedList />} />
|
||||||
<Route path={Paths.setting} element={<Setting />} />
|
<Route path={Paths.setting} element={<Setting />} />
|
||||||
<Route path={Paths.sent} element={<SentList />} />
|
<Route path={Paths.sent} element={<SentList />} />
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { DefaultError } from "@tanstack/react-query";
|
||||||
|
import { IBaseResponse } from "./response.types";
|
||||||
|
|
||||||
|
interface IError {
|
||||||
|
message: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IErrorResponse extends IBaseResponse {
|
||||||
|
error: IError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApiErrorRepsonse extends DefaultError {
|
||||||
|
response?: IErrorResponse;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface IBaseResponse {
|
||||||
|
statusCode: number;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
export interface IResponse<T> extends IBaseResponse {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user