mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 15:39:40 +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 -->
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Nach eingehender Analyse aller Web-Apps im Monorepo wurden folgende Bereiche auf
|
|||
| i18n Implementation | ✅ Gut | ~~Mittel~~ | ✅ Erledigt |
|
||||
| Auth Implementation | ✅ Gut | Niedrig | - |
|
||||
| Styling & Tailwind | ✅ Sehr gut | Niedrig | - |
|
||||
| Komponenten & Layouts | ⚠️ Mittel | Mittel | Offen |
|
||||
| Komponenten & Layouts | ✅ Gut | ~~Mittel~~ | ✅ Erledigt |
|
||||
|
||||
### Erledigte Aufgaben (29.01.2026)
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ Nach eingehender Analyse aller Web-Apps im Monorepo wurden folgende Bereiche auf
|
|||
4. ✅ **lucide-svelte entfernt** - shared-ui nutzt jetzt nur noch `@manacore/shared-icons`
|
||||
5. ✅ **@manacore/shared-api-client Package erstellt** - 10 Apps migriert (clock, todo, contacts, storage, calendar, picture, nutriphi, planta, questions, skilltree)
|
||||
6. ✅ **i18n zu 6 Apps hinzugefügt** - todo, skilltree, nutriphi, planta, questions, matrix (jeweils DE + EN)
|
||||
7. ✅ **AuthGateModal zentralisiert** - `@manacore/shared-auth-ui` für 4 Apps (chat, todo, contacts, calendar)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -204,10 +205,15 @@ Alle Apps nutzen **Mana Core Auth** mit `@manacore/shared-auth`.
|
|||
- API: `toastStore.success()`, `.error()`, `.warning()`, `.info()`
|
||||
- `ToastContainer` Komponente mit Phosphor Icons
|
||||
|
||||
#### AuthGateModal
|
||||
#### AuthGateModal ✅
|
||||
|
||||
- Dupliziert in: chat, todo, contacts, calendar
|
||||
- Sollte in `@manacore/shared-auth-ui`
|
||||
> **Status: Erledigt (29.01.2026)**
|
||||
|
||||
- ✅ Zentrales AuthGateModal in `@manacore/shared-auth-ui`
|
||||
- ✅ Migrierte Apps: chat, todo, contacts, calendar
|
||||
- Unterstützt: 'save', 'sync', 'feature', 'ai' Actions
|
||||
- i18n: DE + EN eingebaut
|
||||
- Optionale Migration-Info für Session-Daten
|
||||
|
||||
#### AppLoadingSkeleton
|
||||
|
||||
|
|
@ -247,6 +253,7 @@ Alle Apps nutzen **Mana Core Auth** mit `@manacore/shared-auth`.
|
|||
| ~~lucide-svelte aus shared-ui entfernen~~ | ✅ Erledigt |
|
||||
| ~~API Client Package erstellen~~ | ✅ Erledigt (10 Apps migriert) |
|
||||
| ~~i18n zu 6 Apps hinzufügen~~ | ✅ Erledigt |
|
||||
| ~~AuthGateModal zentralisieren~~ | ✅ Erledigt (4 Apps migriert) |
|
||||
|
||||
### 🔴 Hohe Priorität
|
||||
|
||||
|
|
@ -256,7 +263,7 @@ _(Keine offenen Aufgaben mit hoher Priorität)_
|
|||
|
||||
| Aufgabe | Aufwand | Impact |
|
||||
|---------|---------|--------|
|
||||
| AuthGateModal in Shared Package | Niedrig | Code-Reduktion |
|
||||
| ~~AuthGateModal in Shared Package~~ | ~~Niedrig~~ | ✅ Erledigt |
|
||||
| Global Error Handler extrahieren | Niedrig | Error UX |
|
||||
|
||||
### 🟢 Niedrige Priorität
|
||||
|
|
@ -272,7 +279,7 @@ _(Keine offenen Aufgaben mit hoher Priorität)_
|
|||
|
||||
1. ~~**API Client Package** als nächstes angehen (höchster Impact)~~ ✅ Erledigt
|
||||
2. ~~**i18n** zu fehlenden Apps hinzufügen~~ ✅ Erledigt (6 Apps)
|
||||
3. **AuthGateModal** in Shared Package extrahieren
|
||||
3. ~~**AuthGateModal** in Shared Package extrahieren~~ ✅ Erledigt (4 Apps)
|
||||
4. **Global Error Handler** extrahieren
|
||||
5. Schrittweise weitere Punkte abarbeiten
|
||||
|
||||
|
|
|
|||
249
packages/shared-auth-ui/src/components/AuthGateModal.svelte
Normal file
249
packages/shared-auth-ui/src/components/AuthGateModal.svelte
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
<script lang="ts">
|
||||
import { CloudArrowUp, ArrowsClockwise, Lock, Sparkle, Info, X } from '@manacore/shared-icons';
|
||||
import type { AuthGateTranslations, AuthGateAction } from '../types';
|
||||
|
||||
const defaultTranslationsDE: Record<AuthGateAction, { title: string; description: string }> = {
|
||||
save: {
|
||||
title: 'Anmelden um zu speichern',
|
||||
description:
|
||||
'Im Demo-Modus kannst du die App erkunden. Melde dich an, um deine Daten zu speichern.',
|
||||
},
|
||||
sync: {
|
||||
title: 'Anmelden für Cloud-Sync',
|
||||
description:
|
||||
'Mit einem Account werden deine Daten automatisch synchronisiert und bleiben erhalten.',
|
||||
},
|
||||
feature: {
|
||||
title: 'Anmelden erforderlich',
|
||||
description: 'Diese Funktion erfordert ein Konto. Melde dich an, um sie zu nutzen.',
|
||||
},
|
||||
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.',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultTranslationsEN: Record<AuthGateAction, { title: string; description: string }> = {
|
||||
save: {
|
||||
title: 'Sign in to save',
|
||||
description: 'In demo mode you can explore the app. Sign in to save your data permanently.',
|
||||
},
|
||||
sync: {
|
||||
title: 'Sign in for Cloud Sync',
|
||||
description: 'With an account your data will be automatically synced and preserved.',
|
||||
},
|
||||
feature: {
|
||||
title: 'Sign in required',
|
||||
description: 'This feature requires an account. Sign in to use it.',
|
||||
},
|
||||
ai: {
|
||||
title: 'Get AI responses',
|
||||
description:
|
||||
'To receive AI responses, sign in is required. This allows us to manage AI processing costs.',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultButtonsDE: AuthGateTranslations = {
|
||||
loginButton: 'Anmelden',
|
||||
registerButton: 'Kostenloses Konto erstellen',
|
||||
laterButton: 'Später',
|
||||
migrationInfo: (count) =>
|
||||
`Du hast ${count} ${count === 1 ? 'Unterhaltung' : 'Unterhaltungen'} in deiner Session. Diese werden nach der Anmeldung in deinen Account übertragen.`,
|
||||
};
|
||||
|
||||
const defaultButtonsEN: AuthGateTranslations = {
|
||||
loginButton: 'Sign In',
|
||||
registerButton: 'Create Free Account',
|
||||
laterButton: 'Later',
|
||||
migrationInfo: (count) =>
|
||||
`You have ${count} ${count === 1 ? 'conversation' : 'conversations'} in your session. These will be transferred to your account after signing in.`,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
/** Whether the modal is visible */
|
||||
visible: boolean;
|
||||
/** Callback when modal is closed */
|
||||
onClose: () => void;
|
||||
/** Callback when login is clicked */
|
||||
onLogin: () => void;
|
||||
/** Callback when register is clicked */
|
||||
onRegister: () => void;
|
||||
/** The action type that triggered this modal */
|
||||
action?: AuthGateAction;
|
||||
/** Custom feature name (for action='feature') */
|
||||
featureName?: string;
|
||||
/** Number of items to migrate (shows migration info) */
|
||||
migrationCount?: number;
|
||||
/** Locale for translations (default: 'de') */
|
||||
locale?: 'de' | 'en';
|
||||
/** Custom translations */
|
||||
translations?: Partial<AuthGateTranslations>;
|
||||
/** Custom title (overrides action-based title) */
|
||||
customTitle?: string;
|
||||
/** Custom description (overrides action-based description) */
|
||||
customDescription?: string;
|
||||
/** Custom info text at bottom */
|
||||
infoText?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
visible,
|
||||
onClose,
|
||||
onLogin,
|
||||
onRegister,
|
||||
action = 'save',
|
||||
featureName = '',
|
||||
migrationCount = 0,
|
||||
locale = 'de',
|
||||
translations = {},
|
||||
customTitle,
|
||||
customDescription,
|
||||
infoText,
|
||||
}: Props = $props();
|
||||
|
||||
// Merge translations
|
||||
const defaultMessages = $derived(locale === 'de' ? defaultTranslationsDE : defaultTranslationsEN);
|
||||
const defaultButtons = $derived(locale === 'de' ? defaultButtonsDE : defaultButtonsEN);
|
||||
const t = $derived({ ...defaultButtons, ...translations });
|
||||
|
||||
// Get current message based on action
|
||||
const currentMessage = $derived(() => {
|
||||
const msg = defaultMessages[action];
|
||||
let title = customTitle || msg.title;
|
||||
let description = customDescription || msg.description;
|
||||
|
||||
// Handle feature action with custom name
|
||||
if (action === 'feature' && featureName) {
|
||||
title = locale === 'de' ? `Anmelden für ${featureName}` : `Sign in for ${featureName}`;
|
||||
description =
|
||||
locale === 'de'
|
||||
? `Diese Funktion erfordert ein Konto. Melde dich an, um ${featureName} zu nutzen.`
|
||||
: `This feature requires an account. Sign in to use ${featureName}.`;
|
||||
}
|
||||
|
||||
return { title, description };
|
||||
});
|
||||
|
||||
// Migration info text
|
||||
const migrationText = $derived(() => {
|
||||
if (migrationCount <= 0) return '';
|
||||
if (translations.migrationInfo) {
|
||||
return translations.migrationInfo(migrationCount);
|
||||
}
|
||||
return t.migrationInfo(migrationCount);
|
||||
});
|
||||
|
||||
// Icon for action type
|
||||
const ActionIcon = $derived(() => {
|
||||
switch (action) {
|
||||
case 'save':
|
||||
return CloudArrowUp;
|
||||
case 'sync':
|
||||
return ArrowsClockwise;
|
||||
case 'ai':
|
||||
return Sparkle;
|
||||
default:
|
||||
return Lock;
|
||||
}
|
||||
});
|
||||
|
||||
function handleBackdropClick(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && visible) {
|
||||
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 p-4"
|
||||
onclick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
class="bg-card border-border relative mx-4 w-full max-w-md rounded-xl border p-6 shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="auth-gate-title"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-4 top-4 p-1 text-muted-foreground hover:text-foreground transition-colors rounded-md"
|
||||
onclick={onClose}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="bg-primary/10 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
||||
>
|
||||
<svelte:component this={ActionIcon()} size={32} class="text-primary" />
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h2 id="auth-gate-title" class="mb-2 text-center text-xl font-semibold text-foreground">
|
||||
{currentMessage().title}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-muted-foreground mb-4 text-center text-sm">
|
||||
{currentMessage().description}
|
||||
</p>
|
||||
|
||||
<!-- Migration Info -->
|
||||
{#if migrationText()}
|
||||
<div
|
||||
class="flex gap-3 p-3 mb-4 bg-primary/10 border border-primary/20 rounded-lg text-sm text-primary"
|
||||
>
|
||||
<Info size={20} class="flex-shrink-0 mt-0.5" />
|
||||
<span>{migrationText()}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={onLogin}
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
{t.loginButton}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onRegister}
|
||||
class="bg-secondary text-secondary-foreground hover:bg-secondary/80 w-full rounded-lg px-4 py-3 font-medium transition-colors"
|
||||
>
|
||||
{t.registerButton}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="text-muted-foreground hover:text-foreground w-full py-2 text-sm transition-colors"
|
||||
>
|
||||
{t.laterButton}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info text -->
|
||||
{#if infoText}
|
||||
<p class="text-muted-foreground mt-4 text-center text-xs">
|
||||
{infoText}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -7,6 +7,7 @@ export { default as ForgotPasswordPage } from './pages/ForgotPasswordPage.svelte
|
|||
export { default as GoogleSignInButton } from './components/GoogleSignInButton.svelte';
|
||||
export { default as AppleSignInButton } from './components/AppleSignInButton.svelte';
|
||||
export { default as GuestWelcomeModal } from './components/GuestWelcomeModal.svelte';
|
||||
export { default as AuthGateModal } from './components/AuthGateModal.svelte';
|
||||
|
||||
// Utilities
|
||||
export {
|
||||
|
|
@ -42,4 +43,6 @@ export type {
|
|||
AuthServiceInterface,
|
||||
AuthResult,
|
||||
GuestWelcomeTranslations,
|
||||
AuthGateAction,
|
||||
AuthGateTranslations,
|
||||
} from './types';
|
||||
|
|
|
|||
|
|
@ -77,3 +77,19 @@ export interface GuestWelcomeTranslations {
|
|||
/** App-specific feature list (array of strings) */
|
||||
features?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Action types for the AuthGateModal
|
||||
*/
|
||||
export type AuthGateAction = 'save' | 'sync' | 'feature' | 'ai';
|
||||
|
||||
/**
|
||||
* Translation strings for the auth gate modal
|
||||
*/
|
||||
export interface AuthGateTranslations {
|
||||
loginButton: string;
|
||||
registerButton: string;
|
||||
laterButton: string;
|
||||
/** Function to generate migration info text */
|
||||
migrationInfo: (count: number) => string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue