From e8a6942cfe3cfd150c344901b21850f38dfe6612 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 22 Jan 2026 23:49:25 -0300 Subject: [PATCH] feat: [US-041] - Change password for logged-in user Co-Authored-By: Claude Opus 4.5 --- src/app/dashboard/settings/page.tsx | 161 ++++++++++++++++++++++++++++ src/components/Navbar.tsx | 6 ++ 2 files changed, 167 insertions(+) create mode 100644 src/app/dashboard/settings/page.tsx diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx new file mode 100644 index 0000000..23898f1 --- /dev/null +++ b/src/app/dashboard/settings/page.tsx @@ -0,0 +1,161 @@ +'use client' + +import { useState } from 'react' +import { createClient } from '@/lib/supabase/client' + +export default function SettingsPage() { + const [currentPassword, setCurrentPassword] = useState('') + const [newPassword, setNewPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [error, setError] = useState('') + const [success, setSuccess] = useState('') + const [isLoading, setIsLoading] = useState(false) + + const handleChangePassword = async (e: React.FormEvent) => { + e.preventDefault() + setError('') + setSuccess('') + + if (newPassword !== confirmPassword) { + setError('New passwords do not match.') + return + } + + if (newPassword.length < 6) { + setError('New password must be at least 6 characters.') + return + } + + setIsLoading(true) + + try { + const supabase = createClient() + + // Re-authenticate with current password + const { data: { user } } = await supabase.auth.getUser() + if (!user?.email) { + setError('Unable to verify current user.') + setIsLoading(false) + return + } + + const { error: signInError } = await supabase.auth.signInWithPassword({ + email: user.email, + password: currentPassword, + }) + + if (signInError) { + setError('Current password is incorrect.') + setIsLoading(false) + return + } + + // Update to new password + const { error: updateError } = await supabase.auth.updateUser({ + password: newPassword, + }) + + if (updateError) { + setError(updateError.message) + setIsLoading(false) + return + } + + setSuccess('Password updated successfully.') + setCurrentPassword('') + setNewPassword('') + setConfirmPassword('') + } catch { + setError('An unexpected error occurred.') + } finally { + setIsLoading(false) + } + } + + return ( +
+

+ Settings +

+ +
+

+ Change Password +

+ +
+ {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + +
+ + setCurrentPassword(e.target.value)} + required + className="w-full rounded-md border border-zinc-300 px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-500" + /> +
+ +
+ + setNewPassword(e.target.value)} + required + className="w-full rounded-md border border-zinc-300 px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-500" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + required + className="w-full rounded-md border border-zinc-300 px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-500" + /> +
+ + +
+
+
+ ) +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index d7d1a57..1e7f032 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -29,6 +29,12 @@ export default function Navbar({ userEmail, isAdmin }: NavbarProps) { Invite User )} + + Settings + {userEmail}