Account creation

This commit is contained in:
2024-06-22 23:59:44 +08:00
parent ce51b78b14
commit 4a39c5ad31
9 changed files with 380 additions and 58 deletions

View File

@@ -11,9 +11,12 @@
}, },
"dependencies": { "dependencies": {
"@nextui-org/react": "^2.4.2", "@nextui-org/react": "^2.4.2",
"axios": "^1.7.2",
"formik": "^2.4.6",
"framer-motion": "^11.2.10", "framer-motion": "^11.2.10",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"yup": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.66", "@types/react": "^18.2.66",

150
client/pnpm-lock.yaml generated
View File

@@ -8,6 +8,12 @@ dependencies:
'@nextui-org/react': '@nextui-org/react':
specifier: ^2.4.2 specifier: ^2.4.2
version: 2.4.2(@types/react@18.3.3)(framer-motion@11.2.10)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.4) version: 2.4.2(@types/react@18.3.3)(framer-motion@11.2.10)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.4)
axios:
specifier: ^1.7.2
version: 1.7.2
formik:
specifier: ^2.4.6
version: 2.4.6(react@18.3.1)
framer-motion: framer-motion:
specifier: ^11.2.10 specifier: ^11.2.10
version: 11.2.10(react-dom@18.3.1)(react@18.3.1) version: 11.2.10(react-dom@18.3.1)(react@18.3.1)
@@ -17,6 +23,9 @@ dependencies:
react-dom: react-dom:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.3.1(react@18.3.1) version: 18.3.1(react@18.3.1)
yup:
specifier: ^1.4.0
version: 1.4.0
devDependencies: devDependencies:
'@types/react': '@types/react':
@@ -3247,6 +3256,13 @@ packages:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true dev: true
/@types/hoist-non-react-statics@3.3.5:
resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
dependencies:
'@types/react': 18.3.3
hoist-non-react-statics: 3.3.2
dev: false
/@types/lodash.debounce@4.0.9: /@types/lodash.debounce@4.0.9:
resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==}
dependencies: dependencies:
@@ -3489,6 +3505,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/autoprefixer@10.4.19(postcss@8.4.38): /autoprefixer@10.4.19(postcss@8.4.38):
resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -3505,6 +3525,16 @@ packages:
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
dev: true dev: true
/axios@1.7.2:
resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==}
dependencies:
follow-redirects: 1.15.6
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/balanced-match@1.0.2: /balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -3633,6 +3663,13 @@ packages:
color-string: 1.9.1 color-string: 1.9.1
dev: false dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/commander@4.1.1: /commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -3681,11 +3718,21 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true dev: true
/deepmerge@2.2.1:
resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==}
engines: {node: '>=0.10.0'}
dev: false
/deepmerge@4.3.1: /deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: false dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/detect-node-es@1.1.0: /detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false dev: false
@@ -3945,6 +3992,16 @@ packages:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
dev: true dev: true
/follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/foreground-child@3.2.1: /foreground-child@3.2.1:
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -3952,6 +4009,31 @@ packages:
cross-spawn: 7.0.3 cross-spawn: 7.0.3
signal-exit: 4.1.0 signal-exit: 4.1.0
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/formik@2.4.6(react@18.3.1):
resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==}
peerDependencies:
react: '>=16.8.0'
dependencies:
'@types/hoist-non-react-statics': 3.3.5
deepmerge: 2.2.1
hoist-non-react-statics: 3.3.2
lodash: 4.17.21
lodash-es: 4.17.21
react: 18.3.1
react-fast-compare: 2.0.4
tiny-warning: 1.0.3
tslib: 2.6.3
dev: false
/fraction.js@4.3.7: /fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true dev: true
@@ -4079,6 +4161,12 @@ packages:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
/hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
dev: false
/ignore@5.3.1: /ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -4243,6 +4331,10 @@ packages:
p-locate: 5.0.0 p-locate: 5.0.0
dev: true dev: true
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash.debounce@4.0.8: /lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: false dev: false
@@ -4271,6 +4363,10 @@ packages:
resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==}
dev: false dev: false
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/loose-envify@1.4.0: /loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
@@ -4299,6 +4395,18 @@ packages:
braces: 3.0.3 braces: 3.0.3
picomatch: 2.3.1 picomatch: 2.3.1
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/minimatch@3.1.2: /minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies: dependencies:
@@ -4510,6 +4618,14 @@ packages:
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dev: true dev: true
/property-expr@2.0.6:
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
dev: false
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/punycode@2.3.1: /punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4528,6 +4644,14 @@ packages:
scheduler: 0.23.2 scheduler: 0.23.2
dev: false dev: false
/react-fast-compare@2.0.4:
resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==}
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: false
/react-refresh@0.14.2: /react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -4852,6 +4976,14 @@ packages:
dependencies: dependencies:
any-promise: 1.3.0 any-promise: 1.3.0
/tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
dev: false
/tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/to-fast-properties@2.0.0: /to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -4863,6 +4995,10 @@ packages:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
/toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
dev: false
/ts-api-utils@1.3.0(typescript@5.4.5): /ts-api-utils@1.3.0(typescript@5.4.5):
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -4891,6 +5027,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
dev: false
/typescript@5.4.5: /typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@@ -5063,3 +5204,12 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/yup@1.4.0:
resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==}
dependencies:
property-expr: 2.0.6
tiny-case: 1.0.3
toposort: 2.0.2
type-fest: 2.19.0
dev: false

