diff --git a/app/Http/Controllers/Settings/PasswordController.php b/app/Http/Controllers/Settings_Backup/PasswordController.php similarity index 100% rename from app/Http/Controllers/Settings/PasswordController.php rename to app/Http/Controllers/Settings_Backup/PasswordController.php diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings_Backup/ProfileController.php similarity index 100% rename from app/Http/Controllers/Settings/ProfileController.php rename to app/Http/Controllers/Settings_Backup/ProfileController.php diff --git a/app/Http/Middleware/SpaPathRewrite.php b/app/Http/Middleware/SpaPathRewrite.php new file mode 100644 index 0000000..58a5cbb --- /dev/null +++ b/app/Http/Middleware/SpaPathRewrite.php @@ -0,0 +1,25 @@ +getPathInfo(), '/settings')) { + return response()->view('react-app'); + } + + return $next($request); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index b12d012..0eb3012 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -24,6 +24,7 @@ ]); $middleware->web(append: [ + \App\Http\Middleware\SpaPathRewrite::class, HandleAppearance::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, diff --git a/resources/js/components/ProtectedRoute.tsx b/resources/js/components/ProtectedRoute.tsx index 6d0bc90..b322c4e 100644 --- a/resources/js/components/ProtectedRoute.tsx +++ b/resources/js/components/ProtectedRoute.tsx @@ -10,14 +10,18 @@ interface ProtectedRouteProps { export const ProtectedRoute: React.FC = ({ children }) => { const { isAuthenticated, isLoading } = useAuth(); const location = useLocation(); + + // Check if we have a token in localStorage (even if not validated yet) + const hasStoredToken = !!localStorage.getItem('auth_token'); if (isLoading) { - return ; + return ; } - if (!isAuthenticated) { - // Redirect to login page with return url - return ; + // Only redirect if definitely not authenticated AND no token in storage + if (!isAuthenticated && !hasStoredToken) { + // Go to home page instead of login + return ; } return <>{children}; diff --git a/resources/js/components/Sidebar.tsx b/resources/js/components/Sidebar.tsx index 20e2a60..0755cb8 100644 --- a/resources/js/components/Sidebar.tsx +++ b/resources/js/components/Sidebar.tsx @@ -32,7 +32,6 @@ export const Sidebar: React.FC = ({ isOpen, onClose }) => { const menuItems = [ { icon: Home, label: 'Home', path: '/', show: true }, { icon: BookOpen, label: 'Library', path: '/library', show: isAuthenticated }, - { icon: User, label: 'Profile', path: user ? `/profile/${user.id}` : '/profile', show: isAuthenticated }, { icon: FileText, label: 'Stories', path: '/stories', show: isAuthenticated }, { icon: BarChart3, label: 'Stats', path: '/stats', show: isAuthenticated }, { icon: Edit3, label: 'Write', path: '/posts/create', show: isAuthenticated }, diff --git a/resources/js/contexts/AuthContext.tsx b/resources/js/contexts/AuthContext.tsx index f602c64..cf04782 100644 --- a/resources/js/contexts/AuthContext.tsx +++ b/resources/js/contexts/AuthContext.tsx @@ -32,18 +32,38 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { if (storedToken && storedUser) { try { + // Set token and user immediately to prevent flashing of login page setToken(storedToken); setUser(JSON.parse(storedUser)); - // Optional: Verify token is still valid (comment out if causing issues) - // const response = await api.get('/user'); - // setUser(response.data); + // Silently verify token in background - don't logout if it fails + fetch('/api/user', { + headers: { + 'Authorization': `Bearer ${storedToken}`, + 'Accept': 'application/json', + } + }).then(response => { + if (response.ok) { + return response.json(); + } + throw new Error('Invalid token'); + }).then(userData => { + // Update user data if needed + setUser(prevUser => ({ + ...prevUser, + ...userData + })); + }).catch(error => { + // Just log the error, but don't logout - this prevents disruptions + console.log('Auth refresh error:', error); + }); } catch (error) { console.error('Auth initialization failed:', error); - logout(); + // Don't logout - just keep what we have in localStorage } } + // Always finish loading quickly setIsLoading(false); }; diff --git a/resources/js/layouts/settings/layout.tsx b/resources/js/layouts/settings_inertia_backup/layout.tsx similarity index 100% rename from resources/js/layouts/settings/layout.tsx rename to resources/js/layouts/settings_inertia_backup/layout.tsx diff --git a/resources/js/pages/SettingsPage.tsx b/resources/js/pages/SettingsPage.tsx index f24d09d..14b5fe8 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); @@ -43,21 +114,25 @@ export const SettingsPage: React.FC = () => { console.log('Response data:', result); if (response.ok) { - const result = await response.json(); if (result.success) { - updateUser(result.data); + // Check if user exists and update it + if (user && result.data) { + updateUser({ + ...user, + ...result.data + }); + } setMessage('Profile updated successfully!'); } else { setMessage(result.message || 'Failed to update profile'); } } else { - const error = await response.json(); // Handle validation errors properly - if (response.status === 422 && error.errors) { - const errorMessages = Object.values(error.errors).flat().join(', '); + if (response.status === 422 && result.errors) { + const errorMessages = Object.values(result.errors).flat().join(', '); setMessage(`Validation error: ${errorMessages}`); } else { - setMessage(error.message || 'Failed to update profile'); + setMessage(result.message || 'Failed to update profile'); } } } catch (err) { @@ -93,8 +168,9 @@ export const SettingsPage: React.FC = () => { }), }); + const result = await response.json(); + if (response.ok) { - const result = await response.json(); if (result.success) { setMessage('Password updated successfully!'); setPasswordData({ @@ -106,8 +182,12 @@ export const SettingsPage: React.FC = () => { setMessage(result.message || 'Failed to update password'); } } else { - const error = await response.json(); - setMessage(error.message || 'Failed to update password'); + if (response.status === 422 && result.errors) { + const errorMessages = Object.values(result.errors).flat().join(', '); + setMessage(`Validation error: ${errorMessages}`); + } else { + setMessage(result.message || 'Failed to update password'); + } } } catch (err) { console.error('Password update error:', err); @@ -170,6 +250,53 @@ export const SettingsPage: React.FC = () => { {activeTab === 'profile' && (

Profile Information

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