commit
This commit is contained in:
450
hicoinpay-react/package-lock.json
generated
450
hicoinpay-react/package-lock.json
generated
@@ -8,14 +8,18 @@
|
||||
"name": "hicoinpay-react",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"axios": "^1.12.2",
|
||||
"i18next": "^25.6.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^16.0.0",
|
||||
"react-router-dom": "^7.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react": "^5.0.3",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
@@ -258,6 +262,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -1378,24 +1391,32 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz",
|
||||
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==",
|
||||
"version": "18.3.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
||||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
|
||||
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
@@ -1482,6 +1503,23 @@
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -1544,6 +1582,19 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -1612,6 +1663,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1682,6 +1745,29 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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": {
|
||||
"version": "1.5.224",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz",
|
||||
@@ -1689,6 +1775,51 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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": {
|
||||
"version": "0.25.10",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
|
||||
@@ -2022,6 +2153,42 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -2037,6 +2204,15 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@@ -2047,6 +2223,43 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -2073,6 +2286,18 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -2083,6 +2308,94 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.6.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz",
|
||||
"integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
|
||||
"integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -2154,7 +2467,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -2264,6 +2576,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -2274,6 +2598,36 @@
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -2469,6 +2823,12 @@
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -2480,24 +2840,54 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.1"
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.0.tgz",
|
||||
"integrity": "sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 25.5.2",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
@@ -2601,10 +2991,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"license": "MIT"
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
@@ -2827,6 +3220,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -10,14 +10,18 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"axios": "^1.12.2",
|
||||
"i18next": "^25.6.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^16.0.0",
|
||||
"react-router-dom": "^7.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react": "^5.0.3",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
|
||||
function About() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div>
|
||||
<h1>About Us</h1>
|
||||
<div className="registration-container">
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<div className="logo">
|
||||
<h2>{t('home.title')}</h2>
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
<LanguageSwitcher />
|
||||
<a href="/" className="btn-secondary">{t('common.backToHome')}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div className="registration-card">
|
||||
<h1>{t('navigation.about')}</h1>
|
||||
<p>Learn more about our project.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default About;
|
||||
387
hicoinpay-react/src/ApiDocs.css
Normal file
387
hicoinpay-react/src/ApiDocs.css
Normal file
@@ -0,0 +1,387 @@
|
||||
/* API Documentation Page Styles */
|
||||
.api-docs-container {
|
||||
min-height: 100vh;
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.docs-header {
|
||||
background: linear-gradient(135deg, #DC192C 0%, #B8152A 100%);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.docs-header .container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.docs-header h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-header p {
|
||||
font-size: 1.25rem;
|
||||
opacity: 0.9;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.docs-main {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.docs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr; /* single column layout, sidebar removed */
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
/* Documents Section */
|
||||
.documents-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #1A202C;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
color: #718096;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.documents-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.document-card {
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.document-card:hover {
|
||||
border-color: #DC192C;
|
||||
box-shadow: 0 4px 12px rgba(220, 25, 44, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.document-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.document-header h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.document-description {
|
||||
color: #718096;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.9rem;
|
||||
color: #A0AEC0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.document-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary, .btn-secondary, .btn-outline {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #B8152A;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: #DC192C;
|
||||
border: 2px solid #DC192C;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #718096;
|
||||
border: 2px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
border-color: #DC192C;
|
||||
color: #DC192C;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
display: none; /* removed */
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #718096;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #DC192C;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.document-info {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #F7FAFC;
|
||||
}
|
||||
|
||||
.info-row strong {
|
||||
min-width: 120px;
|
||||
color: #1A202C;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.docs-footer {
|
||||
background: #2D3748;
|
||||
color: white;
|
||||
padding: 3rem 0 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.footer-section h3, .footer-section h4 {
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: #A0AEC0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer-section ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-section ul li a {
|
||||
color: #A0AEC0;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section ul li a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid #4A5568;
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.docs-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.documents-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.document-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.docs-header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.document-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
margin-left: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
152
hicoinpay-react/src/ApiDocs.jsx
Normal file
152
hicoinpay-react/src/ApiDocs.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import './ApiDocs.css';
|
||||
|
||||
function ApiDocs() {
|
||||
const [selectedDoc, setSelectedDoc] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Mock data - this will be replaced with actual API data later
|
||||
const apiDocuments = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'HicoinPay API Reference',
|
||||
version: 'v2.1',
|
||||
description: 'Complete API reference for payment processing, webhooks, and merchant management.',
|
||||
lastUpdated: '2024-01-15',
|
||||
fileSize: '2.4 MB',
|
||||
downloadUrl: '/api-docs/hicoinpay-api-reference-v2.1.pdf'
|
||||
}
|
||||
];
|
||||
|
||||
const handleDownload = (document) => {
|
||||
alert(`Downloading ${document.title} (${document.fileSize})...`);
|
||||
};
|
||||
|
||||
const handleViewDetails = (document) => {
|
||||
setSelectedDoc(document);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="api-docs-container">
|
||||
{/* Header */}
|
||||
<header className="docs-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<div className="breadcrumb">
|
||||
<a href="/">{t('navigation.home')}</a> / <span>{t('apiDocs.title')}</span>
|
||||
</div>
|
||||
{/* Title removed per request */}
|
||||
<p>{t('apiDocs.subtitle')}</p>
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<LanguageSwitcher inverse />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="docs-main">
|
||||
<div className="container">
|
||||
<div className="docs-grid">
|
||||
{/* Documents List */}
|
||||
<div className="documents-section">
|
||||
<div className="section-header">
|
||||
<h2>{t('apiDocs.availableDocs')}</h2>
|
||||
<p>{t('apiDocs.downloadLatest')}</p>
|
||||
</div>
|
||||
|
||||
<div className="documents-grid">
|
||||
{apiDocuments.map((doc) => (
|
||||
<div key={doc.id} className="document-card">
|
||||
<div className="document-header">
|
||||
<h3>{doc.title}</h3>
|
||||
<span className="version-badge">{t('apiDocs.version')}{doc.version}</span>
|
||||
</div>
|
||||
<p className="document-description">{doc.description}</p>
|
||||
<div className="document-meta">
|
||||
<span className="last-updated">{t('apiDocs.updated')}: {doc.lastUpdated}</span>
|
||||
<span className="file-size">{doc.fileSize}</span>
|
||||
</div>
|
||||
<div className="document-actions">
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => handleDownload(doc)}
|
||||
>
|
||||
{t('apiDocs.downloadPdf')}
|
||||
</button>
|
||||
<button
|
||||
className="btn-outline"
|
||||
onClick={() => handleViewDetails(doc)}
|
||||
>
|
||||
{t('apiDocs.viewDetails')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar removed to keep only API Reference */}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Document Details Modal */}
|
||||
{selectedDoc && (
|
||||
<div className="modal-overlay" onClick={() => setSelectedDoc(null)}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h2>{selectedDoc.title}</h2>
|
||||
<button
|
||||
className="close-button"
|
||||
onClick={() => setSelectedDoc(null)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="document-info">
|
||||
<div className="info-row">
|
||||
<strong>{t('apiDocs.modal.version')}:</strong> {selectedDoc.version}
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<strong>{t('apiDocs.modal.lastUpdated')}:</strong> {selectedDoc.lastUpdated}
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<strong>{t('apiDocs.modal.fileSize')}:</strong> {selectedDoc.fileSize}
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<strong>{t('apiDocs.modal.description')}:</strong> {selectedDoc.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-actions">
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => {
|
||||
handleDownload(selectedDoc);
|
||||
setSelectedDoc(null);
|
||||
}}
|
||||
>
|
||||
{t('apiDocs.downloadPdf')}
|
||||
</button>
|
||||
<button
|
||||
className="btn-outline"
|
||||
onClick={() => setSelectedDoc(null)}
|
||||
>
|
||||
{t('apiDocs.close')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApiDocs;
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Routes, Route, BrowserRouter } from 'react-router-dom';
|
||||
import Home from './Home';
|
||||
import SimpleHome from './SimpleHome';
|
||||
import About from './About';
|
||||
import ApiDocs from './ApiDocs';
|
||||
import Registration from './Registration';
|
||||
import Dashboard from './Dashboard';
|
||||
import Login from './Login';
|
||||
import Withdrawal from './Withdrawal';
|
||||
import Settings from './Settings';
|
||||
import ForgotPassword from './ForgotPassword';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -8,6 +16,13 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/api-docs" element={<ApiDocs />} />
|
||||
<Route path="/register" element={<Registration />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/withdrawal" element={<Withdrawal />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
{/* Add more routes as needed */}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
224
hicoinpay-react/src/Dashboard.jsx
Normal file
224
hicoinpay-react/src/Dashboard.jsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import { dashboardAPI, authAPI } from './services/api';
|
||||
|
||||
function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [merchantId, setMerchantId] = useState('');
|
||||
const [balance, setBalance] = useState(0);
|
||||
const [incomingTransactions, setIncomingTransactions] = useState([]);
|
||||
const [outgoingTransactions, setOutgoingTransactions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [merchantResponse, balanceResponse, incomingResponse, outgoingResponse] = await Promise.all([
|
||||
dashboardAPI.getMerchantInfo(),
|
||||
dashboardAPI.getBalance(),
|
||||
dashboardAPI.getTransactions({ type: 'incoming', limit: 5 }),
|
||||
dashboardAPI.getTransactions({ type: 'outgoing', limit: 5 })
|
||||
]);
|
||||
|
||||
setMerchantId(merchantResponse.data.merchantId);
|
||||
setBalance(balanceResponse.data.balance);
|
||||
setIncomingTransactions(incomingResponse.data.transactions);
|
||||
setOutgoingTransactions(outgoingResponse.data.transactions);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch dashboard data:', err);
|
||||
setError('Failed to load dashboard data. Please try again.');
|
||||
// Fallback to mock data for demo
|
||||
setMerchantId('MER-123456');
|
||||
setBalance(12543.78);
|
||||
setIncomingTransactions([
|
||||
{ id: 'IN-1001', date: '2025-09-01', amount: 120.0, method: 'Card', customer: 'Alice' },
|
||||
{ id: 'IN-1002', date: '2025-09-02', amount: 250.5, method: 'Crypto', customer: 'Bob' },
|
||||
{ id: 'IN-1003', date: '2025-09-03', amount: 75.25, method: 'Bank Transfer', customer: 'Charlie' },
|
||||
]);
|
||||
setOutgoingTransactions([
|
||||
{ orderId: 'OUT-2001', orderType: 'Single', coin: 'USDT', wallet: 'TRX', amount: 300.0, status: 'Completed' },
|
||||
{ orderId: 'OUT-2002', orderType: 'Batch', coin: 'USDC', wallet: 'ETH', amount: 1500.0, status: 'Pending' },
|
||||
]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDashboardData();
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await authAPI.logout();
|
||||
} catch (err) {
|
||||
console.error('Logout error:', err);
|
||||
} finally {
|
||||
localStorage.removeItem('authToken');
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
navigate('/withdrawal');
|
||||
};
|
||||
|
||||
const handleTopUp = () => {
|
||||
navigate('/withdrawal');
|
||||
};
|
||||
|
||||
const handleViewAllIncoming = () => {
|
||||
alert('Viewing all incoming transactions. This is a placeholder action.');
|
||||
};
|
||||
|
||||
const handleViewAllOutgoing = () => {
|
||||
alert('Viewing all outgoing transactions. This is a placeholder action.');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container" style={{ padding: '2rem', maxWidth: '1400px', margin: '0 auto' }}>
|
||||
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
|
||||
<div>
|
||||
<h2>{t('dashboard.title')}</h2>
|
||||
<div style={{ color: '#718096', fontSize: '0.9rem', marginTop: '0.25rem' }}>
|
||||
{t('dashboard.merchantId')}: <span style={{ color: '#1A202C', fontWeight: 600 }}>{merchantId}</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav style={{ display: 'flex', gap: '0.75rem' }}>
|
||||
<LanguageSwitcher />
|
||||
<Link to="/api-docs" className="btn-outline">
|
||||
{t('navigation.apiDocs')}
|
||||
</Link>
|
||||
<button className="btn-outline" onClick={() => navigate('/settings')}>
|
||||
{t('common.settings')}
|
||||
</button>
|
||||
<button onClick={handleLogout} className="btn-secondary">
|
||||
{t('common.logout')}
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{loading && (
|
||||
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
||||
<p>{t('common.loading')}...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div style={{ textAlign: 'center', padding: '1rem', color: '#DC192C', backgroundColor: '#FED7D7', borderRadius: '6px', marginBottom: '1rem' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<>
|
||||
{/* Balance Summary */}
|
||||
<section style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '1rem', marginBottom: '1.5rem' }}>
|
||||
<div style={{ background: 'white', borderRadius: '12px', padding: '1.5rem', boxShadow: '0 10px 25px rgba(0,0,0,0.1)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '1rem' }}>
|
||||
<div>
|
||||
<h3 style={{ marginBottom: '0.25rem' }}>Current Balance</h3>
|
||||
<div style={{ fontSize: '2rem', fontWeight: 700, color: '#DC192C' }}>
|
||||
$ {balance.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</div>
|
||||
<p style={{ color: '#718096', marginTop: '0.25rem' }}>Total funds received via HicoinPay.</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<button className="btn-secondary" onClick={handleTopUp}>
|
||||
Top Up
|
||||
</button>
|
||||
<button className="btn-primary" onClick={handleWithdraw}>
|
||||
Withdraw
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Transactions */}
|
||||
<section style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '1.5rem' }}>
|
||||
<div style={{ background: 'white', borderRadius: '12px', padding: '1rem', boxShadow: '0 10px 25px rgba(0,0,0,0.08)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
|
||||
<h4 style={{ margin: 0 }}>Incoming Transactions</h4>
|
||||
<button className="btn-outline" onClick={handleViewAllIncoming}>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ textAlign: 'left', color: '#718096' }}>
|
||||
<th style={{ padding: '0.5rem 0' }}>ID</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Date</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Amount</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Method</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Customer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{incomingTransactions.map((tx) => (
|
||||
<tr key={tx.id}>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.id}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.date}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7', color: '#2F855A' }}>+${tx.amount.toFixed(2)}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.method}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.customer}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ background: 'white', borderRadius: '12px', padding: '1rem', boxShadow: '0 10px 25px rgba(0,0,0,0.08)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
|
||||
<h4 style={{ margin: 0 }}>Outgoing Transactions</h4>
|
||||
<button className="btn-outline" onClick={handleViewAllOutgoing}>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ textAlign: 'left', color: '#718096' }}>
|
||||
<th style={{ padding: '0.5rem 0' }}>Order ID</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Order Type</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Coin</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Wallet</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Amount</th>
|
||||
<th style={{ padding: '0.5rem 0' }}>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{outgoingTransactions.map((tx) => (
|
||||
<tr key={tx.orderId}>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.orderId}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.orderType}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.coin}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.wallet}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7', color: '#C53030' }}>-${tx.amount.toFixed(2)}</td>
|
||||
<td style={{ padding: '0.5rem 0', borderTop: '1px solid #EDF2F7' }}>{tx.status}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
50
hicoinpay-react/src/ErrorBoundary.jsx
Normal file
50
hicoinpay-react/src/ErrorBoundary.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '2rem',
|
||||
textAlign: 'center',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
color: '#DC192C'
|
||||
}}>
|
||||
<h2>Something went wrong.</h2>
|
||||
<p>Please refresh the page or contact support if the problem persists.</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
padding: '0.5rem 1rem',
|
||||
backgroundColor: '#DC192C',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '1rem'
|
||||
}}
|
||||
>
|
||||
Refresh Page
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
203
hicoinpay-react/src/ForgotPassword.jsx
Normal file
203
hicoinpay-react/src/ForgotPassword.jsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import { authAPI } from './services/api';
|
||||
|
||||
function ForgotPassword() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [step, setStep] = useState(1); // 1: request, 2: reset
|
||||
const [email, setEmail] = useState('');
|
||||
const [code, setCode] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmNewPassword, setConfirmNewPassword] = useState('');
|
||||
const [isCodeSent, setIsCodeSent] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const passwordMeetsComplexity = (pwd) => /[A-Z]/.test(pwd) && /[a-z]/.test(pwd) && /[0-9]/.test(pwd) && pwd.length >= 8;
|
||||
|
||||
const handleSendCode = async () => {
|
||||
const err = {};
|
||||
if (!email) err.email = t('registration.form.required');
|
||||
else if (!emailRegex.test(email)) err.email = t('registration.form.emailInvalid');
|
||||
setErrors(err);
|
||||
if (Object.keys(err).length > 0) return;
|
||||
|
||||
try {
|
||||
await authAPI.sendResetCode(email);
|
||||
setIsCodeSent(true);
|
||||
alert('Verification code sent to your email.');
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.message || 'Failed to send verification code. Please try again.';
|
||||
alert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
const err = {};
|
||||
if (!email) err.email = t('registration.form.required');
|
||||
else if (!emailRegex.test(email)) err.email = t('registration.form.emailInvalid');
|
||||
if (!isCodeSent) err.general = t('registration.verification.codeSent');
|
||||
setErrors(err);
|
||||
if (Object.keys(err).length > 0) return;
|
||||
setStep(2);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const err = {};
|
||||
if (!code) err.code = t('registration.form.codeRequired');
|
||||
else if (!/^\d{6}$/.test(code)) err.code = t('registration.form.codeRequired');
|
||||
if (!newPassword) err.newPassword = t('registration.form.required');
|
||||
else if (!passwordMeetsComplexity(newPassword)) err.newPassword = t('registration.form.passwordComplexity');
|
||||
if (!confirmNewPassword) err.confirmNewPassword = t('registration.form.required');
|
||||
else if (confirmNewPassword !== newPassword) err.confirmNewPassword = t('registration.form.passwordMismatch');
|
||||
setErrors(err);
|
||||
if (Object.keys(err).length > 0) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await authAPI.resetPassword({
|
||||
email,
|
||||
code,
|
||||
newPassword
|
||||
});
|
||||
alert('Password reset successfully.');
|
||||
navigate('/login');
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.message || 'Password reset failed. Please try again.';
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="registration-container">
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<Link to="/" className="logo"><h2>{t('home.title')}</h2></Link>
|
||||
<div className="header-actions">
|
||||
<LanguageSwitcher />
|
||||
<Link to="/login" className="btn-secondary">{t('login.title')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div className="registration-card">
|
||||
<form className="registration-form" onSubmit={handleSubmit}>
|
||||
<div className="step-content">
|
||||
<h2>{step === 1 ? t('login.form.forgotPassword') : t('common.update')}</h2>
|
||||
{step === 1 ? (
|
||||
<p>{t('registration.form.descriptions.verification')}</p>
|
||||
) : (
|
||||
<p>{t('settings.updateLoginPassword.title')}</p>
|
||||
)}
|
||||
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<label htmlFor="fp-email">{t('registration.form.verificationEmail')} *</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<input
|
||||
type="email"
|
||||
id="fp-email"
|
||||
name="fp-email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={errors.email ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.verificationEmail')}
|
||||
/>
|
||||
<button type="button" className="btn-outline" onClick={handleSendCode}>{t('common.sendCode')}</button>
|
||||
</div>
|
||||
{errors.email && <span className="error-message">{errors.email}</span>}
|
||||
{isCodeSent && <span style={{ color: '#718096', fontSize: '0.85rem', display: 'block', marginTop: '0.25rem' }}>{t('registration.verification.codeSent')}</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="fp-code">{t('registration.form.verificationCode')} *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="fp-code"
|
||||
name="fp-code"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
className={errors.code ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.verificationCode')}
|
||||
maxLength={6}
|
||||
/>
|
||||
{errors.code && <span className="error-message">{errors.code}</span>}
|
||||
</div>
|
||||
|
||||
{errors.general && <span className="error-message">{errors.general}</span>}
|
||||
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="button" className="btn-primary" onClick={handleNext}>{t('registration.nextStep')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="fp-new">{t('settings.updateLoginPassword.newPassword')}</label>
|
||||
<input
|
||||
type="password"
|
||||
id="fp-new"
|
||||
name="fp-new"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
className={errors.newPassword ? 'error' : ''}
|
||||
placeholder={t('settings.updateLoginPassword.newPassword')}
|
||||
/>
|
||||
{errors.newPassword && <span className="error-message">{errors.newPassword}</span>}
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="fp-confirm">{t('settings.updateLoginPassword.confirmPassword')}</label>
|
||||
<input
|
||||
type="password"
|
||||
id="fp-confirm"
|
||||
name="fp-confirm"
|
||||
value={confirmNewPassword}
|
||||
onChange={(e) => setConfirmNewPassword(e.target.value)}
|
||||
className={errors.confirmNewPassword ? 'error' : ''}
|
||||
placeholder={t('settings.updateLoginPassword.confirmPassword')}
|
||||
/>
|
||||
{errors.confirmNewPassword && <span className="error-message">{errors.confirmNewPassword}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<button type="button" className="btn-outline" onClick={() => setStep(1)}>{t('common.previous')}</button>
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="submit" className="btn-primary" disabled={isSubmitting}>{isSubmitting ? t('common.loading') : t('common.update')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
554
hicoinpay-react/src/Home.css
Normal file
554
hicoinpay-react/src/Home.css
Normal file
@@ -0,0 +1,554 @@
|
||||
/* HicoinPay Landing Page Styles */
|
||||
:root {
|
||||
--primary-color: #DC192C;
|
||||
--primary-dark: #B8152A;
|
||||
--primary-light: #E53E3E;
|
||||
--secondary-color: #2D3748;
|
||||
--text-dark: #1A202C;
|
||||
--text-light: #718096;
|
||||
--background: #FFFFFF;
|
||||
--background-light: #F7FAFC;
|
||||
--border-color: #E2E8F0;
|
||||
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-dark);
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.home-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: var(--background);
|
||||
box-shadow: var(--shadow);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo h2 {
|
||||
color: var(--primary-color);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-menu a:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Ensure header Get Started button text is white */
|
||||
.header .btn-primary {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary, .btn-secondary, .btn-outline {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--text-dark);
|
||||
border: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-primary.large, .btn-secondary.large {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #F7FAFC 0%, #EDF2F7 100%);
|
||||
padding: 4rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat p {
|
||||
color: var(--text-light);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Payment Card Visual */
|
||||
.hero-visual {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.payment-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 16px;
|
||||
width: 300px;
|
||||
height: 180px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
position: relative;
|
||||
transform: rotate(5deg);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.payment-card:hover {
|
||||
transform: rotate(0deg) scale(1.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.card-chip {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.card-logo {
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-number {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-holder, .card-expiry {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.features {
|
||||
padding: 6rem 2rem;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.features h2 {
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 3rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--background);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: var(--text-light);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Pricing Section */
|
||||
.pricing {
|
||||
padding: 6rem 2rem;
|
||||
background: var(--background-light);
|
||||
}
|
||||
|
||||
.pricing h2 {
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 3rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.pricing-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
background: var(--background);
|
||||
padding: 2.5rem 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.pricing-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.pricing-card.featured {
|
||||
border: 2px solid var(--primary-color);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pricing-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.price {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.currency {
|
||||
font-size: 1.5rem;
|
||||
vertical-align: top;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.period {
|
||||
color: var(--text-light);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
list-style: none;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.features-list li {
|
||||
padding: 0.5rem 0;
|
||||
color: var(--text-light);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.features-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* CTA Section */
|
||||
.cta {
|
||||
padding: 6rem 2rem;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta h2 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cta p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cta .btn-primary {
|
||||
background: white;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.cta .btn-primary:hover {
|
||||
background: var(--background-light);
|
||||
}
|
||||
|
||||
.cta .btn-secondary {
|
||||
border-color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cta .btn-secondary:hover {
|
||||
background: white;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
padding: 3rem 2rem 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.footer-section h3, .footer-section h4 {
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: #A0AEC0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.footer-section ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-section ul li a {
|
||||
color: #A0AEC0;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section ul li a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid #4A5568;
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.hero-content {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pricing-card.featured {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hero {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.features, .pricing, .cta {
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,122 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import './Home.css';
|
||||
|
||||
function Home() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', backgroundColor: '#f0f0f0', color: '#000' }}>
|
||||
<h1>HiCoinPay React App</h1>
|
||||
<p>Welcome to the home page!</p>
|
||||
<p>If you can see this, the app is working correctly!</p>
|
||||
<div className="home-container" style={{ minHeight: '100vh', backgroundColor: '#f5f5f5' }}>
|
||||
{/* Header */}
|
||||
<header className="header" style={{ backgroundColor: 'white', padding: '1rem', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||
<div className="nav-container">
|
||||
<div className="logo">
|
||||
<h2>{t('home.title')}</h2>
|
||||
</div>
|
||||
<nav className="nav-menu">
|
||||
<a href="#features">{t('navigation.features')}</a>
|
||||
<a href="#contact">{t('navigation.contact')}</a>
|
||||
<LanguageSwitcher />
|
||||
<Link to="/login" className="btn-outline">{t('common.clientLogin')}</Link>
|
||||
<Link to="/register" className="btn-primary">{t('common.getStarted')}</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="hero">
|
||||
<div className="hero-content">
|
||||
<h1>{t('home.subtitle')}</h1>
|
||||
<p>{t('home.description')}</p>
|
||||
<div className="hero-buttons">
|
||||
<Link to="/register" className="btn-primary large">{t('common.getStarted')}</Link>
|
||||
<Link to="/api-docs" className="btn-secondary large">{t('navigation.apiDocs')}</Link>
|
||||
</div>
|
||||
<div className="hero-stats">
|
||||
<div className="stat">
|
||||
<h3>99.9%</h3>
|
||||
<p>{t('home.stats.uptime')}</p>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<h3>50K+</h3>
|
||||
<p>{t('home.stats.merchants')}</p>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<h3>$2B+</h3>
|
||||
<p>{t('home.stats.processed')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-visual">
|
||||
<div className="payment-card">
|
||||
<div className="card-header">
|
||||
<div className="card-chip"></div>
|
||||
<div className="card-logo">HicoinPay</div>
|
||||
</div>
|
||||
<div className="card-number">•••• •••• •••• 1234</div>
|
||||
<div className="card-footer">
|
||||
<div className="card-holder">JOHN DOE</div>
|
||||
<div className="card-expiry">12/25</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section id="features" className="features">
|
||||
<div className="container">
|
||||
<h2>{t('home.features.title')}</h2>
|
||||
<div className="features-grid">
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">🔒</div>
|
||||
<h3>{t('home.features.secure.title')}</h3>
|
||||
<p>{t('home.features.secure.description')}</p>
|
||||
</div>
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">⚡</div>
|
||||
<h3>{t('home.features.fast.title')}</h3>
|
||||
<p>{t('home.features.fast.description')}</p>
|
||||
</div>
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">🌍</div>
|
||||
<h3>{t('home.features.global.title')}</h3>
|
||||
<p>{t('home.features.global.description')}</p>
|
||||
</div>
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">📱</div>
|
||||
<h3>{t('home.features.mobile.title')}</h3>
|
||||
<p>{t('home.features.mobile.description')}</p>
|
||||
</div>
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">📊</div>
|
||||
<h3>{t('home.features.analytics.title')}</h3>
|
||||
<p>{t('home.features.analytics.description')}</p>
|
||||
</div>
|
||||
<div className="feature-card">
|
||||
<div className="feature-icon">🔧</div>
|
||||
<h3>{t('home.features.integration.title')}</h3>
|
||||
<p>{t('home.features.integration.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="cta">
|
||||
<div className="container">
|
||||
<h2>{t('home.cta.title')}</h2>
|
||||
<p>{t('home.cta.description')}</p>
|
||||
<div className="cta-buttons">
|
||||
<Link to="/register" className="btn-primary large">{t('common.getStarted')}</Link>
|
||||
<Link to="/api-docs" className="btn-secondary large">{t('navigation.apiDocs')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
117
hicoinpay-react/src/Login.jsx
Normal file
117
hicoinpay-react/src/Login.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import { authAPI } from './services/api';
|
||||
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [loginId, setLoginId] = useState('');
|
||||
const [loginPassword, setLoginPassword] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const response = await authAPI.login({
|
||||
loginId,
|
||||
password: loginPassword
|
||||
});
|
||||
|
||||
// Store auth token
|
||||
localStorage.setItem('authToken', response.data.token);
|
||||
|
||||
// Navigate to dashboard
|
||||
navigate('/dashboard');
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || 'Login failed. Please try again.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="registration-container">
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<Link to="/" className="logo">
|
||||
<h2>{t('home.title')}</h2>
|
||||
</Link>
|
||||
<div className="header-actions">
|
||||
<LanguageSwitcher />
|
||||
<Link to="/register" className="btn-secondary">{t('common.register')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div className="registration-card">
|
||||
<form onSubmit={handleSubmit} className="registration-form">
|
||||
<div className="step-content">
|
||||
<h2>{t('login.title')}</h2>
|
||||
<p>{t('login.subtitle')}</p>
|
||||
|
||||
{error && (
|
||||
<div className="error-message" style={{ marginBottom: '1rem', textAlign: 'center' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="loginId">{t('login.form.loginId')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="loginId"
|
||||
name="loginId"
|
||||
value={loginId}
|
||||
onChange={(e) => setLoginId(e.target.value)}
|
||||
placeholder={t('login.form.loginId')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="loginPassword">{t('login.form.password')}</label>
|
||||
<input
|
||||
type="password"
|
||||
id="loginPassword"
|
||||
name="loginPassword"
|
||||
value={loginPassword}
|
||||
onChange={(e) => setLoginPassword(e.target.value)}
|
||||
placeholder={t('login.form.password')}
|
||||
/>
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<Link to="/forgot-password" style={{ color: '#DC192C', textDecoration: 'none' }}>
|
||||
{t('login.form.forgotPassword')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="submit" className="btn-primary" disabled={isSubmitting}>
|
||||
{isSubmitting ? t('common.loading') : t('login.title')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
534
hicoinpay-react/src/Registration.css
Normal file
534
hicoinpay-react/src/Registration.css
Normal file
@@ -0,0 +1,534 @@
|
||||
/* Registration Page Styles */
|
||||
.registration-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #F7FAFC 0%, #EDF2F7 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.registration-header {
|
||||
background: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.registration-header .container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-decoration: none;
|
||||
color: #DC192C;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
color: #B8152A;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.registration-main {
|
||||
padding: 2rem 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.registration-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Progress Bar */
|
||||
.progress-bar {
|
||||
padding: 2rem;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-step:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: calc(50% + 15px);
|
||||
right: calc(-50% + 15px);
|
||||
height: 2px;
|
||||
background: #E2E8F0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.progress-step.completed:not(:last-child)::after {
|
||||
background: #DC192C;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: #E2E8F0;
|
||||
color: #718096;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.progress-step.active .step-number {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.progress-step.completed .step-number {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: #718096;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-step.active .step-label {
|
||||
color: #DC192C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-step.completed .step-label {
|
||||
color: #DC192C;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: 2px;
|
||||
background: #E2E8F0;
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #DC192C;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.registration-form {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.step-content h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #1A202C;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
color: #718096;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Form Elements */
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #E2E8F0;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #DC192C;
|
||||
box-shadow: 0 0 0 3px rgba(220, 25, 44, 0.1);
|
||||
}
|
||||
|
||||
.form-group input.error,
|
||||
.form-group select.error {
|
||||
border-color: #E53E3E;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #E53E3E;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Checkboxes */
|
||||
.legal-checkboxes {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #E2E8F0;
|
||||
border-radius: 4px;
|
||||
margin-right: 0.75rem;
|
||||
margin-top: 0.1rem;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"]:checked + .checkmark {
|
||||
background: #DC192C;
|
||||
border-color: #DC192C;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"]:checked + .checkmark::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"].error + .checkmark {
|
||||
border-color: #E53E3E;
|
||||
}
|
||||
|
||||
.checkbox-label a {
|
||||
color: #DC192C;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.checkbox-label a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Security Notice */
|
||||
.security-notice {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
background: #F7FAFC;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
border-left: 4px solid #DC192C;
|
||||
}
|
||||
|
||||
.security-icon {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.security-notice h4 {
|
||||
margin: 0 0 0.25rem 0;
|
||||
color: #1A202C;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.security-notice p {
|
||||
margin: 0;
|
||||
color: #718096;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Compliance Notice */
|
||||
.compliance-notice {
|
||||
background: #F7FAFC;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 2rem;
|
||||
border: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.compliance-notice h4 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #1A202C;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.compliance-notice ul {
|
||||
margin: 0;
|
||||
padding-left: 1.5rem;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.compliance-notice li {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Navigation Buttons */
|
||||
.form-navigation {
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary, .btn-secondary, .btn-outline {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #B8152A;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #A0AEC0;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: #DC192C;
|
||||
border: 2px solid #DC192C;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #DC192C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #718096;
|
||||
border: 2px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
border-color: #DC192C;
|
||||
color: #DC192C;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.registration-footer {
|
||||
background: #2D3748;
|
||||
color: white;
|
||||
padding: 3rem 0 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.footer-section h3, .footer-section h4 {
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: #A0AEC0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer-section ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-section ul li a {
|
||||
color: #A0AEC0;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section ul li a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid #4A5568;
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.progress-step:not(:last-child)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.registration-form {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.step-content h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.progress-bar {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.security-notice {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.compliance-notice {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
427
hicoinpay-react/src/Registration.jsx
Normal file
427
hicoinpay-react/src/Registration.jsx
Normal file
@@ -0,0 +1,427 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||
import './Registration.css';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
import { authAPI } from './services/api';
|
||||
|
||||
function Registration() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
// Business Information (trimmed)
|
||||
businessName: '',
|
||||
domainName: '',
|
||||
|
||||
// Login Details
|
||||
loginId: '',
|
||||
loginPassword: '',
|
||||
confirmLoginPassword: '',
|
||||
|
||||
// Verification
|
||||
verificationEmail: '',
|
||||
verificationCode: '',
|
||||
|
||||
// Agreement
|
||||
agreeToTerms: false,
|
||||
agreeToPrivacy: false
|
||||
});
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isCodeSent, setIsCodeSent] = useState(false);
|
||||
|
||||
// Back to 4 steps with Verification
|
||||
const totalSteps = 4;
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === 'checkbox' ? checked : value,
|
||||
}));
|
||||
|
||||
if (errors[name]) {
|
||||
setErrors((prev) => ({ ...prev, [name]: '' }));
|
||||
}
|
||||
};
|
||||
|
||||
const passwordMeetsComplexity = (pwd) => {
|
||||
if (!pwd) return false;
|
||||
const hasUpper = /[A-Z]/.test(pwd);
|
||||
const hasLower = /[a-z]/.test(pwd);
|
||||
const hasNumber = /[0-9]/.test(pwd);
|
||||
const hasLen = pwd.length >= 8;
|
||||
return hasUpper && hasLower && hasNumber && hasLen;
|
||||
};
|
||||
|
||||
const validateStep = (step) => {
|
||||
const newErrors = {};
|
||||
|
||||
switch (step) {
|
||||
case 1: // Business Information
|
||||
if (!formData.businessName) newErrors.businessName = t('registration.form.required');
|
||||
if (formData.domainName) {
|
||||
const domainRegex = /^(?!-)([A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,}$/;
|
||||
if (!domainRegex.test(formData.domainName)) newErrors.domainName = t('registration.form.domainInvalid');
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Login Details
|
||||
if (!formData.loginId) newErrors.loginId = t('registration.form.required');
|
||||
if (!formData.loginPassword) newErrors.loginPassword = t('registration.form.required');
|
||||
else if (!passwordMeetsComplexity(formData.loginPassword)) newErrors.loginPassword = t('registration.form.passwordComplexity');
|
||||
if (!formData.confirmLoginPassword) newErrors.confirmLoginPassword = t('registration.form.required');
|
||||
else if (formData.confirmLoginPassword !== formData.loginPassword) newErrors.confirmLoginPassword = t('registration.form.passwordMismatch');
|
||||
break;
|
||||
|
||||
case 3: // Verification
|
||||
if (!formData.verificationEmail) newErrors.verificationEmail = t('registration.form.required');
|
||||
else {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formData.verificationEmail)) newErrors.verificationEmail = t('registration.form.emailInvalid');
|
||||
}
|
||||
if (!formData.verificationCode) newErrors.verificationCode = t('registration.form.codeRequired');
|
||||
else if (!/^\d{6}$/.test(formData.verificationCode)) newErrors.verificationCode = t('registration.form.codeRequired');
|
||||
break;
|
||||
|
||||
case 4: // Agreement
|
||||
if (!formData.agreeToTerms) newErrors.agreeToTerms = t('registration.form.required');
|
||||
if (!formData.agreeToPrivacy) newErrors.agreeToPrivacy = t('registration.form.required');
|
||||
break;
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (validateStep(currentStep)) {
|
||||
setCurrentStep((prev) => Math.min(prev + 1, totalSteps));
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevious = () => {
|
||||
setCurrentStep((prev) => Math.max(prev - 1, 1));
|
||||
};
|
||||
|
||||
const handleSendCode = async () => {
|
||||
if (!formData.verificationEmail) {
|
||||
setErrors((prev) => ({ ...prev, verificationEmail: t('registration.form.required') }));
|
||||
return;
|
||||
}
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formData.verificationEmail)) {
|
||||
setErrors((prev) => ({ ...prev, verificationEmail: t('registration.form.emailInvalid') }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await authAPI.verifyEmail({
|
||||
type: 'Register',
|
||||
email: formData.verificationEmail
|
||||
});
|
||||
setIsCodeSent(true);
|
||||
alert('Verification code sent to your email.');
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.message || 'Failed to send verification code. Please try again.';
|
||||
alert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!validateStep(currentStep)) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const response = await authAPI.register(formData);
|
||||
console.log('Registration successful:', response.data);
|
||||
alert('Registration successful! You will receive an email with your API credentials shortly.');
|
||||
navigate('/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
const errorMessage = error.response?.data?.message || 'Registration failed. Please try again.';
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<div className="step-content">
|
||||
<h2>{t('registration.steps.businessInfo')}</h2>
|
||||
<p>{t('registration.form.descriptions.businessInfo')}</p>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="businessName">{t('registration.form.businessName')} *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="businessName"
|
||||
name="businessName"
|
||||
value={formData.businessName}
|
||||
onChange={handleInputChange}
|
||||
className={errors.businessName ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.businessName')}
|
||||
/>
|
||||
{errors.businessName && <span className="error-message">{errors.businessName}</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="domainName">{t('registration.form.domainName')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="domainName"
|
||||
name="domainName"
|
||||
value={formData.domainName}
|
||||
onChange={handleInputChange}
|
||||
placeholder={t('registration.form.placeholders.domainName')}
|
||||
/>
|
||||
{errors.domainName && <span className="error-message">{errors.domainName}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<div className="step-content">
|
||||
<h2>{t('registration.steps.loginDetails')}</h2>
|
||||
<p>{t('registration.form.descriptions.loginDetails')}</p>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="loginId">{t('registration.form.loginId')} *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="loginId"
|
||||
name="loginId"
|
||||
value={formData.loginId}
|
||||
onChange={handleInputChange}
|
||||
className={errors.loginId ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.loginId')}
|
||||
/>
|
||||
{errors.loginId && <span className="error-message">{errors.loginId}</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group" style={{ position: 'relative' }}>
|
||||
<label htmlFor="loginPassword">{t('registration.form.loginPassword')} *</label>
|
||||
<input
|
||||
type="password"
|
||||
id="loginPassword"
|
||||
name="loginPassword"
|
||||
value={formData.loginPassword}
|
||||
onChange={handleInputChange}
|
||||
className={errors.loginPassword ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.loginPassword')}
|
||||
/>
|
||||
{errors.loginPassword && <span className="error-message">{errors.loginPassword}</span>}
|
||||
{!errors.loginPassword && formData.loginPassword && (
|
||||
<span style={{ color: '#718096', fontSize: '0.85rem', display: 'block', marginTop: '0.25rem' }}>
|
||||
{t('registration.form.passwordHint')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ position: 'relative' }}>
|
||||
<label htmlFor="confirmLoginPassword">{t('registration.form.confirmLoginPassword')} *</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmLoginPassword"
|
||||
name="confirmLoginPassword"
|
||||
value={formData.confirmLoginPassword}
|
||||
onChange={handleInputChange}
|
||||
className={errors.confirmLoginPassword ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.confirmLoginPassword')}
|
||||
/>
|
||||
{errors.confirmLoginPassword && <span className="error-message">{errors.confirmLoginPassword}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 3:
|
||||
return (
|
||||
<div className="step-content">
|
||||
<h2>{t('registration.steps.verification')}</h2>
|
||||
<p>{t('registration.form.descriptions.verification')}</p>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="verificationEmail">{t('registration.form.verificationEmail')} *</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<input
|
||||
type="email"
|
||||
id="verificationEmail"
|
||||
name="verificationEmail"
|
||||
value={formData.verificationEmail}
|
||||
onChange={handleInputChange}
|
||||
className={errors.verificationEmail ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.verificationEmail')}
|
||||
/>
|
||||
<button type="button" className="btn-outline" onClick={handleSendCode}>{t('common.sendCode')}</button>
|
||||
</div>
|
||||
{errors.verificationEmail && <span className="error-message">{errors.verificationEmail}</span>}
|
||||
{isCodeSent && <span style={{ color: '#718096', fontSize: '0.85rem', display: 'block', marginTop: '0.25rem' }}>{t('registration.verification.codeSent')}</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="verificationCode">{t('registration.form.verificationCode')} *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="verificationCode"
|
||||
name="verificationCode"
|
||||
value={formData.verificationCode}
|
||||
onChange={handleInputChange}
|
||||
className={errors.verificationCode ? 'error' : ''}
|
||||
placeholder={t('registration.form.placeholders.verificationCode')}
|
||||
maxLength={6}
|
||||
/>
|
||||
{errors.verificationCode && <span className="error-message">{errors.verificationCode}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 4:
|
||||
return (
|
||||
<div className="step-content">
|
||||
<h2>{t('registration.steps.agreement')}</h2>
|
||||
<p>{t('registration.form.descriptions.agreement')}</p>
|
||||
|
||||
<div className="legal-checkboxes">
|
||||
<div className="checkbox-group">
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="agreeToTerms"
|
||||
checked={formData.agreeToTerms}
|
||||
onChange={handleInputChange}
|
||||
className={errors.agreeToTerms ? 'error' : ''}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
{t('registration.form.agreeTos')} *
|
||||
</label>
|
||||
{errors.agreeToTerms && <span className="error-message">{errors.agreeToTerms}</span>}
|
||||
</div>
|
||||
|
||||
<div className="checkbox-group">
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="agreeToPrivacy"
|
||||
checked={formData.agreeToPrivacy}
|
||||
onChange={handleInputChange}
|
||||
className={errors.agreeToPrivacy ? 'error' : ''}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
{t('registration.form.agreePrivacy')} *
|
||||
</label>
|
||||
{errors.agreeToPrivacy && <span className="error-message">{errors.agreeToPrivacy}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="compliance-notice">
|
||||
<h4>{t('registration.agreementInfo.title')}</h4>
|
||||
<ul>
|
||||
<li>{t('registration.agreementInfo.reviewDays')}</li>
|
||||
<li>{t('registration.agreementInfo.apiCredentials')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="registration-container">
|
||||
{/* Header */}
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<Link to="/" className="logo">
|
||||
<h2>{t('home.title')}</h2>
|
||||
</Link>
|
||||
<div className="header-actions">
|
||||
<LanguageSwitcher />
|
||||
<Link to="/api-docs" className="btn-outline">{t('navigation.apiDocs')}</Link>
|
||||
<Link to="/" className="btn-secondary">{t('common.backToHome')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div className="registration-card">
|
||||
{/* Progress Bar */}
|
||||
<div className="progress-bar">
|
||||
<div className="progress-steps">
|
||||
{Array.from({ length: totalSteps }, (_, index) => (
|
||||
<div
|
||||
key={index + 1}
|
||||
className={`progress-step ${currentStep > index + 1 ? 'completed' : currentStep === index + 1 ? 'active' : ''}`}
|
||||
>
|
||||
<div className="step-number">{index + 1}</div>
|
||||
<div className="step-label">
|
||||
{index === 0 && t('registration.steps.businessInfo')}
|
||||
{index === 1 && t('registration.steps.loginDetails')}
|
||||
{index === 2 && t('registration.steps.verification')}
|
||||
{index === 3 && t('registration.steps.agreement')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="progress-line">
|
||||
<div
|
||||
className="progress-fill"
|
||||
style={{ width: `${((currentStep - 1) / (totalSteps - 1)) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="registration-form">
|
||||
{renderStepContent()}
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
{currentStep > 1 && (
|
||||
<button type="button" onClick={handlePrevious} className="btn-outline">
|
||||
{t('common.previous')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="nav-spacer"></div>
|
||||
|
||||
{currentStep < totalSteps ? (
|
||||
<button type="button" onClick={handleNext} className="btn-primary">
|
||||
{t('registration.nextStep')}
|
||||
</button>
|
||||
) : (
|
||||
<button type="submit" className="btn-primary" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Submitting...' : t('registration.completeRegistration')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Registration;
|
||||
171
hicoinpay-react/src/Settings.jsx
Normal file
171
hicoinpay-react/src/Settings.jsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
|
||||
function Settings() {
|
||||
const [activeItem, setActiveItem] = useState('batch-withdrawal'); // batch-withdrawal | update-login | update-transaction | ssh-key
|
||||
const [batchRemainingDays, setBatchRemainingDays] = useState(14);
|
||||
const [isLoginCodeSent, setIsLoginCodeSent] = useState(false);
|
||||
const [isTxCodeSent, setIsTxCodeSent] = useState(false);
|
||||
|
||||
const extendBatchFeature = (daysToAdd) => {
|
||||
setBatchRemainingDays((prev) => prev + daysToAdd);
|
||||
};
|
||||
|
||||
const handleSendLoginCode = () => {
|
||||
setIsLoginCodeSent(true);
|
||||
alert('Login password verification code sent (placeholder).');
|
||||
};
|
||||
|
||||
const handleSendTxCode = () => {
|
||||
setIsTxCodeSent(true);
|
||||
alert('Transaction password verification code sent (placeholder).');
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeItem) {
|
||||
case 'batch-withdrawal':
|
||||
return (
|
||||
<div>
|
||||
<h3>Batch Withdrawal</h3>
|
||||
<p>Manage your Batch Withdrawal feature: view remaining days and extend your usage period.</p>
|
||||
<div style={{ margin: '1rem 0', padding: '1rem', background: '#F7FAFC', border: '1px solid #E2E8F0', borderRadius: '8px' }}>
|
||||
<strong>Remaining Days:</strong> {batchRemainingDays} day{batchRemainingDays === 1 ? '' : 's'}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
|
||||
<button className="btn-outline" onClick={() => extendBatchFeature(30)}>Extend 1 Month</button>
|
||||
<button className="btn-outline" onClick={() => extendBatchFeature(182)}>Extend 6 Months</button>
|
||||
<button className="btn-outline" onClick={() => extendBatchFeature(365)}>Extend 1 Year</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'update-login':
|
||||
return (
|
||||
<div>
|
||||
<h3>Update Login Password</h3>
|
||||
<form className="registration-form" style={{ padding: 0 }}>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="new-login-pass" style={{ whiteSpace: 'nowrap' }}>New Password</label>
|
||||
<input type="password" id="new-login-pass" name="new-login-pass" placeholder="New password" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="confirm-login-pass" style={{ whiteSpace: 'nowrap' }}>Confirm New Password</label>
|
||||
<input type="password" id="confirm-login-pass" name="confirm-login-pass" placeholder="Confirm new password" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="login-verification-code">Verification Code *</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<input type="text" id="login-verification-code" name="login-verification-code" placeholder="Enter 6-digit code" maxLength={6} />
|
||||
<button type="button" className="btn-outline" onClick={handleSendLoginCode}>Send Code</button>
|
||||
</div>
|
||||
{isLoginCodeSent && <span style={{ color: '#718096', fontSize: '0.85rem', display: 'block', marginTop: '0.25rem' }}>Code sent. Please check your email.</span>}
|
||||
</div>
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="button" className="btn-primary">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
case 'update-transaction':
|
||||
return (
|
||||
<div>
|
||||
<h3>Update Transaction Password</h3>
|
||||
<form className="registration-form" style={{ padding: 0 }}>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="new-tx-pass" style={{ whiteSpace: 'nowrap' }}>New Transaction Password</label>
|
||||
<input type="password" id="new-tx-pass" name="new-tx-pass" placeholder="New transaction password" style={{ height: '44px' }} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="confirm-tx-pass" style={{ whiteSpace: 'nowrap' }}>Confirm New Transaction Password</label>
|
||||
<input type="password" id="confirm-tx-pass" name="confirm-tx-pass" placeholder="Confirm new transaction password" style={{ height: '44px' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="tx-verification-code">Verification Code *</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<input type="text" id="tx-verification-code" name="tx-verification-code" placeholder="Enter 6-digit code" maxLength={6} />
|
||||
<button type="button" className="btn-outline" onClick={handleSendTxCode}>Send Code</button>
|
||||
</div>
|
||||
{isTxCodeSent && <span style={{ color: '#718096', fontSize: '0.85rem', display: 'block', marginTop: '0.25rem' }}>Code sent. Please check your email.</span>}
|
||||
</div>
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="button" className="btn-primary">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
case 'ssh-key':
|
||||
return (
|
||||
<div>
|
||||
<h3>SSH Key</h3>
|
||||
<p>Add or rotate your SSH public key.</p>
|
||||
<form className="registration-form" style={{ padding: 0 }}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="ssh-key">Public Key</label>
|
||||
<textarea id="ssh-key" name="ssh-key" rows="6" placeholder="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."></textarea>
|
||||
</div>
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="button" className="btn-primary">Save Key</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="registration-container">
|
||||
{/* Header */}
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<Link to="/" className="logo"><h2>HicoinPay</h2></Link>
|
||||
<div className="header-actions">
|
||||
<Link to="/dashboard" className="btn-secondary">Back to Dashboard</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main */}
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: '2rem' }}>
|
||||
{/* Side Navigation Card */}
|
||||
<div className="registration-card" style={{ padding: '1.5rem', position: 'sticky', top: '90px', alignSelf: 'start', minWidth: '320px' }}>
|
||||
<nav style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<button style={{ width: '100%', textAlign: 'left', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} className={activeItem === 'batch-withdrawal' ? 'btn-primary' : 'btn-outline'} onClick={() => setActiveItem('batch-withdrawal')}>Batch Withdrawal</button>
|
||||
<button style={{ width: '100%', textAlign: 'left', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} className={activeItem === 'update-login' ? 'btn-primary' : 'btn-outline'} onClick={() => setActiveItem('update-login')}>Update Login Password</button>
|
||||
<button style={{ width: '100%', textAlign: 'left', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} className={activeItem === 'update-transaction' ? 'btn-primary' : 'btn-outline'} onClick={() => setActiveItem('update-transaction')}>Update Transaction Password</button>
|
||||
<button style={{ width: '100%', textAlign: 'left', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} className={activeItem === 'ssh-key' ? 'btn-primary' : 'btn-outline'} onClick={() => setActiveItem('ssh-key')}>SSH Key</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Content Area (no card) */}
|
||||
<section>
|
||||
{renderContent()}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
21
hicoinpay-react/src/SimpleHome.jsx
Normal file
21
hicoinpay-react/src/SimpleHome.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function SimpleHome() {
|
||||
return (
|
||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||||
<h1>HicoinPay</h1>
|
||||
<p>Welcome to HicoinPay</p>
|
||||
<div style={{ marginTop: '2rem' }}>
|
||||
<Link to="/login" style={{ marginRight: '1rem', padding: '0.5rem 1rem', backgroundColor: '#DC192C', color: 'white', textDecoration: 'none', borderRadius: '4px' }}>
|
||||
Login
|
||||
</Link>
|
||||
<Link to="/register" style={{ padding: '0.5rem 1rem', backgroundColor: '#2D3748', color: 'white', textDecoration: 'none', borderRadius: '4px' }}>
|
||||
Register
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SimpleHome;
|
||||
12
hicoinpay-react/src/TestApp.jsx
Normal file
12
hicoinpay-react/src/TestApp.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
function TestApp() {
|
||||
return (
|
||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||||
<h1>HicoinPay Test App</h1>
|
||||
<p>If you can see this, React is working!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestApp;
|
||||
128
hicoinpay-react/src/Withdrawal.jsx
Normal file
128
hicoinpay-react/src/Withdrawal.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import SiteFooter from './components/SiteFooter';
|
||||
|
||||
function Withdrawal() {
|
||||
const [activeTab, setActiveTab] = useState('single'); // 'single' | 'batch'
|
||||
|
||||
const handleSubmitSingle = (e) => {
|
||||
e.preventDefault();
|
||||
alert('Single withdrawal submitted (placeholder).');
|
||||
};
|
||||
|
||||
const handleSubmitBatch = (e) => {
|
||||
e.preventDefault();
|
||||
alert('Batch withdrawal submitted (placeholder).');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="registration-container">
|
||||
<header className="registration-header">
|
||||
<div className="container">
|
||||
<div className="header-content">
|
||||
<Link to="/" className="logo"><h2>HicoinPay</h2></Link>
|
||||
<div className="header-actions">
|
||||
<Link to="/dashboard" className="btn-secondary">Back to Dashboard</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="registration-main">
|
||||
<div className="container">
|
||||
<div className="registration-card" style={{ padding: 0 }}>
|
||||
<div style={{ padding: '1rem 1.5rem', borderBottom: '1px solid #E2E8F0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<h2 style={{ margin: 0 }}>Withdrawal</h2>
|
||||
</div>
|
||||
|
||||
{/* Tab Bar */}
|
||||
<div style={{ padding: '0 1.5rem' }}>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', borderBottom: '1px solid #E2E8F0' }}>
|
||||
<button
|
||||
className={activeTab === 'single' ? 'btn-primary' : 'btn-outline'}
|
||||
style={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }}
|
||||
onClick={() => setActiveTab('single')}
|
||||
>
|
||||
Single Withdrawal
|
||||
</button>
|
||||
<button
|
||||
className={activeTab === 'batch' ? 'btn-primary' : 'btn-outline'}
|
||||
style={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }}
|
||||
onClick={() => setActiveTab('batch')}
|
||||
>
|
||||
Batch Withdrawal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '1.5rem' }}>
|
||||
{activeTab === 'single' ? (
|
||||
<form onSubmit={handleSubmitSingle} className="registration-form" style={{ padding: 0 }}>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="single-coin">Coin</label>
|
||||
<select id="single-coin" name="single-coin">
|
||||
<option value="USDT">USDT</option>
|
||||
<option value="USDC">USDC</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="single-wallet">Wallet Network</label>
|
||||
<select id="single-wallet" name="single-wallet">
|
||||
<option value="BNB">BNB</option>
|
||||
<option value="ETH">ETH</option>
|
||||
<option value="TRX">TRX</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label htmlFor="single-address">Destination Address</label>
|
||||
<input type="text" id="single-address" name="single-address" placeholder="Enter destination address" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="single-amount">Amount</label>
|
||||
<input type="number" step="0.00000001" id="single-amount" name="single-amount" placeholder="0.00" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="single-memo">Memo / Note (optional)</label>
|
||||
<input type="text" id="single-memo" name="single-memo" placeholder="Add a note (optional)" />
|
||||
</div>
|
||||
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="submit" className="btn-primary">Submit Withdrawal</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={handleSubmitBatch} className="registration-form" style={{ padding: 0 }}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="batch-upload">Upload CSV</label>
|
||||
<input type="file" id="batch-upload" name="batch-upload" accept=".csv" />
|
||||
<p style={{ color: '#718096', fontSize: '0.9rem', marginTop: '0.5rem' }}>CSV columns: address, amount, coin(USDT/USDC), wallet(BNB/ETH/TRX), memo(optional)</p>
|
||||
</div>
|
||||
|
||||
<div className="form-navigation">
|
||||
<div className="nav-buttons">
|
||||
<div className="nav-spacer"></div>
|
||||
<button type="submit" className="btn-primary">Submit Batch</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Withdrawal;
|
||||
106
hicoinpay-react/src/components/LanguageSwitcher.jsx
Normal file
106
hicoinpay-react/src/components/LanguageSwitcher.jsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LanguageSwitcher = ({ inverse = false }) => {
|
||||
const { i18n } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const languages = [
|
||||
{ code: 'en', name: 'EN', sub: 'English' },
|
||||
{ code: 'zh-CN', name: '简体中文', sub: 'Chinese (Simplified)' }
|
||||
];
|
||||
|
||||
const currentCode = i18n.language === 'zh-TW' ? 'zh-CN' : i18n.language;
|
||||
const current = languages.find(l => l.code === currentCode) || languages[0];
|
||||
|
||||
const changeLanguage = (lng) => {
|
||||
i18n.changeLanguage(lng);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onClickOutside = (e) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', onClickOutside);
|
||||
return () => document.removeEventListener('mousedown', onClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} style={{ position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-outline"
|
||||
onClick={() => setOpen(v => !v)}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
style={{
|
||||
padding: '0.45rem 0.9rem',
|
||||
borderRadius: '9999px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
color: inverse ? '#FFFFFF' : undefined,
|
||||
borderColor: inverse ? '#FFFFFF' : undefined,
|
||||
background: 'transparent'
|
||||
}}
|
||||
>
|
||||
<span>{current.name}</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
role="listbox"
|
||||
aria-label="Select language"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
marginTop: '8px',
|
||||
minWidth: '180px',
|
||||
background: '#FFFFFF',
|
||||
border: '1px solid #E2E8F0',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 10px 25px rgba(0,0,0,0.12)',
|
||||
overflow: 'hidden',
|
||||
zIndex: 20
|
||||
}}
|
||||
>
|
||||
{languages.map(lang => {
|
||||
const isActive = lang.code === current.code;
|
||||
return (
|
||||
<button
|
||||
key={lang.code}
|
||||
role="option"
|
||||
aria-selected={isActive}
|
||||
onClick={() => changeLanguage(lang.code)}
|
||||
className={isActive ? 'btn-primary' : ''}
|
||||
style={{
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
padding: '0.65rem 0.9rem',
|
||||
background: isActive ? undefined : '#FFFFFF',
|
||||
border: 'none',
|
||||
borderBottom: '1px solid #F1F5F9',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<span style={{ fontSize: '0.95rem', color: isActive ? '#FFFFFF' : '#1A202C' }}>{lang.name}</span>
|
||||
<span style={{ fontSize: '0.75rem', color: isActive ? 'rgba(255,255,255,0.9)' : '#64748B' }}>{lang.sub}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
40
hicoinpay-react/src/components/SiteFooter.jsx
Normal file
40
hicoinpay-react/src/components/SiteFooter.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function SiteFooter() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<footer className="registration-footer">
|
||||
<div className="container">
|
||||
<div className="footer-content" style={{ display: 'flex', flexDirection: 'row', gap: '2rem', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div className="footer-section" style={{ minWidth: '220px' }}>
|
||||
<h3>{t('home.title')}</h3>
|
||||
<p>{t('footer.description')}</p>
|
||||
</div>
|
||||
<div className="footer-section" style={{ minWidth: '200px' }}>
|
||||
<h4>{t('footer.sections.support')}</h4>
|
||||
<ul>
|
||||
<li><a href="#help">{t('footer.links.helpCenter')}</a></li>
|
||||
<li><a href="#contact">{t('footer.links.contactUs')}</a></li>
|
||||
<li><a href="#status">{t('footer.links.status')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="footer-section" style={{ minWidth: '200px' }}>
|
||||
<h4>{t('footer.sections.legal')}</h4>
|
||||
<ul>
|
||||
<li><a href="#terms">{t('footer.links.terms')}</a></li>
|
||||
<li><a href="#privacy">{t('footer.links.privacy')}</a></li>
|
||||
<li><a href="#compliance">{t('footer.links.compliance')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-bottom">
|
||||
<p>{t('footer.copyright')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default SiteFooter;
|
||||
39
hicoinpay-react/src/i18n.js
Normal file
39
hicoinpay-react/src/i18n.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// Import translation files
|
||||
import enTranslation from './locales/en.json';
|
||||
import zhCNTranslation from './locales/zh-CN.json';
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: enTranslation
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: zhCNTranslation
|
||||
}
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
debug: false,
|
||||
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator', 'htmlTag'],
|
||||
caches: ['localStorage'],
|
||||
},
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // React already does escaping
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('i18n initialization error:', error);
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -1,68 +1,49 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #1A202C;
|
||||
background-color: #FFFFFF;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
/* Focus styles for accessibility */
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
a:focus {
|
||||
outline: 2px solid #DC192C;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #DC192C;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #B8152A;
|
||||
}
|
||||
|
||||
293
hicoinpay-react/src/locales/en.json
Normal file
293
hicoinpay-react/src/locales/en.json
Normal file
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"common": {
|
||||
"getStarted": "Get Started",
|
||||
"clientLogin": "Client Login",
|
||||
"register": "Register",
|
||||
"logout": "Logout",
|
||||
"backToDashboard": "Back to Dashboard",
|
||||
"backToHome": "Back to Home",
|
||||
"settings": "Settings",
|
||||
"withdraw": "Withdraw",
|
||||
"topUp": "Top Up",
|
||||
"viewAll": "View All",
|
||||
"sendCode": "Send Code",
|
||||
"update": "Update",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"submit": "Submit",
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"complete": "Complete",
|
||||
"loading": "Loading...",
|
||||
"error": "Error",
|
||||
"success": "Success"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"features": "Features",
|
||||
"contact": "Contact",
|
||||
"apiDocs": "API Docs",
|
||||
"about": "About"
|
||||
},
|
||||
"home": {
|
||||
"title": "HicoinPay",
|
||||
"subtitle": "Secure Payment Gateway for Cryptocurrency",
|
||||
"description": "Accept cryptocurrency payments with ease. Fast, secure, and reliable payment processing for your business.",
|
||||
"features": {
|
||||
"title": "Why Choose HicoinPay?",
|
||||
"secure": {
|
||||
"title": "Secure",
|
||||
"description": "Bank-level security with end-to-end encryption"
|
||||
},
|
||||
"fast": {
|
||||
"title": "Fast",
|
||||
"description": "Lightning-fast transaction processing"
|
||||
},
|
||||
"reliable": {
|
||||
"title": "Reliable",
|
||||
"description": "99.9% uptime guarantee"
|
||||
},
|
||||
"global": {
|
||||
"title": "Global Reach",
|
||||
"description": "Accept payments from 190+ countries with 100+ payment methods."
|
||||
},
|
||||
"mobile": {
|
||||
"title": "Mobile Optimized",
|
||||
"description": "Seamless mobile payment experience across all devices and platforms."
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytics Dashboard",
|
||||
"description": "Real-time insights and detailed reporting for your business growth."
|
||||
},
|
||||
"integration": {
|
||||
"title": "Easy Integration",
|
||||
"description": "Simple APIs and SDKs for quick integration with any platform."
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "Ready to Get Started?",
|
||||
"description": "Join thousands of businesses already using HicoinPay to process their payments."
|
||||
},
|
||||
"stats": {
|
||||
"uptime": "Uptime",
|
||||
"merchants": "Merchants",
|
||||
"processed": "Processed"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"title": "Create Account",
|
||||
"subtitle": "Join thousands of merchants using HicoinPay",
|
||||
"steps": {
|
||||
"businessInfo": "Business Information",
|
||||
"loginDetails": "Login Details",
|
||||
"agreement": "Agreement",
|
||||
"verification": "Verification"
|
||||
},
|
||||
"nextStep": "Next Step",
|
||||
"completeRegistration": "Complete Registration",
|
||||
"form": {
|
||||
"businessName": "Business Name",
|
||||
"businessType": "Business Type",
|
||||
"domainName": "Domain Name",
|
||||
"loginId": "Login ID",
|
||||
"loginPassword": "Login Password",
|
||||
"confirmLoginPassword": "Confirm Login Password",
|
||||
"transactionPassword": "Transaction Password",
|
||||
"verificationEmail": "Email Address",
|
||||
"verificationCode": "Verification Code",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"agreeTos": "I agree to the Terms of Service",
|
||||
"agreeTerms": "I agree to the Terms of Service and Privacy Policy",
|
||||
"agreePrivacy": "I agree to the Privacy Policy",
|
||||
"required": "This field is required",
|
||||
"emailInvalid": "Please enter a valid email address",
|
||||
"passwordMismatch": "Passwords do not match",
|
||||
"passwordComplexity": "Password must be 8+ chars with uppercase, lowercase, and number",
|
||||
"passwordHint": "Use at least 8 characters, including uppercase, lowercase and a number.",
|
||||
"domainInvalid": "Please enter a valid domain (e.g., example.com)",
|
||||
"codeRequired": "Please enter the 6-digit verification code",
|
||||
"placeholders": {
|
||||
"businessName": "Enter your business name",
|
||||
"domainName": "yourdomain.com",
|
||||
"loginId": "Enter your login ID",
|
||||
"loginPassword": "Enter your login password",
|
||||
"confirmLoginPassword": "Confirm your login password",
|
||||
"transactionPassword": "Enter your transaction password",
|
||||
"verificationEmail": "Enter your email address",
|
||||
"verificationCode": "Enter 6-digit code"
|
||||
},
|
||||
"descriptions": {
|
||||
"businessInfo": "Tell us about your business to get started with HicoinPay.",
|
||||
"loginDetails": "Set your login credentials to access the dashboard.",
|
||||
"agreement": "Review and accept our terms to complete your registration.",
|
||||
"verification": "Verify your email address to activate your account."
|
||||
}
|
||||
},
|
||||
"agreementInfo": {
|
||||
"title": "Agreement Information",
|
||||
"reviewDays": "Account approval typically takes 1-3 business days",
|
||||
"apiCredentials": "You will receive API credentials upon approval"
|
||||
},
|
||||
"compliance": {
|
||||
"title": "Account Approval & API Credentials",
|
||||
"description": "Your account will be reviewed and approved within 24-48 hours. Once approved, you'll receive your API credentials via email."
|
||||
},
|
||||
"verification": {
|
||||
"title": "Email Verification",
|
||||
"description": "We've sent a 6-digit verification code to your email address. Please enter the code below to complete your registration.",
|
||||
"codeSent": "Verification code sent to your email",
|
||||
"resendCode": "Resend Code"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"title": "Client Login",
|
||||
"subtitle": "Access your HicoinPay dashboard",
|
||||
"form": {
|
||||
"loginId": "Login ID",
|
||||
"password": "Password",
|
||||
"rememberMe": "Remember me",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"noAccount": "Don't have an account?",
|
||||
"signUp": "Sign up here"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"welcome": "Welcome back!",
|
||||
"merchantId": "Merchant ID",
|
||||
"activation": {
|
||||
"title": "Activate Your Account",
|
||||
"description": "Please activate your account to access all features and start processing payments.",
|
||||
"activateButton": "Activate Account"
|
||||
},
|
||||
"balance": {
|
||||
"title": "Current Balance",
|
||||
"amount": "$0.00"
|
||||
},
|
||||
"transactions": {
|
||||
"incoming": {
|
||||
"title": "Incoming Transactions",
|
||||
"noData": "No incoming transactions yet"
|
||||
},
|
||||
"outgoing": {
|
||||
"title": "Outgoing Transactions",
|
||||
"noData": "No outgoing transactions yet",
|
||||
"columns": {
|
||||
"orderId": "Order ID",
|
||||
"orderType": "Order Type",
|
||||
"coin": "Coin",
|
||||
"wallet": "Wallet",
|
||||
"amount": "Amount",
|
||||
"status": "Status"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"withdrawal": {
|
||||
"title": "Withdrawal",
|
||||
"subtitle": "Manage your withdrawals",
|
||||
"tabs": {
|
||||
"single": "Single Withdrawal",
|
||||
"batch": "Batch Withdrawal"
|
||||
},
|
||||
"single": {
|
||||
"coin": "Coin",
|
||||
"walletNetwork": "Wallet Network",
|
||||
"destinationAddress": "Destination Address",
|
||||
"amount": "Amount",
|
||||
"memo": "Memo (Optional)"
|
||||
},
|
||||
"batch": {
|
||||
"title": "Batch Withdrawal",
|
||||
"description": "Upload a CSV file with withdrawal details",
|
||||
"csvFormat": "CSV Format: coin,wallet,address,amount,memo",
|
||||
"uploadFile": "Upload CSV File",
|
||||
"downloadTemplate": "Download Template"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"navigation": {
|
||||
"batchWithdrawal": "Batch Withdrawal",
|
||||
"updateLoginPassword": "Update Login Password",
|
||||
"updateTransactionPassword": "Update Transaction Password",
|
||||
"sshKey": "SSH Key"
|
||||
},
|
||||
"batchWithdrawal": {
|
||||
"title": "Batch Withdrawal",
|
||||
"description": "Manage your Batch Withdrawal feature: view remaining days and extend your usage period.",
|
||||
"remainingDays": "Remaining Days",
|
||||
"extendOptions": {
|
||||
"oneMonth": "Extend 1 Month",
|
||||
"sixMonths": "Extend 6 Months",
|
||||
"oneYear": "Extend 1 Year"
|
||||
}
|
||||
},
|
||||
"updateLoginPassword": {
|
||||
"title": "Update Login Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"verificationCode": "Verification Code",
|
||||
"codeSent": "Code sent. Please check your email."
|
||||
},
|
||||
"updateTransactionPassword": {
|
||||
"title": "Update Transaction Password",
|
||||
"newPassword": "New Transaction Password",
|
||||
"confirmPassword": "Confirm New Transaction Password",
|
||||
"verificationCode": "Verification Code",
|
||||
"codeSent": "Code sent. Please check your email."
|
||||
},
|
||||
"sshKey": {
|
||||
"title": "SSH Key",
|
||||
"description": "Add or rotate your SSH public key.",
|
||||
"publicKey": "Public Key"
|
||||
}
|
||||
},
|
||||
"apiDocs": {
|
||||
"title": "API Documentation",
|
||||
"subtitle": "Integrate HicoinPay into your application",
|
||||
"sections": {
|
||||
"apiReference": "API Reference",
|
||||
"integrationGuide": "Integration Guide"
|
||||
},
|
||||
"availableDocs": "Available Documentation",
|
||||
"downloadLatest": "Download the latest API reference.",
|
||||
"downloadPdf": "Download PDF",
|
||||
"viewDetails": "View Details",
|
||||
"updated": "Updated",
|
||||
"version": "v",
|
||||
"close": "Close",
|
||||
"modal": {
|
||||
"version": "Version",
|
||||
"lastUpdated": "Last Updated",
|
||||
"fileSize": "File Size",
|
||||
"description": "Description"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"description": "HicoinPay - Secure cryptocurrency payment gateway for modern businesses.",
|
||||
"sections": {
|
||||
"product": "Product",
|
||||
"support": "Support",
|
||||
"company": "Company",
|
||||
"legal": "Legal"
|
||||
},
|
||||
"links": {
|
||||
"features": "Features",
|
||||
"apiDocs": "API Documentation",
|
||||
"sdks": "SDKs",
|
||||
"helpCenter": "Help Center",
|
||||
"contactUs": "Contact Us",
|
||||
"status": "Status",
|
||||
"security": "Security",
|
||||
"about": "About",
|
||||
"careers": "Careers",
|
||||
"privacy": "Privacy",
|
||||
"terms": "Terms",
|
||||
"compliance": "Compliance",
|
||||
"apiReference": "API Reference",
|
||||
"integrationGuide": "Integration Guide"
|
||||
},
|
||||
"copyright": "© 2024 HicoinPay. All rights reserved."
|
||||
}
|
||||
}
|
||||
293
hicoinpay-react/src/locales/zh-CN.json
Normal file
293
hicoinpay-react/src/locales/zh-CN.json
Normal file
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"common": {
|
||||
"getStarted": "开始使用",
|
||||
"clientLogin": "客户登录",
|
||||
"register": "注册",
|
||||
"logout": "退出登录",
|
||||
"backToDashboard": "返回仪表板",
|
||||
"backToHome": "返回首页",
|
||||
"settings": "设置",
|
||||
"withdraw": "提现",
|
||||
"topUp": "充值",
|
||||
"viewAll": "查看全部",
|
||||
"sendCode": "发送验证码",
|
||||
"update": "更新",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"submit": "提交",
|
||||
"next": "下一步",
|
||||
"previous": "上一步",
|
||||
"complete": "完成",
|
||||
"loading": "加载中...",
|
||||
"error": "错误",
|
||||
"success": "成功"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "首页",
|
||||
"features": "功能特色",
|
||||
"contact": "联系我们",
|
||||
"apiDocs": "API文档",
|
||||
"about": "关于"
|
||||
},
|
||||
"home": {
|
||||
"title": "HicoinPay",
|
||||
"subtitle": "安全的加密货币支付网关",
|
||||
"description": "轻松接受加密货币支付。快速、安全、可靠的业务支付处理。",
|
||||
"features": {
|
||||
"title": "为什么选择HicoinPay?",
|
||||
"secure": {
|
||||
"title": "安全",
|
||||
"description": "银行级安全,端到端加密"
|
||||
},
|
||||
"fast": {
|
||||
"title": "快速",
|
||||
"description": "闪电般快速的交易处理"
|
||||
},
|
||||
"reliable": {
|
||||
"title": "可靠",
|
||||
"description": "99.9%正常运行时间保证"
|
||||
},
|
||||
"global": {
|
||||
"title": "全球覆盖",
|
||||
"description": "支持190多个国家和100多种支付方式。"
|
||||
},
|
||||
"mobile": {
|
||||
"title": "移动优化",
|
||||
"description": "跨所有设备和平台的无缝移动支付体验。"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "分析仪表板",
|
||||
"description": "实时洞察和详细报告,助力业务增长。"
|
||||
},
|
||||
"integration": {
|
||||
"title": "易于集成",
|
||||
"description": "简单的API和SDK,快速集成到任何平台。"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "准备开始了吗?",
|
||||
"description": "加入数千家已经使用HicoinPay处理支付的企业。"
|
||||
},
|
||||
"stats": {
|
||||
"uptime": "正常运行时间",
|
||||
"merchants": "商户",
|
||||
"processed": "已处理"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"title": "创建账户",
|
||||
"subtitle": "加入数千家使用HicoinPay的商户",
|
||||
"steps": {
|
||||
"businessInfo": "企业信息",
|
||||
"loginDetails": "登录详情",
|
||||
"agreement": "协议",
|
||||
"verification": "验证"
|
||||
},
|
||||
"nextStep": "下一步",
|
||||
"completeRegistration": "完成注册",
|
||||
"form": {
|
||||
"businessName": "企业名称",
|
||||
"businessType": "企业类型",
|
||||
"domainName": "域名",
|
||||
"loginId": "登录ID",
|
||||
"loginPassword": "登录密码",
|
||||
"confirmLoginPassword": "确认登录密码",
|
||||
"transactionPassword": "交易密码",
|
||||
"verificationEmail": "邮箱地址",
|
||||
"verificationCode": "验证码",
|
||||
"confirmPassword": "确认密码",
|
||||
"agreeTos": "我同意服务条款",
|
||||
"agreeTerms": "我同意服务条款和隐私政策",
|
||||
"agreePrivacy": "我同意隐私政策",
|
||||
"required": "此字段为必填项",
|
||||
"emailInvalid": "请输入有效的邮箱地址",
|
||||
"passwordMismatch": "两次密码不一致",
|
||||
"passwordComplexity": "密码需至少8位,且包含大写、小写字母与数字",
|
||||
"passwordHint": "请使用至少8位,包含大写、小写字母和数字。",
|
||||
"domainInvalid": "请输入有效的域名(例如:example.com)",
|
||||
"codeRequired": "请输入6位验证码",
|
||||
"placeholders": {
|
||||
"businessName": "输入您的企业名称",
|
||||
"domainName": "yourdomain.com",
|
||||
"loginId": "输入您的登录ID",
|
||||
"loginPassword": "输入您的登录密码",
|
||||
"confirmLoginPassword": "再次输入登录密码",
|
||||
"transactionPassword": "输入您的交易密码",
|
||||
"verificationEmail": "输入您的邮箱地址",
|
||||
"verificationCode": "输入6位验证码"
|
||||
},
|
||||
"descriptions": {
|
||||
"businessInfo": "告诉我们关于您的企业信息,开始使用HicoinPay。",
|
||||
"loginDetails": "设置您的登录凭据以访问仪表板。",
|
||||
"agreement": "查看并接受我们的条款以完成注册。",
|
||||
"verification": "验证您的邮箱地址以激活账户。"
|
||||
}
|
||||
},
|
||||
"agreementInfo": {
|
||||
"title": "协议信息",
|
||||
"reviewDays": "账户审核通常需要1-3个工作日",
|
||||
"apiCredentials": "审核通过后您将收到API凭证"
|
||||
},
|
||||
"compliance": {
|
||||
"title": "账户审批和API凭证",
|
||||
"description": "您的账户将在24-48小时内审核并批准。批准后,您将通过邮件收到API凭证。"
|
||||
},
|
||||
"verification": {
|
||||
"title": "邮箱验证",
|
||||
"description": "我们已向您的邮箱发送了6位验证码。请在下方输入验证码以完成注册。",
|
||||
"codeSent": "验证码已发送到您的邮箱",
|
||||
"resendCode": "重新发送验证码"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"title": "客户登录",
|
||||
"subtitle": "访问您的HicoinPay仪表板",
|
||||
"form": {
|
||||
"loginId": "登录ID",
|
||||
"password": "密码",
|
||||
"rememberMe": "记住我",
|
||||
"forgotPassword": "忘记密码?",
|
||||
"noAccount": "没有账户?",
|
||||
"signUp": "立即注册"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "仪表板",
|
||||
"welcome": "欢迎回来!",
|
||||
"merchantId": "商户ID",
|
||||
"activation": {
|
||||
"title": "激活您的账户",
|
||||
"description": "请激活您的账户以访问所有功能并开始处理支付。",
|
||||
"activateButton": "激活账户"
|
||||
},
|
||||
"balance": {
|
||||
"title": "当前余额",
|
||||
"amount": "$0.00"
|
||||
},
|
||||
"transactions": {
|
||||
"incoming": {
|
||||
"title": "入账交易",
|
||||
"noData": "暂无入账交易"
|
||||
},
|
||||
"outgoing": {
|
||||
"title": "出账交易",
|
||||
"noData": "暂无出账交易",
|
||||
"columns": {
|
||||
"orderId": "订单ID",
|
||||
"orderType": "订单类型",
|
||||
"coin": "币种",
|
||||
"wallet": "钱包",
|
||||
"amount": "金额",
|
||||
"status": "状态"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"withdrawal": {
|
||||
"title": "提现",
|
||||
"subtitle": "管理您的提现",
|
||||
"tabs": {
|
||||
"single": "单笔提现",
|
||||
"batch": "批量提现"
|
||||
},
|
||||
"single": {
|
||||
"coin": "币种",
|
||||
"walletNetwork": "钱包网络",
|
||||
"destinationAddress": "目标地址",
|
||||
"amount": "金额",
|
||||
"memo": "备注(可选)"
|
||||
},
|
||||
"batch": {
|
||||
"title": "批量提现",
|
||||
"description": "上传包含提现详情的CSV文件",
|
||||
"csvFormat": "CSV格式:币种,钱包,地址,金额,备注",
|
||||
"uploadFile": "上传CSV文件",
|
||||
"downloadTemplate": "下载模板"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"navigation": {
|
||||
"batchWithdrawal": "批量提现",
|
||||
"updateLoginPassword": "更新登录密码",
|
||||
"updateTransactionPassword": "更新交易密码",
|
||||
"sshKey": "SSH密钥"
|
||||
},
|
||||
"batchWithdrawal": {
|
||||
"title": "批量提现",
|
||||
"description": "管理您的批量提现功能:查看剩余天数并延长使用期限。",
|
||||
"remainingDays": "剩余天数",
|
||||
"extendOptions": {
|
||||
"oneMonth": "延长1个月",
|
||||
"sixMonths": "延长6个月",
|
||||
"oneYear": "延长1年"
|
||||
}
|
||||
},
|
||||
"updateLoginPassword": {
|
||||
"title": "更新登录密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmPassword": "确认新密码",
|
||||
"verificationCode": "验证码",
|
||||
"codeSent": "验证码已发送,请查看您的邮箱。"
|
||||
},
|
||||
"updateTransactionPassword": {
|
||||
"title": "更新交易密码",
|
||||
"newPassword": "新交易密码",
|
||||
"confirmPassword": "确认新交易密码",
|
||||
"verificationCode": "验证码",
|
||||
"codeSent": "验证码已发送,请查看您的邮箱。"
|
||||
},
|
||||
"sshKey": {
|
||||
"title": "SSH密钥",
|
||||
"description": "添加或轮换您的SSH公钥。",
|
||||
"publicKey": "公钥"
|
||||
}
|
||||
},
|
||||
"apiDocs": {
|
||||
"title": "API文档",
|
||||
"subtitle": "将HicoinPay集成到您的应用程序中",
|
||||
"sections": {
|
||||
"apiReference": "API参考",
|
||||
"integrationGuide": "集成指南"
|
||||
},
|
||||
"availableDocs": "可用文档",
|
||||
"downloadLatest": "下载最新的API参考。",
|
||||
"downloadPdf": "下载PDF",
|
||||
"viewDetails": "查看详情",
|
||||
"updated": "更新于",
|
||||
"version": "v",
|
||||
"close": "关闭",
|
||||
"modal": {
|
||||
"version": "版本",
|
||||
"lastUpdated": "最后更新",
|
||||
"fileSize": "文件大小",
|
||||
"description": "描述"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"description": "HicoinPay - 为现代企业提供安全的加密货币支付网关。",
|
||||
"sections": {
|
||||
"product": "产品",
|
||||
"support": "支持",
|
||||
"company": "公司",
|
||||
"legal": "法律"
|
||||
},
|
||||
"links": {
|
||||
"features": "功能特色",
|
||||
"apiDocs": "API文档",
|
||||
"sdks": "SDK",
|
||||
"helpCenter": "帮助中心",
|
||||
"contactUs": "联系我们",
|
||||
"status": "状态",
|
||||
"security": "安全",
|
||||
"about": "关于",
|
||||
"careers": "招聘",
|
||||
"privacy": "隐私",
|
||||
"terms": "条款",
|
||||
"compliance": "合规",
|
||||
"apiReference": "API参考",
|
||||
"integrationGuide": "集成指南"
|
||||
},
|
||||
"copyright": "© 2024 HicoinPay。保留所有权利。"
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@
|
||||
import React, { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.jsx';
|
||||
import ErrorBoundary from './ErrorBoundary.jsx';
|
||||
import './index.css';
|
||||
import './i18n';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
</ErrorBoundary>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
109
hicoinpay-react/src/services/api.js
Normal file
109
hicoinpay-react/src/services/api.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Create axios instance with base configuration
|
||||
const api = axios.create({
|
||||
baseURL: 'https://test-rwa-service.hicoinpay.com',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor to add auth token
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response interceptor to handle common errors
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Handle unauthorized access
|
||||
localStorage.removeItem('authToken');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// API endpoints
|
||||
export const authAPI = {
|
||||
// Login
|
||||
login: (credentials) => api.post('/auth/login', credentials),
|
||||
|
||||
// Register
|
||||
register: (userData) => api.post('/auth/register', userData),
|
||||
|
||||
// Forgot password - send code
|
||||
sendResetCode: (email) => api.post('/auth/forgot-password', { email }),
|
||||
|
||||
// Reset password
|
||||
resetPassword: (data) => api.post('/auth/reset-password', data),
|
||||
|
||||
// Verify email
|
||||
verifyEmail: (data) => api.post('/api/merchant/common/sendEmailCode', data),
|
||||
|
||||
// Logout
|
||||
logout: () => api.post('/auth/logout'),
|
||||
};
|
||||
|
||||
export const userAPI = {
|
||||
// Get user profile
|
||||
getProfile: () => api.get('/user/profile'),
|
||||
|
||||
// Update profile
|
||||
updateProfile: (data) => api.put('/user/profile', data),
|
||||
|
||||
// Update password
|
||||
updatePassword: (data) => api.put('/user/password', data),
|
||||
|
||||
// Update transaction password
|
||||
updateTransactionPassword: (data) => api.put('/user/transaction-password', data),
|
||||
};
|
||||
|
||||
export const dashboardAPI = {
|
||||
// Get balance
|
||||
getBalance: () => api.get('/dashboard/balance'),
|
||||
|
||||
// Get transaction history
|
||||
getTransactions: (params) => api.get('/dashboard/transactions', { params }),
|
||||
|
||||
// Get merchant info
|
||||
getMerchantInfo: () => api.get('/dashboard/merchant'),
|
||||
};
|
||||
|
||||
export const withdrawalAPI = {
|
||||
// Single withdrawal
|
||||
createWithdrawal: (data) => api.post('/withdrawal/single', data),
|
||||
|
||||
// Batch withdrawal
|
||||
createBatchWithdrawal: (data) => api.post('/withdrawal/batch', data),
|
||||
|
||||
// Get withdrawal history
|
||||
getWithdrawalHistory: (params) => api.get('/withdrawal/history', { params }),
|
||||
};
|
||||
|
||||
export const settingsAPI = {
|
||||
// Get settings
|
||||
getSettings: () => api.get('/settings'),
|
||||
|
||||
// Update settings
|
||||
updateSettings: (data) => api.put('/settings', data),
|
||||
|
||||
// Upload SSH key
|
||||
uploadSSHKey: (data) => api.post('/settings/ssh-key', data),
|
||||
};
|
||||
|
||||
export default api;
|
||||
Reference in New Issue
Block a user