View File

@@ -1,61 +1,141 @@
import { Button, Checkbox, Input, Link } from "@nextui-org/react"; import { Button, Checkbox, Link } from "@nextui-org/react";
import { useState } from "react"; import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
import axios from "axios";
import config from "./config";
import NextUIFormikInput from "./components/NextUIFormikInput";
const validationSchema = Yup.object({
firstName: Yup.string()
.trim()
.min(1)
.max(100)
.required("First Name is required"),
lastName: Yup.string()
.trim()
.min(1)
.max(100)
.required("Last Name is required"),
email: Yup.string()
.trim()
.min(5)
.max(69)
.email("Invalid email format")
.required("Email is required"),
phoneNumber: Yup.string()
.trim()
.matches(/^[0-9]+$/, "Phone number must contain only numerical characters")
.length(8, "Phone number must be 8 digits")
.required("Phone number is required"),
password: Yup.string()
.trim()
.min(8, "Password must be at least 8 characters")
.required("Password is required"),
terms: Yup.boolean().oneOf(
[true],
"You must accept the terms and conditions"
),
});
export default function App() { export default function App() {
const [userAgreedToTermsAndServices, setUserAgreedToTermsAndServices] = const initialValues = {
useState(false); firstName: "",
lastName: "",
email: "",
phoneNumber: "",
password: "",
terms: false,
};
const handleSubmit = async (values: any) => {
try {
const response = await axios.post(
config.serverAddress + "/users",
values
);
console.log("User created successfully:", response.data);
} catch (error) {
console.error("Error creating user:", error);
}
};
return ( return (
<div> <div>
<div className="flex flex-col p-4 gap-8 relative *:w-max"> <div className="flex flex-col p-4 gap-8 relative *:w-max">
<h1 className="text-3xl font-bold ">ecoconnect</h1> <h1 className="text-3xl font-bold ">ecoconnect</h1>
<div className="flex flex-col gap-4"> <Formik
<Input initialValues={initialValues}
label="First Name" validationSchema={validationSchema}
labelPlacement="outside" onSubmit={handleSubmit}
placeholder="John"
></Input>
<Input
label="Last Name"
labelPlacement="outside"
placeholder="Doe"
></Input>
<Input
label="Email"
labelPlacement="outside"
placeholder="johndoe@email.com"
type="email"
></Input>
<Input
label="Phone number"
labelPlacement="outside"
placeholder="XXXXXXXX"
startContent={
<p className="text-sm pr-2 border-r-2 border-neutral-700">+65</p>
}
></Input>
<Input
label="New Password"
labelPlacement="outside"
placeholder=" "
type="password"
></Input>
</div>
<Checkbox
isSelected={userAgreedToTermsAndServices}
onValueChange={setUserAgreedToTermsAndServices}
aria-label="Terms and services agreement checkbox"
> >
I have read and agreed to the <Link href="#">Terms and Services</Link> {({ isValid, dirty }) => (
</Checkbox> <Form className="flex flex-col gap-4">
<Button <NextUIFormikInput
onSubmit={() => { label="First Name"
console.log("Submitting..."); name="firstName"
}} type="text"
color="primary" placeholder="John"
isDisabled={!userAgreedToTermsAndServices} labelPlacement="outside"
> />
Sign up <NextUIFormikInput
</Button> label="Last Name"
name="lastName"
type="text"
placeholder="Doe"
labelPlacement="outside"
/>
<NextUIFormikInput
label="Email"
name="email"
type="email"
placeholder="johndoe@email.com"
labelPlacement="outside"
/>
<NextUIFormikInput
label="Phone number"
name="phoneNumber"
type="text"
placeholder="XXXXXXXX"
labelPlacement="outside"
startContent={
<p className="text-sm pr-2 border-r-2 border-neutral-700">
+65
</p>
}
/>
<NextUIFormikInput
label="New Password"
name="password"
type="password"
placeholder=" "
labelPlacement="outside"
/>
<div>
<Field
name="terms"
type="checkbox"
as={Checkbox}
aria-label="Terms and services agreement checkbox"
>
I have read and agreed to the{" "}
<Link href="#">Terms and Services</Link>
</Field>
<ErrorMessage
name="terms"
component="div"
className="text-red-500"
/>
</div>
<Button
type="submit"
color="primary"
isDisabled={!isValid || !dirty}
>
Sign up
</Button>
</Form>
)}
</Formik>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,32 @@
import { Input } from "@nextui-org/react";
import { useField } from "formik";
interface NextUIFormikInputProps {
label: string;
name: string;
type: string;
placeholder: string;
labelPlacement?: "inside" | "outside";
startContent?: JSX.Element;
}
const NextUIFormikInput = ({
label,
startContent,
...props
}: NextUIFormikInputProps) => {
const [field, meta] = useField(props.name);
return (
<Input
{...field}
{...props}
label={label}
isInvalid={meta.touched && !!meta.error}
errorMessage={meta.touched && meta.error ? meta.error : ""}
startContent={startContent}
/>
);
};
export default NextUIFormikInput;

5
client/src/config.ts Normal file
View File

@@ -0,0 +1,5 @@
const config = {
serverAddress: "http://localhost:5183",
};
export default config;

View File

@@ -3,7 +3,7 @@ module.exports = (sequelize, DataTypes) => {
"User", "User",
{ {
id: { id: {
type: DataTypes.INTEGER(), type: DataTypes.STRING(36),
allowNull: false, allowNull: false,
primaryKey: true, primaryKey: true,
}, },
@@ -23,7 +23,7 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.STRING(8), type: DataTypes.STRING(8),
allowNull: false, allowNull: false,
}, },
passwordHash: { password: {
type: DataTypes.STRING(255), type: DataTypes.STRING(255),
allowNull: false, allowNull: false,
}, },

View File

@@ -10,12 +10,14 @@
"keywords": [], "keywords": [],
"author": "Wind_Explorer", "author": "Wind_Explorer",
"dependencies": { "dependencies": {
"argon2": "^0.40.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"mysql2": "^3.10.1", "mysql2": "^3.10.1",
"nodemon": "^3.1.3", "nodemon": "^3.1.3",
"sequelize": "^6.37.3", "sequelize": "^6.37.3",
"uuid": "^10.0.0",
"yup": "^1.4.0" "yup": "^1.4.0"
} }
} }

36
server/pnpm-lock.yaml generated
View File

@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
argon2:
specifier: ^0.40.3
version: 0.40.3
cors: cors:
specifier: ^2.8.5 specifier: ^2.8.5
version: 2.8.5 version: 2.8.5
@@ -23,12 +26,20 @@ dependencies:
sequelize: sequelize:
specifier: ^6.37.3 specifier: ^6.37.3
version: 6.37.3(mysql2@3.10.1) version: 6.37.3(mysql2@3.10.1)
uuid:
specifier: ^10.0.0
version: 10.0.0
yup: yup:
specifier: ^1.4.0 specifier: ^1.4.0
version: 1.4.0 version: 1.4.0
packages: packages:
/@phc/format@1.0.0:
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
dev: false
/@types/debug@4.1.12: /@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
dependencies: dependencies:
@@ -65,6 +76,16 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: false dev: false
/argon2@0.40.3:
resolution: {integrity: sha512-FrSmz4VeM91jwFvvjsQv9GYp6o/kARWoYKjbjDB2U5io1H3e5X67PYGclFDeQff6UXIhUd4aHR3mxCdBbMMuQw==}
engines: {node: '>=16.17.0'}
requiresBuild: true
dependencies:
'@phc/format': 1.0.0
node-addon-api: 8.0.0
node-gyp-build: 4.8.1
dev: false
/array-flatten@1.1.1: /array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false dev: false
@@ -579,6 +600,16 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/node-addon-api@8.0.0:
resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==}
engines: {node: ^18 || ^20 || >= 21}
dev: false
/node-gyp-build@4.8.1:
resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
hasBin: true
dev: false
/nodemon@3.1.3: /nodemon@3.1.3:
resolution: {integrity: sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==} resolution: {integrity: sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -904,6 +935,11 @@ packages:
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dev: false dev: false
/uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
dev: false
/uuid@8.3.2: /uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true hasBin: true

View File

@@ -2,25 +2,39 @@ const express = require("express");
const yup = require("yup"); const yup = require("yup");
const { Op } = require("sequelize"); const { Op } = require("sequelize");
const { User } = require("../models"); const { User } = require("../models");
const argon2 = require("argon2");
const router = express.Router(); const router = express.Router();
const { v4: uuidV4 } = require("uuid");
let validationSchema = yup.object({ let validationSchema = yup.object({
id: yup.number().min(0).required(), id: yup.string().trim().min(36).max(36).required(),
firstName: yup.string().trim().min(1).max(100).required(), firstName: yup.string().trim().min(1).max(100).required(),
lastName: yup.string().trim().min(1).max(100).required(), lastName: yup.string().trim().min(1).max(100).required(),
email: yup.string().trim().min(5).max(69).email().required(), email: yup.string().trim().min(5).max(69).email().required(),
phoneNumber: yup.string().trim().length(8).required(), phoneNumber: yup
passwordHash: yup.string().trim().min(128).max(255).required(), .string()
.trim()
.matches(/^[0-9]+$/)
.length(8)
.required(),
password: yup.string().trim().max(255).required(),
}); });
router.post("/", async (req, res) => { router.post("/", async (req, res) => {
let data = req.body; let data = req.body;
try { try {
data.id = uuidV4();
data.password = await argon2.hash(data.password);
console.log("Validating schema...");
data = await validationSchema.validate(data, { abortEarly: false }); data = await validationSchema.validate(data, { abortEarly: false });
// Process valid data
console.log("Creating user...");
let result = await User.create(data); let result = await User.create(data);
res.json(result); res.json(result);
console.log("Success!");
} catch (err) { } catch (err) {
console.log("Error caught! Info: " + err);
res.status(400).json({ errors: err.errors }); res.status(400).json({ errors: err.errors });
} }
}); });