From fa88d62375d3caca66b202cf48a586cd2bc11091 Mon Sep 17 00:00:00 2001 From: weeihan Date: Tue, 21 Jul 2026 22:31:20 +0800 Subject: [PATCH] feat(auth): add reset-password page and login success banner --- app/(auth)/login/page.tsx | 12 +++- app/(auth)/reset-password/page.tsx | 97 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 app/(auth)/reset-password/page.tsx diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 146fdf3..8cdb5f0 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,9 +1,19 @@ // app/(auth)/login/page.tsx import { LoginForm } from '@/components/auth/login-form' -export default function LoginPage() { +interface Props { + searchParams: Promise<{ message?: string }> +} + +export default async function LoginPage({ searchParams }: Props) { + const { message } = await searchParams return (
+ {message === 'password_reset' && ( +
+ Password updated — sign in with your new password. +
+ )}
diff --git a/app/(auth)/reset-password/page.tsx b/app/(auth)/reset-password/page.tsx new file mode 100644 index 0000000..4bb80b5 --- /dev/null +++ b/app/(auth)/reset-password/page.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import Link from 'next/link' +import { createClient } from '@/lib/supabase/client' + +export default function ResetPasswordPage() { + const router = useRouter() + const [password, setPassword] = useState('') + const [confirm, setConfirm] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError(null) + + if (password.length < 8) { + setError('Password must be at least 8 characters.') + return + } + if (password !== confirm) { + setError('Passwords do not match.') + return + } + + setLoading(true) + const supabase = createClient() + const { error: updateError } = await supabase.auth.updateUser({ password }) + setLoading(false) + + if (updateError) { + if ( + updateError.message.toLowerCase().includes('session') || + updateError.message.toLowerCase().includes('expired') + ) { + setError('Reset link has expired. Request a new one.') + } else { + setError(updateError.message) + } + return + } + + router.push('/login?message=password_reset') + } + + return ( +
+
+
+

Set new password

+

Choose a strong password.

+
+ + {error && ( +

+ {error}{' '} + {error.includes('expired') && ( + + Request new link + + )} +

+ )} + + setPassword(e.target.value)} + required + minLength={8} + autoComplete="new-password" + className="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + setConfirm(e.target.value)} + required + autoComplete="new-password" + className="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + + +
+
+ ) +}