- settings/layout: serif h1 title + uppercase tracking subtitle, matching Agents page - SettingsNav: uppercase tracking-wider tabs with foreground underline on active - All settings pages (general, ai, appearance, profile, mcp, about, data): remove duplicate h1 (now in layout header), replace with uppercase section label - notes.ts: decouple history guards from global userAISettings - note-document-info-panel: add 'Save version' button with loading feedback
92 lines
4.7 KiB
TypeScript
92 lines
4.7 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { updateProfile, changePassword } from '@/app/actions/profile'
|
|
import { toast } from 'sonner'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { User, Lock } from 'lucide-react'
|
|
|
|
export function ProfileForm({ user, userAISettings }: { user: any; userAISettings?: any }) {
|
|
const { t } = useLanguage()
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<p className="text-[11px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
|
|
{t('profile.description')}
|
|
</p>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Profile info card */}
|
|
<div className="bg-card rounded-lg border border-border p-6 shadow-sm flex flex-col gap-4">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
|
|
<User className="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-foreground">{t('profile.title')}</h3>
|
|
<p className="text-sm text-muted-foreground">{t('profile.description')}</p>
|
|
</div>
|
|
</div>
|
|
<form action={async (formData) => {
|
|
const result = await updateProfile({ name: formData.get('name') as string })
|
|
if (result?.error) toast.error(t('profile.updateFailed'))
|
|
else toast.success(t('profile.updateSuccess'))
|
|
}} className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="name" className="text-sm font-medium text-foreground">{t('profile.displayName')}</label>
|
|
<Input id="name" name="name" defaultValue={user.name} className="bg-muted border-border focus:border-primary" />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="email" className="text-sm font-medium text-foreground">{t('profile.email')}</label>
|
|
<Input id="email" value={user.email} disabled className="bg-muted border-border opacity-60" />
|
|
</div>
|
|
<Button type="submit" className="w-full mt-2">{t('general.save')}</Button>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Password card */}
|
|
<div className="bg-card rounded-lg border border-border p-6 shadow-sm flex flex-col gap-4">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
|
|
<Lock className="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-foreground">{t('profile.changePassword')}</h3>
|
|
<p className="text-sm text-muted-foreground">{t('profile.changePasswordDescription')}</p>
|
|
</div>
|
|
</div>
|
|
<form action={async (formData) => {
|
|
const result = await changePassword(formData)
|
|
if (result?.error) {
|
|
const msg = '_form' in result.error
|
|
? result.error._form[0]
|
|
: result.error.currentPassword?.[0] || result.error.newPassword?.[0] || result.error.confirmPassword?.[0] || t('profile.passwordChangeFailed')
|
|
toast.error(msg)
|
|
} else {
|
|
toast.success(t('profile.passwordChangeSuccess'))
|
|
const form = document.querySelector('form#password-form') as HTMLFormElement
|
|
form?.reset()
|
|
}
|
|
}} id="password-form" className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="currentPassword" className="text-sm font-medium text-foreground">{t('profile.currentPassword')}</label>
|
|
<Input id="currentPassword" name="currentPassword" type="password" required className="bg-muted border-border focus:border-primary" />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="newPassword" className="text-sm font-medium text-foreground">{t('profile.newPassword')}</label>
|
|
<Input id="newPassword" name="newPassword" type="password" required minLength={6} className="bg-muted border-border focus:border-primary" />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<label htmlFor="confirmPassword" className="text-sm font-medium text-foreground">{t('profile.confirmPassword')}</label>
|
|
<Input id="confirmPassword" name="confirmPassword" type="password" required minLength={6} className="bg-muted border-border focus:border-primary" />
|
|
</div>
|
|
<Button type="submit" className="w-full mt-2">{t('profile.updatePassword')}</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|