aaa
This commit is contained in:
205
src/App.jsx
205
src/App.jsx
@@ -76,6 +76,8 @@ export default function App() {
|
|||||||
const [showInspectModal, setShowInspectModal] = useState(false);
|
const [showInspectModal, setShowInspectModal] = useState(false);
|
||||||
const [selectedUserClothes, setSelectedUserClothes] = useState([]);
|
const [selectedUserClothes, setSelectedUserClothes] = useState([]);
|
||||||
const [selectedUserLooks, setSelectedUserLooks] = useState([]);
|
const [selectedUserLooks, setSelectedUserLooks] = useState([]);
|
||||||
|
const [inspectingCommunityLook, setInspectingCommunityLook] = useState(null);
|
||||||
|
const [inspectingCommunityItem, setInspectingCommunityItem] = useState(null);
|
||||||
|
|
||||||
// Estado para Partilha de Looks
|
// Estado para Partilha de Looks
|
||||||
const sharedLookRef = useRef('');
|
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
|
// Copiar o look partilhado para o armário do utilizador atual
|
||||||
const copySharedLook = async () => {
|
const copySharedLook = async () => {
|
||||||
if (!user || !sharedLookData) return;
|
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" />
|
<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">
|
<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>
|
||||||
</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>
|
<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">
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-4">
|
||||||
{selectedUserClothes.map(item => (
|
{selectedUserClothes.map(item => (
|
||||||
<div key={item.id} className="aspect-square rounded-2xl overflow-hidden bg-gray-100 dark:bg-gray-800 shadow-md">
|
<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" alt="Item" />
|
<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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -3083,6 +3179,109 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user