This commit is contained in:
2026-05-13 10:30:08 +01:00
parent 6089ba11d5
commit 2608eff482

View File

@@ -76,6 +76,8 @@ export default function App() {
const [showInspectModal, setShowInspectModal] = useState(false);
const [selectedUserClothes, setSelectedUserClothes] = useState([]);
const [selectedUserLooks, setSelectedUserLooks] = useState([]);
const [inspectingCommunityLook, setInspectingCommunityLook] = useState(null);
const [inspectingCommunityItem, setInspectingCommunityItem] = useState(null);
// Estado para Partilha de Looks
const sharedLookRef = useRef('');
@@ -852,6 +854,84 @@ export default function App() {
}
};
const copyCommunityItem = async (item) => {
if (!user) return;
setToastMessage(t('copying') || 'A copiar...');
try {
const clothesCol = collection(db, 'artifacts', appId, 'users', user.uid, 'clothes');
await addDoc(clothesCol, {
name: item.name,
category: item.category,
color: item.color,
imageUrl: item.imageUrl,
status: 'active',
favorite: false,
createdAt: new Date().getTime(),
updatedAt: new Date().getTime(),
});
setToastMessage(t('itemCopied') || 'Peça copiada para o seu armário!');
setTimeout(() => setToastMessage(null), 3000);
} catch (err) {
console.error('Erro ao copiar peça:', err);
setToastMessage('Erro ao copiar peça.');
setTimeout(() => setToastMessage(null), 3000);
}
};
const copyCommunityLook = async (look) => {
if (!user || !selectedCommunityUser) return;
setToastMessage(t('copying') || 'A copiar...');
try {
const newItemIds = [];
for (const itemId of look.items) {
const item = selectedUserClothes.find(c => c.id === itemId);
if (item) {
const clothesCol = collection(db, 'artifacts', appId, 'users', user.uid, 'clothes');
const newItemRef = await addDoc(clothesCol, {
name: item.name,
category: item.category,
color: item.color,
imageUrl: item.imageUrl,
status: 'active',
favorite: false,
createdAt: new Date().getTime(),
updatedAt: new Date().getTime(),
});
newItemIds.push(newItemRef.id);
}
}
const looksCol = collection(db, 'artifacts', appId, 'users', user.uid, 'looks');
await addDoc(looksCol, {
name: look.name,
items: newItemIds,
createdAt: new Date().getTime(),
updatedAt: new Date().getTime(),
});
// Notify the owner via coleção pública (inboxNotifications)
try {
const inboxCol = collection(db, 'artifacts', appId, 'inboxNotifications');
await addDoc(inboxCol, {
type: 'look_copied',
recipientUid: selectedCommunityUser.uid,
lookName: look.name,
copiedByEmail: userProfile?.username || user.email || 'Alguém',
createdAt: new Date().getTime(),
read: false
});
} catch (notifErr) {
console.error('Não foi possível enviar notificação ao dono do look:', notifErr);
}
setToastMessage(t('lookCopied') || 'Look copiado para o seu armário!');
setTimeout(() => setToastMessage(null), 3000);
} catch (err) {
console.error('Erro ao copiar look:', err);
setToastMessage('Erro ao copiar look.');
setTimeout(() => setToastMessage(null), 3000);
}
};
// Copiar o look partilhado para o armário do utilizador atual
const copySharedLook = async () => {
if (!user || !sharedLookData) return;
@@ -2371,7 +2451,15 @@ export default function App() {
<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>
<span className="text-white font-black text-sm flex-1">{look.name}</span>
<div className="flex gap-2">
<button onClick={(e) => { e.stopPropagation(); setInspectingCommunityLook(look); }} className="p-2 bg-white/20 hover:bg-white/40 rounded-xl backdrop-blur-sm transition-colors text-white" title="Inspecionar Outfit">
<Search size={16} />
</button>
<button onClick={(e) => { e.stopPropagation(); copyCommunityLook(look); }} className="p-2 bg-white/20 hover:bg-white/40 rounded-xl backdrop-blur-sm transition-colors text-white" title="Copiar para o meu armário">
<Copy size={16} />
</button>
</div>
</div>
</div>
))}
@@ -2382,8 +2470,16 @@ export default function App() {
<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 key={item.id} className="aspect-square rounded-2xl overflow-hidden bg-gray-100 dark:bg-gray-800 shadow-md group relative">
<img src={item.imageUrl} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" alt="Item" />
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
<button onClick={(e) => { e.stopPropagation(); setInspectingCommunityItem(item); }} className="p-2.5 bg-white/20 hover:bg-white/40 rounded-xl backdrop-blur-sm transition-colors text-white" title="Inspecionar Peça">
<Search size={18} />
</button>
<button onClick={(e) => { e.stopPropagation(); copyCommunityItem(item); }} className="p-2.5 bg-white/20 hover:bg-white/40 rounded-xl backdrop-blur-sm transition-colors text-white" title="Copiar para o meu armário">
<Copy size={18} />
</button>
</div>
</div>
))}
</div>
@@ -3083,6 +3179,109 @@ export default function App() {
</div>
</div>
)}
{/* Modal de Inspecionar Look da Comunidade */}
{inspectingCommunityLook && (
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setInspectingCommunityLook(null)}>
<div
className={`w-full max-w-lg rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
onClick={e => e.stopPropagation()}
>
<div className="relative p-8 pb-6" style={{ background: 'linear-gradient(135deg, hsl(var(--primary-600)), hsl(var(--primary-400)))' }}>
<div className="absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(circle at 80% 20%, white 0%, transparent 60%)' }} />
<div className="relative z-10">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<div className="p-2 bg-white/20 rounded-xl backdrop-blur-sm">
<Search size={20} className="text-white" />
</div>
<span className="text-white/80 font-black uppercase text-[10px] tracking-widest">{t('inspectOutfit') || 'Inspecionar Outfit'}</span>
</div>
<button onClick={() => setInspectingCommunityLook(null)} className="text-white/80 hover:text-white transition-colors">
<X size={20} />
</button>
</div>
<h2 className="text-3xl font-black text-white tracking-tight">{inspectingCommunityLook.name}</h2>
<p className="text-white/60 text-sm font-bold mt-1">{inspectingCommunityLook.items?.length || 0} peça{(inspectingCommunityLook.items?.length !== 1) ? 's' : ''}</p>
</div>
</div>
<div className={`p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
<p className="text-[10px] font-black uppercase tracking-widest opacity-40 mb-4">{t('includedPieces') || 'Peças incluídas'}</p>
<div className="flex flex-wrap gap-3 mb-8">
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
const item = selectedUserClothes.find(c => c.id === itemId);
if (!item) return null;
return (
<div key={idx} className="relative group/item">
<div className="w-20 h-20 rounded-2xl overflow-hidden border-2 border-gray-100 dark:border-gray-700 shadow-lg">
<img src={item.imageUrl} alt={item.name} className="w-full h-full object-cover group-hover/item:scale-110 transition-transform duration-500" />
</div>
<div className="absolute -bottom-1 left-1/2 -translate-x-1/2 bg-gray-900/90 text-white text-[8px] font-black uppercase tracking-wide px-2 py-0.5 rounded-full whitespace-nowrap opacity-0 group-hover/item:opacity-100 transition-opacity">
{item.name}
</div>
</div>
);
})}
</div>
<div className={`space-y-2 mb-8 max-h-32 overflow-y-auto custom-scrollbar`}>
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
const item = selectedUserClothes.find(c => c.id === itemId);
if (!item) return null;
return (
<div key={idx} className={`flex items-center gap-3 px-4 py-2.5 rounded-xl ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
<span className="text-xs font-black truncate flex-1">{item.name}</span>
<span className={`text-[9px] font-black uppercase tracking-widest opacity-40 shrink-0`}>{item.category}</span>
</div>
);
})}
</div>
<button
onClick={() => setInspectingCommunityLook(null)}
className={`w-full py-4 font-black uppercase text-[10px] tracking-widest rounded-2xl transition-all ${darkMode ? 'bg-gray-800 text-gray-400 hover:bg-gray-700' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
>
{t('close') || 'Fechar'}
</button>
</div>
</div>
</div>
)}
{/* Modal de Inspecionar Peça da Comunidade */}
{inspectingCommunityItem && (
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setInspectingCommunityItem(null)}>
<div
className={`w-full max-w-sm rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
onClick={e => e.stopPropagation()}
>
<div className="relative aspect-square">
<img src={inspectingCommunityItem.imageUrl} className="w-full h-full object-cover" alt="Item" />
<button onClick={() => setInspectingCommunityItem(null)} className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-colors">
<X size={20} />
</button>
</div>
<div className="p-8 text-center space-y-4">
<h3 className="text-2xl font-black text-inherit">{inspectingCommunityItem.name}</h3>
<div className="flex items-center justify-center gap-2 opacity-60 font-bold uppercase tracking-widest text-[10px] text-inherit">
<span>{inspectingCommunityItem.category}</span>
<span></span>
<span>{inspectingCommunityItem.color}</span>
</div>
<button
onClick={() => {
copyCommunityItem(inspectingCommunityItem);
setInspectingCommunityItem(null);
}}
className="w-full py-4 mt-4 bg-primary-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all flex items-center justify-center gap-2"
>
<Copy size={16} /> {t('copyToMyCloset') || 'Copiar para Armário'}
</button>
</div>
</div>
</div>
)}
</div>
);
}