mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
🐛 fix(calendar): fix app hanging and layout issues
- Fix $effect infinite loop causing app to hang after guest mode - Track viewType and currentDate primitives instead of derived viewRange - Fix root layout to use h-screen flex container for full height - Fix guest banner offset using padding instead of margin - Calendar now fills entire screen with UI floating on top - Simplify i18n initialization
This commit is contained in:
parent
be365a0c1e
commit
3df7157389
4 changed files with 47 additions and 61 deletions
|
|
@ -35,17 +35,12 @@ function getInitialLocale(): SupportedLocale {
|
|||
}
|
||||
|
||||
// Initialize i18n at module scope (required for SSR)
|
||||
// Always set initialLocale to ensure it's never undefined
|
||||
// getInitialLocale() internally checks for browser and falls back to defaultLocale
|
||||
init({
|
||||
fallbackLocale: defaultLocale,
|
||||
initialLocale: browser ? getInitialLocale() : defaultLocale,
|
||||
initialLocale: getInitialLocale(),
|
||||
});
|
||||
|
||||
// On browser, also explicitly set locale to ensure it's loaded
|
||||
if (browser) {
|
||||
locale.set(getInitialLocale());
|
||||
}
|
||||
|
||||
// Set locale and persist to localStorage
|
||||
export function setLocale(newLocale: SupportedLocale) {
|
||||
locale.set(newLocale);
|
||||
|
|
|
|||
|
|
@ -854,13 +854,11 @@
|
|||
}
|
||||
|
||||
/* Offset content when guest banner is visible */
|
||||
.layout-container:has(.guest-banner) .main-content {
|
||||
margin-top: 40px;
|
||||
.layout-container:has(.guest-banner) {
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.layout-container:has(.guest-banner) .main-content.floating-mode {
|
||||
padding-top: calc(70px + 40px);
|
||||
}
|
||||
/* Floating mode already has padding-top, no extra adjustment needed since container handles banner offset */
|
||||
|
||||
/* Mobile: Fixed viewport, no scroll */
|
||||
@media (max-width: 768px) {
|
||||
|
|
@ -869,6 +867,12 @@
|
|||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-container:has(.guest-banner) {
|
||||
height: calc(100vh - 40px);
|
||||
margin-top: 40px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
|
@ -979,6 +983,11 @@
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Calendar fills entire screen - UI elements float on top */
|
||||
.main-content:has(.content-wrapper.calendar-expanded) {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Immersive Mode - fullscreen, no UI elements visible */
|
||||
.main-content.immersive {
|
||||
padding: 0 !important;
|
||||
|
|
|
|||
|
|
@ -76,15 +76,25 @@
|
|||
// Event is automatically removed from store
|
||||
}
|
||||
|
||||
// Track view changes to refetch events
|
||||
let lastViewType = $state(viewStore.viewType);
|
||||
let lastDateKey = $state(viewStore.currentDate.toDateString());
|
||||
|
||||
onMount(async () => {
|
||||
// Fetch events for current view range (works in both guest and authenticated mode)
|
||||
await eventsStore.fetchEvents(viewStore.viewRange.start, viewStore.viewRange.end);
|
||||
initialized = true;
|
||||
});
|
||||
|
||||
// Refetch events when view changes
|
||||
// Refetch events when view type or date changes
|
||||
$effect(() => {
|
||||
if (initialized) {
|
||||
const currentViewType = viewStore.viewType;
|
||||
const currentDateKey = viewStore.currentDate.toDateString();
|
||||
|
||||
// Only refetch if view actually changed
|
||||
if (initialized && (currentViewType !== lastViewType || currentDateKey !== lastDateKey)) {
|
||||
lastViewType = currentViewType;
|
||||
lastDateKey = currentDateKey;
|
||||
eventsStore.fetchEvents(viewStore.viewRange.start, viewStore.viewRange.end);
|
||||
}
|
||||
});
|
||||
|
|
@ -224,20 +234,20 @@
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius-full);
|
||||
background: hsl(var(--color-surface));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: all 150ms ease;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.sidebar-collapse-btn:hover {
|
||||
background: hsl(var(--color-muted));
|
||||
color: hsl(var(--color-foreground));
|
||||
background: var(--color-muted);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.calendar-main {
|
||||
|
|
@ -247,9 +257,9 @@
|
|||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: hsl(var(--color-surface));
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
|
|
@ -268,8 +278,8 @@
|
|||
.calendar-sidebar-mobile {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
background: hsl(var(--color-surface));
|
||||
border-top: 1px solid hsl(var(--color-border));
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 0.75rem;
|
||||
overflow-y: auto;
|
||||
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
|
@ -290,22 +300,19 @@
|
|||
flex-direction: column;
|
||||
gap: 0;
|
||||
flex: 1;
|
||||
height: 100%; /* Fill parent container */
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Hide desktop elements on mobile */
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show mobile elements */
|
||||
.mobile-only {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Calendar container */
|
||||
.calendar-main {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
|
|
@ -313,25 +320,21 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* When todos are visible: 50/50 split */
|
||||
.calendar-layout:has(.calendar-sidebar-mobile:not(.collapsed)) .calendar-main {
|
||||
flex: 0 0 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
/* When todos are collapsed: calendar takes full space */
|
||||
.calendar-layout:has(.calendar-sidebar-mobile.collapsed) .calendar-main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Calendar content must scroll internally */
|
||||
.calendar-content {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Todos section takes other half */
|
||||
.calendar-sidebar-mobile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -345,7 +348,6 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Make TodoSidebarSection fill the container */
|
||||
.calendar-sidebar-mobile > :global(*) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
|
@ -359,7 +361,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Tablet: Keep desktop layout but smaller sidebar */
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.calendar-sidebar {
|
||||
width: 220px;
|
||||
|
|
|
|||
|
|
@ -1,51 +1,32 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
// Initialize i18n early - must be imported before any component that uses $_
|
||||
import { waitLocale } from '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import '$lib/i18n';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { userSettings } from '$lib/stores/user-settings.svelte';
|
||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { toastStore } from '$lib/stores/toast.svelte';
|
||||
import ToastContainer from '$lib/components/ToastContainer.svelte';
|
||||
import { AppLoadingSkeleton } from '$lib/components/skeletons';
|
||||
import { isLoading as i18nLoading } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
// Wait for i18n locale to be loaded
|
||||
await waitLocale();
|
||||
|
||||
// Initialize theme
|
||||
theme.initialize();
|
||||
|
||||
// Initialize auth
|
||||
await authStore.initialize();
|
||||
|
||||
loading = false;
|
||||
});
|
||||
|
||||
// Load user settings when authenticated
|
||||
$effect(() => {
|
||||
if (authStore.isAuthenticated) {
|
||||
userSettings.load().then(() => {
|
||||
// Enable cloud sync for calendar settings after user settings are loaded
|
||||
settingsStore.enableCloudSync();
|
||||
});
|
||||
} else {
|
||||
settingsStore.disableCloudSync();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<ToastContainer />
|
||||
|
||||
{#if loading}
|
||||
{#if !appReady}
|
||||
<AppLoadingSkeleton />
|
||||
{:else}
|
||||
<div class="min-h-screen bg-background text-foreground">
|
||||
<div class="h-screen flex flex-col bg-background text-foreground overflow-hidden">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ToastContainer />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue