Compare commits

...

4 Commits

Author SHA1 Message Date
676057bcac saveeeeeeeeeee 2026-06-09 01:44:19 +01:00
fc78f29157 again 2026-06-09 01:11:18 +01:00
a6bfcb10b0 savesave 2026-06-09 01:10:51 +01:00
ab19a3bfc9 save rq 2026-06-08 17:02:38 +01:00
7 changed files with 475 additions and 303 deletions

2
.env
View File

@@ -8,5 +8,3 @@ VITE_FIREBASE_APP_ID="1:219982610263:web:0ebe67d9cf0e7d2753c812"
VITE_APP_ID=my-closet-app
VITE_INITIAL_AUTH_TOKEN=
VITE_FIREBASE_VAPID_KEY=BM55REBdX3g7x4JT7C6sN0uBDLATqsCnvgvx3mJnVmgU2dFvZL1przcV-V-Kl50Ao6-i8OA-5PZFWTuhETL9-v4

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-DUBk-k5k.css vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyCloset</title>
<link rel="manifest" href="/manifest.json" />
<script type="module" crossorigin src="/assets/index-SfxBfoF_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DFJLltIj.css">
<script type="module" crossorigin src="/assets/index-C3FJNYuR.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DUBk-k5k.css">
</head>
<body>
<div id="root"></div>

View File

