Skip to content

Commit fbbacc4

Browse files
committed
feat(auth): add password reset flow
- add forgot-password page and forgot password form component with hcaptcha - add reset-password page and reset password form component that exchanges recovery code and updates user password - update login page link to point to forgot-password - add api callback default redirect to /d instead of /dashboard - update internal links and back button defaults to use /d - update auth proxy to redirect authenticated users to /d - add robots noindex for reset-password and disallow in robots.txt - refactor globals.css formatting and improve transitions, utilities, and badges
1 parent 731c5eb commit fbbacc4

13 files changed

Lines changed: 860 additions & 166 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import Link from "next/link";
2+
import Image from "next/image";
3+
import { Metadata } from "next/types";
4+
import ForgotPasswordForm from "@/app/components/auth/ForgotPasswordForm";
5+
6+
export const metadata: Metadata = {
7+
title: "Forgot Password - Devpulse",
8+
description:
9+
"Lost access to your Devpulse account? Enter your email address to receive a password reset link and get back to tracking your coding activity and competing on leaderboards.",
10+
alternates: {
11+
canonical: "https://devpulse.hallofcodes.org/forgot-password",
12+
},
13+
openGraph: {
14+
title: "Forgot Password - Devpulse",
15+
description:
16+
"Lost access to your Devpulse account? Enter your email address to receive a password reset link and get back to tracking your coding activity and competing on leaderboards.",
17+
url: "https://devpulse.hallofcodes.org/forgot-password",
18+
siteName: "Devpulse",
19+
images: [
20+
{
21+
url: "https://devpulse.hallofcodes.org/images/devpulse.cover.png",
22+
width: 1200,
23+
height: 630,
24+
alt: "Devpulse Cover Image",
25+
},
26+
],
27+
locale: "en_US",
28+
type: "website",
29+
},
30+
twitter: {
31+
card: "summary_large_image",
32+
title: "Forgot Password - Devpulse",
33+
description:
34+
"Lost access to your Devpulse account? Enter your email address to receive a password reset link and get back to tracking your coding activity and competing on leaderboards.",
35+
images: [
36+
{
37+
url: "https://devpulse.hallofcodes.org/images/devpulse.cover.png",
38+
alt: "Devpulse Cover Image",
39+
},
40+
],
41+
},
42+
};
43+
44+
export default async function Signup(props: {
45+
searchParams?: Promise<{ redirect?: string }>;
46+
}) {
47+
const redirectParam = (await props.searchParams)?.redirect;
48+
const redirectTo =
49+
redirectParam &&
50+
redirectParam.startsWith("/") &&
51+
!redirectParam.startsWith("//")
52+
? redirectParam
53+
: undefined;
54+
55+
return (
56+
<div className="min-h-screen flex bg-[#0a0a1a] text-white relative">
57+
{/* Left Side - Visual / Branding */}
58+
<div className="hidden lg:flex lg:w-1/2 relative flex-col justify-between p-12 md:p-16 xl:p-24 border-r border-white/5 bg-gradient-to-br from-[#0a0a1a] to-[#0a0a1a] overflow-hidden">
59+
{/* Background elements */}
60+
<div className="absolute inset-0 grid-bg opacity-30" />
61+
62+
<div className="relative z-10">
63+
<Link
64+
href="/"
65+
className="flex items-center gap-3 w-fit hover:opacity-80 transition"
66+
>
67+
<Image src="/logo.svg" alt="Devpulse Logo" width={40} height={40} />
68+
<span className="text-2xl font-bold tracking-tight text-white">
69+
Devpulse
70+
</span>
71+
</Link>
72+
</div>
73+
74+
<div className="relative z-10 max-w-md">
75+
<h1 className="text-4xl font-extrabold mb-5 leading-tight text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-400">
76+
Loss of access? No problem!
77+
</h1>
78+
<p className="text-gray-400 text-lg leading-relaxed mb-8">
79+
We got you covered. All you need to do is enter your email address
80+
and we will send you a password reset link to get you back on track
81+
with monitoring your coding activity and competing on leaderboards.
82+
</p>
83+
84+
<div className="glass-card border border-white/5 rounded-2xl p-5 bg-white/5 backdrop-blur-md shadow-2xl">
85+
<div className="flex items-center gap-2 mb-4">
86+
<div className="w-3 h-3 rounded-full bg-red-500/80"></div>
87+
<div className="w-3 h-3 rounded-full bg-yellow-500/80"></div>
88+
<div className="w-3 h-3 rounded-full bg-green-500/80"></div>
89+
<span className="ml-2 text-xs font-mono text-gray-500">
90+
setup.ts
91+
</span>
92+
</div>
93+
<div className="space-y-1.5 font-mono text-sm">
94+
<div className="flex">
95+
<span className="text-purple-400 mr-2">const</span>
96+
<span className="text-blue-400">auth</span>
97+
<span className="text-gray-200 mx-2">=</span>
98+
<span className="text-indigo-400 mr-2">new</span>
99+
<span className="text-yellow-400">SupabaseAuth</span>
100+
<span className="text-gray-200">();</span>
101+
</div>
102+
<div className="flex mt-2">
103+
<span className="text-blue-400">auth</span>
104+
<span className="text-gray-200">.</span>
105+
<span className="text-yellow-400">sendPasswordResetEmail</span>
106+
<span className="text-gray-200">(</span>
107+
<span className="text-green-400">email</span>
108+
<span className="text-gray-200">);</span>
109+
</div>
110+
<div className="flex mt-3">
111+
<span className="text-emerald-400/80">
112+
{"// Check your inbox. "}
113+
</span>
114+
</div>
115+
</div>
116+
</div>
117+
</div>
118+
119+
<div className="relative z-10 text-sm text-gray-500 font-medium">
120+
&copy; {new Date().getFullYear()} Devpulse. All rights reserved.
121+
</div>
122+
</div>
123+
124+
{/* Right Side - Form */}
125+
<div className="w-full lg:w-1/2 flex flex-col justify-center items-center p-8 sm:p-12 xl:p-20 relative">
126+
<div className="absolute inset-0 grid-bg opacity-20 lg:hidden" />
127+
128+
<div className="w-full max-w-sm relative z-10">
129+
<Link
130+
href="/"
131+
className="lg:hidden flex items-center justify-center gap-3 mb-10"
132+
>
133+
<Image src="/logo.svg" alt="Devpulse Logo" width={40} height={40} />
134+
<h2 className="text-3xl font-bold text-white">Devpulse</h2>
135+
</Link>
136+
137+
<div className="mb-8 text-left">
138+
<h2 className="text-3xl font-bold text-white mb-2">
139+
Forgot your password?
140+
</h2>
141+
<p className="text-gray-400">
142+
No worries! Just enter your email address and we&apos;ll send you
143+
an email.
144+
</p>
145+
</div>
146+
147+
<ForgotPasswordForm />
148+
149+
<p className="mt-8 text-center text-sm text-gray-400">
150+
Already have an account?{" "}
151+
<Link
152+
href={
153+
redirectTo
154+
? `/login?redirect=${encodeURIComponent(redirectTo)}`
155+
: "/login"
156+
}
157+
className="text-indigo-400 hover:text-indigo-300 font-semibold transition-colors underline-offset-4 hover:underline"
158+
>
159+
Log in
160+
</Link>
161+
</p>
162+
</div>
163+
</div>
164+
</div>
165+
);
166+
}

