This commit is contained in:
wchun1996
2025-10-13 21:43:39 +08:00
parent 0c540043db
commit 51dcbec88f
26 changed files with 4502 additions and 97 deletions

View File

@@ -8,14 +8,18 @@
"name": "hicoinpay-react", "name": "hicoinpay-react",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"react": "^19.1.1", "axios": "^1.12.2",
"react-dom": "^19.1.1", "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" "react-router-dom": "^7.9.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
"@types/react": "^19.1.13", "@types/react": "^18.3.26",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^5.0.3", "@vitejs/plugin-react": "^5.0.3",
"eslint": "^9.36.0", "eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
@@ -258,6 +262,15 @@
"@babel/core": "^7.0.0-0" "@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": { "node_modules/@babel/template": {
"version": "7.27.2", "version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -1378,24 +1391,32 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@types/react": {
"version": "19.1.14", "version": "18.3.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "19.1.9", "version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^18.0.0"
} }
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
@@ -1482,6 +1503,23 @@
"dev": true, "dev": true,
"license": "Python-2.0" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1544,6 +1582,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1612,6 +1663,18 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1682,6 +1745,29 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/electron-to-chromium": {
"version": "1.5.224", "version": "1.5.224",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz",
@@ -1689,6 +1775,51 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/esbuild": {
"version": "0.25.10", "version": "0.25.10",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
@@ -2022,6 +2153,42 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2037,6 +2204,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2047,6 +2223,43 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"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": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2073,6 +2286,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2083,6 +2308,94 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"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": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2154,7 +2467,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@@ -2264,6 +2576,18 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -2274,6 +2598,36 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"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": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2469,6 +2823,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -2480,24 +2840,54 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "19.1.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "19.1.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"scheduler": "^0.26.0" "loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
}, },
"peerDependencies": { "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": { "node_modules/react-refresh": {
@@ -2601,10 +2991,13 @@
} }
}, },
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.26.0", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT" "license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.1", "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -10,14 +10,18 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"react": "^19.1.1", "axios": "^1.12.2",
"react-dom": "^19.1.1", "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" "react-router-dom": "^7.9.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
"@types/react": "^19.1.13", "@types/react": "^18.3.26",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^5.0.3", "@vitejs/plugin-react": "^5.0.3",
"eslint": "^9.36.0", "eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",

View File

@@ -1,12 +1,39 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import LanguageSwitcher from './components/LanguageSwitcher';
import SiteFooter from './components/SiteFooter';
function About() { function About() {
return ( const { t } = useTranslation();
<div>
<h1>About Us</h1> return (
<p>Learn more about our project.</p> <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> </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; export default About;

View 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;
}
}

View 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;

View File

@@ -1,6 +1,14 @@
import { Routes, Route, BrowserRouter } from 'react-router-dom'; import { Routes, Route, BrowserRouter } from 'react-router-dom';
import Home from './Home'; import Home from './Home';
import SimpleHome from './SimpleHome';
import About from './About'; 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() { function App() {
return ( return (
@@ -8,6 +16,13 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/about" element={<About />} /> <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 */} {/* Add more routes as needed */}
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>

View 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;

View 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;

View 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;

View 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;
}
}

View File

@@ -1,11 +1,122 @@
import React from 'react'; 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() { function Home() {
const { t } = useTranslation();
return ( return (
<div style={{ padding: '20px', backgroundColor: '#f0f0f0', color: '#000' }}> <div className="home-container" style={{ minHeight: '100vh', backgroundColor: '#f5f5f5' }}>
<h1>HiCoinPay React App</h1> {/* Header */}
<p>Welcome to the home page!</p> <header className="header" style={{ backgroundColor: 'white', padding: '1rem', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
<p>If you can see this, the app is working correctly!</p> <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> </div>
); );
} }

View 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;

View 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;
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -1,68 +1,49 @@
:root { /* Reset and Base Styles */
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; * {
line-height: 1.5; margin: 0;
font-weight: 400; padding: 0;
box-sizing: border-box;
}
color-scheme: light dark; body {
color: rgba(255, 255, 255, 0.87); font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #242424; line-height: 1.6;
color: #1A202C;
font-synthesis: none; background-color: #FFFFFF;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
a { #root {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh; min-height: 100vh;
} }
h1 { /* Smooth scrolling */
font-size: 3.2em; html {
line-height: 1.1; scroll-behavior: smooth;
} }
button { /* Focus styles for accessibility */
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;
}
button:focus, button:focus,
button:focus-visible { a:focus {
outline: 4px auto -webkit-focus-ring-color; outline: 2px solid #DC192C;
outline-offset: 2px;
} }
@media (prefers-color-scheme: light) { /* Custom scrollbar */
:root { ::-webkit-scrollbar {
color: #213547; width: 8px;
background-color: #ffffff; }
}
a:hover { ::-webkit-scrollbar-track {
color: #747bff; background: #f1f1f1;
} }
button {
background-color: #f9f9f9; ::-webkit-scrollbar-thumb {
} background: #DC192C;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #B8152A;
} }

View 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."
}
}

View 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。保留所有权利。"
}
}

View File

@@ -2,10 +2,15 @@
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App.jsx'; import App from './App.jsx';
import ErrorBoundary from './ErrorBoundary.jsx';
import './index.css'; import './index.css';
import './i18n';
ReactDOM.createRoot(document.getElementById('root')).render( const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<StrictMode> <StrictMode>
<App /> <ErrorBoundary>
</StrictMode>, <App />
</ErrorBoundary>
</StrictMode>
); );

View 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;