a
This commit is contained in:
3324
dist/assets/index-0fNqCu5T.js
vendored
Normal file
3324
dist/assets/index-0fNqCu5T.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-CvAWiRXE.css
vendored
1
dist/assets/index-CvAWiRXE.css
vendored
File diff suppressed because one or more lines are too long
3370
dist/assets/index-D-Q7LD7U.js
vendored
3370
dist/assets/index-D-Q7LD7U.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-D1z9PPLp.css
vendored
Normal file
1
dist/assets/index-D1z9PPLp.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>MyCloset</title>
|
<title>MyCloset</title>
|
||||||
<script type="module" crossorigin src="/assets/index-D-Q7LD7U.js"></script>
|
<script type="module" crossorigin src="/assets/index-0fNqCu5T.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CvAWiRXE.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D1z9PPLp.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
244
src/App.jsx
244
src/App.jsx
@@ -7,7 +7,7 @@ import {
|
|||||||
PanelLeftClose, PanelLeftOpen, Sparkles, CloudSun,
|
PanelLeftClose, PanelLeftOpen, Sparkles, CloudSun,
|
||||||
ArrowRight, Droplets, CheckCircle2, PieChart, History,
|
ArrowRight, Droplets, CheckCircle2, PieChart, History,
|
||||||
X, Download, Bell, Globe, Filter, ShoppingBag, Share2,
|
X, Download, Bell, Globe, Filter, ShoppingBag, Share2,
|
||||||
FolderOpen, Tag, Link, Calendar, ChevronLeft, ChevronRight
|
FolderOpen, Tag, Link, Calendar, ChevronLeft, ChevronRight, Users
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from 'firebase/auth';
|
} from 'firebase/auth';
|
||||||
import {
|
import {
|
||||||
collection, doc, onSnapshot, addDoc, updateDoc,
|
collection, doc, onSnapshot, addDoc, updateDoc,
|
||||||
deleteDoc, writeBatch, setDoc, getDoc, query, where
|
deleteDoc, writeBatch, setDoc, getDoc, query, where, getDocs, collectionGroup
|
||||||
} from 'firebase/firestore';
|
} from 'firebase/firestore';
|
||||||
|
|
||||||
import { auth, db, appId } from './lib/firebase';
|
import { auth, db, appId } from './lib/firebase';
|
||||||
@@ -64,6 +64,15 @@ export default function App() {
|
|||||||
const [cardSize, setCardSize] = useState('large');
|
const [cardSize, setCardSize] = useState('large');
|
||||||
const [defaultPage, setDefaultPage] = useState('dashboard');
|
const [defaultPage, setDefaultPage] = useState('dashboard');
|
||||||
const [weatherData, setWeatherData] = useState(null);
|
const [weatherData, setWeatherData] = useState(null);
|
||||||
|
const [isPrivate, setIsPrivate] = useState(false);
|
||||||
|
const [userStatus, setUserStatus] = useState('online');
|
||||||
|
|
||||||
|
// Estado da Comunidade
|
||||||
|
const [communitySearchTerm, setCommunitySearchTerm] = useState('');
|
||||||
|
const [communityUsers, setCommunityUsers] = useState([]);
|
||||||
|
const [selectedCommunityUser, setSelectedCommunityUser] = useState(null);
|
||||||
|
const [selectedUserClothes, setSelectedUserClothes] = useState([]);
|
||||||
|
const [selectedUserLooks, setSelectedUserLooks] = useState([]);
|
||||||
|
|
||||||
// Estado para Partilha de Looks
|
// Estado para Partilha de Looks
|
||||||
const sharedLookRef = useRef('');
|
const sharedLookRef = useRef('');
|
||||||
@@ -127,6 +136,11 @@ export default function App() {
|
|||||||
await setDoc(profileDoc, {
|
await setDoc(profileDoc, {
|
||||||
settings: { [key]: value }
|
settings: { [key]: value }
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
|
|
||||||
|
if (key === 'isPrivate') {
|
||||||
|
const publicProfileDoc = doc(db, 'artifacts', appId, 'publicProfiles', user.uid);
|
||||||
|
await setDoc(publicProfileDoc, { isPrivate: value, uid: user.uid }, { merge: true });
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error saving setting:', err);
|
console.error('Error saving setting:', err);
|
||||||
}
|
}
|
||||||
@@ -168,6 +182,21 @@ export default function App() {
|
|||||||
saveUserSetting('defaultPage', newVal);
|
saveUserSetting('defaultPage', newVal);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePrivacyToggle = (newVal) => {
|
||||||
|
setIsPrivate(newVal);
|
||||||
|
saveUserSetting('isPrivate', newVal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleStatus = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
const statuses = ['online', 'away', 'offline'];
|
||||||
|
const currentIndex = statuses.indexOf(userStatus);
|
||||||
|
const nextStatus = statuses[(currentIndex + 1) % statuses.length];
|
||||||
|
setUserStatus(nextStatus);
|
||||||
|
saveUserSetting('status', nextStatus);
|
||||||
|
};
|
||||||
|
|
||||||
// Buscar o look partilhado pelo link
|
// Buscar o look partilhado pelo link
|
||||||
const fetchSharedLook = async (lookId) => {
|
const fetchSharedLook = async (lookId) => {
|
||||||
if (!lookId) return;
|
if (!lookId) return;
|
||||||
@@ -328,6 +357,8 @@ export default function App() {
|
|||||||
if (data.settings.defaultPage !== undefined) {
|
if (data.settings.defaultPage !== undefined) {
|
||||||
setDefaultPage(data.settings.defaultPage === 'planning' ? 'planner' : data.settings.defaultPage);
|
setDefaultPage(data.settings.defaultPage === 'planning' ? 'planner' : data.settings.defaultPage);
|
||||||
}
|
}
|
||||||
|
if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);
|
||||||
|
if (data.settings.status !== undefined) setUserStatus(data.settings.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else setUserProfile({});
|
else setUserProfile({});
|
||||||
@@ -393,6 +424,68 @@ export default function App() {
|
|||||||
fetchWeather();
|
fetchWeather();
|
||||||
}, [userProfile?.location, user]);
|
}, [userProfile?.location, user]);
|
||||||
|
|
||||||
|
// Sync do perfil público para a Comunidade
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && userProfile) {
|
||||||
|
const publicProfileDoc = doc(db, 'artifacts', appId, 'publicProfiles', user.uid);
|
||||||
|
setDoc(publicProfileDoc, {
|
||||||
|
uid: user.uid,
|
||||||
|
username: userProfile.username || '',
|
||||||
|
fullName: userProfile.fullName || '',
|
||||||
|
avatar: userProfile.avatar || null,
|
||||||
|
isPrivate: userProfile.settings?.isPrivate || false
|
||||||
|
}, { merge: true }).catch(console.error);
|
||||||
|
}
|
||||||
|
}, [user, userProfile?.username, userProfile?.fullName, userProfile?.avatar, userProfile?.settings?.isPrivate]);
|
||||||
|
|
||||||
|
// Fetch utilizadores da comunidade
|
||||||
|
useEffect(() => {
|
||||||
|
if (view !== 'community') return;
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
try {
|
||||||
|
const profilesRef = collection(db, 'artifacts', appId, 'publicProfiles');
|
||||||
|
const snap = await getDocs(profilesRef);
|
||||||
|
const users = snap.docs.map(d => d.data()).filter(u => u.uid !== user?.uid);
|
||||||
|
|
||||||
|
if (communitySearchTerm.trim()) {
|
||||||
|
let term = communitySearchTerm.toLowerCase();
|
||||||
|
if (term.startsWith('@')) term = term.substring(1);
|
||||||
|
setCommunityUsers(users.filter(u =>
|
||||||
|
u.username && u.username.toLowerCase().includes(term)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
setCommunityUsers(users);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao buscar comunidade", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchUsers();
|
||||||
|
}, [view, communitySearchTerm, user?.uid]);
|
||||||
|
|
||||||
|
const viewCommunityUser = async (targetUser) => {
|
||||||
|
setSelectedCommunityUser(targetUser);
|
||||||
|
if (targetUser.isPrivate) {
|
||||||
|
setSelectedUserClothes([]);
|
||||||
|
setSelectedUserLooks([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Roupas
|
||||||
|
const clothesCol = collection(db, 'artifacts', appId, 'users', targetUser.uid, 'clothes');
|
||||||
|
const snapClothes = await getDocs(clothesCol);
|
||||||
|
setSelectedUserClothes(snapClothes.docs.map(d => ({id: d.id, ...d.data()})).filter(c => c.status !== 'trash'));
|
||||||
|
|
||||||
|
// Looks
|
||||||
|
const looksCol = collection(db, 'artifacts', appId, 'users', targetUser.uid, 'looks');
|
||||||
|
const snapLooks = await getDocs(looksCol);
|
||||||
|
setSelectedUserLooks(snapLooks.docs.map(d => ({id: d.id, ...d.data()})));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar perfil do utilizador", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- Lógicas de Negócio ---
|
// --- Lógicas de Negócio ---
|
||||||
|
|
||||||
const activeClothes = useMemo(() => clothes.filter(c => c.status === 'active'), [clothes]);
|
const activeClothes = useMemo(() => clothes.filter(c => c.status === 'active'), [clothes]);
|
||||||
@@ -961,6 +1054,23 @@ export default function App() {
|
|||||||
const fd = new FormData(e.target);
|
const fd = new FormData(e.target);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let usernameInput = (fd.get('username') || '').trim();
|
||||||
|
if (usernameInput.startsWith('@')) usernameInput = usernameInput.substring(1);
|
||||||
|
|
||||||
|
if (usernameInput) {
|
||||||
|
// Verificar se o nome de utilizador já existe
|
||||||
|
const publicProfilesRef = collection(db, 'artifacts', appId, 'publicProfiles');
|
||||||
|
const q = query(publicProfilesRef, where('username', '==', usernameInput));
|
||||||
|
const snap = await getDocs(q);
|
||||||
|
|
||||||
|
const isTaken = snap.docs.some(doc => doc.data().uid !== user.uid);
|
||||||
|
if (isTaken) {
|
||||||
|
alert(t('usernameTaken') || 'Este nome de utilizador já está em uso.');
|
||||||
|
setSavingProfile(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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');
|
||||||
const dobMonth = fd.get('dobMonth');
|
const dobMonth = fd.get('dobMonth');
|
||||||
@@ -972,7 +1082,7 @@ export default function App() {
|
|||||||
|
|
||||||
// Perform optimistc setDoc without blocking the UI
|
// Perform optimistc setDoc without blocking the UI
|
||||||
setDoc(profileDoc, {
|
setDoc(profileDoc, {
|
||||||
username: fd.get('username') || '',
|
username: usernameInput,
|
||||||
fullName: fd.get('fullName') || '',
|
fullName: fd.get('fullName') || '',
|
||||||
dob: dob,
|
dob: dob,
|
||||||
bio: fd.get('bio') || '',
|
bio: fd.get('bio') || '',
|
||||||
@@ -1098,6 +1208,7 @@ export default function App() {
|
|||||||
{ id: 'laundry', label: t('laundry'), icon: Droplets },
|
{ id: 'laundry', label: t('laundry'), icon: Droplets },
|
||||||
{ id: 'outfits', label: t('outfits'), icon: Sparkles },
|
{ id: 'outfits', label: t('outfits'), icon: Sparkles },
|
||||||
{ id: 'planner', label: t('planning'), icon: Calendar },
|
{ id: 'planner', label: t('planning'), icon: Calendar },
|
||||||
|
{ id: 'community', label: t('community'), icon: Users },
|
||||||
{ id: 'settings', label: t('settings'), icon: Settings },
|
{ id: 'settings', label: t('settings'), icon: Settings },
|
||||||
].map(item => (
|
].map(item => (
|
||||||
<button
|
<button
|
||||||
@@ -1112,7 +1223,7 @@ 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">
|
||||||
<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 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 shrink-0 flex items-center justify-center font-black text-white shadow-xl overflow-hidden ${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 overflow-hidden ${darkMode ? 'bg-primary-500' : 'bg-primary-600'}`}>
|
||||||
{userProfile?.avatar ? (
|
{userProfile?.avatar ? (
|
||||||
<img src={userProfile.avatar} className="w-full h-full object-cover" alt="Avatar" />
|
<img src={userProfile.avatar} className="w-full h-full object-cover" alt="Avatar" />
|
||||||
@@ -1120,11 +1231,15 @@ export default function App() {
|
|||||||
(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 text-left">
|
||||||
<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 text-inherit">@{userProfile?.username || user?.email?.split('@')[0] || t('userTitle')}</p>
|
||||||
<Badge variant="success">{t('online')}</Badge>
|
<div onClick={toggleStatus} className="inline-block mt-1 cursor-pointer hover:opacity-80 transition-opacity" title="Mudar estado">
|
||||||
|
<Badge variant={userStatus === 'online' ? 'success' : (userStatus === 'away' ? 'warning' : 'default')}>
|
||||||
|
{t(userStatus)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
// Limpar dados locais antes de fazer logout
|
// Limpar dados locais antes de fazer logout
|
||||||
if (user?.uid) localStorage.removeItem(`app-theme-${user.uid}`);
|
if (user?.uid) localStorage.removeItem(`app-theme-${user.uid}`);
|
||||||
@@ -1152,6 +1267,7 @@ export default function App() {
|
|||||||
{view === 'laundry' && t('laundry')}
|
{view === 'laundry' && t('laundry')}
|
||||||
{view === 'outfits' && t('outfitsAndStyle')}
|
{view === 'outfits' && t('outfitsAndStyle')}
|
||||||
{view === 'planner' && t('planning')}
|
{view === 'planner' && t('planning')}
|
||||||
|
{view === 'community' && t('community')}
|
||||||
{view === 'settings' && t('settings')}
|
{view === 'settings' && t('settings')}
|
||||||
{view === 'profile' && t('profileInfo')}
|
{view === 'profile' && t('profileInfo')}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -2011,7 +2127,13 @@ export default function App() {
|
|||||||
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><UserCircle className="text-primary-600" /> {t('profileInfo')}</h3>
|
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><UserCircle className="text-primary-600" /> {t('profileInfo')}</h3>
|
||||||
<form key={`${userProfile?.username}-${userProfile?.fullName}-${userProfile?.dob}-${userProfile?.bio}-${userProfile?.location}`} onSubmit={saveProfile} className="space-y-6">
|
<form key={`${userProfile?.username}-${userProfile?.fullName}-${userProfile?.dob}-${userProfile?.bio}-${userProfile?.location}`} onSubmit={saveProfile} className="space-y-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-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" />
|
<div className="space-y-2 relative">
|
||||||
|
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('username')}</label>
|
||||||
|
<div className="relative">
|
||||||
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 opacity-40 font-black">@</span>
|
||||||
|
<input name="username" defaultValue={userProfile?.username || ''} placeholder="amari" className={`w-full p-4 pl-10 rounded-xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Input label={t('fullName')} name="fullName" defaultValue={userProfile?.fullName || ''} placeholder="Ex: Amari Rodriguez" />
|
<Input label={t('fullName')} name="fullName" defaultValue={userProfile?.fullName || ''} placeholder="Ex: Amari Rodriguez" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('dob')} {t('optional')}</label>
|
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('dob')} {t('optional')}</label>
|
||||||
@@ -2041,6 +2163,101 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* COMUNIDADE */}
|
||||||
|
{view === 'community' && (
|
||||||
|
<div className="max-w-7xl mx-auto space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
|
{!selectedCommunityUser ? (
|
||||||
|
<>
|
||||||
|
<div className="relative mb-8">
|
||||||
|
<Search className="absolute left-6 top-1/2 -translate-y-1/2 opacity-40 text-inherit" size={24} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t('searchUsers')}
|
||||||
|
value={communitySearchTerm}
|
||||||
|
onChange={(e) => setCommunitySearchTerm(e.target.value)}
|
||||||
|
className={`w-full p-6 pl-16 rounded-3xl font-black text-lg outline-none focus:ring-4 focus:ring-primary-500/20 transition-all shadow-xl shadow-black/5 text-inherit ${darkMode ? 'bg-gray-800' : 'bg-white'}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{communityUsers.length === 0 ? (
|
||||||
|
<div className="col-span-full text-center py-12 opacity-50 text-inherit font-black text-xl">
|
||||||
|
{t('noUsersFound')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
communityUsers.map(u => (
|
||||||
|
<Card key={u.uid} className="p-6 cursor-pointer hover:scale-105 transition-transform" darkMode={darkMode} onClick={() => viewCommunityUser(u)}>
|
||||||
|
<div className="flex items-center gap-4 text-inherit">
|
||||||
|
<div className="w-16 h-16 rounded-2xl bg-primary-600 text-white flex items-center justify-center font-black text-2xl overflow-hidden">
|
||||||
|
{u.avatar ? <img src={u.avatar} className="w-full h-full object-cover" alt="Avatar"/> : <span>{(u.fullName?.[0] || u.username?.[0] || 'U').toUpperCase()}</span>}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-black text-lg">{u.fullName || t('userTitle')}</h3>
|
||||||
|
<p className="text-sm opacity-60 font-bold">@{u.username || 'user'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<button onClick={() => setSelectedCommunityUser(null)} className="flex items-center gap-2 opacity-60 hover:opacity-100 transition-opacity font-black text-inherit uppercase text-xs tracking-widest">
|
||||||
|
<ChevronLeft size={16} /> Voltar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Card className="p-8 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 text-white flex items-center justify-center font-black text-4xl overflow-hidden">
|
||||||
|
{selectedCommunityUser.avatar ? <img src={selectedCommunityUser.avatar} className="w-full h-full object-cover" alt="Avatar"/> : <span>{(selectedCommunityUser.fullName?.[0] || selectedCommunityUser.username?.[0] || 'U').toUpperCase()}</span>}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-3xl font-black tracking-tighter">{selectedCommunityUser.fullName || t('userTitle')}</h3>
|
||||||
|
<p className="opacity-60 font-bold text-sm">@{selectedCommunityUser.username || 'user'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{selectedCommunityUser.isPrivate ? (
|
||||||
|
<div className="text-center py-20 opacity-50 font-black text-2xl text-inherit">
|
||||||
|
<ShieldAlert className="w-16 h-16 mx-auto mb-4 opacity-50" />
|
||||||
|
{t('isPrivateUser')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-12 text-inherit">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-black mb-6 uppercase tracking-widest text-[11px] opacity-50">{t('userOutfits')} ({selectedUserLooks.length})</h3>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6">
|
||||||
|
{selectedUserLooks.map(look => (
|
||||||
|
<div key={look.id} className="group relative aspect-[3/4] rounded-[2rem] overflow-hidden bg-gray-100 dark:bg-gray-800 cursor-pointer shadow-lg">
|
||||||
|
{look.items && look.items[0] && selectedUserClothes.find(c => c.id === look.items[0]) && (
|
||||||
|
<img src={selectedUserClothes.find(c => c.id === look.items[0]).imageUrl} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" alt="Look" />
|
||||||
|
)}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent flex items-end p-6">
|
||||||
|
<span className="text-white font-black text-sm">{look.name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-black mb-6 uppercase tracking-widest text-[11px] opacity-50">{t('userCloset')} ({selectedUserClothes.length})</h3>
|
||||||
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-4">
|
||||||
|
{selectedUserClothes.map(item => (
|
||||||
|
<div key={item.id} className="aspect-square rounded-2xl overflow-hidden bg-gray-100 dark:bg-gray-800 shadow-md">
|
||||||
|
<img src={item.imageUrl} className="w-full h-full object-cover" alt="Item" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* DEFINIÇÕES */}
|
{/* DEFINIÇÕES */}
|
||||||
{view === 'settings' && (
|
{view === 'settings' && (
|
||||||
<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">
|
||||||
@@ -2101,6 +2318,15 @@ 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>
|
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${weatherAlerts ? 'left-7' : 'left-1'}`}></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-inherit flex items-center gap-2">{t('privateProfile')}</p>
|
||||||
|
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">{t('privateProfileDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => handlePrivacyToggle(!isPrivate)} className={`w-14 h-8 rounded-full transition-colors relative ${isPrivate ? 'bg-primary-600' : 'bg-gray-200'}`}>
|
||||||
|
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${isPrivate ? 'left-7' : 'left-1'}`}></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-inherit">{t('cardSize') || 'Tamanho do Card'}</p>
|
<p className="font-bold text-inherit">{t('cardSize') || 'Tamanho do Card'}</p>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export const translations = {
|
|||||||
outfits: "Outfits",
|
outfits: "Outfits",
|
||||||
settings: "Definições",
|
settings: "Definições",
|
||||||
online: "Online",
|
online: "Online",
|
||||||
|
away: "Ausente",
|
||||||
|
offline: "Offline",
|
||||||
dailyOutfit: "Outfit Diário",
|
dailyOutfit: "Outfit Diário",
|
||||||
noOutfitPlanned: "Nenhum Outfit Planeado",
|
noOutfitPlanned: "Nenhum Outfit Planeado",
|
||||||
goToPlanning: "Vá ao planeamento para adicionar",
|
goToPlanning: "Vá ao planeamento para adicionar",
|
||||||
@@ -203,6 +205,16 @@ export const translations = {
|
|||||||
large: "Grande",
|
large: "Grande",
|
||||||
defaultPage: "Página Inicial",
|
defaultPage: "Página Inicial",
|
||||||
defaultPageDesc: "Página que aparece após o login",
|
defaultPageDesc: "Página que aparece após o login",
|
||||||
|
community: "Comunidade",
|
||||||
|
searchUsers: "Procurar por @username...",
|
||||||
|
privateProfile: "Perfil Privado",
|
||||||
|
privateProfileDesc: "Ocultar armário de outros utilizadores",
|
||||||
|
viewProfile: "Ver Perfil",
|
||||||
|
noUsersFound: "Nenhum utilizador encontrado",
|
||||||
|
isPrivateUser: "Este perfil é privado.",
|
||||||
|
userOutfits: "Outfits do Utilizador",
|
||||||
|
userCloset: "Armário",
|
||||||
|
usernameTaken: "Este nome de utilizador já está em uso.",
|
||||||
},
|
},
|
||||||
EN: {
|
EN: {
|
||||||
loginModeIntro: "The Future of Your Style",
|
loginModeIntro: "The Future of Your Style",
|
||||||
@@ -223,6 +235,8 @@ export const translations = {
|
|||||||
outfits: "Outfits",
|
outfits: "Outfits",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
online: "Online",
|
online: "Online",
|
||||||
|
away: "Away",
|
||||||
|
offline: "Offline",
|
||||||
dailyOutfit: "Daily Outfit",
|
dailyOutfit: "Daily Outfit",
|
||||||
noOutfitPlanned: "No Outfit Planned",
|
noOutfitPlanned: "No Outfit Planned",
|
||||||
goToPlanning: "Go to planning to add one",
|
goToPlanning: "Go to planning to add one",
|
||||||
@@ -408,6 +422,16 @@ export const translations = {
|
|||||||
large: "Large",
|
large: "Large",
|
||||||
defaultPage: "Home Page",
|
defaultPage: "Home Page",
|
||||||
defaultPageDesc: "Page that appears after login",
|
defaultPageDesc: "Page that appears after login",
|
||||||
|
community: "Community",
|
||||||
|
searchUsers: "Search by @username...",
|
||||||
|
privateProfile: "Private Profile",
|
||||||
|
privateProfileDesc: "Hide closet from other users",
|
||||||
|
viewProfile: "View Profile",
|
||||||
|
noUsersFound: "No users found",
|
||||||
|
isPrivateUser: "This profile is private.",
|
||||||
|
userOutfits: "User's Outfits",
|
||||||
|
userCloset: "Closet",
|
||||||
|
usernameTaken: "This username is already taken.",
|
||||||
},
|
},
|
||||||
ES: {
|
ES: {
|
||||||
loginModeIntro: "El Futuro de Tu Estilo",
|
loginModeIntro: "El Futuro de Tu Estilo",
|
||||||
@@ -428,6 +452,8 @@ export const translations = {
|
|||||||
outfits: "Outfits",
|
outfits: "Outfits",
|
||||||
settings: "Ajustes",
|
settings: "Ajustes",
|
||||||
online: "En línea",
|
online: "En línea",
|
||||||
|
away: "Ausente",
|
||||||
|
offline: "Desconectado",
|
||||||
dailyOutfit: "Outfit Diario",
|
dailyOutfit: "Outfit Diario",
|
||||||
noOutfitPlanned: "Sin Outfit Planeado",
|
noOutfitPlanned: "Sin Outfit Planeado",
|
||||||
goToPlanning: "Ve a planificación para añadir",
|
goToPlanning: "Ve a planificación para añadir",
|
||||||
@@ -613,6 +639,16 @@ export const translations = {
|
|||||||
large: "Grande",
|
large: "Grande",
|
||||||
defaultPage: "Página de Inicio",
|
defaultPage: "Página de Inicio",
|
||||||
defaultPageDesc: "Página que aparece después de iniciar sesión",
|
defaultPageDesc: "Página que aparece después de iniciar sesión",
|
||||||
|
community: "Comunidad",
|
||||||
|
searchUsers: "Buscar por @username...",
|
||||||
|
privateProfile: "Perfil Privado",
|
||||||
|
privateProfileDesc: "Ocultar armario de otros usuarios",
|
||||||
|
viewProfile: "Ver Perfil",
|
||||||
|
noUsersFound: "Ningún usuario encontrado",
|
||||||
|
isPrivateUser: "Este perfil es privado.",
|
||||||
|
userOutfits: "Outfits del Usuario",
|
||||||
|
userCloset: "Armario",
|
||||||
|
usernameTaken: "Este nombre de usuario ya está en uso.",
|
||||||
},
|
},
|
||||||
FR: {
|
FR: {
|
||||||
loginModeIntro: "Le Futur de Ton Style",
|
loginModeIntro: "Le Futur de Ton Style",
|
||||||
@@ -633,6 +669,8 @@ export const translations = {
|
|||||||
outfits: "Tenues",
|
outfits: "Tenues",
|
||||||
settings: "Paramètres",
|
settings: "Paramètres",
|
||||||
online: "En ligne",
|
online: "En ligne",
|
||||||
|
away: "Absent",
|
||||||
|
offline: "Hors ligne",
|
||||||
dailyOutfit: "Tenue du Jour",
|
dailyOutfit: "Tenue du Jour",
|
||||||
noOutfitPlanned: "Aucune Tenue Prévue",
|
noOutfitPlanned: "Aucune Tenue Prévue",
|
||||||
goToPlanning: "Allez dans planification pour ajouter",
|
goToPlanning: "Allez dans planification pour ajouter",
|
||||||
@@ -818,6 +856,16 @@ export const translations = {
|
|||||||
large: "Grand",
|
large: "Grand",
|
||||||
defaultPage: "Page d'Accueil",
|
defaultPage: "Page d'Accueil",
|
||||||
defaultPageDesc: "Page qui apparaît après la connexion",
|
defaultPageDesc: "Page qui apparaît après la connexion",
|
||||||
|
community: "Communauté",
|
||||||
|
searchUsers: "Rechercher par @username...",
|
||||||
|
privateProfile: "Profil Privé",
|
||||||
|
privateProfileDesc: "Cacher le placard aux autres utilisateurs",
|
||||||
|
viewProfile: "Voir le Profil",
|
||||||
|
noUsersFound: "Aucun utilisateur trouvé",
|
||||||
|
isPrivateUser: "Ce profil est privé.",
|
||||||
|
userOutfits: "Outfits de l'Utilisateur",
|
||||||
|
userCloset: "Placard",
|
||||||
|
usernameTaken: "Ce nom d'utilisateur est déjà utilisé.",
|
||||||
},
|
},
|
||||||
DE: {
|
DE: {
|
||||||
loginModeIntro: "Die Zukunft deines Stils",
|
loginModeIntro: "Die Zukunft deines Stils",
|
||||||
@@ -838,6 +886,8 @@ export const translations = {
|
|||||||
outfits: "Outfits",
|
outfits: "Outfits",
|
||||||
settings: "Einstellungen",
|
settings: "Einstellungen",
|
||||||
online: "Online",
|
online: "Online",
|
||||||
|
away: "Abwesend",
|
||||||
|
offline: "Offline",
|
||||||
dailyOutfit: "Tägliches Outfit",
|
dailyOutfit: "Tägliches Outfit",
|
||||||
noOutfitPlanned: "Kein Outfit Geplant",
|
noOutfitPlanned: "Kein Outfit Geplant",
|
||||||
goToPlanning: "Gehen Sie zur Planung, um eins hinzuzufügen",
|
goToPlanning: "Gehen Sie zur Planung, um eins hinzuzufügen",
|
||||||
@@ -1023,5 +1073,15 @@ export const translations = {
|
|||||||
large: "Groß",
|
large: "Groß",
|
||||||
defaultPage: "Startseite",
|
defaultPage: "Startseite",
|
||||||
defaultPageDesc: "Seite, die nach der Anmeldung angezeigt wird",
|
defaultPageDesc: "Seite, die nach der Anmeldung angezeigt wird",
|
||||||
|
community: "Gemeinschaft",
|
||||||
|
searchUsers: "Nach @username suchen...",
|
||||||
|
privateProfile: "Privates Profil",
|
||||||
|
privateProfileDesc: "Kleiderschrank vor anderen Benutzern verbergen",
|
||||||
|
viewProfile: "Profil anzeigen",
|
||||||
|
noUsersFound: "Keine Benutzer gefunden",
|
||||||
|
isPrivateUser: "Dieses Profil ist privat.",
|
||||||
|
userOutfits: "Outfits des Benutzers",
|
||||||
|
userCloset: "Kleiderschrank",
|
||||||
|
usernameTaken: "Dieser Benutzername ist bereits vergeben.",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user