Account creation
This commit is contained in:
@@ -11,9 +11,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/react": "^2.4.2",
|
||||
"axios": "^1.7.2",
|
||||
"formik": "^2.4.6",
|
||||
"framer-motion": "^11.2.10",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
|
||||
150
client/pnpm-lock.yaml
generated
150
client/pnpm-lock.yaml
generated
@@ -8,6 +8,12 @@ dependencies:
|
||||
'@nextui-org/react':
|
||||
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)
|
||||
axios:
|
||||
specifier: ^1.7.2
|
||||
version: 1.7.2
|
||||
formik:
|
||||
specifier: ^2.4.6
|
||||
version: 2.4.6(react@18.3.1)
|
||||
framer-motion:
|
||||
specifier: ^11.2.10
|
||||
version: 11.2.10(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -17,6 +23,9 @@ dependencies:
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
yup:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
@@ -3247,6 +3256,13 @@ packages:
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==}
|
||||
dependencies:
|
||||
@@ -3489,6 +3505,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: false
|
||||
|
||||
/autoprefixer@10.4.19(postcss@8.4.38):
|
||||
resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -3505,6 +3525,16 @@ packages:
|
||||
postcss-value-parser: 4.2.0
|
||||
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:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
@@ -3633,6 +3663,13 @@ packages:
|
||||
color-string: 1.9.1
|
||||
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:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3681,11 +3718,21 @@ packages:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/deepmerge@2.2.1:
|
||||
resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
dev: false
|
||||
@@ -3945,6 +3992,16 @@ packages:
|
||||
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -3952,6 +4009,31 @@ packages:
|
||||
cross-spawn: 7.0.3
|
||||
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:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
dev: true
|
||||
@@ -4079,6 +4161,12 @@ packages:
|
||||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -4243,6 +4331,10 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
dev: false
|
||||
|
||||
/lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
dev: false
|
||||
@@ -4271,6 +4363,10 @@ packages:
|
||||
resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==}
|
||||
dev: false
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
@@ -4299,6 +4395,18 @@ packages:
|
||||
braces: 3.0.3
|
||||
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:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
@@ -4510,6 +4618,14 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
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:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4528,6 +4644,14 @@ packages:
|
||||
scheduler: 0.23.2
|
||||
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:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4852,6 +4976,14 @@ packages:
|
||||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4863,6 +4995,10 @@ packages:
|
||||
dependencies:
|
||||
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):
|
||||
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -4891,6 +5027,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/type-fest@2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/typescript@5.4.5:
|
||||
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -5063,3 +5204,12 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
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
|
||||
|
||||
@@ -1,61 +1,141 @@
|
||||
import { Button, Checkbox, Input, Link } from "@nextui-org/react";
|
||||
import { useState } from "react";
|
||||
import { Button, Checkbox, Link } from "@nextui-org/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() {
|
||||
const [userAgreedToTermsAndServices, setUserAgreedToTermsAndServices] =
|
||||
useState(false);
|
||||
const initialValues = {
|
||||
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 (
|
||||
<div>
|
||||
<div className="flex flex-col p-4 gap-8 relative *:w-max">
|
||||
<h1 className="text-3xl font-bold ">ecoconnect</h1>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input
|
||||
label="First Name"
|
||||
labelPlacement="outside"
|
||||
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"
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
I have read and agreed to the <Link href="#">Terms and Services</Link>
|
||||
</Checkbox>
|
||||
<Button
|
||||
onSubmit={() => {
|
||||
console.log("Submitting...");
|
||||
}}
|
||||
color="primary"
|
||||
isDisabled={!userAgreedToTermsAndServices}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<NextUIFormikInput
|
||||
label="First Name"
|
||||
name="firstName"
|
||||
type="text"
|
||||
placeholder="John"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
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>
|
||||
);
|
||||
|
||||
32
client/src/components/NextUIFormikInput.tsx
Normal file
32
client/src/components/NextUIFormikInput.tsx
Normal 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
5
client/src/config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
const config = {
|
||||
serverAddress: "http://localhost:5183",
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -3,7 +3,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
"User",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER(),
|
||||
type: DataTypes.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
},
|
||||
@@ -23,7 +23,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.STRING(8),
|
||||
allowNull: false,
|
||||
},
|
||||
passwordHash: {
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
"keywords": [],
|
||||
"author": "Wind_Explorer",
|
||||
"dependencies": {
|
||||
"argon2": "^0.40.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mysql2": "^3.10.1",
|
||||
"nodemon": "^3.1.3",
|
||||
"sequelize": "^6.37.3",
|
||||
"uuid": "^10.0.0",
|
||||
"yup": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
36
server/pnpm-lock.yaml
generated
36
server/pnpm-lock.yaml
generated
@@ -5,6 +5,9 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
argon2:
|
||||
specifier: ^0.40.3
|
||||
version: 0.40.3
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
@@ -23,12 +26,20 @@ dependencies:
|
||||
sequelize:
|
||||
specifier: ^6.37.3
|
||||
version: 6.37.3(mysql2@3.10.1)
|
||||
uuid:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
yup:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
|
||||
packages:
|
||||
|
||||
/@phc/format@1.0.0:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/@types/debug@4.1.12:
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
dependencies:
|
||||
@@ -65,6 +76,16 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
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:
|
||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||
dev: false
|
||||
@@ -579,6 +600,16 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
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:
|
||||
resolution: {integrity: sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -904,6 +935,11 @@ packages:
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: false
|
||||
|
||||
/uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/uuid@8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
|
||||
@@ -2,25 +2,39 @@ const express = require("express");
|
||||
const yup = require("yup");
|
||||
const { Op } = require("sequelize");
|
||||
const { User } = require("../models");
|
||||
const argon2 = require("argon2");
|
||||
const router = express.Router();
|
||||
const { v4: uuidV4 } = require("uuid");
|
||||
|
||||
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(),
|
||||
lastName: yup.string().trim().min(1).max(100).required(),
|
||||
email: yup.string().trim().min(5).max(69).email().required(),
|
||||
phoneNumber: yup.string().trim().length(8).required(),
|
||||
passwordHash: yup.string().trim().min(128).max(255).required(),
|
||||
phoneNumber: yup
|
||||
.string()
|
||||
.trim()
|
||||
.matches(/^[0-9]+$/)
|
||||
.length(8)
|
||||
.required(),
|
||||
password: yup.string().trim().max(255).required(),
|
||||
});
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
let data = req.body;
|
||||
try {
|
||||
data.id = uuidV4();
|
||||
data.password = await argon2.hash(data.password);
|
||||
|
||||
console.log("Validating schema...");
|
||||
data = await validationSchema.validate(data, { abortEarly: false });
|
||||
// Process valid data
|
||||
|
||||
console.log("Creating user...");
|
||||
let result = await User.create(data);
|
||||
res.json(result);
|
||||
console.log("Success!");
|
||||
} catch (err) {
|
||||
console.log("Error caught! Info: " + err);
|
||||
res.status(400).json({ errors: err.errors });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user