app/(public)/(auth)/login/page.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,19 +155,18 @@ export default async function Login(props: {
155155

156156
<LoginForm />
157157

158-
<p className="mt-8 text-center text-sm text-gray-400">
159-
Don&apos;t have an account?{" "}
158+
<div className="mt-6 flex items-center gap-3 text-sm text-indigo-300/90">
160159
<Link
161160
href={
162161
redirectTo
163-
? `/signup?redirect=${encodeURIComponent(redirectTo)}`
164-
: "/signup"
162+
? `/forgot-password?redirect=${encodeURIComponent(redirectTo)}`
163+
: "/forgot-password"
165164
}
166-
className="text-indigo-400 hover:text-indigo-300 font-semibold transition-colors underline-offset-4 hover:underline"
165+
className="font-semibold transition-colors hover:text-indigo-200 underline-offset-4 hover:underline"
167166
>
168-
Sign up
167+
Forgot your password?
169168
</Link>
170-
</p>
169+
</div>
171170
</div>
172171
</div>
173172
</div>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Link from "next/link";
2+
import Image from "next/image";
3+
import { Metadata } from "next/types";
4+
import ResetPasswordForm from "@/app/components/auth/ResetPasswordForm";
5+
6+
// doesnt need description or keywords since this page is only accessible via a link in the email sent to the user,
7+
// and we dont want it indexed by search engines
8+
export const metadata: Metadata = {
9+
title: "Reset Password - Devpulse",
10+
description: "",
11+
alternates: {
12+
canonical: "https://devpulse.hallofcodes.org/reset-password",
13+
},
14+
robots: {
15+
index: false,
16+
follow: false,
17+
},
18+
openGraph: {
19+
title: "Reset Password - Devpulse",
20+
description: "",
21+
url: "https://devpulse.hallofcodes.org/reset-password",
22+
siteName: "Devpulse",
23+
images: [
24+
{
25+
url: "https://devpulse.hallofcodes.org/images/devpulse.cover.png",
26+
width: 1200,
27+
height: 630,
28+
alt: "Devpulse Cover Image",
29+
},
30+
],
31+
locale: "en_US",
32+
type: "website",
33+
},
34+
twitter: {
35+
card: "summary_large_image",
36+
title: "Reset Password - Devpulse",
37+
description: "",
38+
images: [
39+
{
40+
url: "https://devpulse.hallofcodes.org/images/devpulse.cover.png",
41+
alt: "Devpulse Cover Image",
42+
},
43+
],
44+
},
45+
};
46+
47+
export default async function ResetPassword() {
48+
return (
49+
<div className="min-h-screen flex bg-[#0a0a1a] text-white relative">
50+
{/* Left Side - Visual / Branding */}
51+
<div className="hidden lg:flex lg:w-1/2 relative flex-col justify-between p-12 md:p-16 xl:p-24 border-r border-white/5 bg-gradient-to-br from-[#0a0a1a] to-[#0a0a1a] overflow-hidden">
52+
{/* Background elements */}
53+
<div className="absolute inset-0 grid-bg opacity-30" />
54+
55+
<div className="relative z-10">
56+
<Link
57+
href="/"
58+
className="flex items-center gap-3 w-fit hover:opacity-80 transition"
59+
>
60+
<Image src="/logo.svg" alt="Devpulse Logo" width={40} height={40} />
61+
<span className="text-2xl font-bold tracking-tight text-white">
62+
Devpulse
63+
</span>
64+
</Link>
65+
</div>
66+
67+
<div className="relative z-10 max-w-md">
68+
<h1 className="text-4xl font-extrabold mb-5 leading-tight text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-400">
69+
Change your password and get back to tracking your coding activity!
70+
</h1>
71+
<p className="text-gray-400 text-lg leading-relaxed mb-8">
72+
Your Devpulse dashboard is waiting for you. Enter a new password to
73+
regain access and continue your coding journey.
74+
</p>
75+
76+
<div className="glass-card border border-white/5 rounded-2xl p-5 bg-white/5 backdrop-blur-md shadow-2xl">
77+
<div className="flex items-center gap-2 mb-4">
78+
<div className="w-3 h-3 rounded-full bg-red-500/80"></div>
79+
<div className="w-3 h-3 rounded-full bg-yellow-500/80"></div>
80+
<div className="w-3 h-3 rounded-full bg-green-500/80"></div>
81+
<span className="ml-2 text-xs font-mono text-gray-500">
82+
setup.ts
83+
</span>
84+
</div>
85+
<div className="space-y-1.5 font-mono text-sm">
86+
<div className="flex">
87+
<span className="text-purple-400 mr-2">const</span>
88+
<span className="text-blue-400">dev</span>
89+
<span className="text-gray-200 mx-2">=</span>
90+
<span className="text-indigo-400">getAccount</span>
91+
<span className="text-gray-200">(</span>
92+
<span className="text-yellow-200">this</span>
93+
<span className="text-gray-200">);</span>
94+
</div>
95+
<div className="flex mt-2">
96+
<span className="text-blue-400">dev</span>
97+
<span className="text-gray-200">.</span>
98+
<span className="text-yellow-200">setNewPassword</span>
99+
<span className="text-gray-200">(</span>
100+
<span className="text-gray-200">
101+
&quot;your-new-password&quot;
102+
</span>
103+
<span className="text-gray-200">);</span>
104+
</div>
105+
<div className="flex mt-3">
106+
<span className="text-emerald-400/80">
107+
{"// And just like that, you&apos;re back in the game. 🎉"}
108+
</span>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
114+
<div className="relative z-10 text-sm text-gray-500 font-medium">
115+
&copy; {new Date().getFullYear()} Devpulse. All rights reserved.
116+
</div>
117+
</div>
118+
119+
{/* Right Side - Form */}
120+
<div className="w-full lg:w-1/2 flex flex-col justify-center items-center p-8 sm:p-12 xl:p-20 relative">
121+
<div className="absolute inset-0 grid-bg opacity-20 lg:hidden" />
122+
123+
<div className="w-full max-w-sm relative z-10">
124+
<Link
125+
href="/"
126+
className="lg:hidden flex items-center justify-center gap-3 mb-10"
127+
>
128+
<Image src="/logo.svg" alt="Devpulse Logo" width={40} height={40} />
129+
<h2 className="text-3xl font-bold text-white">Devpulse</h2>
130+
</Link>
131+
132+
<div className="mb-8 text-left">
133+
<h2 className="text-3xl font-bold text-white mb-2">
134+
Reset your password
135+
</h2>
136+
<p className="text-gray-400">
137+
New password, who dis? Enter a new password to regain access to
138+
your account and get back to tracking your coding stats!
139+
</p>
140+
</div>
141+
142+
<ResetPasswordForm />
143+
144+
<p className="mt-8 text-center text-sm text-gray-400">
145+
Already have an account?{" "}
146+
<Link
147+
href="/login"
148+
className="text-blue-500 hover:underline transition"
149+
>
150+
Log in
151+
</Link>
152+
</p>
153+
</div>
154+
</div>
155+
</div>
156+
);
157+
}

app/api/auth/callback/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export async function GET(req: NextRequest) {
99
const redirectTo =
1010
redirectParam && redirectParam.startsWith("/") && !redirectParam.startsWith("//")
1111
? redirectParam
12-
: "/dashboard";
12+
: "/d";
1313

1414
const response = NextResponse.redirect(`${origin}${redirectTo}`);
1515
response.cookies.set("devpulse_redirect", "", { path: "/", maxAge: 0 });

0 commit comments

Comments
 (0)