From c4b55ecb6b637afb9912a92d28ea2c7d22801bb7 Mon Sep 17 00:00:00 2001
From: Harry Rigg
Date: Sat, 23 Nov 2024 21:22:40 +0800
Subject: [PATCH 1/4] Create new sign up form
---
client/package-lock.json | 168 ++++++++-
client/package.json | 8 +-
client/src/components/main/EventPage.tsx | 3 +-
client/src/components/main/header/Navbar.tsx | 3 +-
client/src/components/modal/sign-up.tsx | 254 +++++++++++++
client/src/components/ui/BetterDialog.tsx | 9 +-
client/src/components/ui/SignUpModal.tsx | 365 -------------------
client/src/components/ui/form.tsx | 41 ++-
client/src/hooks/useUser.ts | 6 +-
9 files changed, 462 insertions(+), 395 deletions(-)
create mode 100644 client/src/components/modal/sign-up.tsx
delete mode 100644 client/src/components/ui/SignUpModal.tsx
diff --git a/client/package-lock.json b/client/package-lock.json
index e7aaaa4..a7740b5 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -15,6 +15,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.45.1",
@@ -41,6 +42,7 @@
"sonner": "^1.6.1",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
+ "validator": "^13.12.0",
"zod": "^3.23.8",
"zustand": "^5.0.1",
"zustand-persist": "^0.4.0"
@@ -52,6 +54,7 @@
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
+ "@types/validator": "^13.12.2",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"eslint": "^8.57.0",
@@ -1403,25 +1406,107 @@
}
},
"node_modules/@radix-ui/react-popover": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
- "integrity": "sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz",
+ "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==",
+ "license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
- "@radix-ui/react-context": "1.1.0",
- "@radix-ui/react-dismissable-layer": "1.1.0",
- "@radix-ui/react-focus-guards": "1.1.0",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.1",
+ "@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-popper": "1.2.0",
- "@radix-ui/react-portal": "1.1.1",
- "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-portal": "1.1.2",
+ "@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1",
- "react-remove-scroll": "2.5.7"
+ "react-remove-scroll": "2.6.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
+ "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
+ "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
@@ -1438,6 +1523,55 @@
}
}
},
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
+ "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
+ "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.6",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.2.0",
"license": "MIT",
@@ -2217,6 +2351,13 @@
"@types/react": "*"
}
},
+ "node_modules/@types/validator": {
+ "version": "13.12.2",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
+ "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz",
@@ -8206,6 +8347,15 @@
"version": "1.0.2",
"license": "MIT"
},
+ "node_modules/validator": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
+ "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"license": "ISC",
diff --git a/client/package.json b/client/package.json
index 1245101..ee65af0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -15,14 +15,14 @@
"prepare": "cd .. && husky client/.husky"
},
"dependencies": {
- "@hookform/resolvers": "^3.9.0",
+ "@hookform/resolvers": "^3.9.1",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
- "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.45.1",
@@ -44,11 +44,12 @@
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
- "react-hook-form": "^7.52.1",
+ "react-hook-form": "^7.53.1",
"react-icons": "^5.2.1",
"sonner": "^1.6.1",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
+ "validator": "^13.12.0",
"zod": "^3.23.8",
"zustand": "^5.0.1",
"zustand-persist": "^0.4.0"
@@ -60,6 +61,7 @@
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
+ "@types/validator": "^13.12.2",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"eslint": "^8.57.0",
diff --git a/client/src/components/main/EventPage.tsx b/client/src/components/main/EventPage.tsx
index 1bc07a5..da7d831 100644
--- a/client/src/components/main/EventPage.tsx
+++ b/client/src/components/main/EventPage.tsx
@@ -8,10 +8,11 @@ import RsvpButton from "@/components/main/RsvpButton";
import RsvpListModal from "@/components/main/RsvpListModal";
import LogInModal from "@/components/ui/LogInModal";
import PageCard from "@/components/ui/page-card";
-import SignUpModal from "@/components/ui/SignUpModal";
import { useUser } from "@/hooks/useUser";
import type { Event } from "@/types/event";
+import SignUpModal from "../modal/sign-up";
+
type EventPageProps = {
event: Event;
};
diff --git a/client/src/components/main/header/Navbar.tsx b/client/src/components/main/header/Navbar.tsx
index fd219dc..bd8e427 100644
--- a/client/src/components/main/header/Navbar.tsx
+++ b/client/src/components/main/header/Navbar.tsx
@@ -1,9 +1,8 @@
import Image from "next/image";
import Link from "next/link";
-import { useState } from "react";
+import SignUpModal from "@/components/modal/sign-up";
import LogInModal from "@/components/ui/LogInModal";
-import SignUpModal from "@/components/ui/SignUpModal";
import { useAuth } from "@/context/AuthProvider";
import { DropDownNav } from "./DropDown";
diff --git a/client/src/components/modal/sign-up.tsx b/client/src/components/modal/sign-up.tsx
new file mode 100644
index 0000000..3309f30
--- /dev/null
+++ b/client/src/components/modal/sign-up.tsx
@@ -0,0 +1,254 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import { ReloadIcon } from "@radix-ui/react-icons";
+import { useRouter } from "next/router";
+import { ReactNode } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import validator from "validator";
+import { z } from "zod";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/BetterDialog";
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormInput,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { SelectBranch } from "@/components/ui/select-branch";
+import { useAuth } from "@/context/AuthProvider";
+import { useDelayedPending } from "@/hooks/useDelayedPending";
+import { useRegister } from "@/hooks/useUser";
+
+import LogInModal from "../ui/LogInModal";
+
+const schema = z
+ .object({
+ email: z.string().email(),
+ phone: z
+ .string()
+ .refine(validator.isMobilePhone, "Must be a valid phone number"),
+ first_name: z.string().min(1).max(255),
+ last_name: z.string().min(1).max(255),
+ password: z.string().min(6),
+ password_confirm: z.string().min(6),
+ branch_id: z.number(),
+ })
+ .superRefine(({ password, password_confirm }, ctx) => {
+ if (password != password_confirm) {
+ ctx.addIssue({
+ code: "custom",
+ message: "Passwords must match",
+ path: ["confirmPassword"],
+ });
+ }
+ });
+
+type Props = {
+ children: ReactNode;
+};
+
+export default function SignUpModal({ children }: Props) {
+ return (
+
+ );
+}
+
+function SignUpForm() {
+ const router = useRouter();
+ const { login } = useAuth();
+ const { mutate: register, isPending } = useRegister({
+ onSuccess: (_, details) => {
+ login(details.email, details.password).then(() => {
+ router.push("/profile");
+ toast.success("Your account has been created.");
+ });
+ },
+ onError: (error) => {
+ const errorMessage = error.response?.data?.["error"];
+
+ if (
+ typeof errorMessage === "string" &&
+ errorMessage.includes("duplicate")
+ ) {
+ form.setError("email", { message: "Email is already in use" });
+ } else {
+ toast.error("Something went wrong. Please try again later.");
+ }
+ },
+ });
+
+ const showIsPending = useDelayedPending(isPending);
+
+ const form = useForm>({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ email: "",
+ phone: "",
+ first_name: "",
+ last_name: "",
+ password: "",
+ password_confirm: "",
+ },
+ });
+
+ const onSubmit = (values: z.infer) => {
+ register({
+ email: values.email,
+ phone: values.phone,
+ firstName: values.first_name,
+ lastName: values.last_name,
+ password: values.password,
+ branch: values.branch_id,
+ });
+ };
+
+ return (
+
+
+ );
+}
diff --git a/client/src/components/ui/BetterDialog.tsx b/client/src/components/ui/BetterDialog.tsx
index e27a67b..35c293a 100644
--- a/client/src/components/ui/BetterDialog.tsx
+++ b/client/src/components/ui/BetterDialog.tsx
@@ -29,14 +29,17 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
+ React.ComponentPropsWithoutRef & {
+ wrapHeight?: boolean;
+ }
+>(({ className, children, wrapHeight = false, ...props }, ref) => (
{
- alert("Account created successfully.");
- login(details.email, details.password).then(() => {
- router.push("/profile");
- toast.success("You are now logged in.");
- });
- },
- onError: (error) => {
- if (!error.status) {
- alert(
- "There was an server error when trying to create an account. Please try again later.",
- );
- } else {
- alert(`Registration Error. Status Code = ${error.status}`);
- }
- },
- });
-
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [confirmpassword, setConfirmPassword] = useState("");
- const [firstname, setFirstName] = useState("");
- const [lastname, setLastName] = useState("");
- const [branch, setBranch] = useState(NaN);
- const [phone, setPhone] = useState("");
- const onErrorStyle = "border-2 border-red-500";
- const [error, setError] = useState({
- email: false,
- firstname: false,
- lastname: false,
- phone: false,
- password: false,
- confirmpassword: false,
- city: false,
- });
- const [emsg, setMsg] = useState(Array(6).fill(false));
- // 0 - empty fields
- // 1 - invalid email
- // 2 - invalid phone
- // 3 - password mismatch
- // 4 - duplicate email
- // 5 - no branch selected
- //
-
- function emptyFields() {
- let msg = Array(6).fill(false);
- let temp = {
- email: false,
- firstname: false,
- lastname: false,
- phone: false,
- password: false,
- confirmpassword: false,
- city: false,
- };
-
- const fields = {
- email,
- firstname,
- lastname,
- password,
- confirmpassword,
- city: branch,
- };
-
- //check each field if its empty
- Object.entries(fields).forEach(([key, value]) => {
- if (typeof value === "number") {
- if (Number.isNaN(value)) {
- temp[key as keyof typeof temp] = true;
- }
- } else {
- if (!value.trim().length) {
- temp[key as keyof typeof temp] = true;
- }
- }
- });
-
- //if atleast one empty show error message to fill out required fields
- if (
- temp["email"] ||
- temp["firstname"] ||
- temp["lastname"] ||
- temp["password"] ||
- temp["confirmpassword"] ||
- temp["city"]
- ) {
- msg[0] = true;
- setMsg(msg);
- setError(temp);
- return true;
- }
- }
-
- const handleSubmit = async (e: React.FormEvent) => {
- let msg = Array(6).fill(false);
- let temp = {
- email: false,
- firstname: false,
- lastname: false,
- phone: false,
- password: false,
- confirmpassword: false,
- city: false,
- };
-
- e.preventDefault();
-
- // front end checks for empty fields
- if (emptyFields()) {
- return;
- }
-
- // mismatching password
- if (password !== confirmpassword) {
- temp["password"] = true;
- temp["confirmpassword"] = true;
- msg[3] = true;
- setMsg(msg);
- setError(temp);
- return;
- }
-
- //invalid phone number
- if (phone.trim().length) {
- if (!/^\d+$/.test(phone) || phone.length !== 10) {
- temp["phone"] = true;
- msg[2] = true;
- setMsg(msg);
- setError(temp);
- return;
- }
- }
-
- if (Number.isNaN(branch)) {
- temp["city"] = true;
- msg[6] = true;
- setError(temp);
- setMsg(msg);
- return;
- }
-
- //make api call
- register({
- email,
- password,
- firstName: firstname,
- lastName: lastname,
- phone,
- branch,
- });
- };
-
- return (
-
- );
-}
-
-export default SignUpModal;
diff --git a/client/src/components/ui/form.tsx b/client/src/components/ui/form.tsx
index 0710ab4..8881959 100644
--- a/client/src/components/ui/form.tsx
+++ b/client/src/components/ui/form.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import * as React from "react";
@@ -12,6 +10,7 @@ import {
useFormContext,
} from "react-hook-form";
+import { Input, InputProps } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
@@ -80,7 +79,7 @@ const FormItem = React.forwardRef<
return (
-
+
);
});
@@ -88,17 +87,22 @@ FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
+ React.ComponentPropsWithoutRef & {
+ required?: boolean;
+ }
+>(({ className, children, required = false, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
+ >
+ {children}
+ {required && *}
+
);
});
FormLabel.displayName = "FormLabel";
@@ -145,12 +149,12 @@ FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, children, ...props }, ref) => {
+ React.HTMLAttributes & { preserveSpace?: boolean }
+>(({ className, children, preserveSpace = false, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
- if (!body) {
+ if (!body && !preserveSpace) {
return null;
}
@@ -161,17 +165,32 @@ const FormMessage = React.forwardRef<
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
- {body}
+ {body ?? (preserveSpace ? <> > : null)}
);
});
FormMessage.displayName = "FormMessage";
+const FormInput = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ const { error } = useFormField();
+ return (
+
+ );
+ },
+);
+FormInput.displayName = "FormInput";
+
export {
Form,
FormControl,
FormDescription,
FormField,
+ FormInput,
FormItem,
FormLabel,
FormMessage,
diff --git a/client/src/hooks/useUser.ts b/client/src/hooks/useUser.ts
index bb4a7f1..b2f1420 100644
--- a/client/src/hooks/useUser.ts
+++ b/client/src/hooks/useUser.ts
@@ -33,7 +33,11 @@ export const useUser = () => {
export const useRegister = (
args?: Omit<
- UseMutationOptions,
+ UseMutationOptions<
+ unknown,
+ AxiosError<{ [key: string]: unknown }>,
+ RegistrationDetails
+ >,
"mutationKey" | "mutationFn"
>,
) => {
From 3b06c6f96065d411c29d4843f4b109e28ddf3fc4 Mon Sep 17 00:00:00 2001
From: Harry Rigg
Date: Sat, 23 Nov 2024 21:29:54 +0800
Subject: [PATCH 2/4] Fix confirm password error
---
client/src/components/modal/sign-up.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/client/src/components/modal/sign-up.tsx b/client/src/components/modal/sign-up.tsx
index 3309f30..57fefc6 100644
--- a/client/src/components/modal/sign-up.tsx
+++ b/client/src/components/modal/sign-up.tsx
@@ -24,13 +24,12 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
+import LogInModal from "@/components/ui/LogInModal";
import { SelectBranch } from "@/components/ui/select-branch";
import { useAuth } from "@/context/AuthProvider";
import { useDelayedPending } from "@/hooks/useDelayedPending";
import { useRegister } from "@/hooks/useUser";
-import LogInModal from "../ui/LogInModal";
-
const schema = z
.object({
email: z.string().email(),
@@ -48,7 +47,7 @@ const schema = z
ctx.addIssue({
code: "custom",
message: "Passwords must match",
- path: ["confirmPassword"],
+ path: ["password_confirm"],
});
}
});
From e299f7002158c34af4eaaf842b1e6143cd4820c4 Mon Sep 17 00:00:00 2001
From: Harry Rigg
Date: Sat, 23 Nov 2024 21:33:35 +0800
Subject: [PATCH 3/4] Clean up import
---
client/src/components/main/EventPage.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/client/src/components/main/EventPage.tsx b/client/src/components/main/EventPage.tsx
index da7d831..e2afc83 100644
--- a/client/src/components/main/EventPage.tsx
+++ b/client/src/components/main/EventPage.tsx
@@ -6,13 +6,12 @@ import Link from "next/link";
import RsvpButton from "@/components/main/RsvpButton";
import RsvpListModal from "@/components/main/RsvpListModal";
+import SignUpModal from "@/components/modal/sign-up";
import LogInModal from "@/components/ui/LogInModal";
import PageCard from "@/components/ui/page-card";
import { useUser } from "@/hooks/useUser";
import type { Event } from "@/types/event";
-import SignUpModal from "../modal/sign-up";
-
type EventPageProps = {
event: Event;
};
From 233ac45be53d9dbc8cd9bd96bd57a8a068cb5975 Mon Sep 17 00:00:00 2001
From: Harry Rigg
Date: Sat, 23 Nov 2024 21:59:20 +0800
Subject: [PATCH 4/4] Create new sign in form
---
client/src/components/main/EventPage.tsx | 6 +-
client/src/components/main/header/Navbar.tsx | 6 +-
client/src/components/modal/sign-in.tsx | 143 +++++++++++++++++++
client/src/components/modal/sign-up.tsx | 6 +-
client/src/components/ui/LogInModal.tsx | 114 ---------------
5 files changed, 152 insertions(+), 123 deletions(-)
create mode 100644 client/src/components/modal/sign-in.tsx
delete mode 100644 client/src/components/ui/LogInModal.tsx
diff --git a/client/src/components/main/EventPage.tsx b/client/src/components/main/EventPage.tsx
index e2afc83..11e56bb 100644
--- a/client/src/components/main/EventPage.tsx
+++ b/client/src/components/main/EventPage.tsx
@@ -6,8 +6,8 @@ import Link from "next/link";
import RsvpButton from "@/components/main/RsvpButton";
import RsvpListModal from "@/components/main/RsvpListModal";
+import SignInModal from "@/components/modal/sign-in";
import SignUpModal from "@/components/modal/sign-up";
-import LogInModal from "@/components/ui/LogInModal";
import PageCard from "@/components/ui/page-card";
import { useUser } from "@/hooks/useUser";
import type { Event } from "@/types/event";
@@ -58,11 +58,11 @@ export const EventPage = ({
{" "}
or{" "}
-
+
login
- {" "}
+ {" "}
to proceed.
);
diff --git a/client/src/components/main/header/Navbar.tsx b/client/src/components/main/header/Navbar.tsx
index bd8e427..e466f45 100644
--- a/client/src/components/main/header/Navbar.tsx
+++ b/client/src/components/main/header/Navbar.tsx
@@ -1,8 +1,8 @@
import Image from "next/image";
import Link from "next/link";
+import SignInModal from "@/components/modal/sign-in";
import SignUpModal from "@/components/modal/sign-up";
-import LogInModal from "@/components/ui/LogInModal";
import { useAuth } from "@/context/AuthProvider";
import { DropDownNav } from "./DropDown";
@@ -34,9 +34,9 @@ function Links({ isHiddenWhenLg }: { isHiddenWhenLg: boolean }) {
) : (
-
+
-
+
diff --git a/client/src/components/modal/sign-in.tsx b/client/src/components/modal/sign-in.tsx
new file mode 100644
index 0000000..fcf4f0e
--- /dev/null
+++ b/client/src/components/modal/sign-in.tsx
@@ -0,0 +1,143 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import { ReloadIcon } from "@radix-ui/react-icons";
+import { useRouter } from "next/router";
+import { ReactNode, useState } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import { z } from "zod";
+
+import SignUpModal from "@/components/modal/sign-up";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/BetterDialog";
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormInput,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { useAuth } from "@/context/AuthProvider";
+import { useDelayedPending } from "@/hooks/useDelayedPending";
+
+const schema = z.object({
+ email: z.string().email(),
+ password: z.string().min(6),
+});
+
+type Props = {
+ children: ReactNode;
+};
+
+export default function SignInModal({ children }: Props) {
+ return (
+
+ );
+}
+
+function SignInForm() {
+ const router = useRouter();
+ const { login } = useAuth();
+
+ const [isPending, setIsPending] = useState(false);
+ const showIsPending = useDelayedPending(isPending);
+
+ const form = useForm
>({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const onSubmit = async (values: z.infer) => {
+ setIsPending(true);
+ login(values.email, values.password)
+ .then((success) => {
+ setIsPending(false);
+ if (success) {
+ toast.success("You are now logged in.");
+ router.push("/");
+ } else {
+ form.setError("email", {
+ message: "Invalid email or password",
+ });
+ }
+ })
+ .catch(() => {
+ toast.error("Something went wrong. Please try again later.");
+ });
+ };
+
+ return (
+
+
+ );
+}
diff --git a/client/src/components/modal/sign-up.tsx b/client/src/components/modal/sign-up.tsx
index 57fefc6..f232210 100644
--- a/client/src/components/modal/sign-up.tsx
+++ b/client/src/components/modal/sign-up.tsx
@@ -7,6 +7,7 @@ import { toast } from "sonner";
import validator from "validator";
import { z } from "zod";
+import SignInModal from "@/components/modal/sign-in";
import {
Dialog,
DialogContent,
@@ -24,7 +25,6 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
-import LogInModal from "@/components/ui/LogInModal";
import { SelectBranch } from "@/components/ui/select-branch";
import { useAuth } from "@/context/AuthProvider";
import { useDelayedPending } from "@/hooks/useDelayedPending";
@@ -232,13 +232,13 @@ function SignUpForm() {
)}
/>
-
+
-
+