Skip to content

Commit 21006bf

Browse files
authored
Merge pull request #43 from GTBitsOfGood/rishabparuchuri/user-registration-page
Rishabparuchuri/user registration page
2 parents ee51421 + 7ffead9 commit 21006bf

File tree

5 files changed

+172
-8
lines changed

5 files changed

+172
-8
lines changed

scripts/seed.ts

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ async function run() {
1111
await tx.unclaimedItem.deleteMany();
1212
await tx.partnerDetails.deleteMany();
1313
await tx.user.deleteMany();
14+
await tx.userInvite.deleteMany();
1415

1516
await tx.user.createMany({
1617
data: [
@@ -72,6 +73,16 @@ async function run() {
7273
partner: { connect: { email: "partner@test.com" } },
7374
},
7475
});
76+
77+
await tx.userInvite.create({
78+
data: {
79+
email: "new-admin@test.com",
80+
expiration: new Date("July 24, 3000"),
81+
name: "New Admin",
82+
token: "1234",
83+
userType: "ADMIN",
84+
},
85+
});
7586
});
7687
}
7788

src/app/api/invites/[tokenId]/route.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ interface Response {
1717
*/
1818
export async function GET(
1919
_: NextRequest,
20-
{ params }: { params: Promise<{ token: string }> }
20+
{ params }: { params: Promise<{ tokenId: string }> }
2121
) {
22-
const { token } = await params;
22+
const { tokenId } = await params;
2323
const invite = await db.userInvite.findUnique({
24-
where: { token },
24+
where: { token: tokenId },
2525
select: { email: true, name: true, expiration: true },
2626
});
2727
if (!invite || invite.expiration < new Date())

src/app/register/page.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
import RegistrationScreen from "@/screens/RegistrationScreen";
4+
5+
export default RegistrationScreen;

src/components/AuthenticationProvider.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ function VerifyAuthentication({
1515
const { loading, user } = useUser();
1616
const router = useRouter();
1717

18-
const onSignInPage = pathName === "/sign_in";
18+
const onAuthPages = pathName === "/sign_in" || pathName === "/register";
1919

2020
useEffect(() => {
2121
if (loading) return;
22-
if (onSignInPage && user) router.replace("/");
23-
if (!onSignInPage && !user) router.replace("/sign_in");
24-
}, [onSignInPage, loading, user, router]);
22+
if (onAuthPages && user) router.replace("/");
23+
if (!onAuthPages && !user) router.replace("/sign_in");
24+
}, [onAuthPages, loading, user, router]);
2525

2626
if (loading)
2727
return (
@@ -30,7 +30,7 @@ function VerifyAuthentication({
3030
</main>
3131
);
3232

33-
if (!onSignInPage && !user) {
33+
if (!onAuthPages && !user) {
3434
return <></>;
3535
}
3636

src/screens/RegistrationScreen.tsx

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { useSearchParams } from "next/navigation";
2+
import { useEffect, useState } from "react";
3+
import { useRouter } from "next/navigation";
4+
import toast from "react-hot-toast";
5+
import submitHandler from "@/util/formAction";
6+
import { signIn } from "next-auth/react";
7+
8+
export default function RegistrationScreen() {
9+
const searchParams = useSearchParams();
10+
const [email, setEmail] = useState("");
11+
const [error, setError] = useState("");
12+
const router = useRouter();
13+
14+
useEffect(() => {
15+
const token = searchParams.get("token");
16+
if (!token) router.replace("/");
17+
18+
const fetchData = async () => {
19+
try {
20+
const response = await fetch(`api/invites/${token}`);
21+
if (response.status === 400) {
22+
setError("Invalid or expired invite");
23+
}
24+
const json = await response.json();
25+
setEmail(json["email"]);
26+
} catch (err) {
27+
toast.error("Unknown Error");
28+
console.log(err);
29+
}
30+
};
31+
fetchData();
32+
}, [searchParams, router]);
33+
34+
const handleSubmit = submitHandler(async (formData: FormData) => {
35+
if (formData.get("password") !== formData.get("confirm")) {
36+
toast.error("Your passwords do not match");
37+
return;
38+
}
39+
formData.delete("confirm");
40+
formData.append("inviteToken", searchParams.get("token") || "");
41+
42+
const response = await fetch(`api/users`, {
43+
method: "POST",
44+
body: formData,
45+
});
46+
47+
if (response.ok) {
48+
const password = formData.get("password");
49+
await signIn("credentials", {
50+
email,
51+
password,
52+
redirect: false,
53+
});
54+
} else if (response.status === 400) {
55+
setError("Invite has expired");
56+
} else if (response.status === 404) {
57+
setError("Invite does not exist");
58+
} else if (response.status === 409) {
59+
setError("User already exists");
60+
} else {
61+
setError("Unknown error");
62+
}
63+
});
64+
65+
return (
66+
<main className="bg-gradient-to-tr from-[#4AA6EB] to-[#F0424E] w-screen h-screen flex flex-col justify-center items-center">
67+
<div className="bg-white py-6 px-6 rounded-xl w-96 sm:w-[580]">
68+
{!error ? (
69+
<div>
70+
<h1 className="mb-1 text-xl font-semibold">Create Account</h1>
71+
<p className="mb-4 text-sm font-light text-gray-500">
72+
Welcome to the Hope for Haiti database. Please fill in your <br />{" "}
73+
organization’s account information.
74+
</p>
75+
<form onSubmit={handleSubmit}>
76+
<div className="mb-3">
77+
<label
78+
className="block text-gray-800 text-sm mb-2 font-light"
79+
htmlFor="email"
80+
>
81+
Email
82+
<div className="text-red-500 inline">*</div>
83+
</label>
84+
<input
85+
className="bg-zinc-50 appearance-none border-gray-200 rounded w-full py-2 px-3 text-gray-500 leading-tight font-light text-sm"
86+
type="email"
87+
value={email}
88+
disabled
89+
required
90+
/>
91+
</div>
92+
93+
<div className="mb-3">
94+
<label
95+
className="block text-gray-800 text-sm mb-2 font-light"
96+
htmlFor="password"
97+
>
98+
Password
99+
<div className="text-red-500 inline">*</div>
100+
</label>
101+
<input
102+
className="bg-zinc-50 appearance-none border-gray-200 rounded w-full py-2 px-3 text-gray-700 leading-tight font-light text-sm"
103+
id="password"
104+
name="password"
105+
type="password"
106+
placeholder="Password"
107+
required
108+
/>
109+
</div>
110+
111+
<div className="mb-3">
112+
<label
113+
className="block text-gray-800 text-sm mb-2 font-light"
114+
htmlFor="password"
115+
>
116+
Confirm Password
117+
<div className="text-red-500 inline">*</div>
118+
</label>
119+
<input
120+
className="bg-zinc-50 appearance-none border-gray-200 rounded w-full py-2 px-3 text-gray-700 leading-tight font-light text-sm"
121+
id="confirm"
122+
name="confirm"
123+
type="password"
124+
placeholder="Confirm Password"
125+
required
126+
/>
127+
</div>
128+
<div className="justify-end flex">
129+
<button
130+
className="w-36 bg-red-500 hover:bg-red-700 text-white py-1 px-4 mt-2 rounded focus:outline-none focus:shadow-outline"
131+
type="submit"
132+
>
133+
Create account
134+
</button>
135+
</div>
136+
</form>
137+
</div>
138+
) : (
139+
<div className="flex justify-center">
140+
<p className="text-red-500 text-lg font-semibold py-1 mt-2">
141+
{error}
142+
</p>
143+
</div>
144+
)}
145+
</div>
146+
</main>
147+
);
148+
}

0 commit comments

Comments
 (0)