diff --git a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte index 0d6a179ab..395c47fdf 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte @@ -70,6 +70,7 @@ import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte'; import { heatmapStore } from '$lib/stores/heatmap.svelte'; import { sessionEventsStore } from '$lib/stores/session-events.svelte'; + import { GuestWelcomeModal, shouldShowGuestWelcome } from '@manacore/shared-auth-ui'; import type { CalendarViewType } from '@calendar/shared'; // App switcher items @@ -567,6 +568,9 @@ let showAuthGateModal = $state(false); let authGateAction = $state<'save' | 'sync' | 'feature'>('save'); + // Guest welcome modal state + let showGuestWelcome = $state(false); + // Show auth gate modal (can be called from child components) function showAuthGate(action: 'save' | 'sync' | 'feature' = 'save') { authGateAction = action; @@ -587,6 +591,11 @@ // Initialize session events for guest mode sessionEventsStore.initialize(); + // Show guest welcome modal for unauthenticated users + if (!authStore.isAuthenticated && shouldShowGuestWelcome('calendar')) { + showGuestWelcome = true; + } + // Load calendars and tags (works in both guest and authenticated mode) await calendarsStore.fetchCalendars(); @@ -839,6 +848,23 @@ action={authGateAction} /> + + (showGuestWelcome = false)} + onLogin={() => { + showGuestWelcome = false; + goto('/login'); + }} + onRegister={() => { + showGuestWelcome = false; + goto('/register'); + }} + helpHref="/help" + locale={currentLocale === 'en' ? 'en' : 'de'} +/> + diff --git a/packages/shared-auth-ui/src/index.ts b/packages/shared-auth-ui/src/index.ts index f90f2aa60..08b72a38b 100644 --- a/packages/shared-auth-ui/src/index.ts +++ b/packages/shared-auth-ui/src/index.ts @@ -6,6 +6,7 @@ export { default as ForgotPasswordPage } from './pages/ForgotPasswordPage.svelte // Components export { default as GoogleSignInButton } from './components/GoogleSignInButton.svelte'; export { default as AppleSignInButton } from './components/AppleSignInButton.svelte'; +export { default as GuestWelcomeModal } from './components/GuestWelcomeModal.svelte'; // Utilities export { @@ -28,10 +29,17 @@ export { type AppleAuthorizationResponse, } from './utils/appleAuth'; -// Types -export type { AuthUIConfig, AuthServiceInterface, AuthResult } from './types'; +export { + shouldShowGuestWelcome, + markGuestWelcomeSeen, + resetGuestWelcome, + resetAllGuestWelcome, +} from './utils/guestWelcome'; -// Page Translation Types -export type { LoginTranslations } from './pages/LoginPage.svelte'; -export type { RegisterTranslations } from './pages/RegisterPage.svelte'; -export type { ForgotPasswordTranslations } from './pages/ForgotPasswordPage.svelte'; +// Types +export type { + AuthUIConfig, + AuthServiceInterface, + AuthResult, + GuestWelcomeTranslations, +} from './types'; diff --git a/packages/shared-auth-ui/src/types.ts b/packages/shared-auth-ui/src/types.ts index e522a53b0..880641d36 100644 --- a/packages/shared-auth-ui/src/types.ts +++ b/packages/shared-auth-ui/src/types.ts @@ -60,3 +60,20 @@ export interface AuthResult { error?: string; needsVerification?: boolean; } + +/** + * Translation strings for the guest welcome modal + */ +export interface GuestWelcomeTranslations { + title: string; + guestModeTitle: string; + whatYouCanDo: string; + dataWarningTitle: string; + dataWarningText: string; + loginButton: string; + registerButton: string; + helpButton: string; + continueAsGuest: string; + /** App-specific feature list (array of strings) */ + features?: string[]; +} diff --git a/packages/shared-auth-ui/src/utils/guestWelcome.ts b/packages/shared-auth-ui/src/utils/guestWelcome.ts new file mode 100644 index 000000000..6ec910dcb --- /dev/null +++ b/packages/shared-auth-ui/src/utils/guestWelcome.ts @@ -0,0 +1,51 @@ +/** + * Utility functions for managing guest welcome modal state + */ + +const STORAGE_PREFIX = 'guest-welcome-seen'; + +/** + * Check if the guest welcome modal should be shown for an app + * @param appId The app identifier (e.g., 'contacts', 'chat') + * @returns true if the modal should be shown (not seen before) + */ +export function shouldShowGuestWelcome(appId: string): boolean { + if (typeof localStorage === 'undefined') return false; + const key = `${STORAGE_PREFIX}-${appId}`; + return localStorage.getItem(key) !== 'true'; +} + +/** + * Mark the guest welcome modal as seen for an app + * @param appId The app identifier + */ +export function markGuestWelcomeSeen(appId: string): void { + if (typeof localStorage === 'undefined') return; + const key = `${STORAGE_PREFIX}-${appId}`; + localStorage.setItem(key, 'true'); +} + +/** + * Reset the guest welcome modal state for an app (will show again) + * @param appId The app identifier + */ +export function resetGuestWelcome(appId: string): void { + if (typeof localStorage === 'undefined') return; + const key = `${STORAGE_PREFIX}-${appId}`; + localStorage.removeItem(key); +} + +/** + * Reset the guest welcome modal state for all apps + */ +export function resetAllGuestWelcome(): void { + if (typeof localStorage === 'undefined') return; + const keysToRemove: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key?.startsWith(STORAGE_PREFIX)) { + keysToRemove.push(key); + } + } + keysToRemove.forEach((key) => localStorage.removeItem(key)); +}