diff --git a/app/verify-email/page.tsx b/app/verify-email/page.tsx
new file mode 100644
index 0000000..3df9a42
--- /dev/null
+++ b/app/verify-email/page.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { useSearchParams, useRouter } from "next/navigation";
+
+export default function VerifyEmailPage() {
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const token = searchParams.get("token");
+ const [status, setStatus] = useState<"pending" | "success" | "error">("pending");
+ const [message, setMessage] = useState
("Verifying your email...");
+
+ useEffect(() => {
+ if (!token) {
+ setStatus("error");
+ setMessage("Verification token is missing.");
+ return;
+ }
+
+ const verify = async () => {
+ try {
+ const res = await fetch(`/api/auth/verify-email?token=${encodeURIComponent(token)}`);
+ const data = await res.json();
+
+ if (!res.ok || !data.success) {
+ setStatus("error");
+ setMessage(data.error || "Verification failed. Please request a new link.");
+ return;
+ }
+
+ setStatus("success");
+ setMessage("Your email has been verified. You can now create escrows.");
+ } catch (error) {
+ setStatus("error");
+ setMessage("Unable to verify email. Please try again later.");
+ }
+ };
+
+ verify();
+ }, [token]);
+
+ return (
+
+
+
Email Verification
+
{message}
+
+
+
+
+
+
+
+ );
+}
diff --git a/context/EmailAuthContext.tsx b/context/EmailAuthContext.tsx
index 757f0cd..4438411 100644
--- a/context/EmailAuthContext.tsx
+++ b/context/EmailAuthContext.tsx
@@ -14,6 +14,7 @@ export interface AuthUser {
id: string;
email: string;
name: string;
+ emailVerified: boolean;
}
interface EmailAuthContextValue {
diff --git a/lib/auth/users.ts b/lib/auth/users.ts
index 312c242..5e2abcb 100644
--- a/lib/auth/users.ts
+++ b/lib/auth/users.ts
@@ -17,6 +17,9 @@ export interface StoredUser {
name: string;
passwordHash: string;
createdAt: string;
+ emailVerified: boolean;
+ verificationToken?: string;
+ verificationTokenExpiresAt?: string;
notificationPreferences?: NotificationPreferences; // Added to store preferences
}
@@ -24,6 +27,7 @@ export interface PublicUser {
id: string;
email: string;
name: string;
+ emailVerified: boolean;
}
const DEFAULT_NOTIFICATIONS: NotificationPreferences = {
@@ -55,27 +59,69 @@ export function findUserById(id: string): StoredUser | undefined {
return readUsers().find((u) => u.id === id);
}
+export function findUserByVerificationToken(token: string): StoredUser | undefined {
+ return readUsers().find(
+ (u) =>
+ u.verificationToken === token &&
+ u.verificationTokenExpiresAt &&
+ new Date(u.verificationTokenExpiresAt) > new Date()
+ );
+}
+
+export function verifyEmailToken(token: string): StoredUser | null {
+ const users = readUsers();
+ const userIndex = users.findIndex(
+ (u) =>
+ u.verificationToken === token &&
+ u.verificationTokenExpiresAt &&
+ new Date(u.verificationTokenExpiresAt) > new Date()
+ );
+
+ if (userIndex === -1) {
+ return null;
+ }
+
+ users[userIndex].emailVerified = true;
+ delete users[userIndex].verificationToken;
+ delete users[userIndex].verificationTokenExpiresAt;
+ writeUsers(users);
+
+ return users[userIndex];
+}
+
export function createUser(
email: string,
name: string,
passwordHash: string
): StoredUser {
const users = readUsers();
+ const verificationToken = randomUUID();
+ const tokenExpiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
+
const user: StoredUser = {
id: randomUUID(),
email: email.toLowerCase().trim(),
name: name.trim(),
passwordHash,
createdAt: new Date().toISOString(),
+ emailVerified: false,
+ verificationToken,
+ verificationTokenExpiresAt: tokenExpiresAt,
notificationPreferences: DEFAULT_NOTIFICATIONS, // Apply defaults on creation
};
+
users.push(user);
writeUsers(users);
return user;
}
export function toPublicUser(user: StoredUser): PublicUser {
- return { id: user.id, email: user.email, name: user.name };
+ return {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ emailVerified: user.emailVerified,
+ };
}
// ---------------------------------------------------------------------------