feat: implement optimistic profile updates and add navigation to profile view
This commit is contained in:
32
src/App.jsx
32
src/App.jsx
@@ -272,6 +272,7 @@ export default function App() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSavingProfile(true);
|
setSavingProfile(true);
|
||||||
const fd = new FormData(e.target);
|
const fd = new FormData(e.target);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data');
|
const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data');
|
||||||
const dobDay = fd.get('dobDay');
|
const dobDay = fd.get('dobDay');
|
||||||
@@ -282,16 +283,22 @@ export default function App() {
|
|||||||
dob = `${dobYear}-${dobMonth}-${dobDay}`;
|
dob = `${dobYear}-${dobMonth}-${dobDay}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await setDoc(profileDoc, {
|
// Perform optimistc setDoc without blocking the UI
|
||||||
|
setDoc(profileDoc, {
|
||||||
username: fd.get('username') || '',
|
username: fd.get('username') || '',
|
||||||
fullName: fd.get('fullName') || '',
|
fullName: fd.get('fullName') || '',
|
||||||
dob: dob,
|
dob: dob,
|
||||||
bio: fd.get('bio') || ''
|
bio: fd.get('bio') || ''
|
||||||
}, { merge: true });
|
}, { merge: true }).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
} finally {
|
||||||
|
// Re-enable the button shortly after for smooth optimistic UI
|
||||||
|
setTimeout(() => {
|
||||||
setSavingProfile(false);
|
setSavingProfile(false);
|
||||||
|
}, 600);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -339,12 +346,12 @@ export default function App() {
|
|||||||
${sidebarOpen ? 'w-80 translate-x-0' : 'w-0 -translate-x-full md:w-0 md:opacity-0'}
|
${sidebarOpen ? 'w-80 translate-x-0' : 'w-0 -translate-x-full md:w-0 md:opacity-0'}
|
||||||
`}>
|
`}>
|
||||||
<div className="p-10 h-full flex flex-col backdrop-blur-xl">
|
<div className="p-10 h-full flex flex-col backdrop-blur-xl">
|
||||||
<div className="flex items-center gap-4 mb-16">
|
<button onClick={() => setView('closet')} className="flex items-center gap-4 mb-16 hover:scale-[1.02] transition-transform text-left cursor-pointer w-full">
|
||||||
<div className="p-3 bg-primary-600 rounded-2xl shadow-xl shadow-primary-600/30">
|
<div className="p-3 bg-primary-600 rounded-2xl shadow-xl shadow-primary-600/30">
|
||||||
<Shirt className="text-white" size={24} />
|
<Shirt className="text-white" size={24} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-3xl font-black tracking-tighter italic">MyCloset</span>
|
<span className="text-3xl font-black tracking-tighter italic">MyCloset</span>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<nav className="flex-1 space-y-3">
|
<nav className="flex-1 space-y-3">
|
||||||
{[
|
{[
|
||||||
@@ -366,15 +373,15 @@ export default function App() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="mt-auto pt-10 border-t border-inherit">
|
<div className="mt-auto pt-10 border-t border-inherit">
|
||||||
<div className="flex items-center gap-4 mb-8 px-2">
|
<button onClick={() => setView('profile')} className="w-full flex items-center gap-4 mb-8 px-2 text-left hover:bg-gray-100 dark:hover:bg-gray-800 py-3 rounded-2xl transition-all cursor-pointer">
|
||||||
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center font-black text-white shadow-xl ${darkMode ? 'bg-primary-500' : 'bg-primary-600'}`}>
|
<div className={`w-12 h-12 rounded-2xl shrink-0 flex items-center justify-center font-black text-white shadow-xl ${darkMode ? 'bg-primary-500' : 'bg-primary-600'}`}>
|
||||||
{(userProfile?.fullName?.[0] || userProfile?.username?.[0] || user?.email?.[0] || 'U').toUpperCase()}
|
{(userProfile?.fullName?.[0] || userProfile?.username?.[0] || user?.email?.[0] || 'U').toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-black truncate">{userProfile?.username || userProfile?.fullName || user?.email?.split('@')[0] || t('userTitle')}</p>
|
<p className="text-sm font-black truncate">{userProfile?.username || userProfile?.fullName || user?.email?.split('@')[0] || t('userTitle')}</p>
|
||||||
<Badge variant="success">{t('online')}</Badge>
|
<Badge variant="success">{t('online')}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
<button onClick={() => signOut(auth)} className="w-full py-4 text-red-500 font-black uppercase tracking-widest text-[10px] hover:bg-red-500/10 rounded-2xl transition-all flex items-center justify-center gap-3">
|
<button onClick={() => signOut(auth)} className="w-full py-4 text-red-500 font-black uppercase tracking-widest text-[10px] hover:bg-red-500/10 rounded-2xl transition-all flex items-center justify-center gap-3">
|
||||||
<LogOut size={16} /> {t('logout')}
|
<LogOut size={16} /> {t('logout')}
|
||||||
</button>
|
</button>
|
||||||
@@ -397,6 +404,7 @@ export default function App() {
|
|||||||
{view === 'laundry' && t('laundry')}
|
{view === 'laundry' && t('laundry')}
|
||||||
{view === 'outfits' && t('outfitsAndStyle')}
|
{view === 'outfits' && t('outfitsAndStyle')}
|
||||||
{view === 'settings' && t('settings')}
|
{view === 'settings' && t('settings')}
|
||||||
|
{view === 'profile' && t('profileInfo')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -690,8 +698,8 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* DEFINIÇÕES */}
|
{/* PERFIL */}
|
||||||
{view === 'settings' && (
|
{view === 'profile' && (
|
||||||
<div className="max-w-4xl mx-auto space-y-12 animate-in fade-in duration-700 pb-20">
|
<div className="max-w-4xl mx-auto space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
<Card className="p-10 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
<Card className="p-10 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
||||||
<div className="flex items-center gap-8 relative z-10 text-inherit">
|
<div className="flex items-center gap-8 relative z-10 text-inherit">
|
||||||
@@ -735,6 +743,12 @@ export default function App() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* DEFINIÇÕES */}
|
||||||
|
{view === 'settings' && (
|
||||||
|
<div className="max-w-4xl mx-auto space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
|
|
||||||
{/* Preferências */}
|
{/* Preferências */}
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user