Skip to content

Commit da7ed02

Browse files
committed
chore: Registration & Verify Page + Logic
1 parent 7a1ad10 commit da7ed02

File tree

12 files changed

+270
-35
lines changed

12 files changed

+270
-35
lines changed

apps/web/src/app/(main)/(auth)/login/page.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export default function Page() {
3030
errors: {
3131
email: res.message.email ?? [],
3232
password: res.message.password ?? [],
33+
username: res.message.username ?? [],
34+
confirm: res.message.confirm ?? [],
3335
},
3436
})
3537
}
@@ -39,7 +41,7 @@ export default function Page() {
3941
if (user && !isLoading) {
4042
router.push(`/`)
4143
}
42-
}, [user, isLoading])
44+
}, [user, isLoading, router])
4345

4446
return (
4547
<div className="bg-100 relative flex min-h-screen w-full items-start justify-center px-6 pt-36">
@@ -62,24 +64,24 @@ export default function Page() {
6264
type="email"
6365
inputName="Email"
6466
placeholder="Email"
65-
error={errors?.errors?.email?.[0]}
67+
error={errors?.errors.email}
6668
/>
6769
<div className="space-y-2">
6870
<InputField
6971
inputName="Password"
7072
type="password"
7173
placeholder="Password"
72-
error={errors?.errors?.password?.[0]}
74+
error={errors?.errors.password}
7375
/>
7476
<Link
7577
href="/forgot-password"
76-
className="text-500 inline-block text-sm underline hover:no-underline"
78+
className="text-600 inline-block text-sm underline hover:no-underline"
7779
>
7880
Forgot password?
7981
</Link>
8082
</div>
81-
<Button type="submit" className="mt-4 w-full text-center">
82-
<span className="w-max">Sign in</span>
83+
<Button position="center" type="submit" className="mt-4 w-full text-center">
84+
Sign in
8385
</Button>
8486
</Form>
8587
<div className="flex flex-row gap-x-6">

apps/web/src/app/(main)/(auth)/register/page.tsx

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,103 @@
1+
"use client"
2+
13
import Image from "next/image"
24
import Link from "next/link"
3-
import React from "react"
5+
import { useRouter } from "next/navigation"
6+
import React, { useEffect, useState } from "react"
7+
import { useAuth } from "@/app/context/AuthContext"
48
import ThirdPartyButtons from "@/components/layouts/Auth/ThirdPartyButtons"
59
import { Button } from "@mav/ui/components/buttons"
6-
import { InputField } from "@mav/ui/components/fields"
10+
import { Form, type FormState, InputField } from "@mav/ui/components/fields"
11+
import { registerAction } from "@/app/actions/register"
712

813
export default function Page() {
14+
const router = useRouter()
15+
16+
const [errors, setErrors] = useState<FormState>()
17+
const { user, isLoading } = useAuth()
18+
19+
// TODO: Figure out how to handle this in the action file
20+
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
21+
event.preventDefault()
22+
const formData = new FormData(event.target as HTMLFormElement)
23+
const res = await registerAction(formData)
24+
25+
if (res.success) {
26+
router.push(`/verify`)
27+
} else {
28+
setErrors({
29+
message: res.message.error,
30+
errors: {
31+
email: res.message.email ?? [],
32+
password: res.message.password ?? [],
33+
username: res.message.username ?? [],
34+
confirm: res.message.confirm ?? [],
35+
},
36+
})
37+
}
38+
}
39+
40+
useEffect(() => {
41+
if (user && !isLoading) {
42+
router.push(`/`)
43+
}
44+
}, [user, isLoading, router])
45+
946
return (
10-
<div className="bg-100 relative mx-auto flex h-1/2 w-screen flex-col items-center gap-y-6 py-12">
47+
<div className="bg-100 relative flex min-h-screen w-full items-start justify-center px-6 pt-36">
1148
<Image
1249
src="/Backdrop.png"
1350
alt="Backdrop"
1451
height={250}
1552
width={2000}
16-
className="z-0 h-1/4"
53+
className="absolute left-0 top-0 z-0 h-1/4 w-full object-cover"
1754
/>
18-
<div className="bg-100 border-200 absolute z-10 flex w-1/2 flex-col items-center justify-center gap-y-6 rounded-lg border-2 p-12 shadow-md">
19-
<h1 className="text-700 z-10 text-2xl">Register a new account</h1>
55+
<div className="bg-100 border-200 relative z-10 flex w-full max-w-2xl flex-col items-center justify-center gap-y-6 rounded-lg border-2 p-12 shadow-md">
56+
<h1 className="text-700 text-2xl">Register a new account</h1>
2057
<ThirdPartyButtons />
21-
<p>or</p>
22-
<div className="flex w-2/3 flex-col gap-y-4">
23-
<InputField placeholder="Email" type="email" inputName="Email" />
24-
<InputField placeholder="Username" type="text" inputName="Username" />
25-
<span className="text-600 text-xs">
58+
<span>or</span>
59+
<Form onSubmit={handleSubmit} className="flex w-full flex-col gap-y-4">
60+
{errors && (
61+
<p className="text-center text-red-500">{errors.message}</p>
62+
)}
63+
<InputField
64+
type="email"
65+
inputName="Email"
66+
placeholder="Email"
67+
error={errors?.errors?.email}
68+
/>
69+
<InputField
70+
type="name"
71+
inputName="Username"
72+
placeholder="Username"
73+
error={errors?.errors?.username}
74+
/>
75+
<span className="text-600 ">
2676
Username must contain [A-Z][a-z][0-9], underscore, and periods.
2777
</span>
2878
<InputField
29-
placeholder="Password"
30-
type="password"
3179
inputName="Password"
80+
type="password"
81+
placeholder="Password"
82+
error={errors?.errors?.password}
3283
/>
3384
<InputField
34-
placeholder="Repeat Password"
85+
inputName="Confirm"
3586
type="password"
36-
inputName="Repeat Password"
87+
placeholder="Confirm Password"
88+
error={errors?.errors?.confirm}
3789
/>
3890
<Button
39-
variant="primary"
40-
position="center"
91+
type="submit"
4192
className="mt-4 w-full text-center"
93+
position="center"
4294
>
43-
<span className="w-max">Register</span>
95+
Register
4496
</Button>
45-
</div>
97+
</Form>
4698
<div className="flex flex-row gap-x-6">
4799
<Link href="/login" className="text-500 text-sm">
48-
Already have an account? Sign in
100+
Sign in
49101
</Link>
50102
</div>
51103
</div>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Image from "next/image"
2+
import { BACKEND_URL } from "@/utils/constants"
3+
import { Button } from "@mav/ui/components/buttons"
4+
import { LuCheck, LuXOctagon } from "react-icons/lu"
5+
import { redirect } from "next/navigation"
6+
7+
async function verifyEmail(id: string): Promise<boolean> {
8+
try {
9+
const res = await fetch(`${BACKEND_URL}/v1/auth/verify/${id}`, {
10+
method: "POST",
11+
credentials: "include",
12+
})
13+
14+
if (!res.ok) return false
15+
16+
const data = await res.json()
17+
return data
18+
} catch {
19+
return false
20+
}
21+
}
22+
23+
export default async function Page({ params }: { params: { id: string } }) {
24+
const isVerified = await verifyEmail(params.id)
25+
26+
if (isVerified) {
27+
redirect("/login")
28+
}
29+
30+
return (
31+
<div className="bg-100 relative flex min-h-screen w-full items-start justify-center px-6 pt-36">
32+
<Image
33+
src="/Backdrop.png"
34+
alt="Backdrop"
35+
height={250}
36+
width={2000}
37+
className="absolute left-0 top-0 z-0 h-1/4 w-full object-cover"
38+
/>
39+
<div className="bg-100 border-200 relative z-10 flex w-full max-w-2xl flex-col items-center justify-center gap-y-6 rounded-lg border-2 p-12 shadow-md">
40+
<LuXOctagon size={45} />
41+
<h1 className="text-700 text-2xl">Unable to verify account</h1>
42+
<p>There was an issue verifying your email. Please try again later.</p>
43+
</div>
44+
</div>
45+
)
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client"
2+
3+
import Image from "next/image"
4+
import { useRouter } from "next/navigation"
5+
import React, { useEffect, useState } from "react"
6+
import { useAuth } from "@/app/context/AuthContext"
7+
import { Button } from "@mav/ui/components/buttons"
8+
import { LuMail } from "react-icons/lu"
9+
10+
export default function Page() {
11+
const router = useRouter()
12+
const { user, isLoading } = useAuth()
13+
14+
useEffect(() => {
15+
if (user && !isLoading) {
16+
router.push(`/`)
17+
}
18+
}, [user, isLoading, router])
19+
20+
return (
21+
<div className="bg-100 relative flex min-h-screen w-full items-start justify-center px-6 pt-36">
22+
<Image
23+
src="/Backdrop.png"
24+
alt="Backdrop"
25+
height={250}
26+
width={2000}
27+
className="absolute left-0 top-0 z-0 h-1/4 w-full object-cover"
28+
/>
29+
<div className="bg-100 border-200 relative z-10 flex w-full max-w-2xl flex-col items-center justify-center gap-y-6 rounded-lg border-2 p-12 shadow-md">
30+
<LuMail size={45} />
31+
<h1 className="text-700 text-2xl">Welcome to MyArtverse</h1>
32+
<p className="text-center">
33+
You should get an email with a link to verify your account. If you
34+
don't see it, check your spam folder.
35+
</p>
36+
<Button
37+
position="center"
38+
className="mt-4 w-full text-center"
39+
onClick={() => router.push("/login")}
40+
>
41+
Sign in
42+
</Button>
43+
</div>
44+
</div>
45+
)
46+
}

apps/web/src/app/actions/login.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { cookies } from "next/headers"
44
import { LoginFormSchema } from "@/app/lib/definition"
55
import { removeSuffixes } from "@/utils/removeSuffix"
6+
import { BACKEND_URL } from "@/utils/constants"
67

78
export async function loginAction(formData: FormData) {
89
const processedData = removeSuffixes(formData)
@@ -25,7 +26,7 @@ export async function loginAction(formData: FormData) {
2526
}
2627

2728
try {
28-
const res = await fetch("http://localhost:8081/v1/auth/login", {
29+
const res = await fetch(`${BACKEND_URL}/v1/auth/login`, {
2930
method: "POST",
3031
headers: {
3132
"Content-Type": "application/json",
@@ -57,7 +58,7 @@ export async function loginAction(formData: FormData) {
5758
return {
5859
success: false,
5960
message: {
60-
error: data.message || "Invalid email or password",
61+
error: data.error || "Invalid email or password",
6162
},
6263
}
6364
} catch (error) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"use server"
2+
3+
import { cookies } from "next/headers"
4+
import { LoginFormSchema, RegisterFormSchema } from "@/app/lib/definition"
5+
import { removeSuffixes } from "@/utils/removeSuffix"
6+
import { BACKEND_URL } from "@/utils/constants"
7+
8+
export async function registerAction(formData: FormData) {
9+
const processedData = removeSuffixes(formData)
10+
const { email, username, password, confirm } = processedData
11+
12+
const validatedFields = RegisterFormSchema.safeParse({
13+
email,
14+
password,
15+
username,
16+
confirm
17+
})
18+
19+
if (!validatedFields.success) {
20+
return {
21+
success: false,
22+
message: {
23+
error: null,
24+
email: validatedFields.error.flatten().fieldErrors?.email,
25+
password: validatedFields.error.flatten().fieldErrors?.password,
26+
username: validatedFields.error.flatten().fieldErrors?.username,
27+
confirm: validatedFields.error.flatten().fieldErrors?.confirm,
28+
},
29+
}
30+
}
31+
32+
if (password !== confirm) {
33+
return {
34+
success: false,
35+
message: {
36+
username: null,
37+
email: null,
38+
error: "Passwords do not match",
39+
confirm: ["Passwords do not match"],
40+
},
41+
}
42+
}
43+
44+
try {
45+
const res = await fetch(`${BACKEND_URL}/v1/auth/register`, {
46+
method: "POST",
47+
headers: {
48+
"Content-Type": "application/json",
49+
},
50+
body: JSON.stringify({ email, password, username }),
51+
})
52+
53+
const data = await res.json()
54+
if (res.status === 400) {
55+
return {
56+
success: false,
57+
message: {
58+
email: data.email || null,
59+
username: data.username || null,
60+
},
61+
}
62+
}
63+
64+
if (res.ok) {
65+
return { success: true, message: data.message }
66+
}
67+
68+
return {
69+
success: false,
70+
message: {
71+
error: "Something went wrong. Please try again later.",
72+
},
73+
}
74+
} catch (error) {
75+
return {
76+
success: false,
77+
message: {
78+
error: "Something went wrong in our end! Please try again later.",
79+
},
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)