mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 02:26:43 +02:00
♻️ refactor: centralize AuthGateModal in shared-auth-ui
- Create shared AuthGateModal component in @manacore/shared-auth-ui - Migrate 4 apps to use shared component: chat, todo, contacts, calendar - Remove duplicate local AuthGateModal components - Support for 'save', 'sync', 'feature', 'ai' actions - Built-in i18n (DE + EN) with custom translation support - Optional migration info display for session data - Uses Phosphor icons from @manacore/shared-icons - Update CONSISTENCY_REPORT.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0d559c99d6
commit
69d405ca84
12 changed files with 363 additions and 698 deletions
|
|
@ -1,150 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
action?: 'save' | 'sync' | 'feature';
|
||||
featureName?: string;
|
||||
}
|
||||
|
||||
let { visible, onClose, action = 'save', featureName = '' }: Props = $props();
|
||||
|
||||
// Action-specific messages
|
||||
const messages = {
|
||||
save: {
|
||||
title: 'Eigene Termine erstellen',
|
||||
description:
|
||||
'Melde dich an, um deine eigenen Termine zu erstellen und auf allen Geräten zu synchronisieren.',
|
||||
icon: 'cloud',
|
||||
},
|
||||
sync: {
|
||||
title: 'Kostenlos anmelden',
|
||||
description:
|
||||
'Mit einem Account werden deine Termine automatisch synchronisiert und bleiben erhalten.',
|
||||
icon: 'refresh-cw',
|
||||
},
|
||||
feature: {
|
||||
title: `Anmelden für ${featureName}`,
|
||||
description: `Diese Funktion erfordert ein Konto. Melde dich an, um ${featureName} zu nutzen.`,
|
||||
icon: 'lock',
|
||||
},
|
||||
};
|
||||
|
||||
let currentMessage = $derived(messages[action]);
|
||||
|
||||
function handleLogin() {
|
||||
// Store return URL for redirect after login
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}
|
||||
|
||||
function handleBackdropClick(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
onclick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
class="bg-card border-border mx-4 w-full max-w-md rounded-xl border p-6 shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="auth-gate-title"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="bg-primary/10 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
||||
>
|
||||
{#if currentMessage.icon === 'cloud'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
{:else if currentMessage.icon === 'refresh-cw'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h2 id="auth-gate-title" class="mb-2 text-center text-xl font-semibold">
|
||||
{currentMessage.title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-muted-foreground mb-6 text-center text-sm">
|
||||
{currentMessage.description}
|
||||
</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
onclick={handleLogin}
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
<button
|
||||
onclick={handleRegister}
|
||||
class="bg-secondary text-secondary-foreground hover:bg-secondary/80 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Kostenloses Konto erstellen
|
||||
</button>
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="text-muted-foreground hover:text-foreground w-full py-2 text-sm transition-colors"
|
||||
>
|
||||
Später
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info text -->
|
||||
<p class="text-muted-foreground mt-4 text-center text-xs">
|
||||
Du kannst im Demo-Modus die Beispiel-Termine ansehen, aber keine eigenen erstellen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
import EventContextMenu from '$lib/components/event/EventContextMenu.svelte';
|
||||
import ViewModePillContextMenu from '$lib/components/calendar/ViewModePillContextMenu.svelte';
|
||||
import SettingsModal from '$lib/components/settings/SettingsModal.svelte';
|
||||
import AuthGateModal from '$lib/components/AuthGateModal.svelte';
|
||||
import { AuthGateModal } from '@manacore/shared-auth-ui';
|
||||
import VoiceRecordButton from '$lib/components/voice/VoiceRecordButton.svelte';
|
||||
import VoiceRecordingModal from '$lib/components/voice/VoiceRecordingModal.svelte';
|
||||
import { voiceRecordingStore } from '$lib/stores/voice-recording.svelte';
|
||||
|
|
@ -864,7 +864,23 @@
|
|||
<AuthGateModal
|
||||
visible={showAuthGateModal}
|
||||
onClose={() => (showAuthGateModal = false)}
|
||||
onLogin={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}}
|
||||
onRegister={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}}
|
||||
action={authGateAction}
|
||||
locale={currentLocale === 'en' ? 'en' : 'de'}
|
||||
infoText="Du kannst im Demo-Modus die Beispiel-Termine ansehen, aber keine eigenen erstellen."
|
||||
/>
|
||||
|
||||
<!-- Guest Welcome Modal -->
|
||||
|
|
|
|||
|
|
@ -1,230 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
action?: 'save' | 'sync' | 'ai' | 'feature';
|
||||
conversationCount?: number;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
let { open, action = 'ai', conversationCount = 0, onClose }: Props = $props();
|
||||
|
||||
// Messages based on action type
|
||||
const messages = {
|
||||
save: {
|
||||
title: 'Unterhaltungen speichern',
|
||||
description: 'Melde dich an, um deine Unterhaltungen dauerhaft in der Cloud zu speichern.',
|
||||
},
|
||||
sync: {
|
||||
title: 'Unterhaltungen synchronisieren',
|
||||
description: 'Melde dich an, um deine Unterhaltungen auf allen Geräten zu synchronisieren.',
|
||||
},
|
||||
ai: {
|
||||
title: 'KI-Antworten erhalten',
|
||||
description:
|
||||
'Um KI-Antworten zu erhalten, ist eine Anmeldung erforderlich. Dies ermöglicht uns, die Kosten für die KI-Verarbeitung zu verwalten.',
|
||||
},
|
||||
feature: {
|
||||
title: 'Funktion freischalten',
|
||||
description: 'Diese Funktion ist nur für angemeldete Benutzer verfügbar.',
|
||||
},
|
||||
};
|
||||
|
||||
const currentMessage = $derived(messages[action] || messages.ai);
|
||||
|
||||
function handleLogin() {
|
||||
if (browser) {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
if (browser) {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="modal-backdrop" onclick={onClose}>
|
||||
<div class="modal-content" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="modal-header">
|
||||
<h2>{currentMessage.title}</h2>
|
||||
<button class="close-btn" onclick={onClose} aria-label="Schliessen">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>{currentMessage.description}</p>
|
||||
|
||||
{#if conversationCount > 0}
|
||||
<div class="migration-info">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="16" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
||||
</svg>
|
||||
<span
|
||||
>Du hast {conversationCount}
|
||||
{conversationCount === 1 ? 'Unterhaltung' : 'Unterhaltungen'} in deiner Session. Diese
|
||||
werden nach der Anmeldung in deinen Account übertragen.</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick={onClose}> Später </button>
|
||||
<button class="btn btn-primary" onclick={handleLogin}> Anmelden </button>
|
||||
<button class="btn btn-outline" onclick={handleRegister}> Registrieren </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--color-background, white);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
max-width: 28rem;
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-foreground, #1f2937);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
color: var(--color-muted-foreground, #6b7280);
|
||||
border-radius: 0.375rem;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: var(--color-foreground, #1f2937);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
color: var(--color-muted-foreground, #6b7280);
|
||||
margin: 0 0 1rem 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.migration-info {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--color-primary-50, #eff6ff);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-primary-700, #1d4ed8);
|
||||
}
|
||||
|
||||
.migration-info svg {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.625rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary, #3b82f6);
|
||||
color: white;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-600, #2563eb);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-muted, #f3f4f6);
|
||||
color: var(--color-muted-foreground, #6b7280);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-muted-200, #e5e7eb);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
border-color: var(--color-border, #e5e7eb);
|
||||
color: var(--color-foreground, #1f2937);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background-color: var(--color-muted, #f3f4f6);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -24,8 +24,11 @@
|
|||
import { getPillAppItems } from '@manacore/shared-branding';
|
||||
import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n';
|
||||
import { setLocale, supportedLocales } from '$lib/i18n';
|
||||
import AuthGateModal from '$lib/components/AuthGateModal.svelte';
|
||||
import { GuestWelcomeModal, shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import {
|
||||
AuthGateModal,
|
||||
GuestWelcomeModal,
|
||||
shouldShowGuestWelcome,
|
||||
} from '@manacore/shared-auth-ui';
|
||||
import type { LayoutData } from './$types';
|
||||
|
||||
// App switcher items
|
||||
|
|
@ -296,10 +299,25 @@
|
|||
|
||||
<!-- Auth Gate Modal -->
|
||||
<AuthGateModal
|
||||
open={showAuthGateModal}
|
||||
action={authGateAction}
|
||||
conversationCount={sessionConversationCount}
|
||||
visible={showAuthGateModal}
|
||||
onClose={() => (showAuthGateModal = false)}
|
||||
onLogin={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}}
|
||||
onRegister={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}}
|
||||
action={authGateAction}
|
||||
migrationCount={sessionConversationCount}
|
||||
locale={currentLocale === 'en' ? 'en' : 'de'}
|
||||
/>
|
||||
|
||||
<!-- Guest Welcome Modal -->
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
action?: 'save' | 'sync' | 'feature';
|
||||
featureName?: string;
|
||||
}
|
||||
|
||||
let { visible, onClose, action = 'save', featureName = '' }: Props = $props();
|
||||
|
||||
// Action-specific messages
|
||||
const messages = {
|
||||
save: {
|
||||
title: 'Anmelden um zu speichern',
|
||||
description:
|
||||
'Im Demo-Modus kannst du die App erkunden. Melde dich an, um eigene Kontakte zu erstellen und zu speichern.',
|
||||
icon: 'cloud',
|
||||
},
|
||||
sync: {
|
||||
title: 'Anmelden für Cloud-Sync',
|
||||
description:
|
||||
'Mit einem Account werden deine Kontakte automatisch synchronisiert und bleiben erhalten.',
|
||||
icon: 'refresh-cw',
|
||||
},
|
||||
feature: {
|
||||
title: `Anmelden für ${featureName}`,
|
||||
description: `Diese Funktion erfordert ein Konto. Melde dich an, um ${featureName} zu nutzen.`,
|
||||
icon: 'lock',
|
||||
},
|
||||
};
|
||||
|
||||
let currentMessage = $derived(messages[action]);
|
||||
|
||||
function handleLogin() {
|
||||
// Store return URL for redirect after login
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}
|
||||
|
||||
function handleBackdropClick(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
onclick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
class="bg-card border-border mx-4 w-full max-w-md rounded-xl border p-6 shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="auth-gate-title"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="bg-primary/10 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
||||
>
|
||||
{#if currentMessage.icon === 'cloud'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
{:else if currentMessage.icon === 'refresh-cw'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h2 id="auth-gate-title" class="mb-2 text-center text-xl font-semibold">
|
||||
{currentMessage.title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-muted-foreground mb-6 text-center text-sm">
|
||||
{currentMessage.description}
|
||||
</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
onclick={handleLogin}
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
<button
|
||||
onclick={handleRegister}
|
||||
class="bg-secondary text-secondary-foreground hover:bg-secondary/80 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Kostenloses Konto erstellen
|
||||
</button>
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="text-muted-foreground hover:text-foreground w-full py-2 text-sm transition-colors"
|
||||
>
|
||||
Später
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info text -->
|
||||
<p class="text-muted-foreground mt-4 text-center text-xs">
|
||||
Im Demo-Modus werden Beispielkontakte angezeigt. Melde dich an, um eigene Kontakte zu
|
||||
erstellen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -46,8 +46,11 @@
|
|||
formatParsedContactPreview,
|
||||
} from '$lib/utils/contact-parser';
|
||||
import ContactsToolbar from '$lib/components/ContactsToolbar.svelte';
|
||||
import AuthGateModal from '$lib/components/AuthGateModal.svelte';
|
||||
import { GuestWelcomeModal, shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import {
|
||||
AuthGateModal,
|
||||
GuestWelcomeModal,
|
||||
shouldShowGuestWelcome,
|
||||
} from '@manacore/shared-auth-ui';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Tags state for Quick-Create
|
||||
|
|
@ -483,7 +486,23 @@
|
|||
<AuthGateModal
|
||||
visible={showAuthGateModal}
|
||||
onClose={() => (showAuthGateModal = false)}
|
||||
onLogin={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}}
|
||||
onRegister={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}}
|
||||
action={authGateAction}
|
||||
locale={currentLocale === 'en' ? 'en' : 'de'}
|
||||
infoText="Im Demo-Modus werden Beispielkontakte angezeigt. Melde dich an, um eigene Kontakte zu erstellen."
|
||||
/>
|
||||
|
||||
<!-- Guest Welcome Modal -->
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
action?: 'save' | 'sync' | 'feature';
|
||||
featureName?: string;
|
||||
}
|
||||
|
||||
let { visible, onClose, action = 'save', featureName = '' }: Props = $props();
|
||||
|
||||
// Action-specific messages
|
||||
const messages = {
|
||||
save: {
|
||||
title: 'Anmelden um zu speichern',
|
||||
description:
|
||||
'Im Demo-Modus kannst du die App erkunden. Melde dich an, um eigene Aufgaben zu erstellen und zu speichern.',
|
||||
icon: 'cloud',
|
||||
},
|
||||
sync: {
|
||||
title: 'Anmelden für Cloud-Sync',
|
||||
description:
|
||||
'Mit einem Account werden deine Aufgaben automatisch synchronisiert und bleiben erhalten.',
|
||||
icon: 'refresh-cw',
|
||||
},
|
||||
feature: {
|
||||
title: `Anmelden für ${featureName}`,
|
||||
description: `Diese Funktion erfordert ein Konto. Melde dich an, um ${featureName} zu nutzen.`,
|
||||
icon: 'lock',
|
||||
},
|
||||
};
|
||||
|
||||
let currentMessage = $derived(messages[action]);
|
||||
|
||||
function handleLogin() {
|
||||
// Store return URL for redirect after login
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}
|
||||
|
||||
function handleBackdropClick(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-[9995] flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
onclick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
class="bg-card border-border mx-4 w-full max-w-md rounded-xl border p-6 shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="auth-gate-title"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="bg-primary/10 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
||||
>
|
||||
{#if currentMessage.icon === 'cloud'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
{:else if currentMessage.icon === 'refresh-cw'}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="text-primary h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h2 id="auth-gate-title" class="mb-2 text-center text-xl font-semibold">
|
||||
{currentMessage.title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-muted-foreground mb-6 text-center text-sm">
|
||||
{currentMessage.description}
|
||||
</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
onclick={handleLogin}
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
<button
|
||||
onclick={handleRegister}
|
||||
class="bg-secondary text-secondary-foreground hover:bg-secondary/80 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
Kostenloses Konto erstellen
|
||||
</button>
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="text-muted-foreground hover:text-foreground w-full py-2 text-sm transition-colors"
|
||||
>
|
||||
Später
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info text -->
|
||||
<p class="text-muted-foreground mt-4 text-center text-xs">
|
||||
Du kannst die Demo-Aufgaben ansehen, aber um eigene Aufgaben zu erstellen benötigst du ein
|
||||
Konto.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -39,8 +39,11 @@
|
|||
import { getPillAppItems } from '@manacore/shared-branding';
|
||||
import { getTasks } from '$lib/api/tasks';
|
||||
import { parseTaskInput, resolveTaskIds, formatParsedTaskPreview } from '$lib/utils/task-parser';
|
||||
import AuthGateModal from '$lib/components/AuthGateModal.svelte';
|
||||
import { GuestWelcomeModal, shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import {
|
||||
AuthGateModal,
|
||||
GuestWelcomeModal,
|
||||
shouldShowGuestWelcome,
|
||||
} from '@manacore/shared-auth-ui';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// App switcher items
|
||||
|
|
@ -515,7 +518,23 @@
|
|||
<AuthGateModal
|
||||
visible={showAuthGateModal}
|
||||
onClose={() => (showAuthGateModal = false)}
|
||||
onLogin={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/login');
|
||||
}}
|
||||
onRegister={() => {
|
||||
showAuthGateModal = false;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem('auth-return-url', window.location.pathname);
|
||||
}
|
||||
goto('/register');
|
||||
}}
|
||||
action={authGateAction}
|
||||
locale={currentLocale === 'en' ? 'en' : 'de'}
|
||||
infoText="Du kannst die Demo-Aufgaben ansehen, aber um eigene Aufgaben zu erstellen benötigst du ein Konto."
|
||||
/>
|
||||
|
||||
<!-- Guest Welcome Modal -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue