diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b8f1ff0..e01ad9e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,15 +4,26 @@ import { TOOLS } from './toolRegistry.js'; const ICONS = ['๐Ÿ”—','๐ŸŒ','๐Ÿ“Š','๐Ÿ“','๐Ÿ› ','๐Ÿ“ง','๐Ÿ—“','๐Ÿ’พ','๐Ÿ–ฅ','๐Ÿ“ก','๐Ÿ”’','๐Ÿ“–','๐ŸŽฏ','โšก','๐Ÿ ','๐Ÿ”ง','๐Ÿ“ฑ','๐ŸŽฎ','๐Ÿš€','๐Ÿ’ก']; +// โ”€โ”€ Responsive Hook โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function useIsMobile() { + const [mobile, setMobile] = useState(window.innerWidth < 768); + useEffect(() => { + const fn = () => setMobile(window.innerWidth < 768); + window.addEventListener('resize', fn); + return () => window.removeEventListener('resize', fn); + }, []); + return mobile; +} + // โ”€โ”€ Toast โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function Toast({ msg, type }) { if (!msg) return null; const c = type === 'error' ? '#ff6b9d' : '#4ecdc4'; return ( -
+ boxShadow:`0 4px 24px ${c}22`, maxWidth:'calc(100vw - 32px)' }}> {type==='error'?'โœ• ':'โœ“ '}{msg}
); @@ -21,14 +32,16 @@ function Toast({ msg, type }) { // โ”€โ”€ Modal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function Modal({ title, onClose, children }) { return ( -
e.target===e.currentTarget&&onClose()}>
-
- {title} - + borderRadius:'16px 16px 0 0', padding:'20px 20px 40px', width:'100%', + maxWidth:600, maxHeight:'85vh', overflowY:'auto' }}> +
+
+ {title} +
{children}
@@ -46,10 +59,10 @@ function Login({ onLogin }) { catch(e) { setErr(e.message); } finally { setBusy(false); } }; return ( -
-
+
+
-
โš’
+
โš’
DICKENDOCK
@@ -57,109 +70,31 @@ function Login({ onLogin }) { {[['BENUTZERNAME',u,setU,'text'],['PASSWORT',p,setP,'password']].map(([l,v,s,t])=>(
- s(e.target.value)} onKeyDown={e=>e.key==='Enter'&&go()} style={S.inp} /> + s(e.target.value)} + onKeyDown={e=>e.key==='Enter'&&go()} + style={{ ...S.inp, fontSize:16 }} autoCapitalize="none" />
))} {err &&
โœ• {err}
}
); } -// โ”€โ”€ Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) { - const coreNav = [{ id:'dashboard', icon:'โฌก', label:'Dashboard' }]; - const adminNav = user?.role==='admin' ? [{ id:'admin', icon:'โš™', label:'Admin' }] : []; - - const NavBtn = ({ item }) => ( - - ); - - return ( - - ); -} - // โ”€โ”€ Update Modal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function UpdateModal({ data, onClose }) { return ( - +
Installiert: {data.currentVersion} - {' โ†’ '}Aktuell: {data.latestVersion} + {' โ†’ '}{data.latestVersion}
{(data.releases||[]).map((r,i)=>( @@ -168,27 +103,175 @@ function UpdateModal({ data, onClose }) { border:`1px solid ${i===0?'rgba(78,205,196,0.2)':'rgba(255,255,255,0.05)'}`, borderRadius:8 }}>
{r.name||r.version} - {r.publishedAt?new Date(r.publishedAt).toLocaleDateString('de-DE'):''} + + {r.publishedAt?new Date(r.publishedAt).toLocaleDateString('de-DE'):''} +
{r.body}
))}
- Update: sh build.sh && docker compose up -d + Update: docker compose up -d --build
); } -// โ”€โ”€ Dashboard Widgets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -function QuickLinks({ toast }) { - const [links, setLinks] = useState([]); - const [editMode, setEdit] = useState(false); - const [showForm, setForm] = useState(false); - const [form, setF] = useState({ title:'', url:'', icon:'๐Ÿ”—' }); - const [editId, setEditId] = useState(null); +// โ”€โ”€ Desktop Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) { + const allNav = [ + { id:'dashboard', icon:'โฌก', label:'Dashboard' }, + ...TOOLS.map(t => ({ id:t.id, icon:t.icon, label:t.label })), + ...(user?.role==='admin' ? [{ id:'admin', icon:'โš™', label:'Admin' }] : []), + ]; + return ( + + ); +} - useEffect(() => { api('/dashboard/links').then(setLinks).catch(()=>{}); }, []); +// โ”€โ”€ Mobile Bottom Navigation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function BottomNav({ active, setActive, user, updateInfo, onUpdateClick }) { + const [menuOpen, setMenuOpen] = useState(false); + const mainNav = [ + { id:'dashboard', icon:'โฌก', label:'Home' }, + ...TOOLS.slice(0,3).map(t => ({ id:t.id, icon:t.icon, label:t.label.length>8?t.label.slice(0,7)+'โ€ฆ':t.label })), + { id:'_menu', icon:'โ‹ฏ', label:'Mehr' }, + ]; + + return ( + <> + {/* Menu Overlay */} + {menuOpen && ( +
setMenuOpen(false)}> +
e.stopPropagation()}> + {/* Update badge */} + {updateInfo?.hasUpdate && ( + + )} + {/* Extra tools */} + {TOOLS.slice(3).map(t => ( + + ))} + {user?.role==='admin' && ( + + )} +
+
+ )} + + {/* Bottom Bar */} + + + ); +} + +// โ”€โ”€ Dashboard Widgets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function QuickLinks({ toast, mobile }) { + const [links,setLinks]=useState([]); const [editMode,setEdit]=useState(false); + const [showForm,setForm]=useState(false); const [form,setF]=useState({title:'',url:'',icon:'๐Ÿ”—'}); + const [editId,setEditId]=useState(null); + + useEffect(()=>{ api('/dashboard/links').then(setLinks).catch(()=>{}); },[]); const save = async () => { if (!form.title.trim()||!form.url.trim()) { toast('Titel und URL erforderlich','error'); return; } @@ -196,33 +279,35 @@ function QuickLinks({ toast }) { if (!/^https?:\/\//i.test(url)) url = 'https://'+url; try { if (editId) { - const u = await api(`/dashboard/links/${editId}`,{method:'PUT',body:{...form,url}}); + const u=await api(`/dashboard/links/${editId}`,{method:'PUT',body:{...form,url}}); setLinks(p=>p.map(l=>l.id===editId?u:l)); } else { - const n = await api('/dashboard/links',{body:{...form,url}}); + const n=await api('/dashboard/links',{body:{...form,url}}); setLinks(p=>[...p,n]); } - toast(editId?'Link aktualisiert':'Link gespeichert'); + toast(editId?'Aktualisiert':'Gespeichert'); setF({title:'',url:'',icon:'๐Ÿ”—'}); setForm(false); setEditId(null); } catch(e) { toast(e.message,'error'); } }; const del = async id => { - try { await api(`/dashboard/links/${id}`,{method:'DELETE'}); setLinks(p=>p.filter(l=>l.id!==id)); toast('Link gelรถscht'); } + try { await api(`/dashboard/links/${id}`,{method:'DELETE'}); setLinks(p=>p.filter(l=>l.id!==id)); toast('Gelรถscht'); } catch(e) { toast(e.message,'error'); } }; return ( -
-
+
+
SCHNELLZUGRIFF
{editMode && } - +
-
- {links.length===0&&!editMode&&
Klicke "Bearbeiten" um Links hinzuzufรผgen.
} +
+ {links.length===0&&!editMode&&
Tippe auf "โœŽ" um Links hinzuzufรผgen.
} {links.map(l=>(
{editMode&&( @@ -231,35 +316,34 @@ function QuickLinks({ toast }) {
)} - e.preventDefault():undefined} - style={{ display:'flex',flexDirection:'column',alignItems:'center',gap:5,padding:'10px 14px', + e.preventDefault():undefined} + style={{ display:'flex',flexDirection:'column',alignItems:'center',gap:4,padding:'10px 12px', background:'rgba(255,255,255,0.04)',border:'1px solid rgba(255,255,255,0.08)', - borderRadius:10,textDecoration:'none',minWidth:70,cursor:editMode?'default':'pointer' }}> - {l.icon} - {l.title} + borderRadius:10,textDecoration:'none',minWidth:64,cursor:editMode?'default':'pointer' }}> + {l.icon} + {l.title}
))}
{showForm&&( -
+
-
ICON Wร„HLEN
+
ICON
{ICONS.map(ic=>( - + ))}
-
-
- - setF(f=>({...f,title:e.target.value}))} style={S.inp} placeholder="Mein Link" /> -
-
- - setF(f=>({...f,url:e.target.value}))} onKeyDown={e=>e.key==='Enter'&&save()} style={S.inp} placeholder="https://example.com" /> -
+
+ + setF(f=>({...f,title:e.target.value}))} style={{ ...S.inp,fontSize:16 }} placeholder="Mein Link" /> +
+
+ + setF(f=>({...f,url:e.target.value}))} style={{ ...S.inp,fontSize:16 }} placeholder="https://example.com" inputMode="url" autoCapitalize="none" />
@@ -274,7 +358,7 @@ function QuickLinks({ toast }) { function TodoList({ toast }) { const [items,setItems]=useState([]); const [text,setText]=useState(''); useEffect(()=>{api('/dashboard/todos').then(setItems).catch(()=>{});},[]); - const add = async()=>{ + const add=async()=>{ if(!text.trim())return; try{const n=await api('/dashboard/todos',{body:{text:text.trim()}});setItems(p=>[...p,n]);setText('');} catch(e){toast(e.message,'error');} @@ -289,31 +373,32 @@ function TodoList({ toast }) { }; const open=items.filter(i=>!i.done), done=items.filter(i=>i.done); return( -
+
TO-DO LISTE
-
- setText(e.target.value)} onKeyDown={e=>e.key==='Enter'&&add()} style={{...S.inp,flex:1}} placeholder="Neue Aufgabeโ€ฆ"/> - +
+ setText(e.target.value)} onKeyDown={e=>e.key==='Enter'&&add()} + style={{...S.inp,flex:1,fontSize:16}} placeholder="Neue Aufgabeโ€ฆ"/> +
-
+
{open.map(item=>( -
- +
+
))} {done.length>0&&<>
ERLEDIGT
{done.map(item=>( -
- - {item.text} - +
+ + {item.text} +
))} } - {items.length===0&&
Noch keine Aufgaben.
} + {items.length===0&&
Noch keine Aufgaben.
}
); @@ -331,40 +416,40 @@ function Notepad({ toast }) { },900); }; return( -
-
+
+
NOTIZBLOCK
- {saved?(saveTime?`gespeichert ${new Date(saveTime).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}`:'gespeichert'):'โ€ฆ'} + {saved?(saveTime?`โœ“ ${new Date(saveTime).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}`:'โœ“'):'โ€ฆ'}