chore: clean up repo for public release
- Remove BMAD framework, IDE configs, dev screenshots, test files, internal docs, and backup files - Rename keep-notes/ to memento-note/ - Update all references from keep-notes to memento-note - Add Apache 2.0 license with Commons Clause (non-commercial restriction) - Add clean .gitignore and .env.docker.example
This commit is contained in:
179
memento-note/app/(main)/settings/profile/profile-form.tsx
Normal file
179
memento-note/app/(main)/settings/profile/profile-form.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
|
||||
import { updateProfile, changePassword, updateFontSize, updateShowRecentNotes } from '@/app/actions/profile'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
|
||||
|
||||
export function ProfileForm({ user, userAISettings }: { user: any; userAISettings?: any }) {
|
||||
const router = useRouter()
|
||||
|
||||
const [fontSize, setFontSize] = useState(userAISettings?.fontSize || 'medium')
|
||||
const [isUpdatingFontSize, setIsUpdatingFontSize] = useState(false)
|
||||
const [showRecentNotes, setShowRecentNotes] = useState(userAISettings?.showRecentNotes ?? false)
|
||||
const [isUpdatingRecentNotes, setIsUpdatingRecentNotes] = useState(false)
|
||||
const { t } = useLanguage()
|
||||
|
||||
const FONT_SIZES = [
|
||||
{ value: 'small', label: t('profile.fontSizeSmall'), size: '14px' },
|
||||
{ value: 'medium', label: t('profile.fontSizeMedium'), size: '16px' },
|
||||
{ value: 'large', label: t('profile.fontSizeLarge'), size: '18px' },
|
||||
{ value: 'extra-large', label: t('profile.fontSizeExtraLarge'), size: '20px' },
|
||||
]
|
||||
|
||||
const handleFontSizeChange = async (size: string) => {
|
||||
setIsUpdatingFontSize(true)
|
||||
try {
|
||||
const result = await updateFontSize(size)
|
||||
if (result?.error) {
|
||||
toast.error(t('profile.fontSizeUpdateFailed'))
|
||||
} else {
|
||||
setFontSize(size)
|
||||
// Apply font size immediately
|
||||
applyFontSize(size)
|
||||
toast.success(t('profile.fontSizeUpdateSuccess'))
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t('profile.fontSizeUpdateFailed'))
|
||||
} finally {
|
||||
setIsUpdatingFontSize(false)
|
||||
}
|
||||
}
|
||||
|
||||
const applyFontSize = (size: string) => {
|
||||
// Base font size in pixels (16px is standard)
|
||||
const fontSizeMap = {
|
||||
'small': '14px', // ~87% of 16px
|
||||
'medium': '16px', // 100% (standard)
|
||||
'large': '18px', // ~112% of 16px
|
||||
'extra-large': '20px' // 125% of 16px
|
||||
}
|
||||
const fontSizeFactorMap = {
|
||||
'small': 0.95,
|
||||
'medium': 1.0,
|
||||
'large': 1.1,
|
||||
'extra-large': 1.25
|
||||
}
|
||||
const fontSizeValue = fontSizeMap[size as keyof typeof fontSizeMap] || '16px'
|
||||
const fontSizeFactor = fontSizeFactorMap[size as keyof typeof fontSizeFactorMap] || 1.0
|
||||
|
||||
document.documentElement.style.setProperty('--user-font-size', fontSizeValue)
|
||||
document.documentElement.style.setProperty('--user-font-size-factor', fontSizeFactor.toString())
|
||||
localStorage.setItem('user-font-size', size)
|
||||
}
|
||||
|
||||
// Apply saved font size on mount
|
||||
useEffect(() => {
|
||||
const savedFontSize = localStorage.getItem('user-font-size') || userAISettings?.fontSize || 'medium'
|
||||
applyFontSize(savedFontSize as string)
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const handleShowRecentNotesChange = async (enabled: boolean) => {
|
||||
setIsUpdatingRecentNotes(true)
|
||||
const previousValue = showRecentNotes
|
||||
|
||||
try {
|
||||
const result = await updateShowRecentNotes(enabled)
|
||||
if (result?.error) {
|
||||
toast.error(result.error)
|
||||
} else {
|
||||
setShowRecentNotes(enabled)
|
||||
toast.success(t('profile.recentNotesUpdateSuccess') || 'Paramètre mis à jour')
|
||||
// Force full page reload to ensure settings are reloaded
|
||||
window.location.href = '/settings/profile'
|
||||
}
|
||||
} catch (error: any) {
|
||||
setShowRecentNotes(previousValue)
|
||||
toast.error(error?.message || 'Erreur')
|
||||
} finally {
|
||||
setIsUpdatingRecentNotes(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('profile.title')}</CardTitle>
|
||||
<CardDescription>{t('profile.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<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'))
|
||||
}
|
||||
}}>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="name" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{t('profile.displayName')}</label>
|
||||
<Input id="name" name="name" defaultValue={user.name} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{t('profile.email')}</label>
|
||||
<Input id="email" value={user.email} disabled className="bg-muted" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button type="submit">{t('general.save')}</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('profile.changePassword')}</CardTitle>
|
||||
<CardDescription>{t('profile.changePasswordDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<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'))
|
||||
// Reset form manually or redirect
|
||||
const form = document.querySelector('form#password-form') as HTMLFormElement
|
||||
form?.reset()
|
||||
}
|
||||
}} id="password-form">
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="currentPassword" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{t('profile.currentPassword')}</label>
|
||||
<Input id="currentPassword" name="currentPassword" type="password" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="newPassword" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{t('profile.newPassword')}</label>
|
||||
<Input id="newPassword" name="newPassword" type="password" required minLength={6} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="confirmPassword" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{t('profile.confirmPassword')}</label>
|
||||
<Input id="confirmPassword" name="confirmPassword" type="password" required minLength={6} />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button type="submit">{t('profile.updatePassword')}</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user