mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
fix(calendar): add auth gate to prevent 401 errors and fix CSP for analytics
Child components' onMount callbacks fire before the parent layout's auth check in Svelte, causing API calls (todo, contacts, calendar) to fire without a valid token on initial page load. Added appReady gate so children only render after auth is confirmed. Also added stats.mana.how to CSP script-src to allow Umami analytics. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
28d8cfcbe7
commit
79becc971b
2 changed files with 117 additions and 105 deletions
|
|
@ -48,7 +48,7 @@ window.__PUBLIC_CONTACTS_API_URL__ = "${PUBLIC_CONTACTS_API_URL}";
|
|||
'Content-Security-Policy',
|
||||
[
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline'",
|
||||
"script-src 'self' 'unsafe-inline' https://stats.mana.how",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https:",
|
||||
`connect-src 'self' ${PUBLIC_MANA_CORE_AUTH_URL_CLIENT} ${PUBLIC_BACKEND_URL_CLIENT} ${PUBLIC_STT_URL} ${PUBLIC_TODO_BACKEND_URL} ${PUBLIC_CONTACTS_API_URL}`,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@
|
|||
|
||||
let { children } = $props();
|
||||
|
||||
// Auth gate - prevent children from mounting before auth is confirmed
|
||||
let appReady = $state(false);
|
||||
|
||||
// InputBar search - search events
|
||||
async function handleSearch(query: string): Promise<QuickInputItem[]> {
|
||||
if (!query.trim()) return [];
|
||||
|
|
@ -378,6 +381,9 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Auth confirmed - allow children to render
|
||||
appReady = true;
|
||||
|
||||
// Initialize split-panel from URL/localStorage
|
||||
splitPanel.initialize();
|
||||
|
||||
|
|
@ -408,114 +414,120 @@
|
|||
|
||||
<svelte:window onkeydown={handleKeydown} onresize={updateMobileState} />
|
||||
|
||||
<SplitPaneContainer>
|
||||
<div class="layout-container">
|
||||
<a
|
||||
href="#main-content"
|
||||
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-primary focus:px-4 focus:py-2 focus:text-white"
|
||||
>
|
||||
Zum Inhalt springen
|
||||
</a>
|
||||
|
||||
<!-- UI Elements (hidden in immersive mode) -->
|
||||
{#if !settingsStore.immersiveModeEnabled}
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
{prependElements}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="Kalender"
|
||||
homeRoute="/"
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
themeMode={theme.mode}
|
||||
onThemeModeChange={handleThemeModeChange}
|
||||
showLanguageSwitcher={true}
|
||||
{languageItems}
|
||||
{currentLanguageLabel}
|
||||
showLogout={authStore.isAuthenticated}
|
||||
onLogout={handleLogout}
|
||||
loginHref="/login"
|
||||
primaryColor="#3b82f6"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
manaHref="/mana"
|
||||
profileHref="/profile"
|
||||
allAppsHref="/apps"
|
||||
onOpenInPanel={handleOpenInPanel}
|
||||
ariaLabel="Hauptnavigation"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Unified Bar: QuickInputBar + DateStrip + TagStrip + CalendarToolbar -->
|
||||
<UnifiedBar
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}
|
||||
onSearchChange={handleSearchChange}
|
||||
placeholder="Neuer Termin oder suchen..."
|
||||
emptyText="Keine Termine gefunden"
|
||||
searchingText="Suche..."
|
||||
createText="Erstellen"
|
||||
appIcon="calendar"
|
||||
defaultOptions={calendarOptions}
|
||||
selectedDefaultId={selectedDefaultCalendarId}
|
||||
defaultOptionLabel="Standard-Kalender"
|
||||
onDefaultChange={handleDefaultCalendarChange}
|
||||
onShowShortcuts={handleShowShortcuts}
|
||||
onShowSyntaxHelp={handleShowSyntaxHelp}
|
||||
showCalendarLayers={showCalendarToolbar}
|
||||
{isMobile}
|
||||
hidden={settingsStore.immersiveModeEnabled}
|
||||
>
|
||||
{#snippet leftAction()}
|
||||
{#if voiceRecordingStore.isSupported}
|
||||
<VoiceRecordButton onResult={handleVoiceResult} size={32} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</UnifiedBar>
|
||||
|
||||
<!-- Voice Recording Modal -->
|
||||
<VoiceRecordingModal onResult={handleVoiceResult} />
|
||||
|
||||
<!-- Immersive Mode Toggle (always visible on main calendar page) -->
|
||||
<ImmersiveModeToggle
|
||||
isImmersive={settingsStore.immersiveModeEnabled}
|
||||
onToggle={() => settingsStore.toggleImmersiveMode()}
|
||||
visible={showCalendarToolbar}
|
||||
/>
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
class="main-content bg-background"
|
||||
class:has-toolbar={showCalendarToolbar}
|
||||
class:immersive={settingsStore.immersiveModeEnabled}
|
||||
aria-label="Kalender"
|
||||
>
|
||||
<div
|
||||
class="content-wrapper"
|
||||
class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}
|
||||
class:immersive={settingsStore.immersiveModeEnabled}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
{#if !appReady}
|
||||
<div class="flex items-center justify-center h-screen bg-background">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
</SplitPaneContainer>
|
||||
{:else}
|
||||
<SplitPaneContainer>
|
||||
<div class="layout-container">
|
||||
<a
|
||||
href="#main-content"
|
||||
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-primary focus:px-4 focus:py-2 focus:text-white"
|
||||
>
|
||||
Zum Inhalt springen
|
||||
</a>
|
||||
|
||||
<!-- InputBar Help Modal -->
|
||||
<InputBarHelpModal open={helpModalOpen} onClose={handleCloseHelpModal} mode={helpModalMode} />
|
||||
<!-- UI Elements (hidden in immersive mode) -->
|
||||
{#if !settingsStore.immersiveModeEnabled}
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
{prependElements}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="Kalender"
|
||||
homeRoute="/"
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
themeMode={theme.mode}
|
||||
onThemeModeChange={handleThemeModeChange}
|
||||
showLanguageSwitcher={true}
|
||||
{languageItems}
|
||||
{currentLanguageLabel}
|
||||
showLogout={authStore.isAuthenticated}
|
||||
onLogout={handleLogout}
|
||||
loginHref="/login"
|
||||
primaryColor="#3b82f6"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
manaHref="/mana"
|
||||
profileHref="/profile"
|
||||
allAppsHref="/apps"
|
||||
onOpenInPanel={handleOpenInPanel}
|
||||
ariaLabel="Hauptnavigation"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<SettingsModal visible={showSettingsModal} onClose={() => (showSettingsModal = false)} />
|
||||
<!-- Unified Bar: QuickInputBar + DateStrip + TagStrip + CalendarToolbar -->
|
||||
<UnifiedBar
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}
|
||||
onSearchChange={handleSearchChange}
|
||||
placeholder="Neuer Termin oder suchen..."
|
||||
emptyText="Keine Termine gefunden"
|
||||
searchingText="Suche..."
|
||||
createText="Erstellen"
|
||||
appIcon="calendar"
|
||||
defaultOptions={calendarOptions}
|
||||
selectedDefaultId={selectedDefaultCalendarId}
|
||||
defaultOptionLabel="Standard-Kalender"
|
||||
onDefaultChange={handleDefaultCalendarChange}
|
||||
onShowShortcuts={handleShowShortcuts}
|
||||
onShowSyntaxHelp={handleShowSyntaxHelp}
|
||||
showCalendarLayers={showCalendarToolbar}
|
||||
{isMobile}
|
||||
hidden={settingsStore.immersiveModeEnabled}
|
||||
>
|
||||
{#snippet leftAction()}
|
||||
{#if voiceRecordingStore.isSupported}
|
||||
<VoiceRecordButton onResult={handleVoiceResult} size={32} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</UnifiedBar>
|
||||
|
||||
<!-- App Onboarding Modal (shown once on first visit) -->
|
||||
{#if calendarOnboarding.shouldShow}
|
||||
<MiniOnboardingModal store={calendarOnboarding} appName="Kalender" appEmoji="📅" />
|
||||
<!-- Voice Recording Modal -->
|
||||
<VoiceRecordingModal onResult={handleVoiceResult} />
|
||||
|
||||
<!-- Immersive Mode Toggle (always visible on main calendar page) -->
|
||||
<ImmersiveModeToggle
|
||||
isImmersive={settingsStore.immersiveModeEnabled}
|
||||
onToggle={() => settingsStore.toggleImmersiveMode()}
|
||||
visible={showCalendarToolbar}
|
||||
/>
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
class="main-content bg-background"
|
||||
class:has-toolbar={showCalendarToolbar}
|
||||
class:immersive={settingsStore.immersiveModeEnabled}
|
||||
aria-label="Kalender"
|
||||
>
|
||||
<div
|
||||
class="content-wrapper"
|
||||
class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}
|
||||
class:immersive={settingsStore.immersiveModeEnabled}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</SplitPaneContainer>
|
||||
|
||||
<!-- InputBar Help Modal -->
|
||||
<InputBarHelpModal open={helpModalOpen} onClose={handleCloseHelpModal} mode={helpModalMode} />
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<SettingsModal visible={showSettingsModal} onClose={() => (showSettingsModal = false)} />
|
||||
|
||||
<!-- App Onboarding Modal (shown once on first visit) -->
|
||||
{#if calendarOnboarding.shouldShow}
|
||||
<MiniOnboardingModal store={calendarOnboarding} appName="Kalender" appEmoji="📅" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue