Dev #3

Merged
sandrodach merged 30 commits from dev into main 2026-10-25 18:00:10 +01:00
Showing only changes of commit 107361bb0f - Show all commits
+315 -6
View File
@@ -12,6 +12,8 @@ import {
RotateCcw, RotateCcw,
Palette, Palette,
HelpCircle, HelpCircle,
Plus,
X,
} from 'lucide-react'; } from 'lucide-react';
import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types'; import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types';
import { import {
@@ -118,6 +120,10 @@ export const App: React.FC = () => {
const [showProfileMenu, setShowProfileMenu] = useState(false); const [showProfileMenu, setShowProfileMenu] = useState(false);
const dropdownRef = useRef<HTMLDivElement | null>(null); const dropdownRef = useRef<HTMLDivElement | null>(null);
// Mobile Navigation & Quick-Action State
const [showMobileCapture, setShowMobileCapture] = useState(false);
const [showMobileProfile, setShowMobileProfile] = useState(false);
const handleOpenHelp = () => { const handleOpenHelp = () => {
triggerHaptic(10); triggerHaptic(10);
setPreviousTab((prev) => (activeTab === 'help' ? prev : activeTab)); setPreviousTab((prev) => (activeTab === 'help' ? prev : activeTab));
@@ -273,8 +279,12 @@ export const App: React.FC = () => {
const handleTriggerAddButton = () => { const handleTriggerAddButton = () => {
triggerHaptic(20); triggerHaptic(20);
window.scrollTo({ top: 0, behavior: 'smooth' }); if (window.innerWidth < 640) {
setCaptureFocusCount((c) => c + 1); setShowMobileCapture(true);
} else {
window.scrollTo({ top: 0, behavior: 'smooth' });
setCaptureFocusCount((c) => c + 1);
}
}; };
const handleLogout = async () => { const handleLogout = async () => {
@@ -322,7 +332,26 @@ export const App: React.FC = () => {
<div className="min-h-screen bg-canvas text-main flex flex-col font-sans selection:bg-accent/30"> <div className="min-h-screen bg-canvas text-main flex flex-col font-sans selection:bg-accent/30">
{/* Top Header Bar */} {/* Top Header Bar */}
<header className="sticky top-0 z-30 bg-panel border-b border-border shadow-sm"> <header className="sticky top-0 z-30 bg-panel border-b border-border shadow-sm">
<div className="max-w-6xl mx-auto px-3 sm:px-4 py-2 flex items-start justify-between gap-3 sm:gap-6"> {/* Mobile Header: Centered Logo & Instance Title */}
<div className="sm:hidden flex items-center justify-center relative h-11 px-4">
<div
onClick={() => {
triggerHaptic(10);
setActiveTab('triage');
}}
className="flex items-center gap-2 cursor-pointer select-none"
>
<div className="w-6 h-6 bg-accent text-on-accent rounded-btn flex items-center justify-center">
<Brain className="w-3.5 h-3.5" />
</div>
<span className="font-bold text-sm text-main tracking-tight">
{authStatus.instanceName || 'MindSift'}
</span>
</div>
</div>
{/* Desktop Header: 3-column Layout (Logo, InstantCaptureBar, Profile Pill) */}
<div className="hidden sm:flex max-w-6xl mx-auto px-4 py-2 items-start justify-between gap-6">
{/* Logo & Headline */} {/* Logo & Headline */}
<div <div
onClick={() => { onClick={() => {
@@ -471,8 +500,8 @@ export const App: React.FC = () => {
</div> </div>
</div> </div>
{/* Navigation tabs attached to header */} {/* Desktop Navigation tabs attached to header */}
<nav className="max-w-6xl mx-auto px-1 sm:px-2 border-t border-border flex items-stretch justify-between"> <nav className="hidden sm:flex max-w-6xl mx-auto px-2 border-t border-border items-stretch justify-between">
<div className="flex items-stretch overflow-x-auto no-scrollbar"> <div className="flex items-stretch overflow-x-auto no-scrollbar">
<button <button
onClick={() => { onClick={() => {
@@ -559,7 +588,7 @@ export const App: React.FC = () => {
</header> </header>
{/* Main Container */} {/* Main Container */}
<main className="flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-6"> <main className="flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 sm:pb-6">
<ViewErrorBoundary onReset={() => setActiveTab('triage')}> <ViewErrorBoundary onReset={() => setActiveTab('triage')}>
<section> <section>
{activeTab === 'triage' && ( {activeTab === 'triage' && (
@@ -618,6 +647,286 @@ export const App: React.FC = () => {
</section> </section>
</ViewErrorBoundary> </ViewErrorBoundary>
</main> </main>
{/* MOBILE BOTTOM NAVIGATION BAR */}
<nav className="sm:hidden fixed bottom-0 left-0 right-0 z-40 bg-panel/95 backdrop-blur-md border-t border-border px-3 py-1 flex items-center justify-around pb-[calc(0.4rem+env(safe-area-inset-bottom))] shadow-panel">
{/* Tab 1: Triage */}
<button
type="button"
onClick={() => {
triggerHaptic(10);
setActiveTab('triage');
}}
className={`p-2 rounded-btn flex flex-col items-center justify-center transition-colors relative cursor-pointer ${
activeTab === 'triage' ? 'text-accent' : 'text-muted hover:text-main'
}`}
title="Triage"
>
<Layers className="w-5 h-5" />
{stats.inboxCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 min-w-[15px] h-[15px] px-0.5 rounded-full text-[9px] font-bold leading-none bg-accent text-on-accent flex items-center justify-center shadow-xs pointer-events-none">
{stats.inboxCount}
</span>
)}
</button>
{/* Tab 2: Aufgaben */}
<button
type="button"
onClick={() => {
triggerHaptic(10);
setActiveTab('tasks');
}}
className={`p-2 rounded-btn flex flex-col items-center justify-center transition-colors relative cursor-pointer ${
activeTab === 'tasks' ? 'text-accent' : 'text-muted hover:text-main'
}`}
title="Aufgaben"
>
<CheckSquare className="w-5 h-5" />
{stats.tasksOpenCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 min-w-[15px] h-[15px] px-0.5 rounded-full text-[9px] font-bold leading-none bg-accent text-on-accent flex items-center justify-center shadow-xs pointer-events-none">
{stats.tasksOpenCount}
</span>
)}
</button>
{/* CENTER ACTION: + Quick-Add FAB */}
<button
type="button"
onClick={() => {
triggerHaptic(15);
setShowMobileCapture(true);
}}
className="w-12 h-12 -mt-5 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 (+)"
>
<Plus className="w-6 h-6 stroke-[2.5]" />
</button>
{/* Tab 3: Workspace */}
<button
type="button"
onClick={() => {
triggerHaptic(10);
setActiveTab('workspace');
}}
className={`p-2 rounded-btn flex flex-col items-center justify-center transition-colors relative cursor-pointer ${
activeTab === 'workspace' ? 'text-accent' : 'text-muted hover:text-main'
}`}
title="Workspace"
>
<FileText className="w-5 h-5" />
{(stats.docsTodosOpenCount ?? 0) > 0 && (
<span className="absolute -top-0.5 -right-0.5 min-w-[15px] h-[15px] px-0.5 rounded-full text-[9px] font-bold leading-none bg-accent text-on-accent flex items-center justify-center shadow-xs pointer-events-none">
{stats.docsTodosOpenCount}
</span>
)}
</button>
{/* Tab 4: Listen */}
<button
type="button"
onClick={() => {
triggerHaptic(10);
setActiveTab('lists');
}}
className={`p-2 rounded-btn flex flex-col items-center justify-center transition-colors relative cursor-pointer ${
activeTab === 'lists' ? 'text-accent' : 'text-muted hover:text-main'
}`}
title="Listen"
>
<ShoppingCart className="w-5 h-5" />
</button>
{/* User Profile Avatar */}
<button
type="button"
onClick={() => {
triggerHaptic(10);
setShowMobileProfile(true);
}}
className={`p-1.5 rounded-btn flex items-center justify-center transition-colors cursor-pointer ${
activeTab === 'user_settings' || activeTab === 'admin_settings' || activeTab === 'help'
? 'ring-2 ring-accent'
: 'opacity-85 hover:opacity-100'
}`}
title="Profil & Einstellungen"
>
<div className="w-6 h-6 rounded-full bg-accent text-on-accent text-[11px] font-bold flex items-center justify-center">
{user.displayName ? user.displayName.slice(0, 1).toUpperCase() : 'U'}
</div>
</button>
</nav>
{/* MOBILE QUICK-ADD BOTTOM SHEET / MODAL */}
{showMobileCapture && (
<div
className="sm:hidden fixed inset-0 z-50 flex items-end justify-center bg-black/60 backdrop-blur-xs animate-in fade-in duration-150"
onClick={() => setShowMobileCapture(false)}
>
<div
className="w-full bg-panel border-t border-border rounded-t-2xl shadow-panel p-4 pb-[calc(1.5rem+env(safe-area-inset-bottom))] animate-in slide-in-from-bottom duration-200"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between pb-3 border-b border-border mb-3">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-btn bg-accent/15 text-accent flex items-center justify-center">
<Plus className="w-4 h-4" />
</div>
<h3 className="text-sm font-semibold text-main">Schnell erfassen</h3>
</div>
<button
type="button"
onClick={() => setShowMobileCapture(false)}
className="p-1 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
{/* InstantCaptureBar */}
<div className="pt-1">
<InstantCaptureBar
onCaptured={(thought, target) => {
handleCaptured(thought, target);
setShowMobileCapture(false);
}}
lists={lists}
autoFocusTrigger={Date.now()}
/>
</div>
</div>
</div>
)}
{/* MOBILE USER PROFILE SHEET */}
{showMobileProfile && (
<div
className="sm:hidden fixed inset-0 z-50 flex items-end justify-center bg-black/60 backdrop-blur-xs animate-in fade-in duration-150"
onClick={() => setShowMobileProfile(false)}
>
<div
className="w-full max-h-[85vh] overflow-y-auto bg-panel border-t border-border rounded-t-2xl shadow-panel p-4 pb-[calc(1.5rem+env(safe-area-inset-bottom))] space-y-4 animate-in slide-in-from-bottom duration-200 divide-y divide-border"
onClick={(e) => e.stopPropagation()}
>
{/* User Info Header */}
<div className="flex items-center justify-between pb-2">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-accent text-on-accent text-sm font-bold flex items-center justify-center flex-shrink-0">
{user.displayName ? user.displayName.slice(0, 1).toUpperCase() : 'U'}
</div>
<div>
<p className="font-bold text-sm text-main">{user.displayName}</p>
<p className="text-xs text-muted">{user.email}</p>
<span className="inline-block mt-1 px-1.5 py-0.5 bg-canvas border border-border rounded-btn text-[10px] text-accent font-semibold">
{roleLabel}
</span>
</div>
</div>
<button
type="button"
onClick={() => setShowMobileProfile(false)}
className="p-1 text-faint hover:text-main rounded-btn transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Theme Quick Switcher */}
<div className="pt-3 space-y-2">
<span className="text-[11px] font-semibold text-muted uppercase tracking-wider flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-accent" />
<span>Design-Theme</span>
</span>
<div className="grid grid-cols-2 gap-2">
{AVAILABLE_THEMES.map((t) => (
<button
key={t.id}
type="button"
onClick={() => 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'
}`}
>
<span
className="w-3 h-3 border border-strong flex-shrink-0 rounded-full"
style={{ backgroundColor: t.previewBg }}
/>
<span className="truncate">{t.name.replace('MindSift ', '')}</span>
</button>
))}
</div>
</div>
{/* Menu Items */}
<div className="pt-3 space-y-1">
<button
type="button"
onClick={() => {
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'
}`}
>
<Settings className="w-4 h-4 text-accent" />
<span>Kontoeinstellungen</span>
</button>
<button
type="button"
onClick={() => {
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'
}`}
>
<HelpCircle className="w-4 h-4 text-accent" />
<span>Hilfe & Anleitung</span>
</button>
{isStaff && (
<button
type="button"
onClick={() => {
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'
}`}
>
<Shield className="w-4 h-4 text-accent" />
<span>Administration</span>
</button>
)}
</div>
{/* Logout */}
<div className="pt-3">
<button
type="button"
onClick={() => {
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"
>
<LogOut className="w-4 h-4" />
<span>Abmelden</span>
</button>
</div>
</div>
</div>
)}
</div> </div>
); );
}; };