refresh token + base orders list and detail order
This commit is contained in:
Generated
+93
-43
@@ -22,7 +22,8 @@
|
|||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"next": "15.4.5",
|
"moment-jalaali": "^0.10.4",
|
||||||
|
"next": "^15.5.3",
|
||||||
"rc-rate": "^2.13.1",
|
"rc-rate": "^2.13.1",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@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",
|
||||||
@@ -820,9 +822,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.3.tgz",
|
||||||
"integrity": "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==",
|
"integrity": "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
@@ -836,9 +838,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz",
|
||||||
"integrity": "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA==",
|
"integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -852,9 +854,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.3.tgz",
|
||||||
"integrity": "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA==",
|
"integrity": "sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -868,9 +870,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz",
|
||||||
"integrity": "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ==",
|
"integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -884,9 +886,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz",
|
||||||
"integrity": "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw==",
|
"integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -900,9 +902,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz",
|
||||||
"integrity": "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA==",
|
"integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -916,9 +918,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz",
|
||||||
"integrity": "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ==",
|
"integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -932,9 +934,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz",
|
||||||
"integrity": "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ==",
|
"integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -948,9 +950,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz",
|
||||||
"integrity": "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A==",
|
"integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1853,6 +1855,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/moment-jalaali": {
|
||||||
|
"version": "0.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz",
|
||||||
|
"integrity": "sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": ">=2.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.9",
|
"version": "20.19.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
|
||||||
@@ -2723,9 +2735,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.11.0",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@@ -4777,6 +4789,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jalaali-js": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||||
@@ -5309,6 +5327,38 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-jalaali": {
|
||||||
|
"version": "0.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz",
|
||||||
|
"integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jalaali-js": "^1.2.7",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-timezone": "^0.5.46"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -5358,12 +5408,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.4.5",
|
"version": "15.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.5.3.tgz",
|
||||||
"integrity": "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==",
|
"integrity": "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.4.5",
|
"@next/env": "15.5.3",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@@ -5376,14 +5426,14 @@
|
|||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.4.5",
|
"@next/swc-darwin-arm64": "15.5.3",
|
||||||
"@next/swc-darwin-x64": "15.4.5",
|
"@next/swc-darwin-x64": "15.5.3",
|
||||||
"@next/swc-linux-arm64-gnu": "15.4.5",
|
"@next/swc-linux-arm64-gnu": "15.5.3",
|
||||||
"@next/swc-linux-arm64-musl": "15.4.5",
|
"@next/swc-linux-arm64-musl": "15.5.3",
|
||||||
"@next/swc-linux-x64-gnu": "15.4.5",
|
"@next/swc-linux-x64-gnu": "15.5.3",
|
||||||
"@next/swc-linux-x64-musl": "15.4.5",
|
"@next/swc-linux-x64-musl": "15.5.3",
|
||||||
"@next/swc-win32-arm64-msvc": "15.4.5",
|
"@next/swc-win32-arm64-msvc": "15.5.3",
|
||||||
"@next/swc-win32-x64-msvc": "15.4.5",
|
"@next/swc-win32-x64-msvc": "15.5.3",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
+3
-1
@@ -23,7 +23,8 @@
|
|||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"next": "15.4.5",
|
"moment-jalaali": "^0.10.4",
|
||||||
|
"next": "^15.5.3",
|
||||||
"rc-rate": "^2.13.1",
|
"rc-rate": "^2.13.1",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@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",
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import axios from "@/config/axios";
|
import axios from "@/config/axios";
|
||||||
import { AuthenticationType, VerifyOtpType } from "../types/Types";
|
import {
|
||||||
|
AuthenticationType,
|
||||||
|
RefreshTokenType,
|
||||||
|
VerifyOtpType,
|
||||||
|
} from "../types/Types";
|
||||||
|
|
||||||
export const authentication = async (params: AuthenticationType) => {
|
export const authentication = async (params: AuthenticationType) => {
|
||||||
const { data } = await axios.post("/user/authenticate", params);
|
const { data } = await axios.post("/user/authenticate", params);
|
||||||
@@ -10,3 +14,8 @@ export const verifyOtp = async (params: VerifyOtpType) => {
|
|||||||
const { data } = await axios.post("/user/login/otp", params);
|
const { data } = await axios.post("/user/login/otp", params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const refreshToken = async (params: RefreshTokenType) => {
|
||||||
|
const { data } = await axios.post(`/auth/refresh`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,3 +13,7 @@ export type VerifyOtpType = {
|
|||||||
phone: string;
|
phone: string;
|
||||||
otpCode: string;
|
otpCode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RefreshTokenType = {
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup'
|
||||||
|
import * as yup from 'yup'
|
||||||
|
import Modal from '@/components/Modal'
|
||||||
|
import Select from '@/components/Select'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
interface CancelOrderFormData {
|
||||||
|
reason: string
|
||||||
|
count: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CancelReason {
|
||||||
|
_id: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CancelOrderModalProps {
|
||||||
|
showModalCancel: boolean
|
||||||
|
setShowModalCancel: (show: boolean) => void
|
||||||
|
onSubmit: (data: CancelOrderFormData) => void
|
||||||
|
isLoading: boolean
|
||||||
|
cancelReasons?: Array<{ _id: string; title: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelOrderSchema = yup.object({
|
||||||
|
reason: yup.string().required('دلیل لغو سفارش را انتخاب کنید'),
|
||||||
|
count: yup.string().required('تعداد را وارد کنید'),
|
||||||
|
description: yup.string().required('توضیحات را وارد کنید')
|
||||||
|
})
|
||||||
|
|
||||||
|
const CancelOrderModal: React.FC<CancelOrderModalProps> = ({
|
||||||
|
showModalCancel,
|
||||||
|
setShowModalCancel,
|
||||||
|
onSubmit,
|
||||||
|
isLoading,
|
||||||
|
cancelReasons = []
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
reset
|
||||||
|
} = useForm<CancelOrderFormData>({
|
||||||
|
resolver: yupResolver(cancelOrderSchema)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleFormSubmit = (data: CancelOrderFormData) => {
|
||||||
|
onSubmit(data)
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={showModalCancel}
|
||||||
|
close={() => setShowModalCancel(false)}
|
||||||
|
isHeader
|
||||||
|
title='درخواست لغو سفارش'
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(handleFormSubmit)} className='mt-6'>
|
||||||
|
<Select
|
||||||
|
placeholder='دلیل لغو سفارش'
|
||||||
|
items={cancelReasons.map((item) => ({
|
||||||
|
label: item.title,
|
||||||
|
value: item._id
|
||||||
|
}))}
|
||||||
|
className='bg-white border'
|
||||||
|
value={watch('reason')}
|
||||||
|
name='reason'
|
||||||
|
onChange={(e) => setValue('reason', e.target.value)}
|
||||||
|
error_text={errors.reason?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
variant='floating_outlined'
|
||||||
|
label='تعداد'
|
||||||
|
className='mt-6'
|
||||||
|
{...register('count')}
|
||||||
|
error_text={errors.count?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className="text-sm font-medium mb-2">توضیحات :</div>
|
||||||
|
<textarea
|
||||||
|
{...register('description')}
|
||||||
|
className='h-[123px] rounded-xl p-3 text-sm border w-full resize-none focus:outline-none focus:ring-2 focus:ring-primary/20'
|
||||||
|
placeholder='متن توضیحات خود را بنویسید'
|
||||||
|
/>
|
||||||
|
{errors.description && (
|
||||||
|
<div className='text-xs text-red-500 mt-1'>
|
||||||
|
{errors.description.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2 text-xs text-[#7F7F7F] mt-4 p-3 bg-blue-50 rounded-lg'>
|
||||||
|
<div className="size-5 bg-blue-100 rounded-full flex items-center justify-center">
|
||||||
|
<span className="text-blue-600 text-xs">!</span>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
توجه داشته باشید که بعد از ثبت لغو سفارش طبق
|
||||||
|
</span>
|
||||||
|
<span className='text-primary font-medium'>
|
||||||
|
جدول جرائم
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
جریمه میشود
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className='w-full'
|
||||||
|
disabled={isLoading}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
لغو سفارش
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CancelOrderModal
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import OrderProductItem from './OrderProductItem'
|
||||||
|
import { OrderItemDetail } from '../../types/Types'
|
||||||
|
|
||||||
|
interface OrderMainContentProps {
|
||||||
|
orderItems: OrderItemDetail[]
|
||||||
|
orderId: string
|
||||||
|
getStatusText: (status: string) => string
|
||||||
|
onCancelClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderMainContent: React.FC<OrderMainContentProps> = ({
|
||||||
|
orderItems,
|
||||||
|
orderId,
|
||||||
|
getStatusText,
|
||||||
|
onCancelClick
|
||||||
|
}) => {
|
||||||
|
// محاسبه تعداد مرسوله (length of orderItems)
|
||||||
|
const shipmentCount = orderItems.length
|
||||||
|
|
||||||
|
// محاسبه تعداد اقلام (sum of all itemsCount)
|
||||||
|
const totalItemsCount = orderItems.reduce((sum, item) => sum + item.itemsCount, 0)
|
||||||
|
|
||||||
|
// گرفتن اولین آیتم برای نمایش اطلاعات (در صورت نیاز)
|
||||||
|
const firstOrderItem = orderItems[0]
|
||||||
|
|
||||||
|
// محاسبه مجموع هزینه ارسال و مبلغ مرسوله
|
||||||
|
const totalShipmentCost = orderItems.reduce((sum, item) => sum + item.totalShipmentCost, 0)
|
||||||
|
const totalPaymentPrice = orderItems.reduce((sum, item) => sum + item.totalPaymentPrice, 0)
|
||||||
|
return (
|
||||||
|
<div className='flex-1 w-full bg-white shadow-lg p-6 rounded-2xl'>
|
||||||
|
<div className='flex md:flex-row flex-col gap-4 justify-between md:items-center'>
|
||||||
|
<div className='flex md:flex-row flex-col gap-4'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>تعداد مرسوله :</div>
|
||||||
|
<div>{shipmentCount}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>تعداد اقلام :</div>
|
||||||
|
<div>
|
||||||
|
{totalItemsCount}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='text-primary font-semibold'>
|
||||||
|
{firstOrderItem ? getStatusText(firstOrderItem.status) : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2 mt-6'>
|
||||||
|
<div className='text-[#7F7F7F]'>حداکثر مهلت ارسال :</div>
|
||||||
|
<div>
|
||||||
|
{firstOrderItem?.shipper.deliveryTime} روز
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex md:flex-row flex-col gap-3 justify-between mt-6'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>هزینه ارسال :</div>
|
||||||
|
<div>
|
||||||
|
{totalShipmentCost.toLocaleString()} تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>مبلغ مرسوله :</div>
|
||||||
|
<div>
|
||||||
|
{totalPaymentPrice.toLocaleString()} تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>شناسه سفارش :</div>
|
||||||
|
<div className="font-mono">
|
||||||
|
{orderId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Product Items */}
|
||||||
|
{orderItems.map((orderItem) =>
|
||||||
|
orderItem.shipmentItems.map((item) => (
|
||||||
|
<OrderProductItem
|
||||||
|
key={item._id}
|
||||||
|
item={item}
|
||||||
|
shipperName={orderItem.shipper.name}
|
||||||
|
onCancelClick={onCancelClick}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderMainContent
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { MessageText, ShieldTick, Truck, CloseCircle } from 'iconsax-react'
|
||||||
|
import { ShipmentItemDetail } from '../../types/Types'
|
||||||
|
|
||||||
|
interface OrderProductItemProps {
|
||||||
|
item: ShipmentItemDetail
|
||||||
|
shipperName: string
|
||||||
|
onCancelClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderProductItem: React.FC<OrderProductItemProps> = ({ item, shipperName, onCancelClick }) => {
|
||||||
|
return (
|
||||||
|
<div className='mt-8 py-7 border-t items-start flex gap-6 sm:flex-row flex-col'>
|
||||||
|
<Image
|
||||||
|
src={item.product.imagesUrl.cover}
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className='w-20 h-20 object-cover rounded-lg border'
|
||||||
|
alt={item.product.title_fa}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='flex-1 w-full'>
|
||||||
|
<div className='truncate max-w-[90%] font-medium'>
|
||||||
|
{item.product.title_fa}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 text-[#999999] space-y-3'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div>تعداد اقلام :</div>
|
||||||
|
<div>{item.quantity}</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div>تعداد کنسلی :</div>
|
||||||
|
<div>{item.cancelled_quantity}</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div>تعداد مرجوعی :</div>
|
||||||
|
<div>{item.returned_quantity}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<div className='size-3 bg-black rounded-full'></div>
|
||||||
|
<div>
|
||||||
|
{item.variant.meterage?.value ||
|
||||||
|
item.variant.size?.value ||
|
||||||
|
'ندارد'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<MessageText size={20} className="text-gray-400" />
|
||||||
|
<div className="font-mono">
|
||||||
|
SKP_{item.product._id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<ShieldTick size={20} className="text-green-500" />
|
||||||
|
<div>
|
||||||
|
گارانتی {item.variant.warranty.duration} {item.variant.warranty.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<Truck size={20} className="text-blue-500" />
|
||||||
|
<div>
|
||||||
|
{shipperName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8 flex justify-between items-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg font-semibold'>
|
||||||
|
{Intl.NumberFormat('fa-IR').format(item.selling_price)}
|
||||||
|
</div>
|
||||||
|
<div className='text-xs mt-1 text-gray-500'>تومان</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{item.quantity !== item.cancelled_quantity && (
|
||||||
|
<button
|
||||||
|
onClick={onCancelClick}
|
||||||
|
className='flex cursor-pointer gap-2 text-[#B3261E] hover:bg-red-50 p-2 rounded-lg transition-colors'
|
||||||
|
>
|
||||||
|
<div>لغو سفارش</div>
|
||||||
|
<CloseCircle size={16} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderProductItem
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import TrackCode from './TrackCode'
|
||||||
|
import { Payment, User, ShipmentAddress } from '../../types/Types'
|
||||||
|
import * as api from '../../service/Service'
|
||||||
|
import { toast } from '@/components/Toast'
|
||||||
|
|
||||||
|
interface OrderSidebarProps {
|
||||||
|
orderData: {
|
||||||
|
payment: Payment
|
||||||
|
order: {
|
||||||
|
_id: string
|
||||||
|
user: User
|
||||||
|
shipmentAddress: ShipmentAddress
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderSidebar: React.FC<OrderSidebarProps> = ({ orderData, orderId }) => {
|
||||||
|
const [isLoadingInvoice, setIsLoadingInvoice] = useState(false)
|
||||||
|
|
||||||
|
const handleInvoice = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadingInvoice(true)
|
||||||
|
const blob = await api.getInvoice(orderId.toString())
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `invoice-${orderId}.pdf`
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
toast("فاکتور با موفقیت دانلود شد", "success")
|
||||||
|
} catch (error) {
|
||||||
|
toast("مشکلی در دانلود فاکتور رخ داد", "error")
|
||||||
|
} finally {
|
||||||
|
setIsLoadingInvoice(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='lg:w-[280px] w-full p-5 rounded-2xl shadow-lg'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='text-[#7F7F7F]'>شناسه تراکنش :</div>
|
||||||
|
<div className="font-mono">
|
||||||
|
{orderData.payment.transaction_id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<div className='text-[#7F7F7F]'>گیرنده :</div>
|
||||||
|
<div>
|
||||||
|
{orderData.order.user.fullName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<div className='text-[#7F7F7F]'>کدپستی :</div>
|
||||||
|
<div>
|
||||||
|
{orderData.order.shipmentAddress.postalCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<div className='text-[#7F7F7F] whitespace-nowrap'>تاریخ ثبت سفارش :</div>
|
||||||
|
<div>
|
||||||
|
{orderData.order.createdAt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<div className='text-[#7F7F7F]'>شماره موبایل :</div>
|
||||||
|
<div>
|
||||||
|
{orderData.order.shipmentAddress.phone}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-start gap-2 mt-4'>
|
||||||
|
<div className='text-[#7F7F7F] whitespace-nowrap'>آدرس :</div>
|
||||||
|
<div>
|
||||||
|
{orderData.order.shipmentAddress.address}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TrackCode orderId={orderId} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className='mt-3 w-full'
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleInvoice}
|
||||||
|
disabled={isLoadingInvoice}
|
||||||
|
>
|
||||||
|
{isLoadingInvoice ? 'در حال دانلود...' : 'مشاهده فاکتور'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderSidebar
|
||||||
@@ -1,115 +1,25 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { ArrowRight2, ShieldTick, Truck, MessageText, CloseCircle } from 'iconsax-react'
|
import { ArrowRight2 } from 'iconsax-react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup'
|
|
||||||
import * as yup from 'yup'
|
|
||||||
import PageLoading from '@/components/PageLoading'
|
import PageLoading from '@/components/PageLoading'
|
||||||
import TrackCode from './components/TrackCode'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import Modal from '@/components/Modal'
|
|
||||||
import Select from '@/components/Select'
|
|
||||||
import Input from '@/components/Input'
|
|
||||||
import Layout from '@/hoc/Layout'
|
import Layout from '@/hoc/Layout'
|
||||||
import Image from 'next/image'
|
|
||||||
import Menu from '../../components/Menu'
|
import Menu from '../../components/Menu'
|
||||||
|
import OrderSidebar from './components/OrderSidebar'
|
||||||
|
import OrderMainContent from './components/OrderMainContent'
|
||||||
|
import CancelOrderModal from './components/CancelOrderModal'
|
||||||
|
import { useGetOrderDetail } from '../hooks/useOrdersData'
|
||||||
|
import * as api from '../service/Service'
|
||||||
|
import { toast } from '@/components/Toast'
|
||||||
|
|
||||||
// Mock data
|
|
||||||
const mockOrderDetail = {
|
|
||||||
payment: {
|
|
||||||
transaction_id: 'TRX123456789'
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
_id: 'ORD001',
|
|
||||||
user: {
|
|
||||||
fullName: 'احمد محمدی'
|
|
||||||
},
|
|
||||||
shipmentAddress: {
|
|
||||||
postalCode: '1234567890',
|
|
||||||
phone: '09123456789',
|
|
||||||
address: 'تهران، خیابان ولیعصر، پلاک 123'
|
|
||||||
},
|
|
||||||
createdAt: '1402/12/15',
|
|
||||||
orderItems: {
|
|
||||||
_id: 'ITEM001',
|
|
||||||
itemsCount: 3,
|
|
||||||
status: 'processing',
|
|
||||||
totalShipmentCost: '25000 تومان',
|
|
||||||
totalPaymentPrice: '450000',
|
|
||||||
shipper: {
|
|
||||||
name: 'پست ایران',
|
|
||||||
deliveryTime: 3
|
|
||||||
},
|
|
||||||
shipmentItems: [
|
|
||||||
{
|
|
||||||
_id: 'SHIP001',
|
|
||||||
product: {
|
|
||||||
_id: 'PROD001',
|
|
||||||
title_fa: 'لپتاپ اپل مکبوک پرو 13 اینچی',
|
|
||||||
imagesUrl: {
|
|
||||||
cover: 'https://via.placeholder.com/80x80'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quantity: 1,
|
|
||||||
cancelled_quantity: 0,
|
|
||||||
returned_quantity: 0,
|
|
||||||
selling_price: 450000,
|
|
||||||
variant: {
|
|
||||||
color: {
|
|
||||||
name: 'نقرهای'
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
name: '13 اینچ'
|
|
||||||
},
|
|
||||||
meterages: {
|
|
||||||
name: '256GB'
|
|
||||||
},
|
|
||||||
warranty: {
|
|
||||||
duration: '2 سال',
|
|
||||||
name: 'گارانتی'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockCancelReasons = [
|
|
||||||
{ _id: 'REASON1', title: 'تغییر نظر در خرید' },
|
|
||||||
{ _id: 'REASON2', title: 'یافتن قیمت بهتر' },
|
|
||||||
{ _id: 'REASON3', title: 'مشکل در محصول' },
|
|
||||||
{ _id: 'REASON4', title: 'تأخیر در ارسال' }
|
|
||||||
]
|
|
||||||
|
|
||||||
// Form validation schema
|
|
||||||
const cancelOrderSchema = yup.object({
|
|
||||||
reason: yup.string().required('لطفا دلیل لغو سفارش را انتخاب کنید'),
|
|
||||||
description: yup.string().required('لطفا توضیحات لغو سفارش را وارد کنید'),
|
|
||||||
count: yup.string().required('تعداد لغو سفارش را وارد کنید')
|
|
||||||
})
|
|
||||||
|
|
||||||
type CancelOrderFormData = yup.InferType<typeof cancelOrderSchema>
|
|
||||||
|
|
||||||
const OrderDetail: FC = () => {
|
const OrderDetail: FC = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
const { data: orderDetail } = useGetOrderDetail(id as string)
|
||||||
const [showModalCancel, setShowModalCancel] = useState<boolean>(false)
|
const [showModalCancel, setShowModalCancel] = useState<boolean>(false)
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// Form with react-hook-form
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
watch,
|
|
||||||
setValue,
|
|
||||||
formState: { errors },
|
|
||||||
reset
|
|
||||||
} = useForm<CancelOrderFormData>({
|
|
||||||
resolver: yupResolver(cancelOrderSchema)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getStatusText = (status: string) => {
|
const getStatusText = (status: string) => {
|
||||||
const statusMap: Record<string, string> = {
|
const statusMap: Record<string, string> = {
|
||||||
'processing': 'در حال پردازش',
|
'processing': 'در حال پردازش',
|
||||||
@@ -120,17 +30,24 @@ const OrderDetail: FC = () => {
|
|||||||
return statusMap[status] || status
|
return statusMap[status] || status
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async (cancelData: { reason: string; count: string; description: string }) => {
|
||||||
setIsLoading(true)
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
await api.cancelOrder(id as string, {
|
||||||
|
reason: cancelData.reason,
|
||||||
|
count: parseInt(cancelData.count),
|
||||||
|
description: cancelData.description
|
||||||
|
})
|
||||||
|
|
||||||
|
toast("درخواست لغو سفارش با موفقیت ثبت شد", "success")
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setShowModalCancel(false)
|
setShowModalCancel(false)
|
||||||
reset() // Reset form
|
|
||||||
alert('درخواست لغو سفارش با موفقیت ثبت شد')
|
|
||||||
router.push('/profile/orders')
|
router.push('/profile/orders')
|
||||||
}, 1000)
|
} catch {
|
||||||
|
toast("مشکلی در ثبت درخواست لغو سفارش رخ داد", "error")
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -151,269 +68,40 @@ const OrderDetail: FC = () => {
|
|||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
) : (
|
) : orderDetail?.results.order ? (
|
||||||
<div className='mt-6 lg:flex-row flex-col flex gap-6 items-start'>
|
<div className='mt-6 lg:flex-row flex-col flex gap-6 items-start'>
|
||||||
{/* Sidebar */}
|
<OrderSidebar
|
||||||
<div className='lg:w-[280px] w-full p-5 rounded-2xl shadow-lg'>
|
orderData={{
|
||||||
<div className='flex items-center gap-2'>
|
payment: orderDetail.results.order.payment,
|
||||||
<div className='text-[#7F7F7F]'>شناسه تراکنش :</div>
|
order: {
|
||||||
<div className="font-mono">
|
_id: orderDetail.results.order._id.toString(),
|
||||||
{mockOrderDetail.payment.transaction_id}
|
user: orderDetail.results.order.user,
|
||||||
</div>
|
shipmentAddress: orderDetail.results.order.shipmentAddress,
|
||||||
</div>
|
createdAt: orderDetail.results.order.createdAt
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
orderId={orderDetail.results.order._id}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='flex items-center gap-2 mt-4'>
|
<OrderMainContent
|
||||||
<div className='text-[#7F7F7F]'>گیرنده :</div>
|
orderItems={orderDetail.results.order.orderItems}
|
||||||
<div>
|
orderId={orderDetail.results.order._id.toString()}
|
||||||
{mockOrderDetail.order.user.fullName}
|
getStatusText={getStatusText}
|
||||||
</div>
|
onCancelClick={() => setShowModalCancel(true)}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center gap-2 mt-4'>
|
|
||||||
<div className='text-[#7F7F7F]'>کدپستی :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.shipmentAddress.postalCode}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center gap-2 mt-4'>
|
|
||||||
<div className='text-[#7F7F7F] whitespace-nowrap'>تاریخ ثبت سفارش :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.createdAt}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center gap-2 mt-4'>
|
|
||||||
<div className='text-[#7F7F7F]'>شماره موبایل :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.shipmentAddress.phone}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-start gap-2 mt-4'>
|
|
||||||
<div className='text-[#7F7F7F] whitespace-nowrap'>آدرس :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.shipmentAddress.address}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TrackCode orderId={Number(id) || 0} />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className='mt-3 w-full'
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
مشاهده فاکتور
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<div className='flex-1 w-full bg-white shadow-lg p-6 rounded-2xl'>
|
|
||||||
<div className='flex md:flex-row flex-col gap-4 justify-between md:items-center'>
|
|
||||||
<div className='flex md:flex-row flex-col gap-4'>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<div className='text-[#7F7F7F]'>تعداد مرسوله :</div>
|
|
||||||
<div>1</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<div className='text-[#7F7F7F]'>تعداد اقلام :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.orderItems.itemsCount}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='text-primary font-semibold'>
|
|
||||||
{getStatusText(mockOrderDetail.order.orderItems.status)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center gap-2 mt-6'>
|
|
||||||
<div className='text-[#7F7F7F]'>حداکثر مهلت ارسال :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.orderItems.shipper.deliveryTime} روز
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex md:flex-row flex-col gap-3 justify-between mt-6'>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<div className='text-[#7F7F7F]'>هزینه ارسال :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.orderItems.totalShipmentCost}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<div className='text-[#7F7F7F]'>مبلغ مرسوله :</div>
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.orderItems.totalPaymentPrice} تومان
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<div className='text-[#7F7F7F]'>شناسه سفارش :</div>
|
|
||||||
<div className="font-mono">
|
|
||||||
{mockOrderDetail.order._id}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Product Items */}
|
|
||||||
{mockOrderDetail.order.orderItems.shipmentItems.map((item) => (
|
|
||||||
<div key={item.product._id} className='mt-8 py-7 border-t items-start flex gap-6 sm:flex-row flex-col'>
|
|
||||||
<Image
|
|
||||||
src={item.product.imagesUrl.cover}
|
|
||||||
width={80}
|
|
||||||
height={80}
|
|
||||||
className='w-20 h-20 object-cover rounded-lg border'
|
|
||||||
alt={item.product.title_fa}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='flex-1 w-full'>
|
|
||||||
<div className='truncate max-w-[90%] font-medium'>
|
|
||||||
{item.product.title_fa}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-4 text-[#999999] space-y-3'>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div>تعداد اقلام :</div>
|
|
||||||
<div>{item.quantity}</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div>تعداد کنسلی :</div>
|
|
||||||
<div>{item.cancelled_quantity}</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div>تعداد مرجوعی :</div>
|
|
||||||
<div>{item.returned_quantity}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-3 items-center'>
|
|
||||||
<div className='size-3 bg-black rounded-full'></div>
|
|
||||||
<div>
|
|
||||||
{item.variant.color?.name ||
|
|
||||||
item.variant.size?.name ||
|
|
||||||
item.variant.meterages?.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-3 items-center'>
|
|
||||||
<MessageText size={20} className="text-gray-400" />
|
|
||||||
<div className="font-mono">
|
|
||||||
SKP_{item.product._id}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-3 items-center'>
|
|
||||||
<ShieldTick size={20} className="text-green-500" />
|
|
||||||
<div>
|
|
||||||
گارانتی {item.variant.warranty.duration} {item.variant.warranty.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-3 items-center'>
|
|
||||||
<Truck size={20} className="text-blue-500" />
|
|
||||||
<div>
|
|
||||||
{mockOrderDetail.order.orderItems.shipper.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8 flex justify-between items-center'>
|
|
||||||
<div>
|
|
||||||
<div className='text-lg font-semibold'>
|
|
||||||
{Intl.NumberFormat('fa-IR').format(item.selling_price)}
|
|
||||||
</div>
|
|
||||||
<div className='text-xs mt-1 text-gray-500'>تومان</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{item.quantity !== item.cancelled_quantity && (
|
|
||||||
<button
|
|
||||||
onClick={() => setShowModalCancel(true)}
|
|
||||||
className='flex cursor-pointer gap-2 text-[#B3261E] hover:bg-red-50 p-2 rounded-lg transition-colors'
|
|
||||||
>
|
|
||||||
<div>لغو سفارش</div>
|
|
||||||
<CloseCircle size={16} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='mt-6 text-center'>در حال بارگذاری...</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cancel Order Modal */}
|
<CancelOrderModal
|
||||||
<Modal
|
showModalCancel={showModalCancel}
|
||||||
open={showModalCancel}
|
setShowModalCancel={setShowModalCancel}
|
||||||
close={() => setShowModalCancel(false)}
|
onSubmit={onSubmit}
|
||||||
isHeader
|
isLoading={isLoading}
|
||||||
title='درخواست لغو سفارش'
|
cancelReasons={orderDetail?.results.order.cancelReasons}
|
||||||
>
|
/>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className='mt-6'>
|
|
||||||
<Select
|
|
||||||
placeholder='دلیل لغو سفارش'
|
|
||||||
items={mockCancelReasons.map((item) => ({
|
|
||||||
label: item.title,
|
|
||||||
value: item._id
|
|
||||||
}))}
|
|
||||||
className='bg-white border'
|
|
||||||
value={watch('reason')}
|
|
||||||
name='reason'
|
|
||||||
onChange={(e) => setValue('reason', e.target.value)}
|
|
||||||
error_text={errors.reason?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
variant='floating_outlined'
|
|
||||||
label='تعداد'
|
|
||||||
className='mt-6'
|
|
||||||
{...register('count')}
|
|
||||||
error_text={errors.count?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<div className="text-sm font-medium mb-2">توضیحات :</div>
|
|
||||||
<textarea
|
|
||||||
{...register('description')}
|
|
||||||
className='h-[123px] rounded-xl p-3 text-sm border w-full resize-none focus:outline-none focus:ring-2 focus:ring-primary/20'
|
|
||||||
placeholder='متن توضیحات خود را بنویسید'
|
|
||||||
/>
|
|
||||||
{errors.description && (
|
|
||||||
<div className='text-xs text-red-500 mt-1'>
|
|
||||||
{errors.description.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2 text-xs text-[#7F7F7F] mt-4 p-3 bg-blue-50 rounded-lg'>
|
|
||||||
<div className="size-5 bg-blue-100 rounded-full flex items-center justify-center">
|
|
||||||
<span className="text-blue-600 text-xs">!</span>
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
توجه داشته باشید که بعد از ثبت لغو سفارش طبق
|
|
||||||
</span>
|
|
||||||
<span className='text-primary font-medium'>
|
|
||||||
جدول جرائم
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
جریمه میشود
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className='w-full'
|
|
||||||
disabled={isLoading}
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
لغو سفارش
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
|
interface OrderButtonsProps {
|
||||||
|
status: string
|
||||||
|
orderId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderButtons: React.FC<OrderButtonsProps> = ({ status, orderId }) => {
|
||||||
|
const statusLower = status.toLowerCase();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
switch (statusLower) {
|
||||||
|
case 'pending':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
onClick={() => router.push(`/profile/orders/${orderId}`)}
|
||||||
|
>
|
||||||
|
جزئیات سفارش
|
||||||
|
</Button>
|
||||||
|
<Button className='bg-primary hover:bg-primary/80 px-4 sm:px-6 py-2 h-8 sm:h-9 text-white text-xs sm:text-sm'>
|
||||||
|
پرداخت
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'processing':
|
||||||
|
case 'shipped':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
>
|
||||||
|
پیگیری سفارش
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
onClick={() => router.push(`/profile/orders/${orderId}`)}
|
||||||
|
>
|
||||||
|
جزئیات سفارش
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'delivered':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
>
|
||||||
|
امتیازدهی
|
||||||
|
</Button>
|
||||||
|
<Button className='bg-primary hover:bg-primary/80 px-4 sm:px-6 py-2 h-8 sm:h-9 text-white text-xs sm:text-sm'>
|
||||||
|
خرید مجدد
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'returned':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
onClick={() => router.push(`/profile/orders/${orderId}`)}
|
||||||
|
>
|
||||||
|
جزئیات سفارش
|
||||||
|
</Button>
|
||||||
|
<Button className='bg-primary hover:bg-primary/80 px-4 sm:px-6 py-2 h-8 sm:h-9 text-white text-xs sm:text-sm'>
|
||||||
|
خرید مجدد
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'cancelled':
|
||||||
|
return (
|
||||||
|
<Button className='bg-primary hover:bg-primary/80 px-4 sm:px-6 py-2 h-8 sm:h-9 text-white text-xs sm:text-sm'>
|
||||||
|
سفارش مجدد
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
||||||
|
onClick={() => router.push(`/profile/orders/${orderId}`)}
|
||||||
|
>
|
||||||
|
جزئیات سفارش
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderButtons
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import OrderButtons from './OrderButtons'
|
||||||
|
import OrderProducts from './OrderProducts'
|
||||||
|
import { Order } from '../types/Types'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
|
||||||
|
interface OrderItemProps {
|
||||||
|
order: Order
|
||||||
|
getStatusText: (status: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderItem: React.FC<OrderItemProps> = ({ order, getStatusText }) => {
|
||||||
|
return (
|
||||||
|
<div className='bg-white border border-[#E0E0E0] rounded-[8px] p-3 sm:p-4 md:p-5'>
|
||||||
|
<div className='flex flex-col lg:flex-row gap-4 sm:gap-6'>
|
||||||
|
<div className='lg:max-w-[234px] w-full'>
|
||||||
|
<div className=''>
|
||||||
|
<div className='text-xs sm:text-sm text-[#7F7F7F] mb-1'>
|
||||||
|
شماره سفارش: <span className='text-black'> {order._id.toString()}</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-xs sm:text-sm text-[#7F7F7F] mt-3 sm:mt-4'>
|
||||||
|
مبلغ: <span className='text-black'> {(order.payment?.totalPrice || 0).toLocaleString('fa-IR')} تومان</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-4 sm:mb-6 mt-3 sm:mt-4'>
|
||||||
|
<div className='text-xs sm:text-sm text-[#7F7F7F]'>
|
||||||
|
وضعیت سفارش : <span className='text-black'>{getStatusText(order.status || 'pending')}</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-xs dltr text-right sm:text-sm text-[#7F7F7F] mt-3 sm:mt-4'>
|
||||||
|
تاریخ ثبت سفارش: <span className='text-black'> {moment(order.payment?.createdAt, 'jYYYY/jMM/jDD HH:mm:ss').format('jYYYY-jMM-jDD')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col sm:flex-row gap-2 sm:gap-3'>
|
||||||
|
<OrderButtons status={order.status || 'pending'} orderId={order._id.toString()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<OrderProducts products={order.orderItems.flatMap(item =>
|
||||||
|
item.shipmentItems.map(shipmentItem => ({
|
||||||
|
id: shipmentItem.product._id,
|
||||||
|
image: shipmentItem.product.imagesUrl?.cover || '',
|
||||||
|
name: shipmentItem.product.title_fa
|
||||||
|
}))
|
||||||
|
)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderItem
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import 'swiper/css';
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: number
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderProductsProps {
|
||||||
|
products: Product[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderProducts: React.FC<OrderProductsProps> = ({ products }) => {
|
||||||
|
return (
|
||||||
|
<div className='flex-1 flex gap-3 sm:gap-4 lg:border-r lg:border-border lg:pr-5 overflow-hidden min-h-[200px] sm:min-h-[225px]'>
|
||||||
|
<Swiper
|
||||||
|
spaceBetween={12}
|
||||||
|
slidesPerView={'auto'}
|
||||||
|
className='h-full w-full'
|
||||||
|
grabCursor={true}
|
||||||
|
>
|
||||||
|
{products.map((product) => (
|
||||||
|
<SwiperSlide key={product.id} className='!w-[180px] sm:!w-[200px] lg:!w-[234px]'>
|
||||||
|
<div className='flex w-[180px] sm:w-[200px] lg:w-[234px] h-[180px] sm:h-[200px] lg:h-[234px] flex-col p-4 sm:p-5 lg:p-7 items-center rounded-xl gap-2 border border-border'>
|
||||||
|
<Image
|
||||||
|
src={product.image}
|
||||||
|
className='max-w-[100px] sm:max-w-[120px] lg:max-w-[140px] max-h-[100px] sm:max-h-[120px] lg:max-h-[140px] object-center'
|
||||||
|
alt={product.name}
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
/>
|
||||||
|
<div className='max-w-[160px] truncate h-full sm:max-w-[180px] lg:max-w-[200px] text-center text-xs sm:text-sm font-light mt-2 sm:mt-4'>
|
||||||
|
{product.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderProducts
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
interface Tab {
|
||||||
|
key: 'Processing' | 'Delivered' | 'Cancelled'
|
||||||
|
label: string
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderTabsProps {
|
||||||
|
activeTab: 'Processing' | 'Delivered' | 'Cancelled'
|
||||||
|
setActiveTab: (tab: 'Processing' | 'Delivered' | 'Cancelled') => void
|
||||||
|
tabCounts: {
|
||||||
|
current: number
|
||||||
|
delivered: number
|
||||||
|
returned: number
|
||||||
|
cancelled: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderTabs: React.FC<OrderTabsProps> = ({ activeTab, setActiveTab, tabCounts }) => {
|
||||||
|
const tabs: Tab[] = [
|
||||||
|
{ key: 'Processing', label: 'جاری', count: tabCounts.current },
|
||||||
|
{ key: 'Delivered', label: 'تحویل شده', count: tabCounts.delivered },
|
||||||
|
// { key: 'Returned', label: 'مرجوع شده', count: tabCounts.returned },
|
||||||
|
{ key: 'Cancelled', label: 'لغو شده', count: tabCounts.cancelled }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-6 sm:mt-8 md:mt-10 lg:mt-14 border-b border-gray-200'>
|
||||||
|
<div className='flex gap-2 sm:gap-4 lg:gap-8 overflow-x-auto'>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.key}
|
||||||
|
onClick={() => setActiveTab(tab.key)}
|
||||||
|
className={`pb-3 sm:pb-4 px-1 sm:px-2 border-b-2 transition-colors whitespace-nowrap ${activeTab === tab.key
|
||||||
|
? 'border-primary text-primary'
|
||||||
|
: 'border-transparent text-gray-600 hover:text-gray-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className='ml-1 sm:ml-2 text-xs sm:text-sm'>{tab.label}</span>
|
||||||
|
<span className={`inline-flex items-center justify-center min-w-[16px] sm:min-w-[20px] h-4 sm:h-5 px-1 sm:px-1.5 text-xs rounded-full ${activeTab === tab.key
|
||||||
|
? 'bg-primary text-white'
|
||||||
|
: 'bg-gray-200 text-gray-600'
|
||||||
|
}`}>
|
||||||
|
{tab.count}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderTabs
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/Service";
|
||||||
|
import { OrdersResponse, OrderDetailResponse } from "../types/Types";
|
||||||
|
|
||||||
|
export const useGetOrders = (activeTab: string) => {
|
||||||
|
return useQuery<OrdersResponse>({
|
||||||
|
queryKey: ["orders", activeTab],
|
||||||
|
queryFn: () => api.getOrders(activeTab),
|
||||||
|
enabled: !!activeTab,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetOrderDetail = (orderId: string) => {
|
||||||
|
return useQuery<OrderDetailResponse>({
|
||||||
|
queryKey: ["orderDetail", orderId],
|
||||||
|
queryFn: () => api.getOrderDetail(orderId),
|
||||||
|
enabled: !!orderId,
|
||||||
|
});
|
||||||
|
};
|
||||||
+51
-118
@@ -1,139 +1,72 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import Layout from '@/hoc/Layout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import React, { useState } from 'react'
|
import React, { useState, useMemo } from 'react'
|
||||||
import Menu from '../components/Menu'
|
import Menu from '../components/Menu'
|
||||||
import { Button } from '@/components/ui/button'
|
import { useGetOrders } from './hooks/useOrdersData'
|
||||||
import Image from 'next/image'
|
import { Order } from './types/Types'
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import OrderTabs from './components/OrderTabs'
|
||||||
import 'swiper/css';
|
import OrderItem from './components/OrderItem'
|
||||||
|
|
||||||
|
|
||||||
const Orders: NextPage = () => {
|
const Orders: NextPage = () => {
|
||||||
const [activeTab, setActiveTab] = useState<'current' | 'delivered' | 'returned' | 'cancelled'>('current')
|
|
||||||
|
|
||||||
const mockOrders = [
|
const [activeTab, setActiveTab] = useState<'Processing' | 'Delivered' | 'Cancelled'>('Processing')
|
||||||
{
|
const { data: ordersData } = useGetOrders(activeTab);
|
||||||
id: '656151',
|
|
||||||
amount: '18,500,000',
|
|
||||||
status: 'در انتظار پرداخت',
|
// محاسبه تعداد سفارشات در هر تب از دادههای اصلی
|
||||||
date: '1401 / 10 / 12',
|
const tabCounts = useMemo(() => {
|
||||||
products: [
|
if (!ordersData?.results?.orders) return { current: 0, delivered: 0, returned: 0, cancelled: 0 };
|
||||||
{ id: 1, image: 'https://picsum.photos/80/80?random=1', name: 'گوشی موبایل سامسونگ مدل Galaxy S24 Ultra' },
|
|
||||||
{ id: 2, image: 'https://picsum.photos/80/80?random=2', name: 'لحاف کرسی ستین مدل ساده' },
|
return ordersData.results.orders.reduce((acc, order: Order) => {
|
||||||
{ id: 3, image: 'https://picsum.photos/80/80?random=3', name: 'روغنی ستین کد T-2112 طرح گلریز' },
|
const status = (order.status || 'pending').toLowerCase();
|
||||||
{ id: 4, image: 'https://picsum.photos/80/80?random=4', name: 'شارژر دیواری 65 وات ماگسیونگ' },
|
if (['pending', 'processing', 'shipped'].includes(status)) {
|
||||||
{ id: 5, image: 'https://picsum.photos/80/80?random=5', name: 'گوشی موبایل سامسونگ مدل Galaxy S24 Ultra' },
|
acc.current++;
|
||||||
{ id: 6, image: 'https://picsum.photos/80/80?random=6', name: 'لحاف کرسی ستین مدل ساده' },
|
} else if (status === 'delivered') {
|
||||||
{ id: 7, image: 'https://picsum.photos/80/80?random=7', name: 'روغنی ستین کد T-2112 طرح گلریز' },
|
acc.delivered++;
|
||||||
{ id: 8, image: 'https://picsum.photos/80/80?random=8', name: 'شارژر دیواری 65 وات ماگسیونگ' },
|
} else if (status === 'returned') {
|
||||||
]
|
acc.returned++;
|
||||||
},
|
} else if (status === 'cancelled') {
|
||||||
{
|
acc.cancelled++;
|
||||||
id: '846511',
|
}
|
||||||
amount: '254,500,000',
|
return acc;
|
||||||
status: 'وضعیت سفارش: در حال ارسال',
|
}, { current: 0, delivered: 0, returned: 0, cancelled: 0 });
|
||||||
date: '1401 / 8 / 10',
|
}, [ordersData]);
|
||||||
products: [
|
|
||||||
{ id: 1, image: 'https://picsum.photos/80/80?random=5', name: 'گوشی موبایل سامسونگ مدل Galaxy S24 Ultra' },
|
// تابع تبدیل وضعیت به فارسی
|
||||||
{ id: 2, image: 'https://picsum.photos/80/80?random=6', name: 'لحاف کرسی ستین مدل ساده' },
|
const getStatusText = (status: string) => {
|
||||||
{ id: 3, image: 'https://picsum.photos/80/80?random=7', name: 'روغنی ستین کد T-2112 طرح گلریز' },
|
const statusMap: { [key: string]: string } = {
|
||||||
{ id: 4, image: 'https://picsum.photos/80/80?random=8', name: 'شارژر دیواری 65 وات ماگسیونگ' }
|
'pending': 'در انتظار پرداخت',
|
||||||
]
|
'processing': 'در حال پردازش',
|
||||||
}
|
'shipped': 'در حال ارسال',
|
||||||
]
|
'delivered': 'تحویل شده',
|
||||||
|
'returned': 'مرجوع شده',
|
||||||
|
'cancelled': 'لغو شده'
|
||||||
|
};
|
||||||
|
return statusMap[status.toLowerCase()] || status;
|
||||||
|
};
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ key: 'current' as const, label: 'جاری', count: 10 },
|
|
||||||
{ key: 'delivered' as const, label: 'تحویل شده', count: 3 },
|
|
||||||
{ key: 'returned' as const, label: 'مرجوع شده', count: 3 },
|
|
||||||
{ key: 'cancelled' as const, label: 'لغو شده', count: 3 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-3 sm:p-4 md:p-6'>
|
<div className='p-3 sm:p-4 md:p-6'>
|
||||||
<Menu pageActive='orders' />
|
<Menu pageActive='orders' />
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className='mt-6 sm:mt-8 md:mt-10 lg:mt-14 border-b border-gray-200'>
|
<OrderTabs
|
||||||
<div className='flex gap-2 sm:gap-4 lg:gap-8 overflow-x-auto'>
|
activeTab={activeTab}
|
||||||
{tabs.map((tab) => (
|
setActiveTab={setActiveTab}
|
||||||
<button
|
tabCounts={tabCounts}
|
||||||
key={tab.key}
|
/>
|
||||||
onClick={() => setActiveTab(tab.key)}
|
|
||||||
className={`pb-3 sm:pb-4 px-1 sm:px-2 border-b-2 transition-colors whitespace-nowrap ${activeTab === tab.key
|
|
||||||
? 'border-primary text-primary'
|
|
||||||
: 'border-transparent text-gray-600 hover:text-gray-800'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className='ml-1 sm:ml-2 text-xs sm:text-sm'>{tab.label}</span>
|
|
||||||
<span className={`inline-flex items-center justify-center min-w-[16px] sm:min-w-[20px] h-4 sm:h-5 px-1 sm:px-1.5 text-xs rounded-full ${activeTab === tab.key
|
|
||||||
? 'bg-primary text-white'
|
|
||||||
: 'bg-gray-200 text-gray-600'
|
|
||||||
}`}>
|
|
||||||
{tab.count}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Orders List */}
|
{/* Orders List */}
|
||||||
<div className='mt-4 sm:mt-6 space-y-3 sm:space-y-4'>
|
<div className='mt-4 sm:mt-6 space-y-3 sm:space-y-4'>
|
||||||
{mockOrders.map((order) => (
|
{ordersData?.results?.orders.map((order) => (
|
||||||
<div key={order.id} className='bg-white border border-[#E0E0E0] rounded-[8px] p-3 sm:p-4 md:p-5'>
|
<OrderItem
|
||||||
<div className='flex flex-col lg:flex-row gap-4 sm:gap-6'>
|
key={order._id.toString()}
|
||||||
<div className='lg:max-w-[234px] w-full'>
|
order={order}
|
||||||
<div className=''>
|
getStatusText={getStatusText}
|
||||||
<div className='text-xs sm:text-sm text-[#7F7F7F] mb-1'>
|
/>
|
||||||
شماره سفارش: <span className='text-black'> {order.id}</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-xs sm:text-sm text-[#7F7F7F] mt-3 sm:mt-4'>
|
|
||||||
مبلغ: <span className='text-black'> {order.amount} تومان</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mb-4 sm:mb-6 mt-3 sm:mt-4'>
|
|
||||||
<div className='text-xs sm:text-sm text-[#7F7F7F]'>
|
|
||||||
وضعیت سفارش : <span className='text-black'>{order.status}</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-xs sm:text-sm text-[#7F7F7F] mt-3 sm:mt-4'>
|
|
||||||
تاریخ ثبت سفارش: <span className='text-black'> {order.date}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex flex-col sm:flex-row gap-2 sm:gap-3'>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className='px-4 sm:px-6 py-2 h-8 sm:h-9 border-[#E0E0E0] text-[#666666] hover:bg-gray-50 text-xs sm:text-sm'
|
|
||||||
>
|
|
||||||
جزئیات سفارش
|
|
||||||
</Button>
|
|
||||||
<Button className='bg-primary hover:bg-primary/80 px-4 sm:px-6 py-2 h-8 sm:h-9 text-white text-xs sm:text-sm'>
|
|
||||||
پرداخت
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex-1 flex gap-3 sm:gap-4 lg:border-r lg:border-border lg:pr-5 overflow-hidden min-h-[200px] sm:min-h-[225px]'>
|
|
||||||
<Swiper
|
|
||||||
spaceBetween={12}
|
|
||||||
slidesPerView={'auto'}
|
|
||||||
className='h-full w-full'
|
|
||||||
grabCursor={true}
|
|
||||||
>
|
|
||||||
{order.products.map((product) => (
|
|
||||||
<SwiperSlide key={product.id} className='!w-[180px] sm:!w-[200px] lg:!w-[234px]'>
|
|
||||||
<div className='flex w-[180px] sm:w-[200px] lg:w-[234px] h-[180px] sm:h-[200px] lg:h-[224px] flex-col p-4 sm:p-5 lg:p-7 items-center rounded-xl gap-2 border border-border'>
|
|
||||||
<Image src={product.image} className='max-w-[100px] sm:max-w-[120px] lg:max-w-[140px] max-h-[100px] sm:max-h-[120px] lg:max-h-[140px] object-center' alt={product.name} width={100} height={100} />
|
|
||||||
<div className='max-w-[160px] sm:max-w-[180px] lg:max-w-[200px] text-center text-xs sm:text-sm font-light mt-2 sm:mt-4'>{product.name}</div>
|
|
||||||
</div>
|
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import axios from "@/config/axios";
|
||||||
|
import { OrdersResponse, OrderDetailResponse } from "../types/Types";
|
||||||
|
|
||||||
|
export const getOrders = async (activeTab: string): Promise<OrdersResponse> => {
|
||||||
|
const { data } = await axios.get(`/user/profile/orders?status=${activeTab}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOrderDetail = async (
|
||||||
|
orderId: string
|
||||||
|
): Promise<OrderDetailResponse> => {
|
||||||
|
const { data } = await axios.get(`/order/${orderId}/user`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cancelOrder = async (
|
||||||
|
orderId: string,
|
||||||
|
cancelData: {
|
||||||
|
reason: string;
|
||||||
|
count: number;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(`/order/${orderId}/cancel`, cancelData);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInvoice = async (orderId: string) => {
|
||||||
|
const { data } = await axios.get(`/order/${orderId}/invoice`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
export interface ProductImage {
|
||||||
|
cover: string;
|
||||||
|
list: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductSpecification {
|
||||||
|
title: string;
|
||||||
|
values: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Brand {
|
||||||
|
_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Variant {
|
||||||
|
_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
_id: number;
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
seoTitle: string | null;
|
||||||
|
seoDescription: string | null;
|
||||||
|
source: string;
|
||||||
|
description: string;
|
||||||
|
metaDescription: string;
|
||||||
|
tags: string[];
|
||||||
|
advantages: string[];
|
||||||
|
disAdvantages: string[];
|
||||||
|
imagesUrl: ProductImage;
|
||||||
|
isFake: string;
|
||||||
|
voice: string | null;
|
||||||
|
specifications: ProductSpecification[];
|
||||||
|
brand: Brand;
|
||||||
|
category: Category;
|
||||||
|
variants: Variant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Price {
|
||||||
|
order_limit: number;
|
||||||
|
retailPrice: number;
|
||||||
|
selling_price: number;
|
||||||
|
is_specialSale: boolean;
|
||||||
|
discount_percent: number;
|
||||||
|
specialSale_order_limit: number | null;
|
||||||
|
specialSale_quantity: number | null;
|
||||||
|
specialSale_endDate: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Shop {
|
||||||
|
_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductVariant {
|
||||||
|
_id: string;
|
||||||
|
market_status: string;
|
||||||
|
price: Price;
|
||||||
|
stock: number;
|
||||||
|
postingTime: number;
|
||||||
|
isFreeShip: boolean;
|
||||||
|
isWholeSale: boolean;
|
||||||
|
shop: Shop;
|
||||||
|
warranty: number;
|
||||||
|
meterage?: number;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderItem {
|
||||||
|
product: Product;
|
||||||
|
variant: ProductVariant;
|
||||||
|
quantity: number;
|
||||||
|
cancelled_quantity: number;
|
||||||
|
returned_quantity: number;
|
||||||
|
selling_price: number;
|
||||||
|
retail_price: number;
|
||||||
|
discount_percent: number;
|
||||||
|
shipmentCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentMethod {
|
||||||
|
_id: string;
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
description: string;
|
||||||
|
provider: string;
|
||||||
|
isActive: boolean;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceDetails {
|
||||||
|
shipping_cost: number;
|
||||||
|
process_cost: number;
|
||||||
|
total_retail_price: number;
|
||||||
|
total_payable_price: number;
|
||||||
|
total_discount: number;
|
||||||
|
coupon_discount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Payment {
|
||||||
|
_id: string;
|
||||||
|
transaction_id: string | null;
|
||||||
|
payment_method: PaymentMethod;
|
||||||
|
priceDetails: PriceDetails;
|
||||||
|
totalPrice: number;
|
||||||
|
paymentStatus: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShipmentAddress {
|
||||||
|
address: string;
|
||||||
|
city: string;
|
||||||
|
province: string;
|
||||||
|
plaque: string;
|
||||||
|
postalCode: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MappedOrder {
|
||||||
|
orderId: number;
|
||||||
|
orderItems: OrderItem[];
|
||||||
|
payment: Payment;
|
||||||
|
shipmentAddress: ShipmentAddress;
|
||||||
|
orderStatus: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShopDetails {
|
||||||
|
_id: string;
|
||||||
|
shopName: string;
|
||||||
|
shopCode: number;
|
||||||
|
owner: string;
|
||||||
|
shopDescription: string;
|
||||||
|
telephoneNumber: string;
|
||||||
|
isChatActive: boolean;
|
||||||
|
shopPostalCode: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShipmentItem {
|
||||||
|
product: Product;
|
||||||
|
variant: ProductVariant;
|
||||||
|
quantity: number;
|
||||||
|
cancelled_quantity: number;
|
||||||
|
returned_quantity: number;
|
||||||
|
selling_price: number;
|
||||||
|
retail_price: number;
|
||||||
|
discount_percent: number;
|
||||||
|
shipmentCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderItemDetails {
|
||||||
|
_id: string;
|
||||||
|
shop: ShopDetails;
|
||||||
|
shipmentItems: ShipmentItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Shipper {
|
||||||
|
_id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
deliveryTime: number;
|
||||||
|
deliveryType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Order {
|
||||||
|
_id: number;
|
||||||
|
payment: Payment;
|
||||||
|
orderItems: OrderItemDetails[];
|
||||||
|
shipper: Shipper;
|
||||||
|
trackCode: string | null;
|
||||||
|
itemsCount: number;
|
||||||
|
totalPaymentPrice: number;
|
||||||
|
totalRetailPrice: number;
|
||||||
|
totalSellingPrice: number;
|
||||||
|
totalShipmentCost: number;
|
||||||
|
postingDate: string | null;
|
||||||
|
postingReceipt: string | null;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
_id: string;
|
||||||
|
fullName: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
dateOfBirth: string | null;
|
||||||
|
address: {
|
||||||
|
_id: string;
|
||||||
|
};
|
||||||
|
nationalCode: string | null;
|
||||||
|
homeNumber: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Warranty {
|
||||||
|
_id: number;
|
||||||
|
duration: string;
|
||||||
|
logoUrl: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meterage {
|
||||||
|
_id: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Size {
|
||||||
|
_id: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductVariantDetail {
|
||||||
|
_id: string;
|
||||||
|
market_status: string;
|
||||||
|
price: Price;
|
||||||
|
stock: number;
|
||||||
|
postingTime: number;
|
||||||
|
isFreeShip: boolean;
|
||||||
|
isWholeSale: boolean;
|
||||||
|
shop: ShopDetails;
|
||||||
|
warranty: Warranty;
|
||||||
|
meterage?: Meterage;
|
||||||
|
size?: Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShipmentItemDetail {
|
||||||
|
_id: string;
|
||||||
|
product: Product;
|
||||||
|
variant: ProductVariantDetail;
|
||||||
|
quantity: number;
|
||||||
|
cancelled_quantity: number;
|
||||||
|
returned_quantity: number;
|
||||||
|
selling_price: number;
|
||||||
|
retail_price: number;
|
||||||
|
discount_percent: number;
|
||||||
|
shipmentCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderItemDetail {
|
||||||
|
_id: string;
|
||||||
|
order_id: number;
|
||||||
|
shop: ShopDetails;
|
||||||
|
shipmentItems: ShipmentItemDetail[];
|
||||||
|
shipper: Shipper;
|
||||||
|
trackCode: string | null;
|
||||||
|
itemsCount: number;
|
||||||
|
totalPaymentPrice: number;
|
||||||
|
totalRetailPrice: number;
|
||||||
|
totalSellingPrice: number;
|
||||||
|
totalShipmentCost: number;
|
||||||
|
postingDate: string | null;
|
||||||
|
postingReceipt: string | null;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CancelReason {
|
||||||
|
_id: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderDetail {
|
||||||
|
_id: number;
|
||||||
|
user: User;
|
||||||
|
payment: Payment;
|
||||||
|
orderItems: OrderItemDetail[];
|
||||||
|
shipmentAddress: ShipmentAddress;
|
||||||
|
orderStatus: string;
|
||||||
|
createdAt: string;
|
||||||
|
cancelReasons?: CancelReason[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderDetailResponse {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
results: {
|
||||||
|
order: OrderDetail;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrdersResponse {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
results: {
|
||||||
|
mappedOrders: MappedOrder[];
|
||||||
|
orders: Order[];
|
||||||
|
shipmentAddress: ShipmentAddress;
|
||||||
|
orderStatus: string;
|
||||||
|
createdAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
+72
-8
@@ -1,16 +1,83 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { getToken } from "./func";
|
import {
|
||||||
|
getToken,
|
||||||
|
getRefreshToken,
|
||||||
|
setToken,
|
||||||
|
setRefreshToken,
|
||||||
|
removeToken,
|
||||||
|
removeRefreshToken,
|
||||||
|
} from "./func";
|
||||||
|
import { refreshToken } from "../app/auth/service/Service";
|
||||||
import { BASE_URL } from "./const";
|
import { BASE_URL } from "./const";
|
||||||
// import { Pages } from "./Pages";
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
isRefreshingToken?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
// baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
|
||||||
baseURL: BASE_URL,
|
baseURL: BASE_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
if (error?.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
if (window.isRefreshingToken) {
|
||||||
|
// Wait for the refresh to complete
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const checkComplete = setInterval(() => {
|
||||||
|
if (!window.isRefreshingToken) {
|
||||||
|
clearInterval(checkComplete);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
return axiosInstance(originalRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRequest._retry = true;
|
||||||
|
window.isRefreshingToken = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const refreshTokenValue = await getRefreshToken();
|
||||||
|
if (!refreshTokenValue) {
|
||||||
|
await removeRefreshToken();
|
||||||
|
await removeToken();
|
||||||
|
window.location.href = `/auth`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = await refreshToken({
|
||||||
|
token: refreshTokenValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.accessToken?.token) {
|
||||||
|
await setToken(data?.accessToken?.token);
|
||||||
|
if (data.refreshToken?.token) {
|
||||||
|
await setRefreshToken(data.refreshToken?.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${data.accessToken.token}`;
|
||||||
|
return axiosInstance(originalRequest);
|
||||||
|
} else {
|
||||||
|
await removeRefreshToken();
|
||||||
|
await removeToken();
|
||||||
|
window.location.href = `/auth`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (refreshError) {
|
||||||
|
console.error("Token refresh failed:", refreshError);
|
||||||
|
await removeToken();
|
||||||
|
await removeRefreshToken();
|
||||||
|
window.location.href = `/auth`;
|
||||||
|
return Promise.reject(refreshError);
|
||||||
|
} finally {
|
||||||
|
window.isRefreshingToken = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -18,10 +85,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
axiosInstance.interceptors.request.use(async function (config) {
|
axiosInstance.interceptors.request.use(async function (config) {
|
||||||
const tokenValue = await getToken();
|
const tokenValue = await getToken();
|
||||||
const token = tokenValue ? tokenValue : "";
|
const token = tokenValue ? tokenValue : "";
|
||||||
|
config.headers.Authorization = "Bearer " + token;
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = "Bearer " + token;
|
|
||||||
}
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user