From 892e45363ec7ef82449e129ec77cf32f19fac411 Mon Sep 17 00:00:00 2001 From: M9nx Date: Tue, 9 Sep 2025 23:37:53 +0300 Subject: [PATCH 1/4] Add avatar upload functionality to settings page Introduces avatar upload and preview features in the user settings page. Users can now select and upload a new profile photo, with immediate preview and feedback on upload status. Updates are integrated with the authentication context to reflect changes. --- resources/js/pages/SettingsPage.tsx | 122 +++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/resources/js/pages/SettingsPage.tsx b/resources/js/pages/SettingsPage.tsx index f24d09d..237e7e6 100644 --- a/resources/js/pages/SettingsPage.tsx +++ b/resources/js/pages/SettingsPage.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import { User, Lock, Eye, Trash2 } from 'lucide-react'; +import { User, Lock, Eye, Trash2, Upload } from 'lucide-react'; export const SettingsPage: React.FC = () => { const { user, updateUser } = useAuth(); @@ -11,6 +11,10 @@ export const SettingsPage: React.FC = () => { username: user?.username || '', bio: user?.bio || '', }); + + const [avatarFile, setAvatarFile] = useState(null); + const [avatarPreview, setAvatarPreview] = useState(user?.avatar || null); + const fileInputRef = useRef(null); const [passwordData, setPasswordData] = useState({ current_password: '', @@ -21,6 +25,73 @@ export const SettingsPage: React.FC = () => { const [isLoading, setIsLoading] = useState(false); const [message, setMessage] = useState(''); + const handleAvatarChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const file = e.target.files[0]; + setAvatarFile(file); + + // Create a preview URL + const reader = new FileReader(); + reader.onloadend = () => { + setAvatarPreview(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + + const handleAvatarUpload = async () => { + if (!avatarFile) { + setMessage('Please select an image file'); + return; + } + + setIsLoading(true); + setMessage(''); + + try { + const formData = new FormData(); + formData.append('avatar', avatarFile); + + const response = await fetch('/api/user/avatar', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', + }, + body: formData, + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + // Update user avatar in auth context + if (user) { + updateUser({ + ...user, + avatar: result.data.avatar + }); + } + setMessage('Avatar updated successfully!'); + } else { + setMessage(result.message || 'Failed to update avatar'); + } + } else { + const error = await response.json(); + if (response.status === 422 && error.errors) { + const errorMessages = Object.values(error.errors).flat().join(', '); + setMessage(`Validation error: ${errorMessages}`); + } else { + setMessage(error.message || 'Failed to update avatar'); + } + } + } catch (err) { + console.error('Avatar upload error:', err); + setMessage('An error occurred while uploading your avatar'); + } finally { + setIsLoading(false); + } + }; + const handleProfileUpdate = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); @@ -170,6 +241,53 @@ export const SettingsPage: React.FC = () => { {activeTab === 'profile' && (

Profile Information

+ + {/* Avatar Upload Section */} +
+ +
+ {avatarPreview ? ( +
+ Avatar preview +
+ ) : ( +
+ + {user?.name ? user.name.charAt(0).toUpperCase() : 'U'} + +
+ )} + +
+ + + +
+
+
+