@@ -70,6 +70,10 @@ export default function App() {
const [weatherData, setWeatherData] = useState(null);
const [isPrivate, setIsPrivate] = useState(false);
const [shortcutsEnabled, setShortcutsEnabled] = useState(false);
const [shortcutUpKey, setShortcutUpKey] = useState('q');
const [shortcutDownKey, setShortcutDownKey] = useState('e');
const [recordingKey, setRecordingKey] = useState(null);
const [shortcutConflict, setShortcutConflict] = useState(false);
const [userStatus, setUserStatus] = useState('online');
// Estado da Comunidade
@@ -117,6 +121,16 @@ export default function App() {
const t = (key) => translations[language]?.[key] || translations['PT'][key] || key;
const formatKeyName = (key) => {
if (!key) return '';
if (key === ' ') return t('spaceKey') || 'Espaço';
if (key === 'ArrowUp') return '↑';
if (key === 'ArrowDown') return '↓';
if (key === 'ArrowLeft') return '←';
if (key === 'ArrowRight') return '→';
return key.toUpperCase();
};
// Mapeamento de nomes de cor (PT) para valores CSS
const COLOR_MAP = {
'Vermelho': '#ef4444',
@@ -295,20 +309,50 @@ export default function App() {
useEffect(() => {
if (!shortcutsEnabled) return;
// Se as teclas forem iguais, ambos os atalhos ficam desativados
if (shortcutUpKey.toLowerCase() === shortcutDownKey.toLowerCase()) return;
const handleKeyDown = (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key.toLowerCase() === 'q') {
if (e.key.toLowerCase() === shortcutUpKey.toLowerCase()) {
handleNavShortcut('up');
} else if (e.key.toLowerCase() === 'e') {
} else if (e.key.toLowerCase() === shortcutDownKey.toLowerCase()) {
handleNavShortcut('down');
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcutsEnabled, view]);
}, [shortcutsEnabled, view, shortcutUpKey, shortcutDownKey]);
useEffect(() => {
if (!recordingKey) return;
const handleCapture = (e) => {
e.preventDefault();
e.stopPropagation();
if (['control', 'shift', 'alt', 'meta'].includes(e.key.toLowerCase())) {
return;
}
const key = e.key;
if (recordingKey === 'up') {
setShortcutUpKey(key);
saveUserSetting('shortcutUpKey', key);
setShortcutConflict(key.toLowerCase() === shortcutDownKey.toLowerCase());
} else if (recordingKey === 'down') {
setShortcutDownKey(key);
saveUserSetting('shortcutDownKey', key);
setShortcutConflict(key.toLowerCase() === shortcutUpKey.toLowerCase());
}
setRecordingKey(null);
};
window.addEventListener('keydown', handleCapture, true);
return () => window.removeEventListener('keydown', handleCapture, true);
}, [recordingKey, shortcutUpKey, shortcutDownKey]);
useEffect(() => {
if (editingItem && editingItem.color) {
@@ -454,6 +498,11 @@ export default function App() {
}
if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);
if (data.settings.shortcutsEnabled !== undefined) setShortcutsEnabled(data.settings.shortcutsEnabled);
const loadedUp = data.settings.shortcutUpKey !== undefined ? data.settings.shortcutUpKey : 'q';
const loadedDown = data.settings.shortcutDownKey !== undefined ? data.settings.shortcutDownKey : 'e';
if (data.settings.shortcutUpKey !== undefined) setShortcutUpKey(loadedUp);
if (data.settings.shortcutDownKey !== undefined) setShortcutDownKey(loadedDown);
setShortcutConflict(loadedUp.toLowerCase() === loadedDown.toLowerCase());
if (data.settings.status !== undefined) setUserStatus(data.settings.status);
}
}
@@ -540,6 +589,14 @@ export default function App() {
}
}, [user, userProfile?.username, userProfile?.fullName, userProfile?.avatar, userProfile?.settings?.isPrivate, userProfile?.location]);
// Sync do status público (em tempo real) para a Comunidade
useEffect(() => {
if (user && userStatus) {
const publicProfileDoc = doc(db, 'artifacts', appId, 'publicProfiles', user.uid);
setDoc(publicProfileDoc, { status: userStatus }, { merge: true }).catch(() => {});
}
}, [user, userStatus]);
// Fetch utilizadores da comunidade
useEffect(() => {
if (view !== 'community') return;
@@ -1476,7 +1533,7 @@ export default function App() {
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('up')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mb-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">Q</span> {t('up')}
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">{formatKeyName(shortcutUpKey)}</span> {t('up')}
</button>
)}
<nav className="flex-1 space-y-3">
@@ -1505,7 +1562,7 @@ export default function App() {
</nav>
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('down')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mt-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">E</span> {t('down')}
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">{formatKeyName(shortcutDownKey)}</span> {t('down')}
</button>
)}
@@ -2520,12 +2577,26 @@ export default function App() {
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="relative w-16 h-16 shrink-0">
<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>
<span className={`absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-2 ${darkMode ? 'border-gray-800' : 'border-white'} ${
u.status === 'online' ? 'bg-emerald-500' :
u.status === 'away' ? 'bg-amber-400' :
'bg-gray-400'
}`} title={t(u.status || 'offline')} />
</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>
<span className={`inline-flex items-center gap-1 text-[10px] font-black uppercase tracking-widest mt-1 px-2 py-0.5 rounded-full ${
u.status === 'online' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400' :
u.status === 'away' ? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400' :
'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
}`}>
{t(u.status || 'offline')}
</span>
</div>
</div>
</Card>
@@ -2561,6 +2632,18 @@ export default function App() {
>
<Copy size={14} />
</button>
<span className={`inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest px-2.5 py-1 rounded-full ${
selectedCommunityUser.status === 'online' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400' :
selectedCommunityUser.status === 'away' ? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400' :
'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
}`}>
<span className={`w-2 h-2 rounded-full ${
selectedCommunityUser.status === 'online' ? 'bg-emerald-500' :
selectedCommunityUser.status === 'away' ? 'bg-amber-400' :
'bg-gray-400'
}`} />
{t(selectedCommunityUser.status || 'offline')}
</span>
</div>
</div>
<button onClick={() => setShowInspectModal(true)} className="px-5 py-3 bg-primary-100 text-primary-700 dark:bg-primary-900/50 dark:text-primary-300 rounded-2xl font-black text-sm transition-all hover:scale-105 flex items-center gap-2">
@@ -2583,6 +2666,18 @@ export default function App() {
<div>
<h3 className="text-2xl font-black">{selectedCommunityUser.fullName || t('userTitle')}</h3>
<p className="opacity-60 font-bold">@{selectedCommunityUser.username || 'user'}</p>
<span className={`inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest mt-1.5 px-2.5 py-1 rounded-full ${
selectedCommunityUser.status === 'online' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400' :
selectedCommunityUser.status === 'away' ? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400' :
'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
}`}>
<span className={`w-2 h-2 rounded-full ${
selectedCommunityUser.status === 'online' ? 'bg-emerald-500' :
selectedCommunityUser.status === 'away' ? 'bg-amber-400' :
'bg-gray-400'
}`} />
{t(selectedCommunityUser.status || 'offline')}
</span>
</div>
</div>
@@ -2736,6 +2831,50 @@ export default function App() {
</button>
</div>
{shortcutsEnabled && (
<div className="pl-4 border-l-2 border-primary-500 dark:border-primary-400 flex flex-col gap-3 my-2 transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-inherit">{t('shortcutUp') || 'Navegar para Cima'}</p>
<p className="text-[10px] opacity-50 text-inherit">{t('shortcutUpDesc') || 'Tecla para navegar para cima no menu'}</p>
</div>
<button
onClick={() => setRecordingKey(recordingKey === 'up' ? null : 'up')}
className={`px-3 py-1.5 rounded-xl text-xs font-black uppercase tracking-widest border transition-all ${
recordingKey === 'up'
? 'bg-primary-100 dark:bg-primary-950 border-primary-500 text-primary-600 ring-2 ring-primary-500 animate-pulse'
: 'bg-gray-100 dark:bg-gray-800 border-transparent text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
{recordingKey === 'up' ? t('pressAnyKey') || 'Pressione uma tecla...' : formatKeyName(shortcutUpKey)}
</button>
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-inherit">{t('shortcutDown') || 'Navegar para Baixo'}</p>
<p className="text-[10px] opacity-50 text-inherit">{t('shortcutDownDesc') || 'Tecla para navegar para baixo no menu'}</p>
</div>
<button
onClick={() => setRecordingKey(recordingKey === 'down' ? null : 'down')}
className={`px-3 py-1.5 rounded-xl text-xs font-black uppercase tracking-widest border transition-all ${
recordingKey === 'down'
? 'bg-primary-100 dark:bg-primary-950 border-primary-500 text-primary-600 ring-2 ring-primary-500 animate-pulse'
: 'bg-gray-100 dark:bg-gray-800 border-transparent text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
{recordingKey === 'down' ? t('pressAnyKey') || 'Pressione uma tecla...' : formatKeyName(shortcutDownKey)}
</button>
</div>
</div>
)}
{shortcutsEnabled && shortcutConflict && (
<div className="flex items-center gap-2 px-3 py-2 rounded-xl bg-amber-50 dark:bg-amber-950/40 border border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 text-xs font-medium animate-pulse">
<span></span>
<span>{t('shortcutConflictWarning')}</span>
</div>
)}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<p className="font-bold text-inherit flex items-center gap-2">{t('dailyOutfitNotification')}</p>

View File

@@ -225,6 +225,13 @@ export const translations = {
userOutfits: "Outfits do Utilizador",
userCloset: "Armário",
usernameTaken: "Este nome de utilizador já está em uso.",
shortcutUp: "Navegar para Cima",
shortcutDown: "Navegar para Baixo",
shortcutUpDesc: "Tecla para navegar para cima no menu",
shortcutDownDesc: "Tecla para navegar para baixo no menu",
pressAnyKey: "Pressione uma tecla...",
spaceKey: "Espaço",
shortcutConflictWarning: "Não podes usar a mesma tecla para cima e para baixo. Os atalhos estão desativados.",
},
EN: {
loginModeIntro: "The Future of Your Style",
@@ -452,6 +459,13 @@ export const translations = {
userOutfits: "User's Outfits",
userCloset: "Closet",
usernameTaken: "This username is already taken.",
shortcutUp: "Navigate Up",
shortcutDown: "Navigate Down",
shortcutUpDesc: "Key to navigate up in the menu",
shortcutDownDesc: "Key to navigate down in the menu",
pressAnyKey: "Press a key...",
spaceKey: "Space",
shortcutConflictWarning: "You cannot use the same key for up and down. Shortcuts are disabled.",
},
ES: {
loginModeIntro: "El Futuro de Tu Estilo",
@@ -679,6 +693,13 @@ export const translations = {
userOutfits: "Outfits del Usuario",
userCloset: "Armario",
usernameTaken: "Este nombre de usuario ya está en uso.",
shortcutUp: "Navegar hacia Arriba",
shortcutDown: "Navegar hacia Abajo",
shortcutUpDesc: "Tecla para navegar hacia arriba en el menú",
shortcutDownDesc: "Tecla para navegar hacia abajo en el menú",
pressAnyKey: "Presione una tecla...",
spaceKey: "Espacio",
shortcutConflictWarning: "No puedes usar la misma tecla para arriba y abajo. Los atajos están desactivados.",
},
FR: {
loginModeIntro: "Le Futur de Ton Style",
@@ -906,6 +927,13 @@ export const translations = {
userOutfits: "Outfits de l'Utilisateur",
userCloset: "Placard",
usernameTaken: "Ce nom d'utilisateur est déjà utilisé.",
shortcutUp: "Naviguer vers le Haut",
shortcutDown: "Naviguer vers le Bas",
shortcutUpDesc: "Touche pour naviguer vers le haut dans le menu",
shortcutDownDesc: "Touche pour naviguer vers le bas dans le menu",
pressAnyKey: "Appuyez sur une touche...",
spaceKey: "Espace",
shortcutConflictWarning: "Vous ne pouvez pas utiliser la même touche pour haut et bas. Les raccourcis sont désactivés.",
},
DE: {
loginModeIntro: "Die Zukunft deines Stils",
@@ -1133,5 +1161,12 @@ export const translations = {
userOutfits: "Outfits des Benutzers",
userCloset: "Kleiderschrank",
usernameTaken: "Dieser Benutzername ist bereits vergeben.",
shortcutUp: "Nach oben navigieren",
shortcutDown: "Nach unten navigieren",
shortcutUpDesc: "Taste zum Navigieren nach oben im Menü",
shortcutDownDesc: "Taste zum Navigieren nach unten im Menü",
pressAnyKey: "Taste drücken...",
spaceKey: "Leertaste",
shortcutConflictWarning: "Du kannst dieselbe Taste nicht für oben und unten verwenden. Tastenkürzel sind deaktiviert.",
}
};