Compare commits
2 Commits
56f6d16cd1
...
1930190c8d
| Author | SHA1 | Date | |
|---|---|---|---|
| 1930190c8d | |||
| 0a4de43635 |
118
add_shortcuts.cjs
Normal file
118
add_shortcuts.cjs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let content = fs.readFileSync('src/App.jsx', 'utf8');
|
||||||
|
|
||||||
|
// 1. State
|
||||||
|
content = content.replace(
|
||||||
|
/const \[isPrivate, setIsPrivate\] = useState\(false\);/,
|
||||||
|
`const [isPrivate, setIsPrivate] = useState(false);\n const [shortcutsEnabled, setShortcutsEnabled] = useState(false);`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Toggle and Nav handle
|
||||||
|
const toggleInsert = `
|
||||||
|
const handlePrivacyToggle = (newVal) => {
|
||||||
|
setIsPrivate(newVal);
|
||||||
|
saveUserSetting('isPrivate', newVal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShortcutsToggle = (newVal) => {
|
||||||
|
setShortcutsEnabled(newVal);
|
||||||
|
saveUserSetting('shortcutsEnabled', newVal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavShortcut = (direction) => {
|
||||||
|
const navItems = ['dashboard', 'closet', 'wishlist', 'laundry', 'outfits', 'planner', 'community', 'settings'];
|
||||||
|
const currentIndex = navItems.indexOf(view);
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
let nextIndex;
|
||||||
|
if (direction === 'up') {
|
||||||
|
nextIndex = currentIndex === 0 ? navItems.length - 1 : currentIndex - 1;
|
||||||
|
} else {
|
||||||
|
nextIndex = currentIndex === navItems.length - 1 ? 0 : currentIndex + 1;
|
||||||
|
}
|
||||||
|
setView(navItems[nextIndex]);
|
||||||
|
if (window.innerWidth < 768) setSidebarOpen(false);
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
/const handlePrivacyToggle = \(newVal\) => \{[\s\S]*?saveUserSetting\('isPrivate', newVal\);\n \};/,
|
||||||
|
toggleInsert.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Load from Firebase
|
||||||
|
content = content.replace(
|
||||||
|
/if \(data\.settings\.isPrivate !== undefined\) setIsPrivate\(data\.settings\.isPrivate\);/,
|
||||||
|
`if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);\n if (data.settings.shortcutsEnabled !== undefined) setShortcutsEnabled(data.settings.shortcutsEnabled);`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. UseEffect
|
||||||
|
const useEffectInsert = `
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shortcutsEnabled) return;
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||||
|
|
||||||
|
if (e.key.toLowerCase() === 'q') {
|
||||||
|
handleNavShortcut('up');
|
||||||
|
} else if (e.key.toLowerCase() === 'e') {
|
||||||
|
handleNavShortcut('down');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [shortcutsEnabled, view]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editingItem && editingItem.color) {`;
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
/useEffect\(\(\) => \{\n if \(editingItem && editingItem\.color\) \{/,
|
||||||
|
useEffectInsert.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Settings UI
|
||||||
|
const settingsUI = `
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-inherit flex items-center gap-2">Atalhos de Teclado</p>
|
||||||
|
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">Ativar navegação com Q e E</p>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => handleShortcutsToggle(!shortcutsEnabled)} className={\`w-14 h-8 rounded-full transition-colors relative \${shortcutsEnabled ? 'bg-primary-600' : 'bg-gray-200'}\`}>
|
||||||
|
<div className={\`w-6 h-6 rounded-full bg-white absolute top-1 transition-all \${shortcutsEnabled ? 'left-7' : 'left-1'}\`}></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
content = content.replace(
|
||||||
|
/(<div className="flex items-center justify-between">[\s\S]*?t\('lookReminders'\)}<\/p>[\s\S]*?<\/div>[\s\S]*?<\/button>[\s\S]*?<\/div>)/,
|
||||||
|
`$1\n${settingsUI}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Sidebar UI Top
|
||||||
|
const sidebarTop = `
|
||||||
|
{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> Cima
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<nav className="flex-1 space-y-3">
|
||||||
|
`;
|
||||||
|
content = content.replace(/<nav className="flex-1 space-y-3">/, sidebarTop.trim());
|
||||||
|
|
||||||
|
// 7. Sidebar UI Bottom
|
||||||
|
const sidebarBottom = `
|
||||||
|
))}
|
||||||
|
</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> Baixo
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
content = content.replace(/ \}\)\}\n <\/nav>/, sidebarBottom.trim());
|
||||||
|
|
||||||
|
fs.writeFileSync('src/App.jsx', content);
|
||||||
|
console.log('Shortcuts feature added successfully.');
|
||||||
3324
dist/assets/index-0fNqCu5T.js
vendored
3324
dist/assets/index-0fNqCu5T.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-D1z9PPLp.css
vendored
1
dist/assets/index-D1z9PPLp.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DFJLltIj.css
vendored
Normal file
1
dist/assets/index-DFJLltIj.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3907
dist/assets/index-SfxBfoF_.js
vendored
Normal file
3907
dist/assets/index-SfxBfoF_.js
vendored
Normal file
File diff suppressed because one or more lines are too long
24
dist/firebase-messaging-sw.js
vendored
Normal file
24
dist/firebase-messaging-sw.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
importScripts('https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js');
|
||||||
|
importScripts('https://www.gstatic.com/firebasejs/10.8.0/firebase-messaging-compat.js');
|
||||||
|
|
||||||
|
firebase.initializeApp({
|
||||||
|
apiKey: "AIzaSyBBitFgNKnJ_3B0aqJgbbhGL_erufKd9lk",
|
||||||
|
authDomain: "mycloset1-864c4.firebaseapp.com",
|
||||||
|
projectId: "mycloset1-864c4",
|
||||||
|
storageBucket: "mycloset1-864c4.firebasestorage.app",
|
||||||
|
messagingSenderId: "219982610263",
|
||||||
|
appId: "1:219982610263:web:0ebe67d9cf0e7d2753c812"
|
||||||
|
});
|
||||||
|
|
||||||
|
const messaging = firebase.messaging();
|
||||||
|
|
||||||
|
messaging.onBackgroundMessage((payload) => {
|
||||||
|
console.log('[firebase-messaging-sw.js] Received background message ', payload);
|
||||||
|
const notificationTitle = payload.notification.title;
|
||||||
|
const notificationOptions = {
|
||||||
|
body: payload.notification.body,
|
||||||
|
icon: '/favicon.ico'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.registration.showNotification(notificationTitle, notificationOptions);
|
||||||
|
});
|
||||||
5
dist/index.html
vendored
5
dist/index.html
vendored
@@ -4,8 +4,9 @@
|
|||||||
<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-0fNqCu5T.js"></script>
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D1z9PPLp.css">
|
<script type="module" crossorigin src="/assets/index-SfxBfoF_.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-DFJLltIj.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
15
dist/manifest.json
vendored
Normal file
15
dist/manifest.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "MyCloset",
|
||||||
|
"short_name": "MyCloset",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#4F46E5",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
dist/sw.js
vendored
Normal file
1
dist/sw.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
self.addEventListener('fetch', function(event) {});
|
||||||
41
fix_i18n.cjs
Normal file
41
fix_i18n.cjs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
let content = fs.readFileSync('src/lib/i18n.js', 'utf8');
|
||||||
|
|
||||||
|
// The broken string is like:
|
||||||
|
// weatherCurrentAvg: "{current,
|
||||||
|
// registeredPieces: "Peças Registadas",
|
||||||
|
// ...
|
||||||
|
// }°C Atual • Média {avg}°C",
|
||||||
|
|
||||||
|
// PT
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Baixo",\n\}\°C Atual • Média \{avg\}\°C",/g,
|
||||||
|
`weatherCurrentAvg: "{current}°C Atual • Média {avg}°C",\n$1down: "Baixo",`
|
||||||
|
);
|
||||||
|
|
||||||
|
// EN
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Down",\n\}\°C Current • Average \{avg\}\°C",/g,
|
||||||
|
`weatherCurrentAvg: "{current}°C Current • Average {avg}°C",\n$1down: "Down",`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ES
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Abajo",\n\}\°C Actual • Media \{avg\}\°C",/g,
|
||||||
|
`weatherCurrentAvg: "{current}°C Actual • Media {avg}°C",\n$1down: "Abajo",`
|
||||||
|
);
|
||||||
|
|
||||||
|
// FR
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Bas",\n\}\°C Actuel • Moyenne \{avg\}\°C",/g,
|
||||||
|
`weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C",\n$1down: "Bas",`
|
||||||
|
);
|
||||||
|
|
||||||
|
// DE
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Runter",\n\}\°C Aktuell • Durchschnitt \{avg\}\°C",/g,
|
||||||
|
`weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C",\n$1down: "Runter",`
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync('src/lib/i18n.js', content);
|
||||||
|
console.log('Fixed i18n.js syntax errors.');
|
||||||
10
fix_i18n_all.cjs
Normal file
10
fix_i18n_all.cjs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
let content = fs.readFileSync('src/lib/i18n.js', 'utf8');
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "([^"]+)",\n\}\°C ([^•]+) • ([^\{]+) \{avg\}\°C",/g,
|
||||||
|
'weatherCurrentAvg: "{current}°C $3 • $4 {avg}°C",\n$1down: "$2",'
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync('src/lib/i18n.js', content);
|
||||||
|
console.log('Fixed i18n.js syntax errors dynamically.');
|
||||||
37
fix_spacing.cjs
Normal file
37
fix_spacing.cjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let content = fs.readFileSync('src/App.jsx', 'utf8');
|
||||||
|
|
||||||
|
const replacements = [
|
||||||
|
{ from: /(?<!:)\bp-12\b/g, to: 'p-6 md:p-12' },
|
||||||
|
{ from: /(?<!:)\bp-10\b/g, to: 'p-6 md:p-10' },
|
||||||
|
{ from: /(?<!:)\bp-8\b/g, to: 'p-5 md:p-8' },
|
||||||
|
{ from: /(?<!:)\bgap-10\b/g, to: 'gap-6 md:gap-10' },
|
||||||
|
{ from: /(?<!:)\bgap-12\b/g, to: 'gap-6 md:gap-12' },
|
||||||
|
{ from: /(?<!:)\bgap-8\b/g, to: 'gap-4 md:gap-8' },
|
||||||
|
{ from: /(?<!:)\bmb-10\b/g, to: 'mb-6 md:mb-10' },
|
||||||
|
{ from: /(?<!:)\bmb-16\b/g, to: 'mb-8 md:mb-16' },
|
||||||
|
{ from: /(?<!:)\bmb-8\b/g, to: 'mb-5 md:mb-8' },
|
||||||
|
{ from: /(?<!:)\bmt-10\b/g, to: 'mt-6 md:mt-10' },
|
||||||
|
{ from: /(?<!:)\bmt-12\b/g, to: 'mt-6 md:mt-12' },
|
||||||
|
{ from: /(?<!:)\bmt-8\b/g, to: 'mt-5 md:mt-8' },
|
||||||
|
{ from: /(?<!:)\bpx-12\b/g, to: 'px-6 md:px-12' },
|
||||||
|
{ from: /(?<!:)\bpx-8\b/g, to: 'px-5 md:px-8' },
|
||||||
|
{ from: /(?<!:)\bpy-12\b/g, to: 'py-6 md:py-12' },
|
||||||
|
{ from: /(?<!:)\bpy-16\b/g, to: 'py-8 md:py-16' },
|
||||||
|
{ from: /(?<!:)\bspace-y-10\b/g, to: 'space-y-6 md:space-y-10' },
|
||||||
|
{ from: /(?<!:)\bspace-y-12\b/g, to: 'space-y-8 md:space-y-12' },
|
||||||
|
{ from: /(?<!:)\bspace-y-8\b/g, to: 'space-y-5 md:space-y-8' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let changedContent = content;
|
||||||
|
|
||||||
|
replacements.forEach(r => {
|
||||||
|
changedContent = changedContent.replace(r.from, r.to);
|
||||||
|
});
|
||||||
|
|
||||||
|
// To be even more perfect, let's also fix rounded-[2rem] to rounded-3xl md:rounded-[2rem]
|
||||||
|
changedContent = changedContent.replace(/(?<!:)\brounded-\[2rem\]/g, 'rounded-3xl md:rounded-[2rem]');
|
||||||
|
|
||||||
|
fs.writeFileSync('src/App.jsx', changedContent);
|
||||||
|
console.log('Spacing normalized successfully.');
|
||||||
274
src/App.jsx
274
src/App.jsx
@@ -69,6 +69,7 @@ export default function App() {
|
|||||||
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 [isPrivate, setIsPrivate] = useState(false);
|
||||||
|
const [shortcutsEnabled, setShortcutsEnabled] = useState(false);
|
||||||
const [userStatus, setUserStatus] = useState('online');
|
const [userStatus, setUserStatus] = useState('online');
|
||||||
|
|
||||||
// Estado da Comunidade
|
// Estado da Comunidade
|
||||||
@@ -228,6 +229,26 @@ export default function App() {
|
|||||||
saveUserSetting('isPrivate', newVal);
|
saveUserSetting('isPrivate', newVal);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShortcutsToggle = (newVal) => {
|
||||||
|
setShortcutsEnabled(newVal);
|
||||||
|
saveUserSetting('shortcutsEnabled', newVal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavShortcut = (direction) => {
|
||||||
|
const navItems = ['dashboard', 'closet', 'wishlist', 'laundry', 'outfits', 'planner', 'community', 'settings'];
|
||||||
|
const currentIndex = navItems.indexOf(view);
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
let nextIndex;
|
||||||
|
if (direction === 'up') {
|
||||||
|
nextIndex = currentIndex === 0 ? navItems.length - 1 : currentIndex - 1;
|
||||||
|
} else {
|
||||||
|
nextIndex = currentIndex === navItems.length - 1 ? 0 : currentIndex + 1;
|
||||||
|
}
|
||||||
|
setView(navItems[nextIndex]);
|
||||||
|
if (window.innerWidth < 768) setSidebarOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleStatus = (e) => {
|
const toggleStatus = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -272,6 +293,23 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shortcutsEnabled) return;
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||||
|
|
||||||
|
if (e.key.toLowerCase() === 'q') {
|
||||||
|
handleNavShortcut('up');
|
||||||
|
} else if (e.key.toLowerCase() === 'e') {
|
||||||
|
handleNavShortcut('down');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [shortcutsEnabled, view]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editingItem && editingItem.color) {
|
if (editingItem && editingItem.color) {
|
||||||
setItemColors(editingItem.color.split(',').map(c => c.trim()).filter(Boolean));
|
setItemColors(editingItem.color.split(',').map(c => c.trim()).filter(Boolean));
|
||||||
@@ -415,6 +453,7 @@ export default function App() {
|
|||||||
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.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);
|
||||||
|
if (data.settings.shortcutsEnabled !== undefined) setShortcutsEnabled(data.settings.shortcutsEnabled);
|
||||||
if (data.settings.status !== undefined) setUserStatus(data.settings.status);
|
if (data.settings.status !== undefined) setUserStatus(data.settings.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1340,10 +1379,10 @@ export default function App() {
|
|||||||
if (view === 'auth') {
|
if (view === 'auth') {
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen bg-gradient-to-br from-primary-100 via-white to-purple-50 dark:from-gray-950 dark:to-gray-900 flex items-center justify-center p-6 text-gray-900 ${darkMode ? 'dark' : ''}`}>
|
<div className={`min-h-screen bg-gradient-to-br from-primary-100 via-white to-purple-50 dark:from-gray-950 dark:to-gray-900 flex items-center justify-center p-6 text-gray-900 ${darkMode ? 'dark' : ''}`}>
|
||||||
<Card className="max-w-md w-full p-12 border-none shadow-2xl overflow-hidden" darkMode={darkMode}>
|
<Card className="max-w-md w-full p-6 md:p-12 border-none shadow-2xl overflow-hidden" darkMode={darkMode}>
|
||||||
<div key={authMode} className="animate-custom-zoom">
|
<div key={authMode} className="animate-custom-zoom">
|
||||||
<div className="text-center mb-10">
|
<div className="text-center mb-6 md:mb-10">
|
||||||
<div className="inline-flex p-5 bg-primary-600 rounded-[2rem] shadow-2xl shadow-primary-600/40 mb-6 transition-all duration-300">
|
<div className="inline-flex p-5 bg-primary-600 rounded-3xl md:rounded-[2rem] shadow-2xl shadow-primary-600/40 mb-6 transition-all duration-300">
|
||||||
{authMode === 'login' ? <Shirt className="text-white w-12 h-12" /> : <UserCircle className="text-white w-12 h-12" />}
|
{authMode === 'login' ? <Shirt className="text-white w-12 h-12" /> : <UserCircle className="text-white w-12 h-12" />}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-5xl font-black tracking-tighter italic">MyCloset</h1>
|
<h1 className="text-5xl font-black tracking-tighter italic">MyCloset</h1>
|
||||||
@@ -1361,19 +1400,19 @@ export default function App() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button className="w-full py-5 bg-primary-600 text-white rounded-[2rem] font-black text-lg shadow-2xl hover:scale-[1.02] active:scale-95 transition-all">
|
<button className="w-full py-5 bg-primary-600 text-white rounded-3xl md:rounded-[2rem] font-black text-lg shadow-2xl hover:scale-[1.02] active:scale-95 transition-all">
|
||||||
{authMode === 'login' ? t('loginBtn') : t('registerBtn')}
|
{authMode === 'login' ? t('loginBtn') : t('registerBtn')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-10 text-center">
|
<div className="mt-6 md:mt-10 text-center">
|
||||||
<button type="button" onClick={() => setAuthMode(authMode === 'login' ? 'register' : 'login')} className="text-gray-400 font-black text-[10px] uppercase tracking-[0.3em] hover:text-primary-600 transition-colors text-inherit">
|
<button type="button" onClick={() => setAuthMode(authMode === 'login' ? 'register' : 'login')} className="text-gray-400 font-black text-[10px] uppercase tracking-[0.3em] hover:text-primary-600 transition-colors text-inherit">
|
||||||
{authMode === 'login' ? t('createAccount') : t('haveAccount')}
|
{authMode === 'login' ? t('createAccount') : t('haveAccount')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Language Switcher for Auth Page */}
|
{/* Language Switcher for Auth Page */}
|
||||||
<div className="mt-12 flex justify-center gap-5 pt-8 border-t border-gray-100 dark:border-gray-800/50">
|
<div className="mt-6 md:mt-12 flex justify-center gap-5 pt-8 border-t border-gray-100 dark:border-gray-800/50">
|
||||||
{[
|
{[
|
||||||
{ id: 'PT', flag: '🇵🇹' },
|
{ id: 'PT', flag: '🇵🇹' },
|
||||||
{ id: 'EN', flag: '🇬🇧' },
|
{ id: 'EN', flag: '🇬🇧' },
|
||||||
@@ -1399,12 +1438,12 @@ export default function App() {
|
|||||||
{/* Modal de Forgot Password */}
|
{/* Modal de Forgot Password */}
|
||||||
{showForgotPasswordModal && (
|
{showForgotPasswordModal && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowForgotPasswordModal(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowForgotPasswordModal(false)}>
|
||||||
<Card className="w-full max-w-md p-8 animate-in zoom-in-95" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-md p-5 md:p-8 animate-in zoom-in-95" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<h3 className="text-2xl font-black mb-4 text-center text-inherit">{t('forgotPassword')}</h3>
|
<h3 className="text-2xl font-black mb-4 text-center text-inherit">{t('forgotPassword')}</h3>
|
||||||
<p className="text-center opacity-70 mb-8 text-sm text-inherit">{t('forgotPasswordPrompt')}</p>
|
<p className="text-center opacity-70 mb-5 md:mb-8 text-sm text-inherit">{t('forgotPasswordPrompt')}</p>
|
||||||
<form onSubmit={handleForgotPasswordSubmit} className="space-y-4">
|
<form onSubmit={handleForgotPasswordSubmit} className="space-y-4">
|
||||||
<input name="resetEmail" type="email" placeholder={t('emailPlaceholder')} required value={forgotPasswordEmail} onChange={e => setForgotPasswordEmail(e.target.value)} className="w-full p-5 rounded-2xl bg-gray-50 dark:bg-gray-800 border-none focus:ring-2 focus:ring-primary-500 outline-none font-bold text-inherit" />
|
<input name="resetEmail" type="email" placeholder={t('emailPlaceholder')} required value={forgotPasswordEmail} onChange={e => setForgotPasswordEmail(e.target.value)} className="w-full p-5 rounded-2xl bg-gray-50 dark:bg-gray-800 border-none focus:ring-2 focus:ring-primary-500 outline-none font-bold text-inherit" />
|
||||||
<button type="submit" disabled={loading} className="w-full py-5 bg-primary-600 text-white rounded-[2rem] font-black text-lg shadow-2xl hover:scale-[1.02] active:scale-95 transition-all disabled:opacity-50">
|
<button type="submit" disabled={loading} className="w-full py-5 bg-primary-600 text-white rounded-3xl md:rounded-[2rem] font-black text-lg shadow-2xl hover:scale-[1.02] active:scale-95 transition-all disabled:opacity-50">
|
||||||
{loading ? t('saving') : t('sendEmailBtn')}
|
{loading ? t('saving') : t('sendEmailBtn')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -1427,14 +1466,19 @@ export default function App() {
|
|||||||
${darkMode ? 'bg-gray-900/80 border-gray-800' : 'bg-white border-gray-100'}
|
${darkMode ? 'bg-gray-900/80 border-gray-800' : 'bg-white border-gray-100'}
|
||||||
${sidebarOpen ? 'w-80 translate-x-0' : 'w-0 -translate-x-full md:w-0 md:opacity-0'}
|
${sidebarOpen ? 'w-80 translate-x-0' : 'w-0 -translate-x-full md:w-0 md:opacity-0'}
|
||||||
`}>
|
`}>
|
||||||
<div className="p-10 h-full flex flex-col backdrop-blur-xl">
|
<div className="p-6 md:p-10 h-full flex flex-col backdrop-blur-xl">
|
||||||
<button onClick={() => setView('closet')} className="flex items-center gap-4 mb-16 hover:scale-[1.02] transition-transform text-left cursor-pointer w-full">
|
<button onClick={() => setView('closet')} className="flex items-center gap-4 mb-5 md:mb-8 md:mb-16 hover:scale-[1.02] transition-transform text-left cursor-pointer w-full">
|
||||||
<div className="p-3 bg-primary-600 rounded-2xl shadow-xl shadow-primary-600/30">
|
<div className="p-3 bg-primary-600 rounded-2xl shadow-xl shadow-primary-600/30">
|
||||||
<Shirt className="text-white" size={24} />
|
<Shirt className="text-white" size={24} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-3xl font-black tracking-tighter italic">MyCloset</span>
|
<span className="text-3xl font-black tracking-tighter italic">MyCloset</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{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')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<nav className="flex-1 space-y-3">
|
<nav className="flex-1 space-y-3">
|
||||||
{[
|
{[
|
||||||
{ id: 'dashboard', label: t('dashboard'), icon: LayoutDashboard },
|
{ id: 'dashboard', label: t('dashboard'), icon: LayoutDashboard },
|
||||||
@@ -1459,9 +1503,14 @@ export default function App() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</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')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-auto pt-10 border-t border-inherit">
|
<div className="mt-auto pt-10 border-t border-inherit">
|
||||||
<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 onClick={() => setView('profile')} className="w-full flex items-center gap-4 mb-5 md: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" />
|
||||||
@@ -1540,18 +1589,18 @@ export default function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Conteúdo Dinâmico */}
|
{/* Conteúdo Dinâmico */}
|
||||||
<div className="flex-1 overflow-y-auto p-4 md:p-12 space-y-8 md:space-y-12">
|
<div className="flex-1 overflow-y-auto p-4 md:p-12 space-y-5 md:space-y-8 md:space-y-12">
|
||||||
|
|
||||||
{/* DASHBOARD */}
|
{/* DASHBOARD */}
|
||||||
{view === 'dashboard' && (
|
{view === 'dashboard' && (
|
||||||
<div className="space-y-12 animate-in fade-in duration-700">
|
<div className="space-y-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-8">
|
||||||
{[
|
{[
|
||||||
{ label: t('readyClothes'), val: activeClothes.length, icon: Shirt, col: 'primary' },
|
{ label: t('readyClothes'), val: activeClothes.length, icon: Shirt, col: 'primary' },
|
||||||
{ label: t('inLaundry'), val: laundryClothes.length, icon: Droplets, col: 'blue' },
|
{ label: t('inLaundry'), val: laundryClothes.length, icon: Droplets, col: 'blue' },
|
||||||
{ label: t('myLooks'), val: looks.length, icon: Sparkles, col: 'purple' },
|
{ label: t('myLooks'), val: looks.length, icon: Sparkles, col: 'purple' },
|
||||||
].map((s, i) => (
|
].map((s, i) => (
|
||||||
<Card key={i} className="p-8 group hover:-translate-y-2" darkMode={darkMode}>
|
<Card key={i} className="p-5 md:p-8 group hover:-translate-y-2" darkMode={darkMode}>
|
||||||
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center mb-6 shadow-inner ${darkMode ? 'bg-gray-700 text-primary-400' : 'bg-primary-50 text-primary-600'}`}>
|
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center mb-6 shadow-inner ${darkMode ? 'bg-gray-700 text-primary-400' : 'bg-primary-50 text-primary-600'}`}>
|
||||||
<s.icon size={28} />
|
<s.icon size={28} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1561,8 +1610,8 @@ export default function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-8">
|
||||||
<div className="lg:col-span-2 p-10 rounded-[2rem] relative overflow-hidden shadow-2xl" style={{ backgroundColor: 'hsl(var(--primary-600))', color: 'white' }}>
|
<div className="lg:col-span-2 p-6 md:p-10 rounded-3xl md:rounded-[2rem] relative overflow-hidden shadow-2xl" style={{ backgroundColor: 'hsl(var(--primary-600))', color: 'white' }}>
|
||||||
<div className="relative z-10 flex flex-col justify-between h-full">
|
<div className="relative z-10 flex flex-col justify-between h-full">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
@@ -1576,7 +1625,7 @@ export default function App() {
|
|||||||
{weatherData ? `${t('weatherForecastDesc').replace('{max}', weatherData.maxTemp).replace('{min}', weatherData.minTemp)} ${t('weatherMsg')}` : t('weatherMsg')}
|
{weatherData ? `${t('weatherForecastDesc').replace('{max}', weatherData.maxTemp).replace('{min}', weatherData.minTemp)} ${t('weatherMsg')}` : t('weatherMsg')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10 flex gap-4 items-center">
|
<div className="mt-6 md:mt-10 flex gap-4 items-center">
|
||||||
{activeClothes.filter(c => c.category === 'Tops').slice(0, 2).map(c => (
|
{activeClothes.filter(c => c.category === 'Tops').slice(0, 2).map(c => (
|
||||||
<div key={c.id} className="w-16 h-16 rounded-xl overflow-hidden border-2" style={{ borderColor: 'rgba(255,255,255,0.4)' }}>
|
<div key={c.id} className="w-16 h-16 rounded-xl overflow-hidden border-2" style={{ borderColor: 'rgba(255,255,255,0.4)' }}>
|
||||||
<img src={c.imageUrl} className="w-full h-full object-cover" alt="" />
|
<img src={c.imageUrl} className="w-full h-full object-cover" alt="" />
|
||||||
@@ -1590,8 +1639,8 @@ export default function App() {
|
|||||||
<CloudSun size={350} className="absolute -bottom-20 -right-20" style={{ color: 'rgba(255,255,255,0.1)' }} />
|
<CloudSun size={350} className="absolute -bottom-20 -right-20" style={{ color: 'rgba(255,255,255,0.1)' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="p-8" darkMode={darkMode}>
|
<Card className="p-5 md:p-8" darkMode={darkMode}>
|
||||||
<h3 className="text-lg font-black tracking-tight mb-8 flex items-center gap-2 text-inherit"><PieChart size={20} /> {t('topColors')}</h3>
|
<h3 className="text-lg font-black tracking-tight mb-5 md:mb-8 flex items-center gap-2 text-inherit"><PieChart size={20} /> {t('topColors')}</h3>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{colorStats.length > 0 ? colorStats.map(stat => (
|
{colorStats.length > 0 ? colorStats.map(stat => (
|
||||||
<div key={stat.color} className="space-y-2">
|
<div key={stat.color} className="space-y-2">
|
||||||
@@ -1614,13 +1663,13 @@ export default function App() {
|
|||||||
|
|
||||||
{/* ARMÁRIO & WISHLIST */}
|
{/* ARMÁRIO & WISHLIST */}
|
||||||
{(view === 'closet' || view === 'wishlist') && (
|
{(view === 'closet' || view === 'wishlist') && (
|
||||||
<div className="space-y-10 animate-in slide-in-from-bottom-8 duration-700">
|
<div className="space-y-6 md:space-y-10 animate-in slide-in-from-bottom-8 duration-700">
|
||||||
<div className="flex flex-col xl:flex-row gap-8 items-center justify-between">
|
<div className="flex flex-col xl:flex-row gap-4 md:gap-8 items-center justify-between">
|
||||||
<div className="relative w-full max-w-2xl">
|
<div className="relative w-full max-w-2xl">
|
||||||
<Search className="absolute left-6 top-1/2 -translate-y-1/2 text-gray-400" size={24} />
|
<Search className="absolute left-6 top-1/2 -translate-y-1/2 text-gray-400" size={24} />
|
||||||
<input
|
<input
|
||||||
placeholder={t('searchPlaceholder')}
|
placeholder={t('searchPlaceholder')}
|
||||||
className={`w-full pl-16 pr-8 py-6 rounded-[2rem] shadow-inner outline-none border-none focus:ring-4 focus:ring-primary-500/10 font-bold text-lg ${darkMode ? 'bg-gray-800' : 'bg-gray-100'}`}
|
className={`w-full pl-16 pr-8 py-6 rounded-3xl md:rounded-[2rem] shadow-inner outline-none border-none focus:ring-4 focus:ring-primary-500/10 font-bold text-lg ${darkMode ? 'bg-gray-800' : 'bg-gray-100'}`}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1628,7 +1677,7 @@ export default function App() {
|
|||||||
<div className="flex gap-3 w-full xl:w-auto">
|
<div className="flex gap-3 w-full xl:w-auto">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowClosetFilters(true)}
|
onClick={() => setShowClosetFilters(true)}
|
||||||
className="flex items-center gap-3 px-8 py-4 bg-primary-600 text-white rounded-[2rem] font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all"
|
className="flex items-center gap-3 px-5 md:px-8 py-4 bg-primary-600 text-white rounded-3xl md:rounded-[2rem] font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all"
|
||||||
>
|
>
|
||||||
<Filter size={18} /> {t('advancedFilters')}
|
<Filter size={18} /> {t('advancedFilters')}
|
||||||
{(colorFilter || favoriteFilter || ageFilter !== 'any' || (categoryFilter !== 'Todos' && categoryFilter !== t('all'))) && (
|
{(colorFilter || favoriteFilter || ageFilter !== 'any' || (categoryFilter !== 'Todos' && categoryFilter !== t('all'))) && (
|
||||||
@@ -1667,8 +1716,8 @@ export default function App() {
|
|||||||
|
|
||||||
<div className={
|
<div className={
|
||||||
cardSize === 'small' ? 'grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-5 2xl:grid-cols-6 gap-6'
|
cardSize === 'small' ? 'grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-5 2xl:grid-cols-6 gap-6'
|
||||||
: cardSize === 'medium' ? 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-8'
|
: cardSize === 'medium' ? 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-4 md:gap-8'
|
||||||
: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-10'
|
: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6 md:gap-10'
|
||||||
}>
|
}>
|
||||||
{filteredClothes.map(item => {
|
{filteredClothes.map(item => {
|
||||||
const styles = {
|
const styles = {
|
||||||
@@ -1792,16 +1841,16 @@ export default function App() {
|
|||||||
|
|
||||||
{/* LAVANDARIA */}
|
{/* LAVANDARIA */}
|
||||||
{view === 'laundry' && (
|
{view === 'laundry' && (
|
||||||
<div className="space-y-12 animate-in fade-in duration-700">
|
<div className="space-y-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700">
|
||||||
<div className="text-center max-w-2xl mx-auto space-y-4 text-inherit">
|
<div className="text-center max-w-2xl mx-auto space-y-4 text-inherit">
|
||||||
<div className="w-20 h-20 bg-blue-100 dark:bg-blue-900/30 rounded-[2rem] flex items-center justify-center mx-auto text-blue-600 shadow-inner">
|
<div className="w-20 h-20 bg-blue-100 dark:bg-blue-900/30 rounded-3xl md:rounded-[2rem] flex items-center justify-center mx-auto text-blue-600 shadow-inner">
|
||||||
<Droplets size={40} />
|
<Droplets size={40} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-4xl font-black tracking-tight">{t('laundryBasket')}</h3>
|
<h3 className="text-4xl font-black tracking-tight">{t('laundryBasket')}</h3>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-8">
|
||||||
{laundryClothes.map(item => (
|
{laundryClothes.map(item => (
|
||||||
<Card key={item.id} className="p-4 flex items-center gap-4 border-blue-200 dark:border-blue-900/50" darkMode={darkMode}>
|
<Card key={item.id} className="p-4 flex items-center gap-4 border-blue-200 dark:border-blue-900/50" darkMode={darkMode}>
|
||||||
<img src={item.imageUrl} className="w-16 h-16 rounded-2xl object-cover shadow-sm shrink-0" alt="" />
|
<img src={item.imageUrl} className="w-16 h-16 rounded-2xl object-cover shadow-sm shrink-0" alt="" />
|
||||||
@@ -1825,10 +1874,10 @@ export default function App() {
|
|||||||
|
|
||||||
{/* LOOKS */}
|
{/* LOOKS */}
|
||||||
{view === 'outfits' && (
|
{view === 'outfits' && (
|
||||||
<div className="space-y-12 animate-in fade-in duration-700 pb-20">
|
<div className="space-y-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-12">
|
||||||
<div className="lg:col-span-1 space-y-8">
|
<div className="lg:col-span-1 space-y-5 md:space-y-8">
|
||||||
<Card className="p-8 border-primary-200" darkMode={darkMode}>
|
<Card className="p-5 md:p-8 border-primary-200" darkMode={darkMode}>
|
||||||
<h3 className="text-2xl font-black tracking-tighter mb-6 flex items-center gap-3 text-inherit">
|
<h3 className="text-2xl font-black tracking-tighter mb-6 flex items-center gap-3 text-inherit">
|
||||||
<Sparkles className="text-primary-600" /> {editingLook ? t('editLook') || 'Editar Outfit' : t('createNewLook')}
|
<Sparkles className="text-primary-600" /> {editingLook ? t('editLook') || 'Editar Outfit' : t('createNewLook')}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -1908,7 +1957,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="lg:col-span-2 space-y-10">
|
<div className="lg:col-span-2 space-y-6 md:space-y-10">
|
||||||
{(() => {
|
{(() => {
|
||||||
const filteredBySectionLooks = looks.filter(look => {
|
const filteredBySectionLooks = looks.filter(look => {
|
||||||
const matchesSection = activeSectionFilter === 'all' || (look.sections && look.sections.includes(activeSectionFilter));
|
const matchesSection = activeSectionFilter === 'all' || (look.sections && look.sections.includes(activeSectionFilter));
|
||||||
@@ -1942,7 +1991,7 @@ export default function App() {
|
|||||||
return item && item.status === 'laundry';
|
return item && item.status === 'laundry';
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Card key={look.id} className={`p-8 group hover:shadow-2xl transition-all border-none shadow-md ${hasLaundryPieces ? 'opacity-75' : ''}`} darkMode={darkMode}>
|
<Card key={look.id} className={`p-5 md:p-8 group hover:shadow-2xl transition-all border-none shadow-md ${hasLaundryPieces ? 'opacity-75' : ''}`} darkMode={darkMode}>
|
||||||
<div className="flex justify-between items-start mb-6">
|
<div className="flex justify-between items-start mb-6">
|
||||||
<div className="text-inherit">
|
<div className="text-inherit">
|
||||||
<h4 className="text-xl font-black tracking-tight">{look.name}</h4>
|
<h4 className="text-xl font-black tracking-tight">{look.name}</h4>
|
||||||
@@ -2040,11 +2089,11 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{availableLooks.length > 0 ? (
|
{availableLooks.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
|
||||||
{availableLooks.map(renderLookCard)}
|
{availableLooks.map(renderLookCard)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-12 text-center opacity-20 font-black uppercase tracking-[0.3em] text-sm">{t('noLooksAvailable')}</div>
|
<div className="py-6 md:py-12 text-center opacity-20 font-black uppercase tracking-[0.3em] text-sm">{t('noLooksAvailable')}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2055,7 +2104,7 @@ export default function App() {
|
|||||||
<div className="w-2.5 h-2.5 rounded-full bg-blue-400"></div>
|
<div className="w-2.5 h-2.5 rounded-full bg-blue-400"></div>
|
||||||
<h3 className="text-2xl font-black tracking-tighter text-inherit">{t('toBeWashed')} <span className="text-sm font-bold opacity-40">— {t('unavailable')} ({laundryLooks.length})</span></h3>
|
<h3 className="text-2xl font-black tracking-tighter text-inherit">{t('toBeWashed')} <span className="text-sm font-bold opacity-40">— {t('unavailable')} ({laundryLooks.length})</span></h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
|
||||||
{laundryLooks.map(renderLookCard)}
|
{laundryLooks.map(renderLookCard)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2222,8 +2271,8 @@ export default function App() {
|
|||||||
{/* ADICIONAR / EDITAR */}
|
{/* ADICIONAR / EDITAR */}
|
||||||
{(view === 'add' || view === 'edit') && (
|
{(view === 'add' || view === 'edit') && (
|
||||||
<div className="max-w-4xl mx-auto animate-in zoom-in-95 duration-500">
|
<div className="max-w-4xl mx-auto animate-in zoom-in-95 duration-500">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 md:gap-12 items-start">
|
||||||
<div className="space-y-8">
|
<div className="space-y-5 md:space-y-8">
|
||||||
<h3 className="text-5xl font-black tracking-tighter text-inherit">{editingItem ? t('edit') : t('newItem')}</h3>
|
<h3 className="text-5xl font-black tracking-tighter text-inherit">{editingItem ? t('edit') : t('newItem')}</h3>
|
||||||
<Card className="aspect-[3/4] overflow-hidden shadow-2xl relative" darkMode={darkMode}>
|
<Card className="aspect-[3/4] overflow-hidden shadow-2xl relative" darkMode={darkMode}>
|
||||||
{imageUrlDraft ? (
|
{imageUrlDraft ? (
|
||||||
@@ -2237,8 +2286,8 @@ export default function App() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="p-10 shadow-2xl" darkMode={darkMode}>
|
<Card className="p-6 md:p-10 shadow-2xl" darkMode={darkMode}>
|
||||||
<form onSubmit={saveItem} className="space-y-8">
|
<form onSubmit={saveItem} className="space-y-5 md:space-y-8">
|
||||||
<Input label={t('name')} name="name" defaultValue={editingItem?.name} required />
|
<Input label={t('name')} name="name" defaultValue={editingItem?.name} required />
|
||||||
<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('category')}</label>
|
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('category')}</label>
|
||||||
@@ -2335,7 +2384,7 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="flex gap-4 pt-6">
|
<div className="flex gap-4 pt-6">
|
||||||
<button type="button" onClick={() => { setEditingItem(null); setImageUrlDraft(''); setView('closet'); }} className="flex-1 font-black uppercase text-[10px] opacity-40 hover:opacity-100 tracking-widest transition-all text-inherit">{t('cancel')}</button>
|
<button type="button" onClick={() => { setEditingItem(null); setImageUrlDraft(''); setView('closet'); }} className="flex-1 font-black uppercase text-[10px] opacity-40 hover:opacity-100 tracking-widest transition-all text-inherit">{t('cancel')}</button>
|
||||||
<button type="submit" className="flex-1 py-5 bg-primary-600 text-white rounded-[2rem] font-black uppercase tracking-widest text-[10px] shadow-2xl shadow-primary-600/40 hover:scale-[1.02] active:scale-95 transition-all">
|
<button type="submit" className="flex-1 py-5 bg-primary-600 text-white rounded-3xl md:rounded-[2rem] font-black uppercase tracking-widest text-[10px] shadow-2xl shadow-primary-600/40 hover:scale-[1.02] active:scale-95 transition-all">
|
||||||
{editingItem ? t('save') : t('register')}
|
{editingItem ? t('save') : t('register')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2347,9 +2396,9 @@ export default function App() {
|
|||||||
|
|
||||||
{/* PERFIL */}
|
{/* PERFIL */}
|
||||||
{view === 'profile' && (
|
{view === 'profile' && (
|
||||||
<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-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
<Card className="p-10 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
<Card className="p-6 md:p-10 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
||||||
<div className="flex items-center gap-8 relative z-10 text-inherit">
|
<div className="flex items-center gap-4 md:gap-8 relative z-10 text-inherit">
|
||||||
<div className="w-24 h-24 rounded-[2.5rem] bg-primary-600 flex items-center justify-center text-white text-4xl font-black shadow-2xl relative overflow-hidden group cursor-pointer">
|
<div className="w-24 h-24 rounded-[2.5rem] bg-primary-600 flex items-center justify-center text-white text-4xl font-black shadow-2xl relative overflow-hidden group cursor-pointer">
|
||||||
{userProfile?.avatar ? (
|
{userProfile?.avatar ? (
|
||||||
<img src={userProfile.avatar} className="w-full h-full object-cover" alt="Profile" />
|
<img src={userProfile.avatar} className="w-full h-full object-cover" alt="Profile" />
|
||||||
@@ -2383,7 +2432,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="p-8" darkMode={darkMode}>
|
<Card className="p-5 md:p-8" darkMode={darkMode}>
|
||||||
<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">
|
||||||
@@ -2434,10 +2483,10 @@ export default function App() {
|
|||||||
|
|
||||||
{/* COMUNIDADE */}
|
{/* COMUNIDADE */}
|
||||||
{view === 'community' && (
|
{view === 'community' && (
|
||||||
<div className="max-w-7xl mx-auto space-y-12 animate-in fade-in duration-700 pb-20">
|
<div className="max-w-7xl mx-auto space-y-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
{!selectedCommunityUser ? (
|
{!selectedCommunityUser ? (
|
||||||
<>
|
<>
|
||||||
<div className="relative mb-8 flex gap-4">
|
<div className="relative mb-5 md:mb-8 flex gap-4">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-6 top-1/2 -translate-y-1/2 opacity-40 text-inherit" size={24} />
|
<Search className="absolute left-6 top-1/2 -translate-y-1/2 opacity-40 text-inherit" size={24} />
|
||||||
<input
|
<input
|
||||||
@@ -2456,7 +2505,7 @@ export default function App() {
|
|||||||
setShowRecommended(!showRecommended);
|
setShowRecommended(!showRecommended);
|
||||||
if (!showRecommended) setCommunitySearchTerm('');
|
if (!showRecommended) setCommunitySearchTerm('');
|
||||||
}}
|
}}
|
||||||
className={`px-8 rounded-3xl font-black transition-all shadow-xl shadow-black/5 flex items-center justify-center gap-2 whitespace-nowrap ${showRecommended ? 'bg-primary-600 text-white' : (darkMode ? 'bg-gray-800 text-inherit hover:bg-gray-700' : 'bg-white text-inherit hover:bg-gray-50')}`}
|
className={`px-5 md:px-8 rounded-3xl font-black transition-all shadow-xl shadow-black/5 flex items-center justify-center gap-2 whitespace-nowrap ${showRecommended ? 'bg-primary-600 text-white' : (darkMode ? 'bg-gray-800 text-inherit hover:bg-gray-700' : 'bg-white text-inherit hover:bg-gray-50')}`}
|
||||||
>
|
>
|
||||||
<MapPin size={20} />
|
<MapPin size={20} />
|
||||||
<span className="hidden sm:inline">{t('recommended') || 'Recomendados'}</span>
|
<span className="hidden sm:inline">{t('recommended') || 'Recomendados'}</span>
|
||||||
@@ -2464,7 +2513,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{communityUsers.length === 0 ? (
|
{communityUsers.length === 0 ? (
|
||||||
<div className="col-span-full text-center py-12 opacity-50 text-inherit font-black text-xl">
|
<div className="col-span-full text-center py-6 md:py-12 opacity-50 text-inherit font-black text-xl">
|
||||||
{t('noUsersFound')}
|
{t('noUsersFound')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -2485,13 +2534,13 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-8">
|
<div className="space-y-5 md:space-y-8">
|
||||||
<button onClick={() => { setSelectedCommunityUser(null); setShowInspectModal(false); }} className="flex items-center gap-2 opacity-60 hover:opacity-100 transition-opacity font-black text-inherit uppercase text-xs tracking-widest">
|
<button onClick={() => { setSelectedCommunityUser(null); setShowInspectModal(false); }} 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
|
<ChevronLeft size={16} /> Voltar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Card className="p-8 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
<Card className="p-5 md:p-8 border-primary-100 relative overflow-hidden" darkMode={darkMode}>
|
||||||
<div className="flex items-center gap-8 relative z-10 text-inherit">
|
<div className="flex items-center gap-4 md: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">
|
<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>}
|
{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>
|
||||||
@@ -2523,12 +2572,12 @@ export default function App() {
|
|||||||
|
|
||||||
{showInspectModal && (
|
{showInspectModal && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowInspectModal(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowInspectModal(false)}>
|
||||||
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95 relative" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-lg p-5 md:p-8 animate-in zoom-in-95 relative" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<button onClick={() => setShowInspectModal(false)} className="absolute top-6 right-6 opacity-50 hover:opacity-100 text-inherit">
|
<button onClick={() => setShowInspectModal(false)} className="absolute top-6 right-6 opacity-50 hover:opacity-100 text-inherit">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-6 mb-8 text-inherit">
|
<div className="flex items-center gap-6 mb-5 md:mb-8 text-inherit">
|
||||||
<div className="w-20 h-20 rounded-[2rem] bg-primary-600 text-white flex items-center justify-center font-black text-3xl overflow-hidden">
|
<div className="w-20 h-20 rounded-3xl md:rounded-[2rem] bg-primary-600 text-white flex items-center justify-center font-black text-3xl 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>}
|
{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>
|
||||||
<div>
|
<div>
|
||||||
@@ -2539,29 +2588,29 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="space-y-6 text-inherit bg-gray-50 dark:bg-gray-800/50 p-6 rounded-3xl">
|
<div className="space-y-6 text-inherit bg-gray-50 dark:bg-gray-800/50 p-6 rounded-3xl">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Data de Nascimento</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('dob')}</p>
|
||||||
<p className="font-bold">{selectedUserProfile?.dob ? new Date(selectedUserProfile.dob).toLocaleDateString() : 'Não especificada'}</p>
|
<p className="font-bold">{selectedUserProfile?.dob ? new Date(selectedUserProfile.dob).toLocaleDateString() : 'Não especificada'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Localidade</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('location')}</p>
|
||||||
<p className="font-bold">{selectedUserProfile?.location || selectedCommunityUser.location || 'Não especificada'}</p>
|
<p className="font-bold">{selectedUserProfile?.location || selectedCommunityUser.location || 'Não especificada'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Bio</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('bio')}</p>
|
||||||
<p className="font-bold opacity-80">{selectedUserProfile?.bio || 'Sem biografia'}</p>
|
<p className="font-bold opacity-80">{selectedUserProfile?.bio || 'Sem biografia'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Peças Registadas</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('registeredPieces')}</p>
|
||||||
<p className="text-2xl font-black">{selectedUserClothes.length}</p>
|
<p className="text-2xl font-black">{selectedUserClothes.length}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Outfits Criados</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('createdOutfits')}</p>
|
||||||
<p className="text-2xl font-black">{selectedUserLooks.length}</p>
|
<p className="text-2xl font-black">{selectedUserLooks.length}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Data de Registo da Conta</p>
|
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('accountRegistrationDate')}</p>
|
||||||
<p className="font-bold">{selectedUserProfile?.createdAt ? new Date(selectedUserProfile.createdAt).toLocaleDateString() : 'Desconhecida'}</p>
|
<p className="font-bold">{selectedUserProfile?.createdAt ? new Date(selectedUserProfile.createdAt).toLocaleDateString() : 'Desconhecida'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2575,12 +2624,12 @@ export default function App() {
|
|||||||
{t('isPrivateUser')}
|
{t('isPrivateUser')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-12 text-inherit">
|
<div className="space-y-5 md:space-y-8 md:space-y-12 text-inherit">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black mb-6 uppercase tracking-widest text-[11px] opacity-50">{t('userOutfits')} ({selectedUserLooks.length})</h3>
|
<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">
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6">
|
||||||
{selectedUserLooks.map(look => (
|
{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">
|
<div key={look.id} className="group relative aspect-[3/4] rounded-3xl md: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]) && (
|
{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" />
|
<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" />
|
||||||
)}
|
)}
|
||||||
@@ -2627,11 +2676,11 @@ export default function App() {
|
|||||||
|
|
||||||
{/* 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-5 md:space-y-8 md:space-y-12 animate-in fade-in duration-700 pb-20">
|
||||||
|
|
||||||
{/* Preferências */}
|
{/* Preferências */}
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4 md:gap-8">
|
||||||
<Card className="p-8" darkMode={darkMode}>
|
<Card className="p-5 md:p-8" darkMode={darkMode}>
|
||||||
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Settings className="text-primary-600" /> {t('preferences')}</h3>
|
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Settings className="text-primary-600" /> {t('preferences')}</h3>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -2676,10 +2725,21 @@ export default function App() {
|
|||||||
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${notificationsEnabled ? 'left-7' : 'left-1'}`}></div>
|
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${notificationsEnabled ? '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('keyboardShortcuts')}</p>
|
||||||
|
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">{t('enableNavigationQE')}</p>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => handleShortcutsToggle(!shortcutsEnabled)} className={`w-14 h-8 rounded-full transition-colors relative ${shortcutsEnabled ? 'bg-primary-600' : 'bg-gray-200'}`}>
|
||||||
|
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${shortcutsEnabled ? 'left-7' : 'left-1'}`}></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-inherit flex items-center gap-2">Notificação do Outfit Diário</p>
|
<p className="font-bold text-inherit flex items-center gap-2">{t('dailyOutfitNotification')}</p>
|
||||||
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">Receber notificação com o outfit planeado à hora marcada</p>
|
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">{t('receiveNotificationAtScheduledTime')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 w-full sm:w-auto justify-between sm:justify-end">
|
<div className="flex items-center gap-4 w-full sm:w-auto justify-between sm:justify-end">
|
||||||
{dailyOutfitNotifEnabled && (
|
{dailyOutfitNotifEnabled && (
|
||||||
@@ -2770,7 +2830,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="p-8" darkMode={darkMode}>
|
<Card className="p-5 md:p-8" darkMode={darkMode}>
|
||||||
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Bell className="text-primary-600" /> {t('feedbackTitle') || 'Suporte e Feedback'}</h3>
|
<h3 className="text-xl font-black mb-6 flex items-center gap-3 text-inherit"><Bell className="text-primary-600" /> {t('feedbackTitle') || 'Suporte e Feedback'}</h3>
|
||||||
<p className="opacity-60 text-sm font-medium mb-6">{t('feedbackDesc') || 'Tem alguma ideia, sugestão ou encontrou algum problema? Envie uma mensagem diretamente para nós!'}</p>
|
<p className="opacity-60 text-sm font-medium mb-6">{t('feedbackDesc') || 'Tem alguma ideia, sugestão ou encontrou algum problema? Envie uma mensagem diretamente para nós!'}</p>
|
||||||
<form onSubmit={async (e) => {
|
<form onSubmit={async (e) => {
|
||||||
@@ -2849,13 +2909,13 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="p-10 border-red-200 bg-red-50/10" darkMode={darkMode}>
|
<Card className="p-6 md:p-10 border-red-200 bg-red-50/10" darkMode={darkMode}>
|
||||||
<div className="flex flex-col md:flex-row items-center justify-between gap-8">
|
<div className="flex flex-col md:flex-row items-center justify-between gap-4 md:gap-8">
|
||||||
<div className="text-inherit">
|
<div className="text-inherit">
|
||||||
<h4 className="text-xl font-black text-red-700 flex items-center gap-3"><ShieldAlert /> {t('criticalZone')}</h4>
|
<h4 className="text-xl font-black text-red-700 flex items-center gap-3"><ShieldAlert /> {t('criticalZone')}</h4>
|
||||||
<p className="opacity-60 font-bold text-sm mt-2">{t('fullCleanActions')}</p>
|
<p className="opacity-60 font-bold text-sm mt-2">{t('fullCleanActions')}</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={clearAllToTrash} className="px-8 py-4 bg-red-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-red-700 transition-all">{t('clearAll')}</button>
|
<button onClick={clearAllToTrash} className="px-5 md:px-8 py-4 bg-red-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-red-700 transition-all">{t('clearAll')}</button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -2867,7 +2927,7 @@ export default function App() {
|
|||||||
{/* Modal do Planeador - Escolher Outfit */}
|
{/* Modal do Planeador - Escolher Outfit */}
|
||||||
{showPlannerPicker && plannerPickerDate && (
|
{showPlannerPicker && plannerPickerDate && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowPlannerPicker(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowPlannerPicker(false)}>
|
||||||
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95 flex flex-col max-h-[80vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-lg p-5 md:p-8 animate-in zoom-in-95 flex flex-col max-h-[80vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black text-inherit flex items-center gap-3">
|
<h3 className="text-xl font-black text-inherit flex items-center gap-3">
|
||||||
@@ -2891,7 +2951,7 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
||||||
{looks.length === 0 ? (
|
{looks.length === 0 ? (
|
||||||
<div className="py-12 text-center opacity-30 font-black uppercase tracking-[0.3em] text-sm">{t('noOutfitCreated')}</div>
|
<div className="py-6 md:py-12 text-center opacity-30 font-black uppercase tracking-[0.3em] text-sm">{t('noOutfitCreated')}</div>
|
||||||
) : looks.map(look => {
|
) : looks.map(look => {
|
||||||
const plan = outfitPlans.find(p => p.date === plannerPickerDate);
|
const plan = outfitPlans.find(p => p.date === plannerPickerDate);
|
||||||
const selectedIds = plan ? (plan.lookIds || (plan.lookId ? [plan.lookId] : [])) : [];
|
const selectedIds = plan ? (plan.lookIds || (plan.lookId ? [plan.lookId] : [])) : [];
|
||||||
@@ -2938,7 +2998,7 @@ export default function App() {
|
|||||||
{/* Modal de Notificações */}
|
{/* Modal de Notificações */}
|
||||||
{showNotificationsModal && (
|
{showNotificationsModal && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowNotificationsModal(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowNotificationsModal(false)}>
|
||||||
<Card className="w-full max-w-md p-8 animate-in zoom-in-95 flex flex-col max-h-[80vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-md p-5 md:p-8 animate-in zoom-in-95 flex flex-col max-h-[80vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -2974,7 +3034,7 @@ export default function App() {
|
|||||||
{/* Lista */}
|
{/* Lista */}
|
||||||
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
||||||
{notifications.length === 0 ? (
|
{notifications.length === 0 ? (
|
||||||
<div className="py-16 text-center flex flex-col items-center gap-4 opacity-30">
|
<div className="py-8 md:py-16 text-center flex flex-col items-center gap-4 opacity-30">
|
||||||
<Bell size={40} />
|
<Bell size={40} />
|
||||||
<span className="font-black uppercase tracking-[0.3em] text-sm">{t('noNotifications')}</span>
|
<span className="font-black uppercase tracking-[0.3em] text-sm">{t('noNotifications')}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -3035,8 +3095,8 @@ export default function App() {
|
|||||||
{/* Modal de Gestão de Secções */}
|
{/* Modal de Gestão de Secções */}
|
||||||
{showSectionManager && (
|
{showSectionManager && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowSectionManager(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowSectionManager(false)}>
|
||||||
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95 flex flex-col max-h-[90vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-lg p-5 md:p-8 animate-in zoom-in-95 flex flex-col max-h-[90vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-5 md:mb-8">
|
||||||
<h3 className="text-2xl font-black text-inherit flex items-center gap-3">
|
<h3 className="text-2xl font-black text-inherit flex items-center gap-3">
|
||||||
<FolderOpen size={24} className="text-primary-600" /> {t('manageSections')}
|
<FolderOpen size={24} className="text-primary-600" /> {t('manageSections')}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -3044,7 +3104,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Criar nova secção */}
|
{/* Criar nova secção */}
|
||||||
<div className={`flex gap-3 mb-8 p-4 rounded-2xl ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
<div className={`flex gap-3 mb-5 md:mb-8 p-4 rounded-2xl ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
||||||
<input
|
<input
|
||||||
value={newSectionName}
|
value={newSectionName}
|
||||||
onChange={e => setNewSectionName(e.target.value)}
|
onChange={e => setNewSectionName(e.target.value)}
|
||||||
@@ -3064,7 +3124,7 @@ export default function App() {
|
|||||||
{/* Lista de secções */}
|
{/* Lista de secções */}
|
||||||
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto space-y-3 custom-scrollbar">
|
||||||
{sections.length === 0 ? (
|
{sections.length === 0 ? (
|
||||||
<div className="py-12 text-center opacity-30 font-black uppercase tracking-[0.3em] text-sm">{t('noSections')}</div>
|
<div className="py-6 md:py-12 text-center opacity-30 font-black uppercase tracking-[0.3em] text-sm">{t('noSections')}</div>
|
||||||
) : sections.map(sec => (
|
) : sections.map(sec => (
|
||||||
<div key={sec.id} className={`flex items-center gap-4 p-4 rounded-2xl transition-all ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
<div key={sec.id} className={`flex items-center gap-4 p-4 rounded-2xl transition-all ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
||||||
{editingSectionId === sec.id ? (
|
{editingSectionId === sec.id ? (
|
||||||
@@ -3108,7 +3168,7 @@ export default function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={() => setShowSectionManager(false)} className="mt-8 w-full py-4 font-black uppercase text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">
|
<button onClick={() => setShowSectionManager(false)} className="mt-5 md:mt-8 w-full py-4 font-black uppercase text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -3118,13 +3178,13 @@ export default function App() {
|
|||||||
{/* Modal de Filtros Avançados */}
|
{/* Modal de Filtros Avançados */}
|
||||||
{showClosetFilters && (
|
{showClosetFilters && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowClosetFilters(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowClosetFilters(false)}>
|
||||||
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95 flex flex-col max-h-[90vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-lg p-5 md:p-8 animate-in zoom-in-95 flex flex-col max-h-[90vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-5 md:mb-8">
|
||||||
<h3 className="text-2xl font-black text-inherit flex items-center gap-3"><Filter size={24} className="text-primary-600" /> {t('advancedFilters')}</h3>
|
<h3 className="text-2xl font-black text-inherit flex items-center gap-3"><Filter size={24} className="text-primary-600" /> {t('advancedFilters')}</h3>
|
||||||
<button onClick={() => setShowClosetFilters(false)} className="p-2 bg-gray-100 dark:bg-gray-800 rounded-full hover:scale-110 transition-all text-inherit"><X size={20} /></button>
|
<button onClick={() => setShowClosetFilters(false)} className="p-2 bg-gray-100 dark:bg-gray-800 rounded-full hover:scale-110 transition-all text-inherit"><X size={20} /></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto space-y-8 pr-2 custom-scrollbar">
|
<div className="flex-1 overflow-y-auto space-y-5 md:space-y-8 pr-2 custom-scrollbar">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('closet')}</label>
|
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('closet')}</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
@@ -3167,7 +3227,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-8 flex gap-4 border-t mt-8 border-gray-100 dark:border-gray-800">
|
<div className="pt-8 flex gap-4 border-t mt-5 md:mt-8 border-gray-100 dark:border-gray-800">
|
||||||
<button onClick={() => { setCategoryFilter('Todos'); setColorFilter(''); setAgeFilter('any'); setFavoriteFilter(false); }} className="flex-1 py-4 font-black uppercase text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">{t('clearAll')}</button>
|
<button onClick={() => { setCategoryFilter('Todos'); setColorFilter(''); setAgeFilter('any'); setFavoriteFilter(false); }} className="flex-1 py-4 font-black uppercase text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">{t('clearAll')}</button>
|
||||||
<button onClick={() => setShowClosetFilters(false)} className="flex-1 py-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">{t('applyFilters')}</button>
|
<button onClick={() => setShowClosetFilters(false)} className="flex-1 py-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">{t('applyFilters')}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -3178,8 +3238,8 @@ export default function App() {
|
|||||||
{/* Modal de Idioma */}
|
{/* Modal de Idioma */}
|
||||||
{showLangModal && (
|
{showLangModal && (
|
||||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowLangModal(false)}>
|
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowLangModal(false)}>
|
||||||
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
<Card className="w-full max-w-lg p-5 md:p-8 animate-in zoom-in-95" darkMode={darkMode} onClick={e => e.stopPropagation()}>
|
||||||
<h3 className="text-2xl font-black mb-8 text-center text-inherit">{t('appLanguage')}</h3>
|
<h3 className="text-2xl font-black mb-5 md:mb-8 text-center text-inherit">{t('appLanguage')}</h3>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
{[
|
{[
|
||||||
{ id: 'PT', flag: '🇵🇹', label: t('portuguese') },
|
{ id: 'PT', flag: '🇵🇹', label: t('portuguese') },
|
||||||
@@ -3198,7 +3258,7 @@ export default function App() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setShowLangModal(false)} className="w-full mt-8 py-4 uppercase font-black text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">
|
<button onClick={() => setShowLangModal(false)} className="w-full mt-5 md:mt-8 py-4 uppercase font-black text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -3209,11 +3269,11 @@ export default function App() {
|
|||||||
{showSharedLookModal && sharedLookData && (
|
{showSharedLookModal && sharedLookData && (
|
||||||
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => { setShowSharedLookModal(false); setSharedLookData(null); }}>
|
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => { setShowSharedLookModal(false); setSharedLookData(null); }}>
|
||||||
<div
|
<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'}`}
|
className={`w-full max-w-lg rounded-3xl md:rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header com gradiente */}
|
{/* Header com gradiente */}
|
||||||
<div className="relative p-8 pb-6" style={{ background: 'linear-gradient(135deg, hsl(var(--primary-600)), hsl(var(--primary-400)))' }}>
|
<div className="relative p-5 md: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="absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(circle at 80% 20%, white 0%, transparent 60%)' }} />
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
@@ -3228,9 +3288,9 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Peças do look */}
|
{/* Peças do look */}
|
||||||
<div className={`p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
<div className={`p-5 md:p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
||||||
<p className="text-[10px] font-black uppercase tracking-widest opacity-40 mb-4">{t('includedPieces')}</p>
|
<p className="text-[10px] font-black uppercase tracking-widest opacity-40 mb-4">{t('includedPieces')}</p>
|
||||||
<div className="flex flex-wrap gap-3 mb-8">
|
<div className="flex flex-wrap gap-3 mb-5 md:mb-8">
|
||||||
{sharedLookData.items.map((item, idx) => (
|
{sharedLookData.items.map((item, idx) => (
|
||||||
<div key={idx} className="relative group/item">
|
<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">
|
<div className="w-20 h-20 rounded-2xl overflow-hidden border-2 border-gray-100 dark:border-gray-700 shadow-lg">
|
||||||
@@ -3244,7 +3304,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Descrição das peças */}
|
{/* Descrição das peças */}
|
||||||
<div className={`space-y-2 mb-8 max-h-32 overflow-y-auto custom-scrollbar`}>
|
<div className={`space-y-2 mb-5 md:mb-8 max-h-32 overflow-y-auto custom-scrollbar`}>
|
||||||
{sharedLookData.items.map((item, idx) => (
|
{sharedLookData.items.map((item, idx) => (
|
||||||
<div key={idx} className={`flex items-center gap-3 px-4 py-2.5 rounded-xl ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
<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-xs font-black truncate flex-1">{item.name}</span>
|
||||||
@@ -3282,10 +3342,10 @@ export default function App() {
|
|||||||
{showDailyOutfitModal && (
|
{showDailyOutfitModal && (
|
||||||
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setShowDailyOutfitModal(false)}>
|
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setShowDailyOutfitModal(false)}>
|
||||||
<div
|
<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'}`}
|
className={`w-full max-w-lg rounded-3xl md:rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="relative p-8 pb-6 bg-gradient-to-br from-primary-600 to-primary-400">
|
<div className="relative p-5 md:p-8 pb-6 bg-gradient-to-br from-primary-600 to-primary-400">
|
||||||
<div className="absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(circle at 80% 20%, white 0%, transparent 60%)' }} />
|
<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="relative z-10">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
@@ -3298,7 +3358,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'} max-h-[60vh] overflow-y-auto custom-scrollbar`}>
|
<div className={`p-5 md:p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'} max-h-[60vh] overflow-y-auto custom-scrollbar`}>
|
||||||
{dailyLooks.length > 0 ? (
|
{dailyLooks.length > 0 ? (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{dailyLooks.map(look => (
|
{dailyLooks.map(look => (
|
||||||
@@ -3321,7 +3381,7 @@ export default function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-12 flex flex-col items-center justify-center text-center opacity-50">
|
<div className="py-6 md:py-12 flex flex-col items-center justify-center text-center opacity-50">
|
||||||
<Shirt size={48} className="mb-4 text-gray-400" />
|
<Shirt size={48} className="mb-4 text-gray-400" />
|
||||||
<p className="font-black text-lg text-inherit">{t('noOutfitPlanned')}</p>
|
<p className="font-black text-lg text-inherit">{t('noOutfitPlanned')}</p>
|
||||||
<p className="text-xs mt-2 uppercase tracking-widest">{t('goToPlanning')}</p>
|
<p className="text-xs mt-2 uppercase tracking-widest">{t('goToPlanning')}</p>
|
||||||
@@ -3342,10 +3402,10 @@ export default function App() {
|
|||||||
{inspectingCommunityLook && (
|
{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="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setInspectingCommunityLook(null)}>
|
||||||
<div
|
<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'}`}
|
className={`w-full max-w-lg rounded-3xl md:rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
|
||||||
onClick={e => e.stopPropagation()}
|
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="relative p-5 md: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="absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(circle at 80% 20%, white 0%, transparent 60%)' }} />
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
@@ -3364,9 +3424,9 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`p-8 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
<div className={`p-5 md: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>
|
<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">
|
<div className="flex flex-wrap gap-3 mb-5 md:mb-8">
|
||||||
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
|
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
|
||||||
const item = selectedUserClothes.find(c => c.id === itemId);
|
const item = selectedUserClothes.find(c => c.id === itemId);
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
@@ -3383,7 +3443,7 @@ export default function App() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`space-y-2 mb-8 max-h-32 overflow-y-auto custom-scrollbar`}>
|
<div className={`space-y-2 mb-5 md:mb-8 max-h-32 overflow-y-auto custom-scrollbar`}>
|
||||||
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
|
{(inspectingCommunityLook.items || []).map((itemId, idx) => {
|
||||||
const item = selectedUserClothes.find(c => c.id === itemId);
|
const item = selectedUserClothes.find(c => c.id === itemId);
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
@@ -3411,7 +3471,7 @@ export default function App() {
|
|||||||
{inspectingCommunityItem && (
|
{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="fixed inset-0 z-[300] flex items-center justify-center bg-black/70 backdrop-blur-md p-6" onClick={() => setInspectingCommunityItem(null)}>
|
||||||
<div
|
<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'}`}
|
className={`w-full max-w-sm rounded-3xl md:rounded-[2rem] shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="relative aspect-square">
|
<div className="relative aspect-square">
|
||||||
@@ -3420,7 +3480,7 @@ export default function App() {
|
|||||||
<X size={20} />
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-8 text-center space-y-4">
|
<div className="p-5 md:p-8 text-center space-y-4">
|
||||||
<h3 className="text-2xl font-black text-inherit">{inspectingCommunityItem.name}</h3>
|
<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">
|
<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>{inspectingCommunityItem.category}</span>
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ export const translations = {
|
|||||||
todayIn: "Hoje em Portugal",
|
todayIn: "Hoje em Portugal",
|
||||||
weatherUpdate: "22°C - Ensolarado",
|
weatherUpdate: "22°C - Ensolarado",
|
||||||
weatherCurrentAvg: "{current}°C Atual • Média {avg}°C",
|
weatherCurrentAvg: "{current}°C Atual • Média {avg}°C",
|
||||||
|
|
||||||
|
registeredPieces: "Peças Registadas",
|
||||||
|
createdOutfits: "Outfits Criados",
|
||||||
|
accountRegistrationDate: "Data de Registo da Conta",
|
||||||
|
keyboardShortcuts: "Atalhos de Teclado",
|
||||||
|
enableNavigationQE: "Ativar navegação com Q e E",
|
||||||
|
dailyOutfitNotification: "Notificação do Outfit Diário",
|
||||||
|
receiveNotificationAtScheduledTime: "Receber notificação com o outfit planeado à hora marcada",
|
||||||
|
up: "Cima",
|
||||||
|
down: "Baixo",
|
||||||
weatherForecastDesc: "O dia de hoje tem máximas de {max}°C e mínimas de {min}°C.",
|
weatherForecastDesc: "O dia de hoje tem máximas de {max}°C e mínimas de {min}°C.",
|
||||||
weatherMsg: "Está um dia fantástico! Recomendamos as tuas peças leves. Que tal um visual casual com as tuas sapatilhas favoritas?",
|
weatherMsg: "Está um dia fantástico! Recomendamos as tuas peças leves. Que tal um visual casual com as tuas sapatilhas favoritas?",
|
||||||
exploreSuggestions: "Explorar Sugestões",
|
exploreSuggestions: "Explorar Sugestões",
|
||||||
@@ -252,6 +262,16 @@ export const translations = {
|
|||||||
todayIn: "Today in Portugal",
|
todayIn: "Today in Portugal",
|
||||||
weatherUpdate: "22°C - Sunny",
|
weatherUpdate: "22°C - Sunny",
|
||||||
weatherCurrentAvg: "{current}°C Current • Average {avg}°C",
|
weatherCurrentAvg: "{current}°C Current • Average {avg}°C",
|
||||||
|
|
||||||
|
registeredPieces: "Registered Pieces",
|
||||||
|
createdOutfits: "Created Outfits",
|
||||||
|
accountRegistrationDate: "Account Registration Date",
|
||||||
|
keyboardShortcuts: "Keyboard Shortcuts",
|
||||||
|
enableNavigationQE: "Enable navigation with Q and E",
|
||||||
|
dailyOutfitNotification: "Daily Outfit Notification",
|
||||||
|
receiveNotificationAtScheduledTime: "Receive notification with the planned outfit at the scheduled time",
|
||||||
|
up: "Up",
|
||||||
|
down: "Down",
|
||||||
weatherForecastDesc: "Today has highs of {max}°C and lows of {min}°C.",
|
weatherForecastDesc: "Today has highs of {max}°C and lows of {min}°C.",
|
||||||
weatherMsg: "It's a fantastic day! We recommend your light pieces. How about a casual look with your favorite sneakers?",
|
weatherMsg: "It's a fantastic day! We recommend your light pieces. How about a casual look with your favorite sneakers?",
|
||||||
exploreSuggestions: "Explore Suggestions",
|
exploreSuggestions: "Explore Suggestions",
|
||||||
@@ -469,6 +489,16 @@ export const translations = {
|
|||||||
todayIn: "Hoy en Portugal",
|
todayIn: "Hoy en Portugal",
|
||||||
weatherUpdate: "22°C - Soleado",
|
weatherUpdate: "22°C - Soleado",
|
||||||
weatherCurrentAvg: "{current}°C Actual • Media {avg}°C",
|
weatherCurrentAvg: "{current}°C Actual • Media {avg}°C",
|
||||||
|
|
||||||
|
registeredPieces: "Piezas Registradas",
|
||||||
|
createdOutfits: "Outfits Creados",
|
||||||
|
accountRegistrationDate: "Fecha de Registro de la Cuenta",
|
||||||
|
keyboardShortcuts: "Atajos de Teclado",
|
||||||
|
enableNavigationQE: "Habilitar navegación con Q y E",
|
||||||
|
dailyOutfitNotification: "Notificación del Outfit Diario",
|
||||||
|
receiveNotificationAtScheduledTime: "Recibir notificación con el outfit planeado a la hora programada",
|
||||||
|
up: "Arriba",
|
||||||
|
down: "Abajo",
|
||||||
weatherForecastDesc: "El día de hoy tiene máximas de {max}°C y mínimas de {min}°C.",
|
weatherForecastDesc: "El día de hoy tiene máximas de {max}°C y mínimas de {min}°C.",
|
||||||
weatherMsg: "¡Es un día fantástico! Recomendamos tus piezas ligeras. ¿Qué tal un look casual con tus zapatillas favoritas?",
|
weatherMsg: "¡Es un día fantástico! Recomendamos tus piezas ligeras. ¿Qué tal un look casual con tus zapatillas favoritas?",
|
||||||
exploreSuggestions: "Explorar Sugerencias",
|
exploreSuggestions: "Explorar Sugerencias",
|
||||||
@@ -686,6 +716,16 @@ export const translations = {
|
|||||||
todayIn: "Aujourd'hui au Portugal",
|
todayIn: "Aujourd'hui au Portugal",
|
||||||
weatherUpdate: "22°C - Ensoleillé",
|
weatherUpdate: "22°C - Ensoleillé",
|
||||||
weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C",
|
weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C",
|
||||||
|
|
||||||
|
registeredPieces: "Pièces Enregistrées",
|
||||||
|
createdOutfits: "Outfits Créés",
|
||||||
|
accountRegistrationDate: "Date d'Inscription du Compte",
|
||||||
|
keyboardShortcuts: "Raccourcis Clavier",
|
||||||
|
enableNavigationQE: "Activer la navigation avec Q et E",
|
||||||
|
dailyOutfitNotification: "Notification de Tenue Quotidienne",
|
||||||
|
receiveNotificationAtScheduledTime: "Recevoir une notification avec la tenue prévue à l'heure programmée",
|
||||||
|
up: "Haut",
|
||||||
|
down: "Bas",
|
||||||
weatherForecastDesc: "Aujourd'hui a des maximales de {max}°C et des minimales de {min}°C.",
|
weatherForecastDesc: "Aujourd'hui a des maximales de {max}°C et des minimales de {min}°C.",
|
||||||
weatherMsg: "C'est une journée fantastique ! Nous recommandons vos pièces légères. Que diriez-vous d'un look décontracté avec vos baskets préférées ?",
|
weatherMsg: "C'est une journée fantastique ! Nous recommandons vos pièces légères. Que diriez-vous d'un look décontracté avec vos baskets préférées ?",
|
||||||
exploreSuggestions: "Explorer les Suggestions",
|
exploreSuggestions: "Explorer les Suggestions",
|
||||||
@@ -903,6 +943,16 @@ export const translations = {
|
|||||||
todayIn: "Heute in Portugal",
|
todayIn: "Heute in Portugal",
|
||||||
weatherUpdate: "22°C - Sonnig",
|
weatherUpdate: "22°C - Sonnig",
|
||||||
weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C",
|
weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C",
|
||||||
|
|
||||||
|
registeredPieces: "Registrierte Stücke",
|
||||||
|
createdOutfits: "Erstellte Outfits",
|
||||||
|
accountRegistrationDate: "Konto-Registrierungsdatum",
|
||||||
|
keyboardShortcuts: "Tastenkombinationen",
|
||||||
|
enableNavigationQE: "Navigation mit Q und E aktivieren",
|
||||||
|
dailyOutfitNotification: "Tägliche Outfit-Benachrichtigung",
|
||||||
|
receiveNotificationAtScheduledTime: "Benachrichtigung mit dem geplanten Outfit zur geplanten Zeit erhalten",
|
||||||
|
up: "Oben",
|
||||||
|
down: "Unten",
|
||||||
weatherForecastDesc: "Der heutige Tag hat Höchstwerte von {max}°C und Tiefstwerte von {min}°C.",
|
weatherForecastDesc: "Der heutige Tag hat Höchstwerte von {max}°C und Tiefstwerte von {min}°C.",
|
||||||
weatherMsg: "Es ist ein fantastischer Tag! Wir empfehlen leichte Stücke. Wie wäre es mit einem lässigen Look mit deinen Lieblings-Sneakern?",
|
weatherMsg: "Es ist ein fantastischer Tag! Wir empfehlen leichte Stücke. Wie wäre es mit einem lässigen Look mit deinen Lieblings-Sneakern?",
|
||||||
exploreSuggestions: "Vorschläge entdecken",
|
exploreSuggestions: "Vorschläge entdecken",
|
||||||
|
|||||||
107
translate_app.cjs
Normal file
107
translate_app.cjs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 1. Update i18n.js
|
||||||
|
let i18nCode = fs.readFileSync('src/lib/i18n.js', 'utf8');
|
||||||
|
|
||||||
|
const newTranslations = {
|
||||||
|
PT: {
|
||||||
|
registeredPieces: "Peças Registadas",
|
||||||
|
createdOutfits: "Outfits Criados",
|
||||||
|
accountRegistrationDate: "Data de Registo da Conta",
|
||||||
|
keyboardShortcuts: "Atalhos de Teclado",
|
||||||
|
enableNavigationQE: "Ativar navegação com Q e E",
|
||||||
|
dailyOutfitNotification: "Notificação do Outfit Diário",
|
||||||
|
receiveNotificationAtScheduledTime: "Receber notificação com o outfit planeado à hora marcada",
|
||||||
|
up: "Cima",
|
||||||
|
down: "Baixo"
|
||||||
|
},
|
||||||
|
EN: {
|
||||||
|
registeredPieces: "Registered Pieces",
|
||||||
|
createdOutfits: "Created Outfits",
|
||||||
|
accountRegistrationDate: "Account Registration Date",
|
||||||
|
keyboardShortcuts: "Keyboard Shortcuts",
|
||||||
|
enableNavigationQE: "Enable navigation with Q and E",
|
||||||
|
dailyOutfitNotification: "Daily Outfit Notification",
|
||||||
|
receiveNotificationAtScheduledTime: "Receive notification with the planned outfit at the scheduled time",
|
||||||
|
up: "Up",
|
||||||
|
down: "Down"
|
||||||
|
},
|
||||||
|
ES: {
|
||||||
|
registeredPieces: "Piezas Registradas",
|
||||||
|
createdOutfits: "Outfits Creados",
|
||||||
|
accountRegistrationDate: "Fecha de Registro de la Cuenta",
|
||||||
|
keyboardShortcuts: "Atajos de Teclado",
|
||||||
|
enableNavigationQE: "Habilitar navegación con Q y E",
|
||||||
|
dailyOutfitNotification: "Notificación del Outfit Diario",
|
||||||
|
receiveNotificationAtScheduledTime: "Recibir notificación con el outfit planeado a la hora programada",
|
||||||
|
up: "Arriba",
|
||||||
|
down: "Abajo"
|
||||||
|
},
|
||||||
|
FR: {
|
||||||
|
registeredPieces: "Pièces Enregistrées",
|
||||||
|
createdOutfits: "Outfits Créés",
|
||||||
|
accountRegistrationDate: "Date d'Inscription du Compte",
|
||||||
|
keyboardShortcuts: "Raccourcis Clavier",
|
||||||
|
enableNavigationQE: "Activer la navigation avec Q et E",
|
||||||
|
dailyOutfitNotification: "Notification de Tenue Quotidienne",
|
||||||
|
receiveNotificationAtScheduledTime: "Recevoir une notification avec la tenue prévue à l'heure programmée",
|
||||||
|
up: "Haut",
|
||||||
|
down: "Bas"
|
||||||
|
},
|
||||||
|
DE: {
|
||||||
|
registeredPieces: "Registrierte Stücke",
|
||||||
|
createdOutfits: "Erstellte Outfits",
|
||||||
|
accountRegistrationDate: "Konto-Registrierungsdatum",
|
||||||
|
keyboardShortcuts: "Tastenkombinationen",
|
||||||
|
enableNavigationQE: "Navigation mit Q und E aktivieren",
|
||||||
|
dailyOutfitNotification: "Tägliche Outfit-Benachrichtigung",
|
||||||
|
receiveNotificationAtScheduledTime: "Benachrichtigung mit dem geplanten Outfit zur geplanten Zeit erhalten",
|
||||||
|
up: "Oben",
|
||||||
|
down: "Unten"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const lang in newTranslations) {
|
||||||
|
const keys = newTranslations[lang];
|
||||||
|
const langRegex = new RegExp(`${lang}: \\{([\\s\\S]*?)\\}`, 'g');
|
||||||
|
let match = langRegex.exec(i18nCode);
|
||||||
|
if (match) {
|
||||||
|
let currentKeys = match[1];
|
||||||
|
let newKeysString = "";
|
||||||
|
for (const [k, v] of Object.entries(keys)) {
|
||||||
|
if (!currentKeys.includes(`${k}:`)) {
|
||||||
|
newKeysString += ` ${k}: "${v}",\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newKeysString.length > 0) {
|
||||||
|
i18nCode = i18nCode.replace(currentKeys, currentKeys.trimEnd() + ",\n" + newKeysString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.writeFileSync('src/lib/i18n.js', i18nCode);
|
||||||
|
|
||||||
|
// 2. Update App.jsx
|
||||||
|
let appCode = fs.readFileSync('src/App.jsx', 'utf8');
|
||||||
|
|
||||||
|
const replacements = [
|
||||||
|
{ from: />Data de Nascimento</g, to: '>{t(\'dob\')}<' },
|
||||||
|
{ from: />Localidade</g, to: '>{t(\'location\')}<' },
|
||||||
|
{ from: />Bio</g, to: '>{t(\'bio\')}<' },
|
||||||
|
{ from: />Peças Registadas</g, to: '>{t(\'registeredPieces\')}<' },
|
||||||
|
{ from: />Outfits Criados</g, to: '>{t(\'createdOutfits\')}<' },
|
||||||
|
{ from: />Data de Registo da Conta</g, to: '>{t(\'accountRegistrationDate\')}<' },
|
||||||
|
{ from: />Atalhos de Teclado</g, to: '>{t(\'keyboardShortcuts\')}<' },
|
||||||
|
{ from: />Ativar navegação com Q e E</g, to: '>{t(\'enableNavigationQE\')}<' },
|
||||||
|
{ from: />Notificação do Outfit Diário</g, to: '>{t(\'dailyOutfitNotification\')}<' },
|
||||||
|
{ from: />Receber notificação com o outfit planeado à hora marcada</g, to: '>{t(\'receiveNotificationAtScheduledTime\')}<' },
|
||||||
|
{ from: />Cima</g, to: '>{t(\'up\')}<' },
|
||||||
|
{ from: />Baixo</g, to: '>{t(\'down\')}<' }
|
||||||
|
];
|
||||||
|
|
||||||
|
replacements.forEach(r => {
|
||||||
|
appCode = appCode.replace(r.from, r.to);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync('src/App.jsx', appCode);
|
||||||
|
|
||||||
|
console.log('App successfully translated and i18n updated.');
|
||||||
Reference in New Issue
Block a user