feat: add profile update functionality and integrate it into the Security page

This commit is contained in:
Wikid82
2025-11-21 11:25:58 -05:00
parent 9914e20817
commit 8a0d7952a9
5 changed files with 200 additions and 26 deletions
+148 -10
View File
@@ -1,12 +1,13 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Card } from '../components/ui/Card'
import { Input } from '../components/ui/Input'
import { Button } from '../components/ui/Button'
import { toast } from '../utils/toast'
import client from '../api/client'
import { getProfile, regenerateApiKey } from '../api/user'
import { Copy, RefreshCw, Shield } from 'lucide-react'
import { getProfile, regenerateApiKey, updateProfile } from '../api/user'
import { getSettings, updateSetting } from '../api/settings'
import { Copy, RefreshCw, Shield, Mail, User } from 'lucide-react'
import { PasswordStrengthMeter } from '../components/PasswordStrengthMeter'
export default function Security() {
@@ -15,6 +16,14 @@ export default function Security() {
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
// Profile State
const [name, setName] = useState('')
const [email, setEmail] = useState('')
// Certificate Email State
const [certEmail, setCertEmail] = useState('')
const [useUserEmail, setUseUserEmail] = useState(true)
const queryClient = useQueryClient()
const { data: profile, isLoading: isLoadingProfile } = useQuery({
@@ -22,6 +31,44 @@ export default function Security() {
queryFn: getProfile,
})
const { data: settings } = useQuery({
queryKey: ['settings'],
queryFn: getSettings,
})
// Initialize profile state
useEffect(() => {
if (profile) {
setName(profile.name)
setEmail(profile.email)
}
}, [profile])
// Initialize cert email state
useEffect(() => {
if (settings && profile) {
const savedEmail = settings['caddy.email']
if (savedEmail && savedEmail !== profile.email) {
setCertEmail(savedEmail)
setUseUserEmail(false)
} else {
setCertEmail(profile.email)
setUseUserEmail(true)
}
}
}, [settings, profile])
const updateProfileMutation = useMutation({
mutationFn: updateProfile,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profile'] })
toast.success('Profile updated successfully')
},
onError: (error: any) => {
toast.error(`Failed to update profile: ${error.message}`)
},
})
const regenerateMutation = useMutation({
mutationFn: regenerateApiKey,
onSuccess: () => {
@@ -33,6 +80,21 @@ export default function Security() {
},
})
const saveCertEmailMutation = useMutation({
mutationFn: async () => {
const emailToSave = useUserEmail ? profile?.email : certEmail
if (!emailToSave) return
await updateSetting('caddy.email', emailToSave, 'caddy', 'string')
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['settings'] })
toast.success('Certificate email updated')
},
onError: (error: any) => {
toast.error(`Failed to update certificate email: ${error.message}`)
},
})
const handleChangePassword = async (e: React.FormEvent) => {
e.preventDefault()
if (newPassword !== confirmPassword) {
@@ -70,6 +132,33 @@ export default function Security() {
</h1>
<div className="grid gap-6">
{/* Profile Settings */}
<Card className="max-w-2xl p-6">
<div className="flex items-center gap-2 mb-4">
<User className="w-5 h-5 text-gray-500" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Profile Settings</h2>
</div>
<div className="space-y-4">
<Input
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Input
label="Login Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button
onClick={() => updateProfileMutation.mutate({ name, email })}
isLoading={updateProfileMutation.isPending}
>
Update Profile
</Button>
</div>
</Card>
{/* Change Password */}
<Card className="max-w-2xl p-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Change Password</h2>
@@ -91,19 +180,68 @@ export default function Security() {
/>
<PasswordStrengthMeter password={newPassword} />
</div>
<Input
label="Confirm New Password"
type="password"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
/>
<div className="space-y-1">
<Input
label="Confirm New Password"
type="password"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
className={confirmPassword && newPassword !== confirmPassword ? 'border-red-500 focus:ring-red-500' : ''}
/>
{confirmPassword && newPassword !== confirmPassword && (
<p className="text-xs text-red-500">Passwords do not match</p>
)}
</div>
<Button type="submit" isLoading={loading}>
Update Password
</Button>
</form>
</Card>
{/* Certificate Email Configuration */}
<Card className="max-w-2xl p-6">
<div className="flex items-center gap-2 mb-4">
<Mail className="w-5 h-5 text-gray-500" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Certificate Email</h2>
</div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
This email address is used to register with Let's Encrypt for SSL certificate generation and expiration notifications.
</p>
<div className="space-y-4">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="useUserEmail"
checked={useUserEmail}
onChange={(e) => setUseUserEmail(e.target.checked)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="useUserEmail" className="text-sm text-gray-700 dark:text-gray-300">
Use my login email ({profile?.email})
</label>
</div>
{!useUserEmail && (
<Input
label="Custom Email Address"
type="email"
value={certEmail}
onChange={(e) => setCertEmail(e.target.value)}
placeholder="certs@example.com"
/>
)}
<Button
onClick={() => saveCertEmailMutation.mutate()}
isLoading={saveCertEmailMutation.isPending}
>
Save Email Settings
</Button>
</div>
</Card>
{/* API Key */}
<Card className="max-w-2xl p-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">API Key</h2>