definições
This commit is contained in:
123
src/App.jsx
123
src/App.jsx
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user