maj pricing, configuration du prisma

This commit is contained in:
2026-02-27 23:57:38 +01:00
parent 5268a7dd68
commit 521e529ab0
18 changed files with 358 additions and 42 deletions

View File

@@ -7,7 +7,7 @@ import { useEffect } from 'react';
export default function ProfilePage() {
const router = useRouter();
const { user, loading } = useAuthContext();
const { user, loading, updateProfile } = useAuthContext();
useEffect(() => {
if (!loading && !user) {
@@ -20,7 +20,13 @@ export default function ProfilePage() {
return (
<UserProfileSettings
user={user}
onUpdate={(updates) => console.log('Profile update:', updates)}
onUpdate={async (updates) => {
try {
await updateProfile(updates);
} catch (err) {
// Handled inside updateProfile (console.error)
}
}}
onBack={() => router.push('/dashboard')}
/>
);

View File

@@ -23,15 +23,58 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
const [activeTab, setActiveTab] = useState<'profile' | 'preferences' | 'account'>('profile');
const [formData, setFormData] = useState({
name: user.name,
avatar: user.avatar || '',
bio: user.bio || '',
email: user.email,
theme: user.preferences.theme,
dailyWordGoal: user.preferences.dailyWordGoal
});
const fileInputRef = React.useRef<HTMLInputElement>(null);
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
alert('Veuillez sélectionner une image valide.');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const MAX_SIZE = 256;
let width = img.width;
let height = img.height;
if (width > height && width > MAX_SIZE) {
height = Math.round((height * MAX_SIZE) / width);
width = MAX_SIZE;
} else if (height > MAX_SIZE) {
width = Math.round((width * MAX_SIZE) / height);
height = MAX_SIZE;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(img, 0, 0, width, height);
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
setFormData(prev => ({ ...prev, avatar: dataUrl }));
}
};
img.src = e.target?.result as string;
};
reader.readAsDataURL(file);
};
const handleSave = () => {
onUpdate({
name: formData.name,
avatar: formData.avatar,
bio: formData.bio,
email: formData.email,
preferences: {
@@ -82,11 +125,18 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
{activeTab === 'profile' && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
<div className="flex items-center gap-6 pb-8 border-b border-slate-100">
<div className="relative group">
<img src={user.avatar} className="w-24 h-24 rounded-full object-cover border-4 border-slate-50 shadow-md" alt="Avatar" />
<button className="absolute inset-0 bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity">
<div className="relative group cursor-pointer" onClick={() => fileInputRef.current?.click()}>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
accept="image/*"
className="hidden"
/>
<img src={formData.avatar || 'https://via.placeholder.com/150'} className="w-24 h-24 rounded-full object-cover border-4 border-slate-50 shadow-md" alt="Avatar" />
<div className="absolute inset-0 bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity" title="Changer d'avatar">
<Camera size={20} />
</button>
</div>
</div>
<div>
<h3 className="font-bold text-slate-900 text-lg">{user.name}</h3>
@@ -109,6 +159,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Bio / Citation inspirante</label>
<textarea

View File

@@ -111,5 +111,27 @@ export const useAuth = () => {
}
}, [user]);
return { user, login, signup, logout, incrementUsage, loading };
const updateProfile = useCallback(async (updates: Partial<UserProfile>) => {
if (!user) return;
try {
// Unpack everything that can be updated into a flat object for the API
const apiUpdates: any = {};
if (updates.name !== undefined) apiUpdates.name = updates.name;
if (updates.avatar !== undefined) apiUpdates.avatar = updates.avatar;
if (updates.bio !== undefined) apiUpdates.bio = updates.bio;
if (updates.preferences?.dailyWordGoal !== undefined) apiUpdates.dailyWordGoal = updates.preferences.dailyWordGoal;
// Make the API call to update DB
await api.user.updateProfile(apiUpdates);
// Update local state
setUser(prev => prev ? { ...prev, ...updates } : null);
} catch (err) {
console.error('Failed to update profile:', err);
throw err;
}
}, [user]);
return { user, login, signup, logout, incrementUsage, updateProfile, loading };
};

View File

@@ -44,6 +44,16 @@ const api = {
},
},
// --- USER ---
user: {
async updateProfile(data: any) {
return api.request('/user/profile', {
method: 'PUT',
body: JSON.stringify(data),
});
},
},
// --- PROJECTS ---
projects: {
async list() {