diff --git a/app/login/page.tsx b/app/login/page.tsx index 331f251..f8bc4a1 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -18,6 +18,8 @@ export default function Login() { const [signingUp, setSigningUp] = useState(false) const [signupSuccess, setSignupSuccess] = useState(false) const [showEmailForm, setShowEmailForm] = useState(false) + const [resettingPassword, setResettingPassword] = useState(false) + const [passwordResetSuccess, setPasswordResetSuccess] = useState(false) const router = useRouter() useEffect(() => { @@ -211,11 +213,47 @@ export default function Login() { const handleBackClick = () => { setError(null) setShowEmailForm(false) + setPasswordResetSuccess(false) } const handleContinueEmailClick = () => { setError(null) setShowEmailForm(true) + setPasswordResetSuccess(false) + } + + const handleForgotPassword = async (e: React.MouseEvent) => { + e.preventDefault() + setError(null) + + if (!email.trim()) { + setError("Please enter your email") + return + } + + try { + setResettingPassword(true) + + // Preserve `redirect` so after password reset + sign in we land back where the user started. + const searchParams = new URLSearchParams(window.location.search) + const redirectParam = searchParams.get("redirect") + + const redirectTo = redirectParam + ? `${window.location.origin}/reset-password?redirect=${encodeURIComponent(redirectParam)}` + : `${window.location.origin}/reset-password` + + const { error: resetError } = await supabaseClient.auth.resetPasswordForEmail(email, { + redirectTo + }) + + if (resetError) throw resetError + + setPasswordResetSuccess(true) + } catch (err: any) { + setError(err?.message || "Failed to send password reset email") + } finally { + setResettingPassword(false) + } } return ( @@ -240,67 +278,92 @@ export default function Login() { {error && {error}} {showEmailForm ? ( - { - e.preventDefault() - handleEmailSignIn(e) - }} - > - - setEmail(e.target.value)} - placeholder="Email" - required - disabled={signingIn || signingUp} - /> - - - setPassword(e.target.value)} - placeholder="Password" - required - minLength={6} - disabled={signingIn || signingUp} + passwordResetSuccess ? ( + + - Password must be at least 6 characters - - - - + + + + - {signingUp ? "Signing up..." : "Sign Up"} - - - - ← Back to sign in options - - + Forgot password? + + + + ← Back to sign in options + + + ) ) : ( + + ) : ( +
+ + setPassword(e.target.value)} + placeholder="New password" + required + minLength={6} + disabled={saving} + /> + + + setConfirmPassword(e.target.value)} + placeholder="Confirm new password" + required + minLength={6} + disabled={saving} + /> + + +
+ )} + + + + ) +} + +const BackgroundContainer = styled.section` + background-color: #0a0a0a; + position: fixed; + height: 100vh; + width: 100vw; + top: 0; + left: 0; + z-index: -1; +` + +const Container = styled.main` + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +` + +const Title = styled.h1` + font-size: 2rem; + font-weight: 700; + color: white; + margin: 0; +` + +const Subtitle = styled.p` + color: rgba(255, 255, 255, 0.7); + margin: -1rem 0 0 0; + text-align: center; +` + +const Form = styled.form` + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +` + +const InputGroup = styled.div` + display: flex; + flex-direction: column; + width: 100%; + gap: 0.5rem; +` + +const ErrorMessage = styled.div` + color: #ff6b6b; + background-color: rgba(255, 107, 107, 0.1); + padding: 0.75rem; + border-radius: 0.5rem; + font-size: 0.875rem; + text-align: center; + width: 100%; +` + +const SuccessContainer = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +` diff --git a/supabase/config.toml b/supabase/config.toml index 1a0ac13..8f870e0 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -123,11 +123,18 @@ enabled = true site_url = "https://devx.network" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. additional_redirect_urls = [ + "http://localhost:3000", + "http://localhost:3000/login", + "http://localhost:3000/reset-password", + "http://127.0.0.1:3000", + "http://127.0.0.1:3000/login", + "http://127.0.0.1:3000/reset-password", "https://127.0.0.1:3000", "http://192.168.1.93:3000", "https://192.168.1.93:3000", "https://devx.network", - "https://devx.network/login" + "https://devx.network/login", + "https://devx.network/reset-password" ] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 @@ -206,6 +213,10 @@ otp_expiry = 3600 # subject = "You have been invited" # content_path = "./supabase/templates/invite.html" +[auth.email.template.recovery] +subject = "Reset Your Password" +content_path = "./email-templates/reset-password-email.html" + # Uncomment to customize notification email template # [auth.email.notification.password_changed] # enabled = true