add a page
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
.env
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
1
env.example
Normal file
1
env.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_SERVER=
|
||||||
257
package-lock.json
generated
257
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^5.25.2",
|
"antd": "^5.25.2",
|
||||||
|
"axios": "^1.13.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0"
|
||||||
},
|
},
|
||||||
@@ -1672,6 +1673,21 @@
|
|||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||||
|
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1720,6 +1736,18 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@@ -1788,6 +1816,17 @@
|
|||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/compute-scroll-into-view": {
|
"node_modules/compute-scroll-into-view": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||||
@@ -1860,12 +1899,74 @@
|
|||||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.155",
|
"version": "1.5.155",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
||||||
"integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==",
|
"integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
||||||
@@ -2174,6 +2275,40 @@
|
|||||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -2188,6 +2323,14 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@@ -2197,6 +2340,41 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -2221,6 +2399,17 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -2230,6 +2419,42 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -2411,6 +2636,33 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@@ -2591,6 +2843,11 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^5.25.2",
|
"antd": "^5.25.2",
|
||||||
|
"axios": "^1.13.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
50
src/App.jsx
50
src/App.jsx
@@ -2,6 +2,8 @@ import React, { useState } from 'react';
|
|||||||
import { AppstoreOutlined, MailOutlined, SettingOutlined, CloseOutlined } from '@ant-design/icons';
|
import { AppstoreOutlined, MailOutlined, SettingOutlined, CloseOutlined } from '@ant-design/icons';
|
||||||
import { Menu } from 'antd';
|
import { Menu } from 'antd';
|
||||||
import { Radio, Tabs } from 'antd';
|
import { Radio, Tabs } from 'antd';
|
||||||
|
import TheChild from './pages/TheChild';
|
||||||
|
import Example from './pages/Example';
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
key: 'Dashboard',
|
key: 'Dashboard',
|
||||||
@@ -17,6 +19,7 @@ const items = [
|
|||||||
{ key: 'Devices', label: 'Devices' },
|
{ key: 'Devices', label: 'Devices' },
|
||||||
{ key: 'Tags', label: 'Tags' },
|
{ key: 'Tags', label: 'Tags' },
|
||||||
{ key: 'Products', label: 'Products' },
|
{ key: 'Products', label: 'Products' },
|
||||||
|
{ key: 'Example', label: 'Example' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,7 +77,8 @@ const getLevelKeys = items1 => {
|
|||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
const levelKeys = getLevelKeys(items);
|
const levelKeys = getLevelKeys(items);
|
||||||
const TheChild = () => {return(<div>Hahalo</div>)}
|
// const TheChild = () => {return(<div>Hahalo</div>)}
|
||||||
|
const size = 'small';
|
||||||
const App = () => {
|
const App = () => {
|
||||||
// const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
|
// const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
|
||||||
const [stateOpenKeys, setStateOpenKeys] = useState(['0']);
|
const [stateOpenKeys, setStateOpenKeys] = useState(['0']);
|
||||||
@@ -97,7 +101,7 @@ const App = () => {
|
|||||||
// children: <TheChild />,
|
// children: <TheChild />,
|
||||||
// },
|
// },
|
||||||
]);
|
]);
|
||||||
const [size, setSize] = useState('small');
|
// const [size, setSize] = useState('small');
|
||||||
const [activeKey, setActiveKey] = useState('Dashboard');
|
const [activeKey, setActiveKey] = useState('Dashboard');
|
||||||
|
|
||||||
const onOpenChange = openKeys => {
|
const onOpenChange = openKeys => {
|
||||||
@@ -132,17 +136,29 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
setItemsTab(newItems);
|
setItemsTab(newItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
const add = (key) => {
|
const add = (key) => {
|
||||||
// const newKey = String((items || []).length + 1);
|
// const newKey = String((items || []).length + 1);
|
||||||
// const newKey = String((itemsTab || []).length + 1);
|
// const newKey = String((itemsTab || []).length + 1);
|
||||||
// console.log("newKey", newKey)
|
// console.log("newKey", newKey)
|
||||||
// console.log("items", items)
|
// console.log("items", items)
|
||||||
// console.log("itemsTab before", itemsTab)
|
// console.log("itemsTab before", itemsTab)
|
||||||
|
|
||||||
|
// console.log("key", key)
|
||||||
|
|
||||||
|
let page;
|
||||||
|
if(key[0] === "Example") {
|
||||||
|
page = <Example />
|
||||||
|
} else {
|
||||||
|
page = `Content of editable tab ${key[0]}`;
|
||||||
|
}
|
||||||
|
|
||||||
let newtab = {
|
let newtab = {
|
||||||
// label: `Tab ${newKey}`,
|
// label: `Tab ${newKey}`,
|
||||||
label: `${key[0]}`,
|
label: `${key[0]}`,
|
||||||
key: key[0],
|
key: key[0],
|
||||||
children: `Content of editable tab ${key[0]}`,
|
// children: `Content of editable tab ${key[0]}`,
|
||||||
|
children: page,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemsTab.find(item => item.key === newtab.key)) {
|
if (!itemsTab.find(item => item.key === newtab.key)) {
|
||||||
@@ -159,7 +175,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
const onAddTab = (targetKey, action) => {
|
const onAddTab = (targetKey, action) => {
|
||||||
if (action === 'add') {
|
if (action === 'add') {
|
||||||
add(key);
|
// add(key);
|
||||||
// console.log("Plus plus")
|
// console.log("Plus plus")
|
||||||
} else {
|
} else {
|
||||||
// console.log("targetKey", targetKey)
|
// console.log("targetKey", targetKey)
|
||||||
@@ -170,19 +186,19 @@ const App = () => {
|
|||||||
// setSize(e.target.value);
|
// setSize(e.target.value);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const renderTabBar = (props, DefaultTabBar) => {
|
// const renderTabBar = (props, DefaultTabBar) => {
|
||||||
return (
|
// return (
|
||||||
<DefaultTabBar {...props}>
|
// <DefaultTabBar {...props}>
|
||||||
{(node) => {
|
// {(node) => {
|
||||||
// Filter out the add button node
|
// // Filter out the add button node
|
||||||
if (node.key && node.key.toString().startsWith('rc-tabs-add')) {
|
// if (node.key && node.key.toString().startsWith('rc-tabs-add')) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
return node;
|
// return node;
|
||||||
}}
|
// }}
|
||||||
</DefaultTabBar>
|
// </DefaultTabBar>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|||||||
0
src/Utils/Auth/Logout.jsx
Normal file
0
src/Utils/Auth/Logout.jsx
Normal file
0
src/Utils/Auth/Signin.jsx
Normal file
0
src/Utils/Auth/Signin.jsx
Normal file
171
src/components/Global/ApiRequest.jsx
Normal file
171
src/components/Global/ApiRequest.jsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
const baseURL = import.meta.env.VITE_API_SERVER;
|
||||||
|
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// axios khusus refresh
|
||||||
|
const refreshApi = axios.create({
|
||||||
|
baseURL,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
console.error('🚨 Response Error Interceptor:', {
|
||||||
|
status: error.response?.status,
|
||||||
|
url: originalRequest.url,
|
||||||
|
message: error.response?.data?.message,
|
||||||
|
hasRetried: originalRequest._retry,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
originalRequest._retry = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔄 Refresh token dipanggil...');
|
||||||
|
const refreshRes = await refreshApi.post('/auth/refresh-token');
|
||||||
|
|
||||||
|
const newAccessToken = refreshRes.data.data.accessToken;
|
||||||
|
localStorage.setItem('token', newAccessToken);
|
||||||
|
console.log('✅ Token refreshed successfully');
|
||||||
|
|
||||||
|
// update token di header
|
||||||
|
instance.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
|
||||||
|
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
|
||||||
|
|
||||||
|
console.log('🔁 Retrying original request...');
|
||||||
|
return instance(originalRequest);
|
||||||
|
} catch (refreshError) {
|
||||||
|
console.error(
|
||||||
|
'❌ Refresh token gagal:',
|
||||||
|
refreshError.response?.data || refreshError.message
|
||||||
|
);
|
||||||
|
localStorage.clear();
|
||||||
|
window.location.href = '/signin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function ApiRequest({ method = 'GET', params = {}, prefix = '/', token = true } = {}) {
|
||||||
|
const isFormData = params instanceof FormData;
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
method,
|
||||||
|
url: prefix,
|
||||||
|
data: params,
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': 'en_US',
|
||||||
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawToken = localStorage.getItem('token');
|
||||||
|
if (token && rawToken) {
|
||||||
|
const cleanToken = rawToken.replace(/"/g, '');
|
||||||
|
request.headers['Authorization'] = `Bearer ${cleanToken}`;
|
||||||
|
console.log('🔐 Sending request with token:', cleanToken.substring(0, 20) + '...');
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ No token found in localStorage');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📤 API Request:', { method, url: prefix, hasToken: !!rawToken });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await instance(request);
|
||||||
|
console.log('✅ API Response:', {
|
||||||
|
url: prefix,
|
||||||
|
status: response.status,
|
||||||
|
statusCode: response.data?.statusCode,
|
||||||
|
});
|
||||||
|
return { ...response, error: false };
|
||||||
|
} catch (error) {
|
||||||
|
const status = error?.response?.status || 500;
|
||||||
|
const message = error?.response?.data?.message || error.message || 'Something Wrong';
|
||||||
|
console.error('❌ API Error:', {
|
||||||
|
url: prefix,
|
||||||
|
status,
|
||||||
|
message,
|
||||||
|
fullError: error?.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status !== 401) {
|
||||||
|
await cekError(status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...error.response, error: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cekError(status, message = '') {
|
||||||
|
if (status === 403) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Forbidden',
|
||||||
|
text: message,
|
||||||
|
});
|
||||||
|
} else if (status >= 500) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Server Error',
|
||||||
|
text: message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Peringatan',
|
||||||
|
text: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SendRequest = async (queryParams) => {
|
||||||
|
try {
|
||||||
|
const response = await ApiRequest(queryParams);
|
||||||
|
console.log('📦 SendRequest response:', {
|
||||||
|
hasError: response.error,
|
||||||
|
status: response.status,
|
||||||
|
statusCode: response.data?.statusCode,
|
||||||
|
data: response.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If ApiRequest returned error flag, return error structure
|
||||||
|
if (response.error) {
|
||||||
|
const errorMsg = response.data?.message || response.statusText || 'Request failed';
|
||||||
|
console.error('❌ SendRequest error response:', errorMsg);
|
||||||
|
|
||||||
|
// Return consistent error structure instead of empty array
|
||||||
|
return {
|
||||||
|
statusCode: response.status || 500,
|
||||||
|
message: errorMsg,
|
||||||
|
data: null,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response || { statusCode: 200, data: [], message: 'Success' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ SendRequest catch error:', error);
|
||||||
|
|
||||||
|
// Don't show Swal here, let the calling code handle it
|
||||||
|
// This allows better error handling in each API call
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
message: error.message || 'Something went wrong',
|
||||||
|
data: null,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ApiRequest, SendRequest };
|
||||||
107
src/pages/Example.jsx
Normal file
107
src/pages/Example.jsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const Example = () => {
|
||||||
|
|
||||||
|
// State to store the fetched data
|
||||||
|
const [posts, setPosts] = React.useState([]);
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [error, setError] = React.useState(null);
|
||||||
|
|
||||||
|
// Function to fetch data from API
|
||||||
|
const fetchPosts = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
// Make GET request to JSONPlaceholder API
|
||||||
|
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
|
||||||
|
setPosts(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use useEffect to fetch data when component mounts
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchPosts();
|
||||||
|
}, []); // Empty dependency array means this runs once on mount
|
||||||
|
|
||||||
|
// Function to fetch a single post (example of another API call)
|
||||||
|
const fetchSinglePost = async (postId) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}`);
|
||||||
|
console.log('Single post:', response.data);
|
||||||
|
// Handle the single post data as needed
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching single post:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to create a new post (example of POST request)
|
||||||
|
const createPost = async () => {
|
||||||
|
try {
|
||||||
|
const newPost = {
|
||||||
|
title: 'New Post',
|
||||||
|
body: 'This is a new post created via API',
|
||||||
|
userId: 1,
|
||||||
|
};
|
||||||
|
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
|
||||||
|
console.log('Created post:', response.data);
|
||||||
|
// Optionally refetch posts after creation
|
||||||
|
fetchPosts();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error creating post:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading posts...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Error: {error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return(<div>Example</div>)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Example Posts from JSONPlaceholder</h1>
|
||||||
|
|
||||||
|
<button onClick={fetchPosts} style={{ marginBottom: '20px' }}>
|
||||||
|
Refresh Posts
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onClick={createPost} style={{ margin: '0 10px 20px 10px' }}>
|
||||||
|
Create New Post
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gap: '20px' }}>
|
||||||
|
{posts.slice(0, 5).map((post) => (
|
||||||
|
<div
|
||||||
|
key={post.id}
|
||||||
|
style={{
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '5px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={() => fetchSinglePost(post.id)}
|
||||||
|
>
|
||||||
|
<h3>{post.title}</h3>
|
||||||
|
<p>{post.body}</p>
|
||||||
|
<small>Post ID: {post.id} | User ID: {post.userId}</small>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Showing {Math.min(posts.length, 5)} of {posts.length} posts</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Example;
|
||||||
7
src/pages/TheChild.jsx
Normal file
7
src/pages/TheChild.jsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const TheChild = () => {
|
||||||
|
return(<div>Hahalo</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TheChild;
|
||||||
Reference in New Issue
Block a user