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": {
"@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
View File

@@ -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

View File

@@ -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>
);

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;