feat(ui): add desktop footer, legal custom link and about dialog

This commit is contained in:
sandrodach committed 2026-10-30 20:19:02 +01:00
1 parent bc7bdc481e
commit c181ffea54
12 files changed
+179 -13

No files matched your search

+30 -2
View File
@@ -65,6 +65,8 @@ import { useMobileActions } from './context/MobileActionContext';
import { MobileHeaderMenu } from './components/MobileHeaderMenu';
import { UserAvatar } from './components/UserAvatar';
import { CommandPalette } from './components/CommandPalette';
import { AppFooter } from './components/AppFooter';
import { AboutModal } from './components/AboutModal';
import { APP_VERSION } from './version';
type ActiveTab =
@@ -170,6 +172,7 @@ export const App: React.FC = () => {
// Profile Dropdown State
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [showAbout, setShowAbout] = useState(false);
const dropdownRef = useRef<HTMLDivElement | null>(null);
// Mobile Navigation & Quick-Action State
@@ -1119,8 +1122,8 @@ export const App: React.FC = () => {
{/* Main Container */}
{/* The workspace fills the window exactly and scrolls only inside the note, never the page */}
<main
className={`flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 sm:pb-6 ${
activeTab === 'workspace' ? 'min-h-0 flex flex-col' : ''
className={`flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 ${
activeTab === 'workspace' ? 'min-h-0 flex flex-col sm:pb-0' : 'sm:pb-6'
}`}
>
<ViewErrorBoundary onReset={() => setActiveTab('triage')}>
@@ -1202,6 +1205,11 @@ export const App: React.FC = () => {
</ViewErrorBoundary>
</main>
<AppFooter
legal={authStatus.legal}
onOpenAbout={() => setShowAbout(true)}
/>
{/* MOBILE BOTTOM NAVIGATION BAR (5 equal columns -> + FAB is dead-center) */}
<nav className="sm:hidden fixed bottom-0 left-0 right-0 z-40 bg-panel/95 backdrop-blur-md border-t border-border px-2 pt-1.5 pb-[calc(0.5rem+env(safe-area-inset-bottom))] grid grid-cols-5 items-center shadow-panel">
{/* Col 1: Triage */}
@@ -1594,6 +1602,19 @@ export const App: React.FC = () => {
<span>Hilfe & Anleitung</span>
</button>
<button
type="button"
onClick={() => {
triggerHaptic(10);
setShowMobileProfile(false);
setShowAbout(true);
}}
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 text-main"
>
<Brain className="w-4 h-4 text-accent" />
<span>Über MindSift</span>
</button>
{isServerAdmin && (
<button
type="button"
@@ -1626,6 +1647,11 @@ export const App: React.FC = () => {
<span>Abmelden</span>
</button>
</div>
{/* Version & About Hint on Mobile */}
<div className="pt-2 text-center text-[10px] text-faint font-mono">
MindSift {APP_VERSION}
</div>
</div>
</div>
)}
@@ -1725,6 +1751,8 @@ export const App: React.FC = () => {
onOpenTask={handleOpenTask}
onOpenList={handleOpenList}
/>
{showAbout && <AboutModal onClose={() => setShowAbout(false)} />}
</div>
);
};
+45
View File
@@ -0,0 +1,45 @@
import React from 'react';
import { AuthStatus } from '../lib/types';
import { APP_VERSION } from '../version';
import { LegalLinks } from './LegalLinks';
interface AppFooterProps {
legal?: AuthStatus['legal'];
onOpenAbout: () => void;
}
export const AppFooter: React.FC<AppFooterProps> = ({ legal, onOpenAbout }) => {
return (
<footer className="hidden sm:flex shrink-0 w-full max-w-6xl mx-auto px-4 py-2 items-center justify-between text-[11px] text-faint border-t border-border/40 select-none">
<div className="flex items-center gap-3">
<button
type="button"
onClick={onOpenAbout}
className="font-semibold text-muted hover:text-main transition-colors cursor-pointer"
>
MindSift
</button>
<button
type="button"
onClick={onOpenAbout}
className="font-mono hover:text-main transition-colors cursor-pointer"
title="Über MindSift & Versionsdetails"
>
{APP_VERSION}
</button>
<span className="text-border">·</span>
<button
type="button"
onClick={onOpenAbout}
className="hover:text-main transition-colors cursor-pointer underline-offset-2 hover:underline"
>
Über MindSift
</button>
</div>
<div className="flex items-center gap-3">
<LegalLinks legal={legal} className="!text-faint gap-3" />
</div>
</footer>
);
};
+13 -1
View File
@@ -28,7 +28,9 @@ export const LegalLinks: React.FC<LegalLinksProps> = ({ legal, className = '', c
}))
.filter((e) => e.url || e.hasContent);
if (entries.length === 0 && !children) return null;
const customHref = legal?.customLinkUrl && safeHref(legal.customLinkUrl);
if (entries.length === 0 && !children && !customHref) return null;
const openText = async (type: LegalType) => {
setError(null);
@@ -52,6 +54,16 @@ export const LegalLinks: React.FC<LegalLinksProps> = ({ legal, className = '', c
</button>
)
)}
{customHref && (
<a
href={customHref as string}
target="_blank"
rel="noopener noreferrer"
className="hover:text-main underline-offset-2 hover:underline"
>
{legal?.customLinkLabel || legal?.customLinkUrl}
</a>
)}
{children}
{error && <span className="text-status-err">{error}</span>}
{open && (
+2
View File
@@ -20,6 +20,8 @@ export interface AuthStatus {
hasImprintContent?: boolean;
privacyUrl?: string;
hasPrivacyContent?: boolean;
customLinkLabel?: string;
customLinkUrl?: string;
};
// Voice input: own Whisper server, the browser's recognition, or switched off (STT_* environment)
voice?: { mode: VoiceMode; maxSeconds: number };