import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Layers,
CheckSquare,
FileText,
ShoppingCart,
Brain,
Settings,
Shield,
LogOut,
ChevronDown,
RotateCcw,
Palette,
HelpCircle,
Plus,
X,
Download,
Smartphone,
Share,
Users,
Calendar,
AlertTriangle,
Flame,
Mic,
MicOff,
} from 'lucide-react';
import { AuthStatus, DashboardStats, PriorityLevel, SharedList, Thought, User } from './lib/types';
import {
api,
clearToken,
getToken,
triggerHaptic,
connectWebSocket,
disconnectWebSocket,
} from './lib/api';
import {
AVAILABLE_THEMES,
initThemeOnBoot,
setActiveTheme as applyTheme,
ThemeId,
} from './lib/theme';
import { InstantCaptureBar } from './features/capture/InstantCaptureBar';
import { TriageDeck } from './features/triage/TriageDeck';
import { TasksView } from './features/triage/TasksView';
import { WorkspaceView } from './features/workspace/WorkspaceView';
import { SharedListsView } from './features/lists/SharedListsView';
import { SetupAndAuthView } from './features/auth/SetupAndAuthView';
import { UserSettingsView } from './features/settings/UserSettingsView';
import { AdminSettingsView } from './features/settings/AdminSettingsView';
import { HelpView } from './features/help/HelpView';
import { GroupsView } from './features/teams/GroupsView';
import { useMobileActions } from './context/MobileActionContext';
import { MobileHeaderMenu } from './components/MobileHeaderMenu';
import { UserAvatar } from './components/UserAvatar';
type ActiveTab =
| 'triage'
| 'tasks'
| 'workspace'
| 'lists'
| 'groups'
| 'user_settings'
| 'admin_settings'
| 'help';
interface ErrorBoundaryState {
hasError: boolean;
errorText: string;
}
class ViewErrorBoundary extends React.Component<
{ children: React.ReactNode; onReset: () => void },
ErrorBoundaryState
> {
constructor(props: { children: React.ReactNode; onReset: () => void }) {
super(props);
this.state = { hasError: false, errorText: '' };
}
static getDerivedStateFromError(err: Error): ErrorBoundaryState {
return { hasError: true, errorText: err?.message || 'Unbekannter Darstellungsfehler' };
}
render() {
if (this.state.hasError) {
return (
Ansicht konnte nicht geladen werden
{this.state.errorText}
{
this.setState({ hasError: false, errorText: '' });
this.props.onReset();
}}
className="btn-primary h-8 px-3 text-xs flex items-center gap-1.5"
>
Ansicht zurücksetzen
);
}
return this.props.children;
}
}
export const App: React.FC = () => {
const [activeTheme, setActiveThemeState] = useState(() => initThemeOnBoot());
const [bootLoading, setBootLoading] = useState(true);
const [authStatus, setAuthStatus] = useState({
needsSetup: false,
allowRegistration: false,
instanceName: 'MindSift',
});
const [user, setUser] = useState(null);
const [stats, setStats] = useState({
inboxCount: 0,
tasksOpenCount: 0,
tasksDoneCount: 0,
docsCount: 0,
docsTodosOpenCount: 0,
listsCount: 0,
discardedCount: 0,
});
const [deck, setDeck] = useState([]);
const [lists, setLists] = useState([]);
const [activeTab, setActiveTab] = useState('triage');
const [previousTab, setPreviousTab] = useState('triage');
const [selectedDocId, setSelectedDocId] = useState(null);
const [captureFocusCount, setCaptureFocusCount] = useState(0);
const [refreshRevision, setRefreshRevision] = useState(0);
// Profile Dropdown State
const [showProfileMenu, setShowProfileMenu] = useState(false);
const dropdownRef = useRef(null);
// Mobile Navigation & Quick-Action State
const [showMobileCapture, setShowMobileCapture] = useState(false);
const [showMobileProfile, setShowMobileProfile] = useState(false);
const [mobileTriageContent, setMobileTriageContent] = useState('');
const [mobileTriagePriority, setMobileTriagePriority] = useState('normal');
const [mobileTriageDueDate, setMobileTriageDueDate] = useState('');
const [mobileVoiceActive, setMobileVoiceActive] = useState(false);
const [isVoiceCaptured, setIsVoiceCaptured] = useState(false);
const mobileRecognitionRef = useRef(null);
const toggleMobileVoice = () => {
const SpeechRecognition =
(window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
if (!SpeechRecognition) {
alert('Spracheingabe wird von diesem Browser leider nicht unterstützt.');
return;
}
if (mobileVoiceActive && mobileRecognitionRef.current) {
mobileRecognitionRef.current.stop();
setMobileVoiceActive(false);
return;
}
try {
const recognition = new SpeechRecognition();
recognition.lang = 'de-DE';
recognition.continuous = false;
recognition.interimResults = true;
recognition.onstart = () => {
setMobileVoiceActive(true);
triggerHaptic(20);
};
recognition.onresult = (event: any) => {
let transcript = '';
for (let i = 0; i < event.results.length; i++) {
transcript += event.results[i][0].transcript;
}
setMobileTriageContent((prev) => {
const base = prev.replace(/\s*\[…\]$/, '').trim();
return base ? `${base} ${transcript}` : transcript;
});
setIsVoiceCaptured(true);
};
recognition.onerror = () => setMobileVoiceActive(false);
recognition.onend = () => setMobileVoiceActive(false);
mobileRecognitionRef.current = recognition;
recognition.start();
} catch {
setMobileVoiceActive(false);
}
};
const handleMobileCreateTriage = async (e: React.FormEvent) => {
e.preventDefault();
const trimmed = mobileTriageContent.trim();
if (!trimmed) return;
try {
const thought = await api.captureThought(
trimmed,
mobileTriagePriority,
isVoiceCaptured ? 'voice' : 'manual',
mobileTriageDueDate,
{ target: 'inbox' }
);
handleCaptured(thought, 'inbox');
setMobileTriageContent('');
setMobileTriagePriority('normal');
setMobileTriageDueDate('');
setIsVoiceCaptured(false);
setShowMobileCapture(false);
triggerHaptic(20);
} catch (err: any) {
alert(`Fehler beim Speichern: ${err.message || 'Unbekannter Fehler'}`);
}
};
const openMobileTriageCapture = () => {
setMobileTriageContent('');
setMobileTriagePriority('normal');
setMobileTriageDueDate('');
setIsVoiceCaptured(false);
setMobileVoiceActive(false);
setShowMobileCapture(true);
};
const { isMenuOpen, setIsMenuOpen, closeMenu, setGroups, primaryAction, setPrimaryAction } = useMobileActions();
useEffect(() => {
closeMenu();
if (activeTab === 'triage') {
setPrimaryAction(() => openMobileTriageCapture);
setGroups([
{
id: 'triage-actions',
title: 'Schnellaktionen',
items: [
{
id: 'new-thought',
label: 'Neuen Gedanken erfassen',
icon: ,
variant: 'accent',
onClick: () => openMobileTriageCapture(),
},
{
id: 'refresh-deck',
label: 'Inbox aktualisieren',
icon: ,
badge: stats.inboxCount,
onClick: () => refreshAll(),
},
],
},
{
id: 'triage-nav',
title: 'Bereiche wechseln',
items: [
{
id: 'goto-tasks',
label: 'Aufgaben & Kanban',
icon: ,
badge: stats.tasksOpenCount,
onClick: () => setActiveTab('tasks'),
},
{
id: 'goto-workspace',
label: 'Notizen & Workspace',
icon: ,
badge: stats.docsCount,
onClick: () => setActiveTab('workspace'),
},
{
id: 'goto-lists',
label: 'Einkaufslisten',
icon: ,
badge: stats.listsCount,
onClick: () => setActiveTab('lists'),
},
{
id: 'goto-groups',
label: 'Teams & Gruppen',
icon: ,
onClick: () => setActiveTab('groups'),
},
],
},
]);
} else if (
activeTab === 'groups' ||
activeTab === 'user_settings' ||
activeTab === 'admin_settings' ||
activeTab === 'help'
) {
setGroups([
{
id: 'subview-nav',
title: 'Navigation',
items: [
{
id: 'back-triage',
label: 'Zurück zur Triage (Startseite)',
icon: ,
variant: 'accent',
onClick: () => setActiveTab('triage'),
},
{
id: 'goto-tasks',
label: 'Zu den Aufgaben',
icon: ,
onClick: () => setActiveTab('tasks'),
},
{
id: 'goto-workspace',
label: 'Zum Workspace',
icon: ,
onClick: () => setActiveTab('workspace'),
},
{
id: 'goto-lists',
label: 'Zu den Listen',
icon: ,
onClick: () => setActiveTab('lists'),
},
],
},
]);
}
}, [activeTab, stats.inboxCount, stats.tasksOpenCount, stats.docsCount, stats.listsCount]);
// Mobile Web-App (PWA) Installation State
const [isStandaloneApp, setIsStandaloneApp] = useState(() => {
if (typeof window === 'undefined') return false;
return (
window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true
);
});
const [deferredPwaPrompt, setDeferredPwaPrompt] = useState(() =>
typeof window !== 'undefined' ? (window as any).__deferredPwaPrompt || null : null
);
const [showPwaInstallModal, setShowPwaInstallModal] = useState(false);
const [showManualPwaGuide, setShowManualPwaGuide] = useState(false);
useEffect(() => {
const syncPwaPrompt = () => {
if ((window as any).__deferredPwaPrompt) {
setDeferredPwaPrompt((window as any).__deferredPwaPrompt);
}
};
const onBeforeInstallPrompt = (e: Event) => {
e.preventDefault();
(window as any).__deferredPwaPrompt = e;
setDeferredPwaPrompt(e);
};
const onAppInstalled = () => {
(window as any).__deferredPwaPrompt = null;
setDeferredPwaPrompt(null);
setIsStandaloneApp(true);
setShowPwaInstallModal(false);
setShowManualPwaGuide(false);
};
window.addEventListener('beforeinstallprompt', onBeforeInstallPrompt);
window.addEventListener('mindsift:pwa-ready', syncPwaPrompt);
window.addEventListener('appinstalled', onAppInstalled);
return () => {
window.removeEventListener('beforeinstallprompt', onBeforeInstallPrompt);
window.removeEventListener('mindsift:pwa-ready', syncPwaPrompt);
window.removeEventListener('appinstalled', onAppInstalled);
};
}, []);
// Immediately offer Web-App installation on mobile if not yet running as installed standalone app
useEffect(() => {
if (!user || isStandaloneApp) return;
if (window.innerWidth < 640 && !sessionStorage.getItem('mindsift_pwa_dismissed')) {
setShowPwaInstallModal(true);
}
}, [user?.id, isStandaloneApp]);
const handleInstallWebApp = async () => {
triggerHaptic(15);
const promptEvent = deferredPwaPrompt || (window as any).__deferredPwaPrompt;
if (promptEvent && typeof promptEvent.prompt === 'function') {
try {
await promptEvent.prompt();
const choice = await promptEvent.userChoice;
if (choice && choice.outcome === 'accepted') {
(window as any).__deferredPwaPrompt = null;
setDeferredPwaPrompt(null);
setShowPwaInstallModal(false);
setShowManualPwaGuide(false);
}
} catch {
setShowManualPwaGuide(true);
}
} else {
setShowManualPwaGuide(true);
}
};
const handleDismissPwaModal = () => {
triggerHaptic(10);
sessionStorage.setItem('mindsift_pwa_dismissed', '1');
setShowPwaInstallModal(false);
setShowManualPwaGuide(false);
};
const handleOpenHelp = () => {
triggerHaptic(10);
setPreviousTab((prev) => (activeTab === 'help' ? prev : activeTab));
setActiveTab('help');
setShowProfileMenu(false);
};
const handleChangeTheme = (themeId: ThemeId) => {
triggerHaptic(10);
applyTheme(themeId);
setActiveThemeState(themeId);
};
const refreshAll = useCallback(async () => {
try {
const [meRes, deckRes, listsRes] = await Promise.all([
api.getMe(),
api.getTriageDeck(),
api.getAllLists(),
]);
setUser(meRes.user);
setStats(meRes.stats);
setDeck(Array.isArray(deckRes) ? deckRes : []);
setLists(Array.isArray(listsRes) ? listsRes : []);
} catch {
// If token expired or invalid, api.ts dispatches mindsift:unauthorized
}
}, []);
const checkBootAndAuth = async () => {
setBootLoading(true);
try {
const st = await api.getAuthStatus();
setAuthStatus(st);
if (st.needsSetup) {
clearToken();
setUser(null);
return;
}
if (getToken()) {
await api.flushOfflineQueue();
await refreshAll();
} else {
setUser(null);
}
} catch {
// Server temporarily unreachable
} finally {
setBootLoading(false);
}
};
useEffect(() => {
checkBootAndAuth();
const onUnauthorized = async () => {
setUser(null);
setShowProfileMenu(false);
try {
const st = await api.getAuthStatus();
setAuthStatus(st);
} catch {
// ignore
}
};
const onOnline = async () => {
if (getToken()) {
const synced = await api.flushOfflineQueue();
if (synced > 0) refreshAll();
}
};
window.addEventListener('mindsift:unauthorized', onUnauthorized);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('mindsift:unauthorized', onUnauthorized);
window.removeEventListener('online', onOnline);
};
}, []);
// WebSocket Connection Management
useEffect(() => {
if (user && getToken()) {
connectWebSocket();
} else {
disconnectWebSocket();
}
}, [user?.id]);
// Refresh stats on task/doc real-time events
useEffect(() => {
const handleWs = (e: any) => {
const msg = e.detail;
if (!msg) return;
if (
typeof msg.event === 'string' &&
(msg.event.startsWith('TASK_') || msg.event.startsWith('DOC_') || msg.event.startsWith('SPACE_'))
) {
api.getMe().then((res) => {
setStats(res.stats);
}).catch(() => {});
}
};
window.addEventListener('mindsift:ws', handleWs);
return () => {
window.removeEventListener('mindsift:ws', handleWs);
};
}, []);
// Close profile dropdown on outside click
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setShowProfileMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleAuthenticated = async (loggedInUser: User) => {
setUser(loggedInUser);
setAuthStatus((prev) => ({ ...prev, needsSetup: false }));
setActiveTab('triage');
await refreshAll();
};
const handleCaptured = (
thought: Thought,
target: 'inbox' | 'task' | 'list_item' | 'document' = 'inbox'
) => {
if (target === 'inbox' && thought.status === 'inbox') {
setDeck((prev) => {
if (prev.some((t) => t.id === thought.id)) return prev;
return [thought, ...prev];
});
setStats((prev) => ({ ...prev, inboxCount: prev.inboxCount + 1 }));
} else if (target === 'document' && thought.convertedRefId) {
setSelectedDocId(thought.convertedRefId);
setActiveTab('workspace');
}
setRefreshRevision((r) => r + 1);
refreshAll();
};
const handleOpenDocument = (docId: string) => {
setSelectedDocId(docId);
setActiveTab('workspace');
};
const handleTriggerAddButton = () => {
triggerHaptic(20);
if (window.innerWidth < 640) {
openMobileTriageCapture();
} else {
window.scrollTo({ top: 0, behavior: 'smooth' });
setCaptureFocusCount((c) => c + 1);
}
};
const handleLogout = async () => {
triggerHaptic(20);
disconnectWebSocket();
try {
await api.logout();
} catch {
clearToken();
}
setUser(null);
setShowProfileMenu(false);
setActiveTab('triage');
try {
const st = await api.getAuthStatus();
setAuthStatus(st);
} catch {
// ignore
}
};
if (bootLoading) {
return (
Verbinde mit MindSift…
);
}
// Show Setup Wizard or Login Screen if needsSetup or not logged in
if (authStatus.needsSetup || !user) {
return (
);
}
const isServerAdmin = user.role === 'admin';
const roleLabel = isServerAdmin ? 'Server-Admin' : 'Mitglied';
const activeTabLabelMap: Record = {
triage: 'Triage',
tasks: 'Aufgaben',
workspace: 'Workspace',
lists: 'Listen',
groups: 'Teams & Gruppen',
user_settings: 'Einstellungen',
admin_settings: 'Administration',
help: 'Hilfe',
};
return (
{/* Top Header Bar: Floating on mobile, solid panel on desktop */}
{/* Mobile Header: Floating Bar with Logo Action Dropdown Left & Profile Avatar Right */}
{
triggerHaptic(12);
setIsMenuOpen((prev) => !prev);
}}
className={`min-h-[44px] px-2.5 -ml-2 rounded-xl flex items-center gap-2.5 cursor-pointer select-none active:scale-95 transition-all ${
isMenuOpen ? 'bg-panel border border-accent/40 shadow-sm' : 'hover:bg-panel/60'
}`}
title="Menü & Ansichts-Optionen öffnen"
>
{authStatus.instanceName || 'MindSift'}
{activeTabLabelMap[activeTab]}
{!isStandaloneApp && (
{
triggerHaptic(10);
setShowManualPwaGuide(false);
setShowPwaInstallModal(true);
}}
className="h-9 px-2.5 rounded-xl bg-accent/15 border border-accent/40 text-accent text-xs font-semibold flex items-center gap-1.5 active:scale-95 transition-transform cursor-pointer"
title="Als Web-App auf dem Smartphone installieren"
>
App
)}
{
triggerHaptic(10);
setShowMobileProfile(true);
}}
className={`w-11 h-11 -mr-1.5 rounded-full flex items-center justify-center transition-transform active:scale-95 cursor-pointer ${
activeTab === 'user_settings' || activeTab === 'admin_settings' || activeTab === 'help'
? 'ring-2 ring-accent'
: ''
}`}
title="Profil & Einstellungen"
>
{/* Desktop Header: 3-column Layout (Logo, InstantCaptureBar, Profile Pill) */}
{/* Logo & Headline */}
{
setActiveTab('triage');
handleTriggerAddButton();
}}
className="h-9 flex items-center gap-2.5 cursor-pointer group select-none flex-shrink-0"
>
{authStatus.instanceName || 'MindSift'}
{/* Quick-Capture Input Bubble directly in Header (above Tabs) */}
{/* User Profile Pill & Dropdown Menu */}
{
triggerHaptic(10);
setShowProfileMenu((prev) => !prev);
}}
className="h-8 flex items-center gap-2 text-xs font-medium text-main hover:opacity-85 transition-opacity cursor-pointer group"
title="Benutzermenü öffnen"
>
{user.displayName}
{showProfileMenu && (
{/* User Info Header */}
{user.displayName}
{user.email}
{roleLabel}
{/* Theme Quick Switcher */}
Design-Theme
{AVAILABLE_THEMES.map((t) => (
handleChangeTheme(t.id)}
className={`h-7 px-2 text-[11px] font-medium border rounded-btn flex items-center gap-1.5 transition-colors ${
activeTheme === t.id
? 'bg-panel-elevated border-accent text-main font-semibold'
: 'bg-canvas border-border text-muted hover:text-main'
}`}
>
{t.name.replace('MindSift ', '').split(' ')[0]}
))}
{/* Menu Items */}
{
triggerHaptic(10);
setShowProfileMenu(false);
setActiveTab('user_settings');
}}
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
activeTab === 'user_settings' ? 'text-accent font-semibold' : 'text-main'
}`}
>
Kontoeinstellungen
{
triggerHaptic(10);
setShowProfileMenu(false);
setActiveTab('groups');
}}
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
activeTab === 'groups' ? 'text-accent font-semibold' : 'text-main'
}`}
>
Gruppen
Hilfe & Anleitung
{isServerAdmin && (
{
triggerHaptic(10);
setShowProfileMenu(false);
setActiveTab('admin_settings');
}}
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
activeTab === 'admin_settings'
? 'text-accent font-semibold'
: 'text-main'
}`}
>
Administration
)}
{/* Logout */}
Abmelden
)}
{/* Desktop Navigation tabs attached to header */}
{
triggerHaptic(10);
setActiveTab('triage');
}}
className={`tab-btn ${activeTab === 'triage' ? 'tab-btn-active' : ''}`}
>
0 ? 'pr-2' : ''}`}>
Triage
{stats.inboxCount > 0 && (
{stats.inboxCount}
)}
{
triggerHaptic(10);
setActiveTab('tasks');
}}
className={`tab-btn ${activeTab === 'tasks' ? 'tab-btn-active' : ''}`}
>
0 ? 'pr-2' : ''}`}>
Aufgaben
{stats.tasksOpenCount > 0 && (
{stats.tasksOpenCount}
)}
{
triggerHaptic(10);
setActiveTab('workspace');
}}
className={`tab-btn ${activeTab === 'workspace' ? 'tab-btn-active' : ''}`}
>
0 ? 'pr-2' : ''}`}>
Workspace
{(stats.docsTodosOpenCount ?? 0) > 0 && (
{stats.docsTodosOpenCount}
)}
{
triggerHaptic(10);
setActiveTab('lists');
}}
className={`tab-btn ${activeTab === 'lists' ? 'tab-btn-active' : ''}`}
>
Listen
{/* Borderless Circled Question Mark Help Button in Tab Bar */}
{/* Main Container */}
setActiveTab('triage')}>
{activeTab === 'triage' && (
)}
{activeTab === 'tasks' && (
)}
{activeTab === 'workspace' && (
)}
{activeTab === 'help' && (
setActiveTab(previousTab || 'triage')} />
)}
{activeTab === 'lists' && (
)}
{activeTab === 'groups' && (
setActiveTab(previousTab || 'triage')}
/>
)}
{activeTab === 'user_settings' && (
{
setUser(u);
refreshAll();
}}
onBack={() => setActiveTab('triage')}
/>
)}
{activeTab === 'admin_settings' && isServerAdmin && (
setActiveTab('triage')}
/>
)}
{/* MOBILE BOTTOM NAVIGATION BAR (5 equal columns -> + FAB is dead-center) */}
{/* Col 1: Triage */}
{
triggerHaptic(10);
setActiveTab('triage');
}}
className={`min-h-[48px] rounded-xl flex flex-col items-center justify-center gap-0.5 transition-colors relative cursor-pointer active:scale-95 ${
activeTab === 'triage' ? 'text-accent font-semibold' : 'text-muted hover:text-main'
}`}
title="Triage"
>
{stats.inboxCount > 0 && (
{stats.inboxCount}
)}
Triage
{/* Col 2: Aufgaben */}
{
triggerHaptic(10);
setActiveTab('tasks');
}}
className={`min-h-[48px] rounded-xl flex flex-col items-center justify-center gap-0.5 transition-colors relative cursor-pointer active:scale-95 ${
activeTab === 'tasks' ? 'text-accent font-semibold' : 'text-muted hover:text-main'
}`}
title="Aufgaben"
>
{stats.tasksOpenCount > 0 && (
{stats.tasksOpenCount}
)}
Aufgaben
{/* Col 3 (EXACT CENTER): + Quick-Add FAB */}
{
triggerHaptic(15);
if (primaryAction) {
primaryAction();
} else {
openMobileTriageCapture();
}
}}
className="w-14 h-14 -mt-6 rounded-full bg-accent text-on-accent shadow-lg flex items-center justify-center active:scale-90 transition-transform border-4 border-panel cursor-pointer"
title="Schnell erfassen (+)"
>
{/* Col 4: Workspace */}
{
triggerHaptic(10);
setActiveTab('workspace');
}}
className={`min-h-[48px] rounded-xl flex flex-col items-center justify-center gap-0.5 transition-colors relative cursor-pointer active:scale-95 ${
activeTab === 'workspace' ? 'text-accent font-semibold' : 'text-muted hover:text-main'
}`}
title="Workspace"
>
{(stats.docsTodosOpenCount ?? 0) > 0 && (
{stats.docsTodosOpenCount}
)}
Workspace
{/* Col 5: Listen */}
{
triggerHaptic(10);
setActiveTab('lists');
}}
className={`min-h-[48px] rounded-xl flex flex-col items-center justify-center gap-0.5 transition-colors relative cursor-pointer active:scale-95 ${
activeTab === 'lists' ? 'text-accent font-semibold' : 'text-muted hover:text-main'
}`}
title="Listen"
>
Listen
{/* MOBILE QUICK-ADD BOTTOM SHEET / MODAL (DESIGN-ALIGNED WITH TASKS VIEW) */}
{showMobileCapture && (
setShowMobileCapture(false)}
>
e.stopPropagation()}
>
{/* Header */}
setShowMobileCapture(false)}
className="p-1.5 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
>
)}
{/* MOBILE USER PROFILE SHEET */}
{showMobileProfile && (
setShowMobileProfile(false)}
>
e.stopPropagation()}
>
{/* User Info Header */}
{user.displayName}
{user.email}
{roleLabel}
setShowMobileProfile(false)}
className="p-1 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
>
{/* Theme Quick Switcher */}
Design-Theme
{AVAILABLE_THEMES.map((t) => (
handleChangeTheme(t.id)}
className={`h-9 px-2.5 text-xs font-medium border rounded-btn flex items-center gap-2 transition-colors cursor-pointer ${
activeTheme === t.id
? 'bg-panel-elevated border-accent text-main font-semibold'
: 'bg-canvas border-border text-muted hover:text-main'
}`}
>
{t.name.replace('MindSift ', '')}
))}
{/* Menu Items */}
{!isStandaloneApp && (
{
triggerHaptic(10);
setShowMobileProfile(false);
setShowManualPwaGuide(false);
setShowPwaInstallModal(true);
}}
className="w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 bg-accent/10 border border-accent/30 text-accent font-semibold transition-colors text-xs cursor-pointer mb-1"
>
Web-App auf Smartphone installieren
)}
{
triggerHaptic(10);
setShowMobileProfile(false);
setActiveTab('user_settings');
}}
className={`w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 hover:bg-panel-elevated transition-colors text-xs cursor-pointer ${
activeTab === 'user_settings' ? 'text-accent font-semibold bg-panel-elevated' : 'text-main'
}`}
>
Kontoeinstellungen
{
triggerHaptic(10);
setShowMobileProfile(false);
setActiveTab('groups');
}}
className={`w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 hover:bg-panel-elevated transition-colors text-xs cursor-pointer ${
activeTab === 'groups' ? 'text-accent font-semibold bg-panel-elevated' : 'text-main'
}`}
>
Gruppen
{
setShowMobileProfile(false);
handleOpenHelp();
}}
className={`w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 hover:bg-panel-elevated transition-colors text-xs cursor-pointer ${
activeTab === 'help' ? 'text-accent font-semibold bg-panel-elevated' : 'text-main'
}`}
>
Hilfe & Anleitung
{isServerAdmin && (
{
triggerHaptic(10);
setShowMobileProfile(false);
setActiveTab('admin_settings');
}}
className={`w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 hover:bg-panel-elevated transition-colors text-xs cursor-pointer ${
activeTab === 'admin_settings' ? 'text-accent font-semibold bg-panel-elevated' : 'text-main'
}`}
>
Administration
)}
{/* Logout */}
{
setShowMobileProfile(false);
handleLogout();
}}
className="w-full px-3 py-2.5 rounded-btn text-left flex items-center gap-3 text-status-err hover:bg-status-err-bg transition-colors text-xs font-medium cursor-pointer"
>
Abmelden
)}
{/* IMMEDIATE MOBILE WEB-APP INSTALLATION OFFER SHEET */}
{showPwaInstallModal && !isStandaloneApp && (
e.stopPropagation()}
>
{authStatus.instanceName || 'MindSift'} als App installieren
Vollbild ohne Browser-Leiste, schnellerer Start & Direktzugriff vom Home-Bildschirm.
{!showManualPwaGuide ? (
Jetzt als Web-App installieren
Später im Browser weiter nutzen
) : (
In 2 Sekunden zum Home-Bildschirm hinzufügen:
Tippe im Browser auf{' '}
Teilen
{' '}
(iPhone/Safari) oder das{' '}
Browser-Menü (⋮) .
Wähle{' '}
„Zum Home-Bildschirm“ bzw.{' '}
„App installieren“ .
Verstanden
)}
)}
);
};
export default App;