definições

This commit is contained in:
2026-04-14 16:59:17 +01:00
parent ed7b76394c
commit 2d015444d3
2 changed files with 140 additions and 28 deletions

View File

@@ -15,7 +15,7 @@ import {
} from 'firebase/auth';
import {
collection, doc, onSnapshot, addDoc, updateDoc,
deleteDoc, writeBatch
deleteDoc, writeBatch, setDoc
} from 'firebase/firestore';
import { auth, db, appId } from './lib/firebase';
@@ -42,10 +42,15 @@ export default function App() {
// Estado para criação de Looks
const [selectedForLook, setSelectedForLook] = useState([]);
// Perfil do Utilizador
const [userProfile, setUserProfile] = useState({});
const [savingProfile, setSavingProfile] = useState(false);
// Estado para Definições
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
const [weatherAlerts, setWeatherAlerts] = useState(true);
const [language, setLanguage] = useState('PT');
const [showLangModal, setShowLangModal] = useState(false);
const [theme, setTheme] = useState(() => localStorage.getItem('app-theme') || 'theme-indigo');
const t = (key) => translations[language]?.[key] || translations['PT'][key] || key;
@@ -89,7 +94,14 @@ export default function App() {
setLooks(snap.docs.map(d => ({ id: d.id, ...d.data() })));
}, (err) => console.error(err));
return () => { unsubClothes(); unsubLooks(); };
// Profile
const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data');
const unsubProfile = onSnapshot(profileDoc, (snap) => {
if (snap.exists()) setUserProfile(snap.data());
else setUserProfile({});
}, (err) => console.error(err));
return () => { unsubClothes(); unsubLooks(); unsubProfile(); };
}, [user]);
// --- Lógicas de Negócio ---
@@ -231,6 +243,25 @@ export default function App() {
finally { setLoading(false); }
};
const saveProfile = async (e) => {
e.preventDefault();
setSavingProfile(true);
const fd = new FormData(e.target);
try {
const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data');
await setDoc(profileDoc, {
username: fd.get('username') || '',
fullName: fd.get('fullName') || '',
dob: fd.get('dob') || '',
bio: fd.get('bio') || ''
}, { merge: true });
} catch (err) {
console.error(err);
} finally {
setSavingProfile(false);
}
};
if (loading && !user) return <div className="h-screen flex items-center justify-center bg-primary-50 dark:bg-gray-950"><Loader2 className="animate-spin text-primary-600" size={40} /></div>;
if (view === 'auth') {
@@ -304,10 +335,10 @@ export default function App() {
<div className="mt-auto pt-10 border-t border-inherit">
<div className="flex items-center gap-4 mb-8 px-2">
<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'}`}>
{user?.email?.[0]?.toUpperCase() || 'U'}
{(userProfile?.fullName?.[0] || userProfile?.username?.[0] || user?.email?.[0] || 'U').toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-black truncate">{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>
</div>
</div>
@@ -632,17 +663,32 @@ export default function App() {
<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="w-24 h-24 rounded-[2.5rem] bg-primary-600 flex items-center justify-center text-white text-4xl font-black shadow-2xl">
{user?.email?.[0]?.toUpperCase() || 'U'}
{(userProfile?.fullName?.[0] || userProfile?.username?.[0] || user?.email?.[0] || 'U').toUpperCase()}
</div>
<div>
<h3 className="text-3xl font-black tracking-tighter">{t('yourAccount')}</h3>
<p className="opacity-60 font-bold text-sm">{user?.email || t('papMode')}</p>
<h3 className="text-3xl font-black tracking-tighter">{userProfile?.fullName || t('yourAccount')}</h3>
<p className="opacity-60 font-bold text-sm">@{userProfile?.username || user?.email?.split('@')[0] || t('papMode')}</p>
</div>
</div>
</Card>
<Card className="p-8" darkMode={darkMode}>
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><UserCircle className="text-primary-600" /> {t('profileInfo')}</h3>
<form onSubmit={saveProfile} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Input label={t('username')} name="username" defaultValue={userProfile?.username || ''} placeholder="Ex: amari" />
<Input label={t('fullName')} name="fullName" defaultValue={userProfile?.fullName || ''} placeholder="Ex: Amari Rodriguez" />
<Input label={`${t('dob')} ${t('optional')}`} name="dob" type="date" defaultValue={userProfile?.dob || ''} />
<Input label={`${t('bio')} ${t('optional')}`} name="bio" defaultValue={userProfile?.bio || ''} placeholder="..." />
</div>
<button disabled={savingProfile} type="submit" className="w-full py-4 bg-primary-600 text-white rounded-xl font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 disabled:opacity-50 hover:scale-[1.01] transition-all">
{savingProfile ? t('saving') : t('save')}
</button>
</form>
</Card>
{/* Preferências */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="flex flex-col gap-8">
<Card className="p-8" darkMode={darkMode}>
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Settings className="text-primary-600" /> {t('preferences')}</h3>
<div className="space-y-6">
@@ -697,24 +743,25 @@ export default function App() {
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${weatherAlerts ? 'left-7' : 'left-1'}`}></div>
</button>
</div>
</div>
</Card>
<Card className="p-8" darkMode={darkMode}>
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Globe className="text-primary-600" /> {t('systemAndData')}</h3>
<div className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('appLanguage')}</label>
<select value={language} onChange={(e) => setLanguage(e.target.value)} className={`w-full p-4 rounded-xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`}>
<option value="PT">{t('portuguese')}</option>
<option value="EN">{t('english')}</option>
<option value="ES">{t('spanish')}</option>
<option value="FR">{t('french')}</option>
<option value="DE">{t('german')}</option>
</select>
<div className="flex items-center justify-between pt-4 border-t border-gray-100 dark:border-gray-800">
<div>
<p className="font-bold text-inherit">{t('appLanguage')}</p>
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">
{language === 'PT' ? '🇵🇹 ' + t('portuguese') :
language === 'EN' ? '🇬🇧 ' + t('english') :
language === 'ES' ? '🇪🇸 ' + t('spanish') :
language === 'FR' ? '🇫🇷 ' + t('french') :
language === 'DE' ? '🇩🇪 ' + t('german') : language}
</p>
</div>
<button onClick={() => setShowLangModal(true)} className="px-5 py-2 font-black text-[10px] uppercase tracking-widest bg-primary-50 text-primary-600 rounded-xl hover:bg-primary-100 transition-colors dark:bg-primary-900/40 dark:text-primary-400">
{t('edit')}
</button>
</div>
</div>
</Card>
</div>
<div className="space-y-6">
@@ -753,6 +800,36 @@ export default function App() {
</div>
</main>
{/* Modal de Idioma */}
{showLangModal && (
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowLangModal(false)}>
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95" darkMode={darkMode} onClick={e => e.stopPropagation()}>
<h3 className="text-2xl font-black mb-8 text-center text-inherit">{t('appLanguage')}</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{[
{ id: 'PT', flag: '🇵🇹', label: t('portuguese') },
{ id: 'EN', flag: '🇬🇧', label: t('english') },
{ id: 'ES', flag: '🇪🇸', label: t('spanish') },
{ id: 'FR', flag: '🇫🇷', label: t('french') },
{ id: 'DE', flag: '🇩🇪', label: t('german') }
].map(lang => (
<button
key={lang.id}
onClick={() => { setLanguage(lang.id); setShowLangModal(false); }}
className={`p-6 rounded-2xl flex flex-col items-center justify-center gap-4 transition-all ${language === lang.id ? 'bg-primary-600 text-white shadow-xl shadow-primary-600/30 scale-105' : 'bg-gray-50 text-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'}`}
>
<span className="text-4xl">{lang.flag}</span>
<span className="font-black text-[10px] uppercase tracking-widest text-center">{lang.label}</span>
</button>
))}
</div>
<button onClick={() => setShowLangModal(false)} className="w-full mt-8 py-4 uppercase font-black text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">
{t('cancel')}
</button>
</Card>
</div>
)}
</div>
);
}