Compare commits
4 Commits
1930190c8d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 676057bcac | |||
| fc78f29157 | |||
| a6bfcb10b0 | |||
| ab19a3bfc9 |
2
.env
2
.env
@@ -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
1
dist/assets/index-DFJLltIj.css
vendored
1
dist/assets/index-DFJLltIj.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DUBk-k5k.css
vendored
Normal file
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
4
dist/index.html
vendored
@@ -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>
|
||||
|
||||
149
src/App.jsx
149
src/App.jsx
@@ -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>
|
||||
|
||||
@@ -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.",
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user