Ashraf Al-Kassem Claude Opus 4.6 commited on
Commit
cfd01d8
·
1 Parent(s): 721b3fc

fix: email verification and password reset links return 404

Browse files

Two issues caused all email links (verification + password reset) to 404:

1. APP_BASE_URL defaulted to http://localhost:3000 in production, so all
email links pointed to localhost instead of the HF Space domain.
Fix: Set ENV APP_BASE_URL in Dockerfile to the production URL.

2. No /verify-email frontend page existed — the email template linked to
it but Next.js returned 404.
Fix: Create verify-email/page.tsx at root level (outside auth group
to avoid redirect-to-dashboard for logged-in users). Page reads the
token from URL, calls GET /auth/verify-email, shows success/error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Dockerfile CHANGED
@@ -44,6 +44,7 @@ RUN chmod +x /app/start.sh
44
  ENV PORT=7860
45
  ENV DATABASE_URL=sqlite+aiosqlite:////app/backend/leadpilot.db
46
  ENV NEXT_PUBLIC_API_BASE_URL=""
 
47
 
48
  EXPOSE 7860
49
 
 
44
  ENV PORT=7860
45
  ENV DATABASE_URL=sqlite+aiosqlite:////app/backend/leadpilot.db
46
  ENV NEXT_PUBLIC_API_BASE_URL=""
47
+ ENV APP_BASE_URL=https://ashrafkassem-leadpilot.hf.space
48
 
49
  EXPOSE 7860
50
 
frontend/src/app/verify-email/page.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useState, Suspense } from "react";
4
+ import { useSearchParams } from "next/navigation";
5
+ import Link from "next/link";
6
+ import { apiClient } from "@/lib/api";
7
+ import { auth } from "@/lib/auth";
8
+
9
+ function VerifyEmailContent() {
10
+ const searchParams = useSearchParams();
11
+ const token = searchParams.get("token");
12
+
13
+ const [status, setStatus] = useState<"loading" | "success" | "error">("loading");
14
+ const [message, setMessage] = useState("");
15
+
16
+ useEffect(() => {
17
+ if (!token) {
18
+ setStatus("error");
19
+ setMessage("Invalid verification link. No token provided.");
20
+ return;
21
+ }
22
+
23
+ const verify = async () => {
24
+ const res = await apiClient.get(`/auth/verify-email?token=${token}`);
25
+ if (res.success && res.data) {
26
+ setStatus("success");
27
+ setMessage(res.data.message || "Email verified successfully!");
28
+ } else {
29
+ setStatus("error");
30
+ setMessage(res.error || "Verification failed. The link may be invalid or expired.");
31
+ }
32
+ };
33
+
34
+ verify();
35
+ }, [token]);
36
+
37
+ return (
38
+ <div className="min-h-screen bg-[#F8FAFC] flex flex-col justify-center py-12 sm:px-6 lg:px-8 font-inter">
39
+ <div className="sm:mx-auto sm:w-full sm:max-w-md text-center">
40
+ <Link href="/" className="inline-block">
41
+ <h1 className="text-3xl font-bold tracking-tight text-[#334155]">
42
+ Lead<span className="text-[#0F766E]">Pilot</span>
43
+ </h1>
44
+ </Link>
45
+ <p className="mt-2 text-sm text-[#64748B]">
46
+ Automate your lead engagement with precision.
47
+ </p>
48
+ </div>
49
+
50
+ <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
51
+ <div className="bg-white py-8 px-4 shadow-sm border border-[#E2E8F0] sm:rounded-lg sm:px-10 text-center">
52
+ {status === "loading" && (
53
+ <div className="space-y-4">
54
+ <div className="animate-spin rounded-full h-10 w-10 border-b-2 border-[#0F766E] mx-auto"></div>
55
+ <p className="text-sm text-[#64748B]">Verifying your email...</p>
56
+ </div>
57
+ )}
58
+
59
+ {status === "success" && (
60
+ <div className="space-y-4">
61
+ <div className="w-12 h-12 bg-emerald-100 rounded-full flex items-center justify-center mx-auto">
62
+ <svg className="w-6 h-6 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
63
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
64
+ </svg>
65
+ </div>
66
+ <h2 className="text-xl font-semibold text-[#334155]">Email Verified</h2>
67
+ <p className="text-sm text-[#64748B]">{message}</p>
68
+ <Link
69
+ href={auth.isAuthed() ? "/dashboard" : "/login"}
70
+ className="inline-block mt-4 px-6 py-2 bg-[#0F766E] text-white rounded-md text-sm font-medium hover:bg-[#14B8A6] transition-colors"
71
+ >
72
+ {auth.isAuthed() ? "Go to Dashboard" : "Sign In"}
73
+ </Link>
74
+ </div>
75
+ )}
76
+
77
+ {status === "error" && (
78
+ <div className="space-y-4">
79
+ <div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto">
80
+ <svg className="w-6 h-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
81
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
82
+ </svg>
83
+ </div>
84
+ <h2 className="text-xl font-semibold text-[#334155]">Verification Failed</h2>
85
+ <p className="text-sm text-[#64748B]">{message}</p>
86
+ <div className="flex flex-col gap-2 mt-4">
87
+ <Link
88
+ href="/login"
89
+ className="inline-block px-6 py-2 bg-[#0F766E] text-white rounded-md text-sm font-medium hover:bg-[#14B8A6] transition-colors"
90
+ >
91
+ Go to Login
92
+ </Link>
93
+ </div>
94
+ </div>
95
+ )}
96
+ </div>
97
+
98
+ <div className="mt-6 text-center">
99
+ <p className="text-xs text-[#94A3B8]">
100
+ &copy; {new Date().getFullYear()} LeadPilot. All rights reserved.
101
+ </p>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ );
106
+ }
107
+
108
+ export default function VerifyEmailPage() {
109
+ return (
110
+ <Suspense
111
+ fallback={
112
+ <div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center">
113
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#0F766E]"></div>
114
+ </div>
115
+ }
116
+ >
117
+ <VerifyEmailContent />
118
+ </Suspense>
119
+ );
120
+ }