Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion app/forgot-password/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,74 @@
"use client";

import React, { useState } from "react";
import Link from "next/link";
import MessagePage from "../ui/layout/message-page";
import { useRouter } from "next/navigation";

export default function Page() {
return <div>FORGOTPASSWORD</div>;
const [email, setEmail] = useState("");
const [emailSent, setEmailSent] = useState(false);
const router = useRouter();

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

if (!email) {
alert("Please enter an email address.");
return;
}
setEmailSent(true);
};

return (
<div className="flex h-screen items-center justify-center">
{emailSent ? (
<MessagePage
title="Check your email"
description={`A password reset link was sent to ${email}.`}
buttons={[
{
type: "primary",
label: "back to login",
onClick: () => router.push("/login"),
},
]}
/>
) : (
<form
onSubmit={handleSubmit}
className="flex w-80 flex-col items-center"
>
{/* Title */}
<h1 className="font-display mb-4 block text-center text-5xl leading-none text-lion md:text-8xl">
reset password
</h1>

{/* Email */}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mb-4 w-full rounded-full border px-4 py-2 focus:ring-2 focus:outline-none"
/>

<div className="flex w-full items-center justify-between">
{/* Forgot Password */}
<Link href="/login" className="mb-8 text-xs">
Remembered password?
</Link>

{/* Email Button */}
<button
type="submit"
className="mb-2 cursor-pointer gap-2 rounded-full bg-blue px-4 py-2 font-medium transition"
>
send reset link
</button>
</div>
</form>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function Page() {

{/* Email */}
<input
type="text"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
Expand Down
73 changes: 73 additions & 0 deletions app/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { useRouter, useSearchParams } from "next/navigation";
import React, { useState } from "react";

export default function Page() {
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const router = useRouter();

const searchParams = useSearchParams();
const pwdResetToken = searchParams.get("token");

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

if (!pwdResetToken) {
alert("Invalid or missing password reset token.");
return;
}

if (!newPassword || !confirmPassword) {
alert("Please fill in all fields.");
return;
}

// TODO: Replace with real password reset API call
if (newPassword !== confirmPassword) {
alert("Passwords do not match.");
} else {
router.push("/reset-password/success");
}
};

return (
<div className="flex h-screen items-center justify-center">
<form onSubmit={handleSubmit} className="flex w-80 flex-col items-center">
{/* Title */}
<h1 className="font-display mb-4 block text-center text-5xl leading-none text-lion md:text-8xl">
reset password
</h1>

{/* New Password */}
<input
type="password"
placeholder="New Password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="mb-4 w-full rounded-full border px-4 py-2 focus:ring-2 focus:outline-none"
/>

{/* Confirm Password */}
<input
type="password"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="mb-4 w-full rounded-full border px-4 py-2 focus:ring-2 focus:outline-none"
/>

{/* Change Password Button */}
<div className="flex w-full">
<button
type="submit"
className="mb-2 ml-auto cursor-pointer gap-2 rounded-full bg-blue px-4 py-2 font-medium transition"
>
reset password
</button>
</div>
</form>
</div>
);
}
23 changes: 23 additions & 0 deletions app/reset-password/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { useRouter } from "next/navigation";
import MessagePage from "../../ui/layout/message-page";

export default function Page() {
const router = useRouter();

return (
<div className="flex h-screen items-center justify-center">
<MessagePage
title="Password Reset Successful"
buttons={[
{
type: "primary",
label: "back to login",
onClick: () => router.push("/login"),
},
]}
/>
</div>
);
}
60 changes: 60 additions & 0 deletions app/sign-up/email-sent/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";

import { useRouter } from "next/navigation";
import { useEffect, useRef } from "react";
import MessagePage from "../../ui/layout/message-page";

export default function Page() {
const router = useRouter();
const email = sessionStorage.getItem("sign_up_email");
const lastEmailResend = useRef(Date.now());

useEffect(() => {
if (!email) {
// the user shouldn't be here
router.push("/login");
}
// clear the email from storage
sessionStorage.removeItem("sign_up_email");
}, []);

if (!email) {
// stop rendering if there's no email
return null;
}

const handleResendEmail = () => {
const emailResendCooldown = 30000; // 30 seconds
let timeLeft =
(emailResendCooldown - (Date.now() - lastEmailResend.current)) / 1000;
timeLeft = Math.ceil(timeLeft);
if (timeLeft > 0) {
alert(`Slow down! ${timeLeft} seconds until you can send again.`);
return;
}
// TODO: Replace with real resend email API logic
console.log("Resending email to:", email);
lastEmailResend.current = Date.now();
};

return (
<div className="flex h-screen items-center justify-center">
<MessagePage
title="Check your email"
description={`A verification link was sent to ${email}.`}
buttons={[
{
type: "secondary",
label: "resend email",
onClick: handleResendEmail,
},
{
type: "primary",
label: "go to login",
onClick: () => router.push("/login"),
},
]}
/>
</div>
);
}
17 changes: 8 additions & 9 deletions app/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useState } from "react";

export default function Page() {
const [email, setEmail] = useState("");
Expand All @@ -18,13 +18,12 @@ export default function Page() {
confirmPassword,
});

// TODO: Replace with real sign up information
if (
email === "test" &&
password === "1234" &&
confirmPassword === password
) {
router.push("/dashboard");
// TODO: Replace with real sign up API logic
if (email && password && confirmPassword === password) {
// add the email to session storage to have it in the email-sent page without
// putting it in the URL
sessionStorage.setItem("sign_up_email", email);
router.push("/sign-up/email-sent");
} else {
alert("WOMP WOMP NO ACCOUNT FOR YOU");
}
Expand All @@ -40,7 +39,7 @@ export default function Page() {

{/* Email */}
<input
type="text"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
Expand Down
42 changes: 42 additions & 0 deletions app/ui/layout/message-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
enum ButtonTypeEnum {
primary = "primary",
secondary = "secondary",
}
type ButtonType = keyof typeof ButtonTypeEnum;

type ButtonData = { type: ButtonType; label: string; onClick: () => void };

type MessagePageProps = {
title: string;
description?: string;
buttons: ButtonData[];
};

export default function MessagePage({
title,
description,
buttons,
}: MessagePageProps) {
return (
<div className="text-center">
<h2 className="mb-4 text-4xl font-bold">{title}</h2>
{description && <p className="mb-4">{description}</p>}
<div className="flex justify-center gap-4">
{buttons.map((button, index) => (
<button
key={index}
type="button"
onClick={button.onClick}
className={`mb-2 cursor-pointer rounded-full px-4 py-2 font-medium transition ${
button.type === "primary"
? "bg-blue dark:bg-red"
: "border border-2 border-blue hover:bg-blue-100 dark:border-red dark:hover:bg-red/25"
}`}
>
{button.label}
</button>
))}
</div>
</div>
);
}
67 changes: 67 additions & 0 deletions app/verify-email/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";

import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import MessagePage from "../ui/layout/message-page";

export default function Page() {
const [verifying, setVerifying] = useState(true);
const [emailVerified, setEmailVerified] = useState(false);
const router = useRouter();

const searchParams = useSearchParams();
const token = searchParams.get("code");

useEffect(() => {
const verifyEmail = async () => {
// simulate network delay
await new Promise((resolve) => setTimeout(resolve, 1000));

if (!token) {
setVerifying(false);
setEmailVerified(false);
return;
}

// TODO: Replace with an actual API call
setVerifying(false);
setEmailVerified(true);
};

verifyEmail();
}, [token]);

return (
<div className="flex h-screen items-center justify-center">
{verifying ? (
<div className="text-center">
<h2 className="mb-6">Verifying...</h2>
</div>
) : emailVerified ? (
<MessagePage
title="Email Verified"
description="Welcome to Plancake!"
buttons={[
{
type: "primary",
label: "go to login",
onClick: () => router.push("/login"),
},
]}
/>
) : (
<MessagePage
title="Failed to Verify Email"
description="This link is invalid or has expired."
buttons={[
{
type: "secondary",
label: "back to sign up",
onClick: () => router.push("/sign-up"),
},
]}
/>
)}
</div>
);
}