single service + 404 page and 500 page
This commit is contained in:
@@ -14,9 +14,12 @@
|
|||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"critters": "^0.0.23",
|
"critters": "^0.0.23",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
|
"framer-motion": "^12.7.3",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"next": "15.2.3",
|
"next": "15.2.3",
|
||||||
|
"rc-rate": "^2.13.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-spinners": "^0.15.0",
|
"react-spinners": "^0.15.0",
|
||||||
@@ -30,6 +33,7 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
Generated
+131
@@ -23,15 +23,24 @@ importers:
|
|||||||
formik:
|
formik:
|
||||||
specifier: ^2.4.6
|
specifier: ^2.4.6
|
||||||
version: 2.4.6(react@19.1.0)
|
version: 2.4.6(react@19.1.0)
|
||||||
|
framer-motion:
|
||||||
|
specifier: ^12.7.3
|
||||||
|
version: 12.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
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:
|
js-cookie:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
moment-jalaali:
|
||||||
|
specifier: ^0.10.4
|
||||||
|
version: 0.10.4
|
||||||
next:
|
next:
|
||||||
specifier: 15.2.3
|
specifier: 15.2.3
|
||||||
version: 15.2.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 15.2.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
rc-rate:
|
||||||
|
specifier: ^2.13.1
|
||||||
|
version: 2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
react:
|
react:
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.1.0
|
version: 19.1.0
|
||||||
@@ -66,6 +75,9 @@ importers:
|
|||||||
'@types/js-cookie':
|
'@types/js-cookie':
|
||||||
specifier: ^3.0.6
|
specifier: ^3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
|
'@types/moment-jalaali':
|
||||||
|
specifier: ^0.7.9
|
||||||
|
version: 0.7.9
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20
|
specifier: ^20
|
||||||
version: 20.17.30
|
version: 20.17.30
|
||||||
@@ -94,6 +106,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.0':
|
||||||
|
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@emnapi/core@1.4.3':
|
'@emnapi/core@1.4.3':
|
||||||
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
|
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
|
||||||
|
|
||||||
@@ -478,6 +494,9 @@ packages:
|
|||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
|
'@types/moment-jalaali@0.7.9':
|
||||||
|
resolution: {integrity: sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==}
|
||||||
|
|
||||||
'@types/node@20.17.30':
|
'@types/node@20.17.30':
|
||||||
resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==}
|
resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==}
|
||||||
|
|
||||||
@@ -740,6 +759,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
classnames@2.5.1:
|
||||||
|
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||||
|
|
||||||
client-only@0.0.1:
|
client-only@0.0.1:
|
||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
|
||||||
@@ -1093,6 +1115,20 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.8.0'
|
react: '>=16.8.0'
|
||||||
|
|
||||||
|
framer-motion@12.7.3:
|
||||||
|
resolution: {integrity: sha512-dNT4l5gEnUo2ytXLUBUf6AI21dZ77TMclDKE3ElaIHZ8m90nJ/NCcExW51zdSIaS0RhAS5iXcF7bEIxZe8XG2g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/is-prop-valid': '*'
|
||||||
|
react: ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/is-prop-valid':
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
@@ -1310,6 +1346,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1467,6 +1506,21 @@ packages:
|
|||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
|
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==}
|
||||||
|
|
||||||
|
motion-dom@12.7.3:
|
||||||
|
resolution: {integrity: sha512-IjMt1YJHrvyvruFvmpmd6bGXXGCvmygrnvSb3aZ8KhOzF4H3PulU+cMBzH+U8TBJHjC/mnmJFRIA1Cu4vBfcBA==}
|
||||||
|
|
||||||
|
motion-utils@12.7.2:
|
||||||
|
resolution: {integrity: sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -1611,6 +1665,19 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
rc-rate@2.13.1:
|
||||||
|
resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==}
|
||||||
|
engines: {node: '>=8.x'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.9.0'
|
||||||
|
react-dom: '>=16.9.0'
|
||||||
|
|
||||||
|
rc-util@5.44.4:
|
||||||
|
resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.9.0'
|
||||||
|
react-dom: '>=16.9.0'
|
||||||
|
|
||||||
react-dom@19.1.0:
|
react-dom@19.1.0:
|
||||||
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1622,6 +1689,9 @@ packages:
|
|||||||
react-is@16.13.1:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
|
react-is@18.3.1:
|
||||||
|
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||||
|
|
||||||
react-spinners@0.15.0:
|
react-spinners@0.15.0:
|
||||||
resolution: {integrity: sha512-ZO3/fNB9Qc+kgpG3SfdlMnvTX6LtLmTnOogb3W6sXIaU/kZ1ydEViPfZ06kSOaEsor58C/tzXw2wROGQu3X2pA==}
|
resolution: {integrity: sha512-ZO3/fNB9Qc+kgpG3SfdlMnvTX6LtLmTnOogb3W6sXIaU/kZ1ydEViPfZ06kSOaEsor58C/tzXw2wROGQu3X2pA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1642,6 +1712,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1:
|
||||||
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
|
|
||||||
regexp.prototype.flags@1.5.4:
|
regexp.prototype.flags@1.5.4:
|
||||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1938,6 +2011,10 @@ snapshots:
|
|||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.0':
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@emnapi/core@1.4.3':
|
'@emnapi/core@1.4.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.0.2
|
'@emnapi/wasi-threads': 1.0.2
|
||||||
@@ -2250,6 +2327,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
|
'@types/moment-jalaali@0.7.9':
|
||||||
|
dependencies:
|
||||||
|
moment: 2.30.1
|
||||||
|
|
||||||
'@types/node@20.17.30':
|
'@types/node@20.17.30':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
@@ -2544,6 +2625,8 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
classnames@2.5.1: {}
|
||||||
|
|
||||||
client-only@0.0.1: {}
|
client-only@0.0.1: {}
|
||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
@@ -3057,6 +3140,15 @@ snapshots:
|
|||||||
tiny-warning: 1.0.3
|
tiny-warning: 1.0.3
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
framer-motion@12.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
motion-dom: 12.7.3
|
||||||
|
motion-utils: 12.7.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
function.prototype.name@1.1.8:
|
function.prototype.name@1.1.8:
|
||||||
@@ -3297,6 +3389,8 @@ snapshots:
|
|||||||
has-symbols: 1.1.0
|
has-symbols: 1.1.0
|
||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
|
|
||||||
|
jalaali-js@1.2.8: {}
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
|
|
||||||
js-cookie@3.0.5: {}
|
js-cookie@3.0.5: {}
|
||||||
@@ -3423,6 +3517,24 @@ snapshots:
|
|||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
|
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: {}
|
||||||
|
|
||||||
|
motion-dom@12.7.3:
|
||||||
|
dependencies:
|
||||||
|
motion-utils: 12.7.2
|
||||||
|
|
||||||
|
motion-utils@12.7.2: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
@@ -3571,6 +3683,21 @@ snapshots:
|
|||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
|
rc-rate@2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
classnames: 2.5.1
|
||||||
|
rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
rc-util@5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
react-is: 18.3.1
|
||||||
|
|
||||||
react-dom@19.1.0(react@19.1.0):
|
react-dom@19.1.0(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@@ -3580,6 +3707,8 @@ snapshots:
|
|||||||
|
|
||||||
react-is@16.13.1: {}
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
|
react-is@18.3.1: {}
|
||||||
|
|
||||||
react-spinners@0.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
react-spinners@0.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@@ -3604,6 +3733,8 @@ snapshots:
|
|||||||
get-proto: 1.0.1
|
get-proto: 1.0.1
|
||||||
which-builtin-type: 1.2.1
|
which-builtin-type: 1.2.1
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1: {}
|
||||||
|
|
||||||
regexp.prototype.flags@1.5.4:
|
regexp.prototype.flags@1.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function Error({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string }
|
||||||
|
reset: () => void
|
||||||
|
}) {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
// Log the error to an error reporting service
|
||||||
|
console.error(error)
|
||||||
|
}, [error])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col items-center justify-center bg-background p-4 relative overflow-hidden">
|
||||||
|
{/* Animated background elements */}
|
||||||
|
{mounted && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.4 }}
|
||||||
|
transition={{ duration: 1 }}
|
||||||
|
className="absolute inset-0 z-0"
|
||||||
|
>
|
||||||
|
{[...Array(6)].map((_, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={`circle-${i}`}
|
||||||
|
className="absolute rounded-full bg-[#FF3E39]/5"
|
||||||
|
initial={{
|
||||||
|
x: Math.random() * 100 - 50 + '%',
|
||||||
|
y: Math.random() * 100 - 50 + '%',
|
||||||
|
scale: Math.random() * 0.5 + 0.5,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x: [
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
],
|
||||||
|
y: [
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 20 + Math.random() * 10,
|
||||||
|
ease: 'easeInOut',
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: `${Math.random() * 300 + 100}px`,
|
||||||
|
height: `${Math.random() * 300 + 100}px`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="max-w-2xl w-full text-center z-10 relative">
|
||||||
|
{mounted && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
<div className="text-9xl font-bold text-primary/10">خطا</div>
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0 }}
|
||||||
|
animate={{ y: [0, -10, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }}
|
||||||
|
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
|
>
|
||||||
|
{/* <svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M60 10C32.4 10 10 32.4 10 60C10 87.6 32.4 110 60 110C87.6 110 110 87.6 110 60C110 32.4 87.6 10 60 10ZM60 100C37.9 100 20 82.1 20 60C20 37.9 37.9 20 60 20C82.1 20 100 37.9 100 60C100 82.1 82.1 100 60 100ZM55 75H65V85H55V75ZM55 35H65V65H55V35Z" fill="#000000" />
|
||||||
|
</svg> */}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.5 }}
|
||||||
|
className="mt-8 text-2xl md:text-3xl font-bold"
|
||||||
|
>
|
||||||
|
یک خطا رخ داده است!
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.5 }}
|
||||||
|
className="mt-4 text-description"
|
||||||
|
>
|
||||||
|
متأسفانه در حین پردازش درخواست شما خطایی رخ داده است. لطفا دوباره تلاش کنید.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.6, duration: 0.5 }}
|
||||||
|
className="mt-8 flex flex-wrap gap-4 justify-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => reset()}
|
||||||
|
className="bg-black hover:bg-black/90 transition-colors text-white py-3 px-8 rounded-2xl"
|
||||||
|
>
|
||||||
|
تلاش مجدد
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.8, duration: 0.5 }}
|
||||||
|
className="mt-12 flex flex-wrap justify-center gap-6"
|
||||||
|
>
|
||||||
|
<Link href="/products" className="text-black hover:underline">
|
||||||
|
محصولات
|
||||||
|
</Link>
|
||||||
|
<Link href="/about" className="text-black hover:underline">
|
||||||
|
درباره ما
|
||||||
|
</Link>
|
||||||
|
<Link href="/contact" className="text-black hover:underline">
|
||||||
|
تماس با ما
|
||||||
|
</Link>
|
||||||
|
<Link href="/blogs" className="text-black hover:underline">
|
||||||
|
بلاگ
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -99,3 +99,7 @@ tbody tr {
|
|||||||
direction: rtl;
|
direction: rtl;
|
||||||
font-family: "irancell" !important;
|
font-family: "irancell" !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rc-rate-star {
|
||||||
|
margin-right: 3px !important; /* فاصله بین ستارهها */
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import 'rc-rate/assets/index.css';
|
||||||
import "@/assets/fonts/irancell/style.css";
|
import "@/assets/fonts/irancell/style.css";
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import Header from "@/shared/Header";
|
import Header from "@/shared/Header";
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col items-center justify-center bg-background p-4 relative overflow-hidden">
|
||||||
|
{/* Animated background elements */}
|
||||||
|
{mounted && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.4 }}
|
||||||
|
transition={{ duration: 1 }}
|
||||||
|
className="absolute inset-0 z-0"
|
||||||
|
>
|
||||||
|
{[...Array(6)].map((_, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={`circle-${i}`}
|
||||||
|
className="absolute rounded-full bg-black/5"
|
||||||
|
initial={{
|
||||||
|
x: Math.random() * 100 - 50 + '%',
|
||||||
|
y: Math.random() * 100 - 50 + '%',
|
||||||
|
scale: Math.random() * 0.5 + 0.5,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x: [
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
],
|
||||||
|
y: [
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
Math.random() * 100 - 50 + '%',
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 20 + Math.random() * 10,
|
||||||
|
ease: 'easeInOut',
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: `${Math.random() * 300 + 100}px`,
|
||||||
|
height: `${Math.random() * 300 + 100}px`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="max-w-2xl w-full text-center z-10 relative">
|
||||||
|
{mounted && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
<div className="text-9xl font-bold text-primary/10">404</div>
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0 }}
|
||||||
|
animate={{ y: [0, -10, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }}
|
||||||
|
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.5 }}
|
||||||
|
className="mt-8 text-2xl md:text-3xl font-bold"
|
||||||
|
>
|
||||||
|
صفحه موردنظر پیدا نشد!
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.5 }}
|
||||||
|
className="mt-4 text-description"
|
||||||
|
>
|
||||||
|
متأسفانه صفحهای که به دنبال آن هستید وجود ندارد یا حذف شده است.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.6, duration: 0.5 }}
|
||||||
|
className="mt-8"
|
||||||
|
>
|
||||||
|
<Link href="/">
|
||||||
|
<button className="bg-black hover:bg-black/90 transition-colors text-white py-3 px-8 rounded-2xl">
|
||||||
|
بازگشت به صفحه اصلی
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.8, duration: 0.5 }}
|
||||||
|
className="mt-12 flex flex-wrap justify-center gap-6"
|
||||||
|
>
|
||||||
|
<Link href="/products" className="text-black hover:underline">
|
||||||
|
محصولات
|
||||||
|
</Link>
|
||||||
|
<Link href="/about" className="text-black hover:underline">
|
||||||
|
درباره ما
|
||||||
|
</Link>
|
||||||
|
<Link href="/contact" className="text-black hover:underline">
|
||||||
|
تماس با ما
|
||||||
|
</Link>
|
||||||
|
<Link href="/blogs" className="text-black hover:underline">
|
||||||
|
بلاگ
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
'use client'
|
||||||
|
import { FC, Fragment } from 'react'
|
||||||
|
import { useGetDetailService } from './hooks/useProductData'
|
||||||
|
import ServiceHeader from './components/ServiceHeader'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import ServiceImages from './components/ServiceImages'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Loading from '../loading'
|
||||||
|
import Rate from 'rc-rate'
|
||||||
|
import { ReviewItem } from './types/ProductTypes'
|
||||||
|
|
||||||
|
// Default avatar image
|
||||||
|
const defaultAvatar = '/images/logo.svg' // Using an existing image
|
||||||
|
|
||||||
|
|
||||||
|
const DetailService: FC<{ id: string }> = ({ id }) => {
|
||||||
|
|
||||||
|
// const { t } = useTranslation('global')
|
||||||
|
// const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
|
||||||
|
|
||||||
|
const getDetailService = useGetDetailService(id)
|
||||||
|
|
||||||
|
// Show the skeleton loading component while data is loading
|
||||||
|
if (getDetailService.isLoading) {
|
||||||
|
return <Loading />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex gap-6 mt-4'>
|
||||||
|
|
||||||
|
{
|
||||||
|
<Fragment>
|
||||||
|
<div className='flex-1 max-w-full'>
|
||||||
|
|
||||||
|
{getDetailService.data?.data.danakService && (
|
||||||
|
<ServiceHeader data={getDetailService.data.data.danakService} />
|
||||||
|
)}
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col overflowX justify-between items-center xl:gap-8 gap-5'>
|
||||||
|
<div className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4'>
|
||||||
|
<div className='text-description xl:text-xs text-[11px]'>
|
||||||
|
توسعه شرکت
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 text-sm'>
|
||||||
|
دانک
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4'>
|
||||||
|
<div className='text-description xl:text-xs text-[11px]'>
|
||||||
|
تعداد کاربر
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 text-sm'>
|
||||||
|
+{getDetailService?.data?.data?.danakService?.userCount}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4'>
|
||||||
|
<div className='text-description xl:text-xs text-[11px]'>
|
||||||
|
سال تولید
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 text-sm'>
|
||||||
|
{getDetailService?.data?.data?.danakService?.createdAt ?
|
||||||
|
moment(getDetailService.data.data.danakService.createdAt).format('jYYYY/jMM/jDD') : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4'>
|
||||||
|
<div className='text-description xl:text-xs text-[11px]'>
|
||||||
|
زبان برنامه
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 text-sm'>
|
||||||
|
{getDetailService?.data?.data?.danakService?.serviceLanguage === 'fa' ? 'فارسی' : 'انگلیسی'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4'>
|
||||||
|
<div className='text-description xl:text-xs text-[11px]'>
|
||||||
|
امتیاز
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 dltr flex gap-1 text-sm'>
|
||||||
|
<Rate
|
||||||
|
count={5}
|
||||||
|
style={{ fontSize: 15 }}
|
||||||
|
value={getDetailService?.data?.data?.averageRating}
|
||||||
|
disabled
|
||||||
|
allowHalf
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{getDetailService?.data?.data?.danakService?.images && (
|
||||||
|
<ServiceImages images={getDetailService.data.data.danakService.images} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='mt-8 bg-white p-6 rounded-3xl'>
|
||||||
|
<div>
|
||||||
|
توضیحات کامل
|
||||||
|
</div>
|
||||||
|
{getDetailService.data?.data?.danakService?.metaDescription && (
|
||||||
|
<p dangerouslySetInnerHTML={{ __html: getDetailService.data.data.danakService.metaDescription }} className='text-[13px] mt-4 leading-6'>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
|
||||||
|
{/* {
|
||||||
|
getDetailService.data?.data?.purchased &&
|
||||||
|
<CreateReview refetch={() => getDetailService.refetch()} />
|
||||||
|
} */}
|
||||||
|
<div className='flex-1 w-full max-w-full bg-white p-6 rounded-3xl'>
|
||||||
|
<div className='text-sm'>
|
||||||
|
نظرات کاربران
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
getDetailService.data?.data.danakService?.reviews?.map((item: ReviewItem) => {
|
||||||
|
return (
|
||||||
|
<div key={item.id} className='mt-5 border border-border rounded-3xl p-6'>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<Image alt='avatar' width={24} height={24} src={item.user?.profilePic || defaultAvatar} className='size-6 rounded-full object-cover' />
|
||||||
|
<div className='text-description text-xs'>
|
||||||
|
{item.user?.firstName + ' ' + item.user?.lastName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 flex justify-between'>
|
||||||
|
<div className='text-sm max-w-[70%] truncate'>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className='text-xs text-description'>
|
||||||
|
{moment(item.createdAt).fromNow()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-2'>
|
||||||
|
<Rate
|
||||||
|
disabled
|
||||||
|
value={item.rating}
|
||||||
|
count={5}
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 text-[11px] leading-6'>
|
||||||
|
{item.comment}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* {
|
||||||
|
getAdsLeft.isSuccess && getAdsLeft.data.data.ads[0] &&
|
||||||
|
<a href={getAdsLeft.data?.data?.ads[0].link} target='_blank' className='bg-white w-sidebar h-fit hidden xl:block rounded-3xl overflow-hidden sticky top-4'>
|
||||||
|
<img
|
||||||
|
src={getAdsLeft.data.data.ads[0].imageUrl}
|
||||||
|
className='w-full backdrop-blur-[100px] h-[550px] object-cover'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'>
|
||||||
|
<div className='max-w-[80%] text-sm leading-6 text-white'>
|
||||||
|
{getAdsLeft.data.data.ads[0].title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
} */}
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailService
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className='w-full flex gap-6 mt-4'>
|
||||||
|
<div className='flex-1 max-w-full'>
|
||||||
|
{/* Service Header Skeleton */}
|
||||||
|
<div className="bg-white p-6 rounded-3xl animate-pulse">
|
||||||
|
<div className="h-8 w-60 bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="mt-4 flex justify-between items-center">
|
||||||
|
<div className="h-5 w-32 bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-10 w-32 bg-gray-200 rounded-3xl"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Skeleton */}
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col overflowX justify-between items-center xl:gap-8 gap-5'>
|
||||||
|
{[1, 2, 3, 4, 5].map((i) => (
|
||||||
|
<div key={`stat-${i}`} className='bg-white flex xl:min-w-[127px] w-full flex-1 flex-col justify-center items-center h-[109px] rounded-2xl py-6 px-4 animate-pulse'>
|
||||||
|
<div className='h-4 w-20 bg-gray-200 rounded-md'></div>
|
||||||
|
<div className='mt-3 h-5 w-16 bg-gray-200 rounded-md'></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Service Images Skeleton */}
|
||||||
|
<div className="mt-8 grid grid-cols-2 gap-4 animate-pulse">
|
||||||
|
<div className="h-64 bg-gray-200 rounded-2xl"></div>
|
||||||
|
<div className="h-64 bg-gray-200 rounded-2xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description Skeleton */}
|
||||||
|
<div className='mt-8 bg-white p-6 rounded-3xl animate-pulse'>
|
||||||
|
<div className="h-6 w-32 bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="mt-4 space-y-3">
|
||||||
|
<div className="h-4 w-full bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-4 w-full bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-4 w-full bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-4 w-3/4 bg-gray-200 rounded-md"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reviews Skeleton */}
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
|
||||||
|
<div className='flex-1 w-full max-w-full bg-white p-6 rounded-3xl animate-pulse'>
|
||||||
|
<div className="h-6 w-32 bg-gray-200 rounded-md"></div>
|
||||||
|
|
||||||
|
{[1, 2].map((i) => (
|
||||||
|
<div key={`review-${i}`} className='mt-5 border border-gray-200 rounded-3xl p-6'>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<div className="h-6 w-6 bg-gray-200 rounded-full"></div>
|
||||||
|
<div className="h-4 w-32 bg-gray-200 rounded-md"></div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-4 flex justify-between'>
|
||||||
|
<div className="h-5 w-3/4 bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-4 w-20 bg-gray-200 rounded-md"></div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 flex'>
|
||||||
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
<div key={`star-${i}-${star}`} className="h-4 w-4 bg-gray-200 rounded-sm mr-1"></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='mt-4 space-y-2'>
|
||||||
|
<div className="h-3 w-full bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-3 w-full bg-gray-200 rounded-md"></div>
|
||||||
|
<div className="h-3 w-1/2 bg-gray-200 rounded-md"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar Ad Skeleton */}
|
||||||
|
<div className='bg-white w-sidebar h-[550px] hidden xl:block rounded-3xl overflow-hidden sticky top-4 animate-pulse'>
|
||||||
|
<div className="w-full h-full bg-gray-200"></div>
|
||||||
|
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-gray-100'>
|
||||||
|
<div className="h-5 w-3/4 bg-gray-200 rounded-md"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,9 +1,39 @@
|
|||||||
import React from 'react'
|
import { Suspense } from 'react'
|
||||||
|
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
|
||||||
|
import { detailServiceQueryOptions } from '../hooks/useProductData'
|
||||||
|
import { NextPage } from 'next'
|
||||||
|
import DetailService from '../DetailService'
|
||||||
|
import Loading from './loading'
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{
|
||||||
|
id: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SingleService: NextPage<PageProps> = async ({ params }) => {
|
||||||
|
|
||||||
|
const { id } = await params
|
||||||
|
|
||||||
const page = () => {
|
|
||||||
return (
|
return (
|
||||||
<div>page</div>
|
<Suspense fallback={<Loading />}>
|
||||||
|
<SingleServicePage id={id} />
|
||||||
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default page
|
async function SingleServicePage({ id }: { id: string }) {
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
queryClient.prefetchQuery(detailServiceQueryOptions(id)),
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||||
|
<DetailService id={id} />
|
||||||
|
</HydrationBoundary>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SingleService
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { ArrowLeft } from 'iconsax-react'
|
||||||
|
import { NumberFormat } from '../../../config/func'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { DanakService } from '../types/ProductTypes'
|
||||||
|
import RadioGroup from '@/components/RadioGroup'
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: DanakService,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceHeader: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { data } = props
|
||||||
|
const [planSelected, setPlanSelected] = useState<string>(data?.subscriptionPlans[0]?.id)
|
||||||
|
const [price, setPrice] = useState<number>(data?.subscriptionPlans[0]?.price)
|
||||||
|
const [finalPrice, setFinalPrice] = useState<number>(0)
|
||||||
|
// const buyService = useBuyService()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const plan: any = data?.subscriptionPlans.find((item) => item.id === planSelected)
|
||||||
|
|
||||||
|
setFinalPrice(plan.finalPrice)
|
||||||
|
setPrice(plan?.price)
|
||||||
|
|
||||||
|
}, [data?.subscriptionPlans, planSelected])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full p-6 bg-white rounded-3xl flex xl:flex-row flex-col items-end xl:justify-between'>
|
||||||
|
<div className='flex w-full xl:gap-8 gap-4'>
|
||||||
|
<div className='xl:size-[70px] xl:min-w-[70px] min-w-[50px] size-[50px] rounded-xl'>
|
||||||
|
<Image alt='icon' width={70} height={70} src={data?.icon} className='w-full h-full rounded-xl' />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{data?.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className='text-description mt-1 text-[11px]'>
|
||||||
|
{data?.description}
|
||||||
|
</p>
|
||||||
|
<div className='hidden xl:flex gap-5 mt-5'>
|
||||||
|
<RadioGroup
|
||||||
|
items={data?.subscriptionPlans?.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
onChange={setPlanSelected}
|
||||||
|
selected={planSelected}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex w-full flex-col justify-between xl:items-end'>
|
||||||
|
<div className='xl:hidden flex gap-5 mt-5'>
|
||||||
|
<RadioGroup
|
||||||
|
items={data?.subscriptionPlans?.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
onChange={setPlanSelected}
|
||||||
|
selected={planSelected}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-4 mt-5 xl:mt-0 items-center justify-between xl:justify-normal'>
|
||||||
|
<div className='text-xs whitespace-nowrap flex gap-1 items-center'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
{
|
||||||
|
finalPrice !== price &&
|
||||||
|
<div className='line-through text-xs text-description'>
|
||||||
|
{NumberFormat(Number(price))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div>{NumberFormat(Number(price))}</div>
|
||||||
|
</div>
|
||||||
|
<span>تومان</span>
|
||||||
|
</div>
|
||||||
|
<Link target='_blank' href={`https://console.danakcorp.com/services/detail/${data?.id}`}>
|
||||||
|
<Button
|
||||||
|
// onClick={handleBuy}
|
||||||
|
// isLoading={buyService.isPending}
|
||||||
|
className='bg-description xl:max-w-full max-w-[100px] text-xs h-8 bg-opacity-20 text-black px-4'
|
||||||
|
>
|
||||||
|
<div className='flex gap-1 items-center'>
|
||||||
|
<div>
|
||||||
|
خرید
|
||||||
|
</div>
|
||||||
|
<ArrowLeft className='size-4' color='black' />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceHeader
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||||
|
|
||||||
|
const SWIPER_SLIDE_STYLE = {
|
||||||
|
overflow: 'visible !important',
|
||||||
|
// width: 220,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
images: { id: string, imageUrl: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceImages: FC<Props> = ({ images }) => {
|
||||||
|
return (
|
||||||
|
<div className='w-full mt-8'>
|
||||||
|
<Swiper
|
||||||
|
className='w-full'
|
||||||
|
spaceBetween={20} // فاصله پیشفرض بین اسلایدها
|
||||||
|
slidesPerView='auto'
|
||||||
|
>
|
||||||
|
|
||||||
|
{
|
||||||
|
images.map((item) => {
|
||||||
|
return (
|
||||||
|
<SwiperSlide key={item.id} className='max-w-[38%]' style={SWIPER_SLIDE_STYLE}>
|
||||||
|
<div className='bg-white bg-opacity-50 w-full p-2 rounded-2xl'>
|
||||||
|
<Image alt={item.imageUrl} width={2000} height={1000} src={item.imageUrl} className='w-full h-auto rounded-2xl' />
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceImages
|
||||||
@@ -33,3 +33,14 @@ export const categoriesQueryOptions = {
|
|||||||
queryKey: ["service-categories"],
|
queryKey: ["service-categories"],
|
||||||
queryFn: () => api.getCategoriesPublic(),
|
queryFn: () => api.getCategoriesPublic(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetDetailService = (serviceId: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: ["service-detail", serviceId],
|
||||||
|
queryFn: () => api.getDetailService(serviceId),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const detailServiceQueryOptions = (serviceId: string) => ({
|
||||||
|
queryKey: ["service-detail", serviceId],
|
||||||
|
queryFn: () => api.getDetailService(serviceId),
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios from "@/config/axios";
|
|||||||
import {
|
import {
|
||||||
CategoryResponse,
|
CategoryResponse,
|
||||||
DanakServiceSuggestedResponse,
|
DanakServiceSuggestedResponse,
|
||||||
|
ServiceDetailResponse,
|
||||||
ServiceResponse,
|
ServiceResponse,
|
||||||
} from "../types/ProductTypes";
|
} from "../types/ProductTypes";
|
||||||
|
|
||||||
@@ -29,3 +30,10 @@ export const getDanakServiceSuggested = async () => {
|
|||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDetailService = async (serviceId: string) => {
|
||||||
|
const { data } = await axios.get<ServiceDetailResponse>(
|
||||||
|
`/danak-services/${serviceId}/public`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
import { DanakSuggestService } from "@/app/home/types/HomeTypes";
|
import { DanakSuggestService } from "@/app/home/types/HomeTypes";
|
||||||
import { ApiResponse } from "@/shared/types/SharedTypes";
|
import { ApiResponse } from "@/shared/types/SharedTypes";
|
||||||
|
|
||||||
|
export interface ReviewItem {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
title: string;
|
||||||
|
comment: string;
|
||||||
|
rating: number;
|
||||||
|
status: string;
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
profilePic: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type ServiceResponse = ApiResponse<{
|
export type ServiceResponse = ApiResponse<{
|
||||||
services: DanakSuggestService[];
|
services: DanakSuggestService[];
|
||||||
nextPage: boolean;
|
nextPage: boolean;
|
||||||
@@ -25,3 +40,51 @@ export type ServiceCategoryType = {
|
|||||||
export type DanakServiceSuggestedResponse = ApiResponse<{
|
export type DanakServiceSuggestedResponse = ApiResponse<{
|
||||||
danakServices: DanakSuggestService[];
|
danakServices: DanakSuggestService[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export interface DanakService {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
isDanakSuggest: boolean;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
createDate: string;
|
||||||
|
userCount: number;
|
||||||
|
serviceLanguage: string;
|
||||||
|
softwareLanguage: string;
|
||||||
|
metaDescription: string;
|
||||||
|
isActive: boolean;
|
||||||
|
link: string;
|
||||||
|
icon: string;
|
||||||
|
category: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
images: {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
imageUrl: string;
|
||||||
|
}[];
|
||||||
|
subscriptionPlans: {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
duration: number;
|
||||||
|
price: number;
|
||||||
|
isActive: boolean;
|
||||||
|
}[];
|
||||||
|
reviews: ReviewItem[];
|
||||||
|
// reviews: any[]; // replace with Review[] if review structure is defined
|
||||||
|
deletedAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceDetailResponse = ApiResponse<{
|
||||||
|
danakService: DanakService;
|
||||||
|
reviews: [];
|
||||||
|
deletedAt: string | null;
|
||||||
|
averageRating: number;
|
||||||
|
}>;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default function TestServerErrorPage() {
|
||||||
|
// ایجاد خطا در سمت سرور
|
||||||
|
throw new Error('این یک خطای سمت سرور آزمایشی است')
|
||||||
|
|
||||||
|
// این بخش هرگز اجرا نمیشود
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>صفحه تست خطای سرور</h1>
|
||||||
|
<p>اگر این متن را میبینید، خطا رخ نداده است!</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
export default function TestErrorPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
// این کد عمداً خطا ایجاد میکند
|
||||||
|
throw new Error('این یک خطای آزمایشی برای نمایش صفحه error است')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// این بخش هرگز نمایش داده نمیشود چون قبل از آن خطا رخ میدهد
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>صفحه تست خطا</h1>
|
||||||
|
<p>اگر این متن را میبینید، خطا رخ نداده است!</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isActive: boolean,
|
||||||
|
value: string,
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Radio: FC<Props> = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<div onClick={() => props.onChange(props.value)} className='size-4 cursor-pointer rounded-full bg-[#EAEDF5] flex justify-center items-center'>
|
||||||
|
{
|
||||||
|
props.isActive &&
|
||||||
|
<div className='size-2 bg-black rounded-full'></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Radio
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import Radio from './Radio'
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
selected: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RadioGroup: FC<Props> = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<div className='flex gap-5 items-center text-xs'>
|
||||||
|
{
|
||||||
|
props.items.map((item, index) => (
|
||||||
|
<div key={index} className='flex gap-2 items-center'>
|
||||||
|
<Radio value={item.value} onChange={props.onChange} isActive={item.value === props.selected} />
|
||||||
|
<div className='mt-0.5'>
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RadioGroup
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DanakSuggestService } from '@/app/home/types/HomeTypes'
|
import { DanakSuggestService } from '@/app/home/types/HomeTypes'
|
||||||
import { ArrowLeft } from 'iconsax-react'
|
import { ArrowLeft } from 'iconsax-react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -10,7 +11,7 @@ type Props = {
|
|||||||
|
|
||||||
const ServiceItem: FC<Props> = (props) => {
|
const ServiceItem: FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className='flex-1 bg-white rounded-4xl xl:p-7 p-4'>
|
<Link href={`/products/${props.item.id}`} className='flex-1 bg-white rounded-4xl xl:p-7 p-4'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<div className='xl:size-[65px] size-[50px] rounded-2xl overflow-hidden'>
|
<div className='xl:size-[65px] size-[50px] rounded-2xl overflow-hidden'>
|
||||||
<Image src={props.item.icon} className='size-full object-cover' alt={props.item.name} width={65} height={65} />
|
<Image src={props.item.icon} className='size-full object-cover' alt={props.item.name} width={65} height={65} />
|
||||||
@@ -53,7 +54,7 @@ const ServiceItem: FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import { getToken } from "./func";
|
|||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
// baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
// baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||||
baseURL: "https://api.danakcorp.com",
|
baseURL: "http://192.168.1.108:4000",
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function toJalaliDate(
|
|||||||
const parsedDate = new Date(date);
|
const parsedDate = new Date(date);
|
||||||
|
|
||||||
if (isNaN(parsedDate.getTime())) {
|
if (isNaN(parsedDate.getTime())) {
|
||||||
|
return "";
|
||||||
throw new Error("Invalid date provided to toJalaliDate");
|
throw new Error("Invalid date provided to toJalaliDate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user