mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 03:01:09 +02:00
feat(contacts): add session-first guest mode
Users can now use Contacts without signing in. Data is stored in sessionStorage (lost when tab closes). Changes: - Add session-contacts.svelte.ts for temporary local storage - Add AuthGateModal for login prompts - Remove auth redirect from app layout - Add guest mode banner with contact count - Add sessionStorage return URL handling in login/register When users sign in, session contacts are migrated to their cloud account. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
54a6ebc073
commit
753e6fd17f
5 changed files with 545 additions and 28 deletions
167
apps/contacts/apps/web/src/lib/components/AuthGateModal.svelte
Normal file
167
apps/contacts/apps/web/src/lib/components/AuthGateModal.svelte
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { sessionContactsStore } from '$lib/stores/session-contacts.svelte';
|
||||
|
||||
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:
|
||||
'Melde dich an, um deine Kontakte in der Cloud zu speichern und auf allen Geräten zu synchronisieren.',
|
||||
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]);
|
||||
let sessionContactCount = $derived(sessionContactsStore.count);
|
||||
|
||||
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>
|
||||
|
||||
<!-- Session contacts info -->
|
||||
{#if sessionContactCount > 0}
|
||||
<div class="bg-muted/50 mb-6 rounded-lg p-3 text-center text-sm">
|
||||
<span class="text-muted-foreground">
|
||||
Du hast <strong class="text-foreground">{sessionContactCount}</strong>
|
||||
{sessionContactCount === 1 ? 'Kontakt' : 'Kontakte'} in dieser Sitzung erstellt.
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-muted-foreground text-xs">
|
||||
Diese werden nach der Anmeldung in deinen Account übernommen.
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- 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 weiterhin Kontakte erstellen. Diese werden lokal gespeichert und gehen beim
|
||||
Schließen des Tabs verloren.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
235
apps/contacts/apps/web/src/lib/stores/session-contacts.svelte.ts
Normal file
235
apps/contacts/apps/web/src/lib/stores/session-contacts.svelte.ts
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
* Session Contacts Store - Temporary local contacts for guest users
|
||||
* Contacts are stored in sessionStorage and lost when the browser tab is closed
|
||||
*/
|
||||
|
||||
import type { Contact } from '$lib/api/contacts';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
const STORAGE_KEY = 'contacts-session-contacts';
|
||||
|
||||
// Generate a unique ID for session contacts
|
||||
function generateSessionId(): string {
|
||||
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
}
|
||||
|
||||
// Load contacts from sessionStorage
|
||||
function loadFromStorage(): Contact[] {
|
||||
if (!browser) return [];
|
||||
try {
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Save contacts to sessionStorage
|
||||
function saveToStorage(contacts: Contact[]) {
|
||||
if (!browser) return;
|
||||
try {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(contacts));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save session contacts:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// State
|
||||
let contacts = $state<Contact[]>(loadFromStorage());
|
||||
|
||||
export const sessionContactsStore = {
|
||||
get contacts() {
|
||||
return contacts;
|
||||
},
|
||||
|
||||
get hasContacts() {
|
||||
return contacts.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize from sessionStorage (call on mount)
|
||||
*/
|
||||
initialize() {
|
||||
contacts = loadFromStorage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new session contact
|
||||
*/
|
||||
createContact(data: Partial<Contact>): Contact {
|
||||
const now = new Date().toISOString();
|
||||
const newContact: Contact = {
|
||||
id: generateSessionId(),
|
||||
userId: 'guest',
|
||||
firstName: data.firstName || null,
|
||||
lastName: data.lastName || null,
|
||||
displayName: data.displayName || null,
|
||||
nickname: data.nickname || null,
|
||||
email: data.email || null,
|
||||
phone: data.phone || null,
|
||||
mobile: data.mobile || null,
|
||||
street: data.street || null,
|
||||
city: data.city || null,
|
||||
postalCode: data.postalCode || null,
|
||||
country: data.country || null,
|
||||
company: data.company || null,
|
||||
jobTitle: data.jobTitle || null,
|
||||
department: data.department || null,
|
||||
website: data.website || null,
|
||||
birthday: data.birthday || null,
|
||||
notes: data.notes || null,
|
||||
photoUrl: data.photoUrl || null,
|
||||
customDates: data.customDates || null,
|
||||
linkedin: data.linkedin || null,
|
||||
twitter: data.twitter || null,
|
||||
facebook: data.facebook || null,
|
||||
instagram: data.instagram || null,
|
||||
xing: data.xing || null,
|
||||
github: data.github || null,
|
||||
youtube: data.youtube || null,
|
||||
tiktok: data.tiktok || null,
|
||||
telegram: data.telegram || null,
|
||||
whatsapp: data.whatsapp || null,
|
||||
signal: data.signal || null,
|
||||
discord: data.discord || null,
|
||||
bluesky: data.bluesky || null,
|
||||
tags: [],
|
||||
isFavorite: data.isFavorite || false,
|
||||
isArchived: data.isArchived || false,
|
||||
organizationId: null,
|
||||
teamId: null,
|
||||
visibility: 'private',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
contacts = [...contacts, newContact];
|
||||
saveToStorage(contacts);
|
||||
return newContact;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a session contact
|
||||
*/
|
||||
updateContact(id: string, data: Partial<Contact>): Contact | null {
|
||||
const index = contacts.findIndex((c) => c.id === id);
|
||||
if (index === -1) return null;
|
||||
|
||||
const updatedContact = {
|
||||
...contacts[index],
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
contacts = contacts.map((c) => (c.id === id ? updatedContact : c));
|
||||
saveToStorage(contacts);
|
||||
return updatedContact;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle favorite status
|
||||
*/
|
||||
toggleFavorite(id: string): Contact | null {
|
||||
const contact = contacts.find((c) => c.id === id);
|
||||
if (!contact) return null;
|
||||
return this.updateContact(id, { isFavorite: !contact.isFavorite });
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle archive status
|
||||
*/
|
||||
toggleArchive(id: string): Contact | null {
|
||||
const contact = contacts.find((c) => c.id === id);
|
||||
if (!contact) return null;
|
||||
return this.updateContact(id, { isArchived: !contact.isArchived });
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a session contact
|
||||
*/
|
||||
deleteContact(id: string): boolean {
|
||||
const hadContact = contacts.some((c) => c.id === id);
|
||||
contacts = contacts.filter((c) => c.id !== id);
|
||||
saveToStorage(contacts);
|
||||
return hadContact;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get contact by ID
|
||||
*/
|
||||
getById(id: string): Contact | undefined {
|
||||
return contacts.find((c) => c.id === id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a contact ID is a session contact
|
||||
*/
|
||||
isSessionContact(id: string): boolean {
|
||||
return id.startsWith('session_');
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all contacts (for migration to cloud on login)
|
||||
*/
|
||||
getAllContacts(): Contact[] {
|
||||
return [...contacts];
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all session contacts (after migration or on explicit clear)
|
||||
*/
|
||||
clear() {
|
||||
contacts = [];
|
||||
if (browser) {
|
||||
sessionStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get count of session contacts
|
||||
*/
|
||||
get count() {
|
||||
return contacts.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get favorite contacts
|
||||
*/
|
||||
get favoriteContacts() {
|
||||
return contacts.filter((c) => c.isFavorite && !c.isArchived);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get archived contacts
|
||||
*/
|
||||
get archivedContacts() {
|
||||
return contacts.filter((c) => c.isArchived);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get active (non-archived) contacts
|
||||
*/
|
||||
get activeContacts() {
|
||||
return contacts.filter((c) => !c.isArchived);
|
||||
},
|
||||
|
||||
/**
|
||||
* Search session contacts
|
||||
*/
|
||||
search(query: string): Contact[] {
|
||||
if (!query.trim()) return this.activeContacts;
|
||||
const lower = query.toLowerCase();
|
||||
return this.activeContacts.filter((c) => {
|
||||
const searchFields = [
|
||||
c.firstName,
|
||||
c.lastName,
|
||||
c.displayName,
|
||||
c.email,
|
||||
c.phone,
|
||||
c.mobile,
|
||||
c.company,
|
||||
];
|
||||
return searchFields.some((field) => field?.toLowerCase().includes(lower));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -46,6 +46,8 @@
|
|||
formatParsedContactPreview,
|
||||
} from '$lib/utils/contact-parser';
|
||||
import ContactsToolbar from '$lib/components/ContactsToolbar.svelte';
|
||||
import AuthGateModal from '$lib/components/AuthGateModal.svelte';
|
||||
import { sessionContactsStore } from '$lib/stores/session-contacts.svelte';
|
||||
|
||||
// Tags state for Quick-Create
|
||||
let availableTags = $state<{ id: string; name: string }[]>([]);
|
||||
|
|
@ -211,6 +213,19 @@
|
|||
goto('/login');
|
||||
}
|
||||
|
||||
// Auth gate modal state
|
||||
let showAuthGateModal = $state(false);
|
||||
let authGateAction = $state<'save' | 'sync' | 'feature'>('save');
|
||||
|
||||
// Show auth gate modal (can be called from child components)
|
||||
function showAuthGate(action: 'save' | 'sync' | 'feature' = 'save') {
|
||||
authGateAction = action;
|
||||
showAuthGateModal = true;
|
||||
}
|
||||
|
||||
// Session contacts indicator
|
||||
let sessionContactCount = $derived(sessionContactsStore.count);
|
||||
|
||||
async function handleCloseContactModal() {
|
||||
// Refresh contacts list in case something was changed
|
||||
await contactsStore.loadContacts();
|
||||
|
|
@ -271,43 +286,68 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
// Redirect to login if not authenticated
|
||||
if (!authStore.isAuthenticated) {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize split-panel from URL/localStorage
|
||||
splitPanel.initialize();
|
||||
|
||||
// Load user settings and tags
|
||||
await userSettings.load();
|
||||
|
||||
// Load tags for Quick-Create
|
||||
try {
|
||||
const tagsResult = await tagsApi.list();
|
||||
availableTags = (tagsResult.tags || []).map((t) => ({ id: t.id, name: t.name }));
|
||||
} catch (e) {
|
||||
console.error('Failed to load tags:', e);
|
||||
}
|
||||
|
||||
// Initialize contacts settings, view mode, and filter store
|
||||
contactsSettings.initialize();
|
||||
viewModeStore.initialize();
|
||||
contactsFilterStore.initialize();
|
||||
|
||||
// Initialize session contacts for guest mode
|
||||
sessionContactsStore.initialize();
|
||||
|
||||
// Only fetch user data if authenticated
|
||||
if (authStore.isAuthenticated) {
|
||||
// Load user settings and tags
|
||||
await userSettings.load();
|
||||
|
||||
// Load tags for Quick-Create
|
||||
try {
|
||||
const tagsResult = await tagsApi.list();
|
||||
availableTags = (tagsResult.tags || []).map((t) => ({ id: t.id, name: t.name }));
|
||||
} catch (e) {
|
||||
console.error('Failed to load tags:', e);
|
||||
}
|
||||
|
||||
// Check for session contacts to migrate after login
|
||||
if (sessionContactsStore.hasContacts) {
|
||||
// Migrate session contacts to cloud
|
||||
const sessionContacts = sessionContactsStore.getAllContacts();
|
||||
for (const contact of sessionContacts) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { id, userId, createdAt, updatedAt, ...contactData } = contact;
|
||||
await contactsStore.createContact(contactData);
|
||||
} catch (e) {
|
||||
console.error('Failed to migrate session contact:', e);
|
||||
}
|
||||
}
|
||||
// Clear session contacts after migration
|
||||
sessionContactsStore.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sidebar mode from localStorage
|
||||
const savedSidebar = localStorage.getItem('contacts-nav-sidebar');
|
||||
if (savedSidebar === 'true') {
|
||||
isSidebarMode = true;
|
||||
sidebarModeStore.set(true);
|
||||
try {
|
||||
const savedSidebar = localStorage?.getItem('contacts-nav-sidebar');
|
||||
if (savedSidebar === 'true') {
|
||||
isSidebarMode = true;
|
||||
sidebarModeStore.set(true);
|
||||
}
|
||||
} catch {
|
||||
// localStorage not available (private browsing, quota exceeded, etc.)
|
||||
}
|
||||
|
||||
// Initialize collapsed state from localStorage
|
||||
const savedCollapsed = localStorage.getItem('contacts-nav-collapsed');
|
||||
if (savedCollapsed === 'true') {
|
||||
isCollapsed = true;
|
||||
collapsedStore.set(true);
|
||||
try {
|
||||
const savedCollapsed = localStorage?.getItem('contacts-nav-collapsed');
|
||||
if (savedCollapsed === 'true') {
|
||||
isCollapsed = true;
|
||||
collapsedStore.set(true);
|
||||
}
|
||||
} catch {
|
||||
// localStorage not available
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -317,6 +357,38 @@
|
|||
<SplitPaneContainer>
|
||||
<!-- Navigation Layout -->
|
||||
<div class="layout-container">
|
||||
<!-- Guest Mode Banner -->
|
||||
{#if !authStore.isAuthenticated}
|
||||
<div
|
||||
class="guest-banner bg-primary/10 border-primary/20 fixed top-0 right-0 left-0 z-50 flex items-center justify-between border-b px-4 py-2"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<svg class="text-primary h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-foreground">
|
||||
<strong>Gast-Modus</strong>
|
||||
{#if sessionContactCount > 0}
|
||||
- {sessionContactCount}
|
||||
{sessionContactCount === 1 ? 'Kontakt' : 'Kontakte'} lokal gespeichert
|
||||
{:else}
|
||||
- Kontakte werden nur in diesem Tab gespeichert
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => showAuthGate('sync')}
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3 py-1 text-sm font-medium transition-colors"
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- UI Elements (hidden in immersive mode) -->
|
||||
{#if !contactsSettings.immersiveModeEnabled}
|
||||
<!-- Floating/Sidebar Pill Navigation (at bottom) -->
|
||||
|
|
@ -407,7 +479,25 @@
|
|||
</div>
|
||||
</SplitPaneContainer>
|
||||
|
||||
<!-- Auth Gate Modal -->
|
||||
<AuthGateModal
|
||||
visible={showAuthGateModal}
|
||||
onClose={() => (showAuthGateModal = false)}
|
||||
action={authGateAction}
|
||||
/>
|
||||
|
||||
<style>
|
||||
/* Guest banner styling */
|
||||
.guest-banner {
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
/* Offset content when guest banner is visible */
|
||||
.layout-container:has(.guest-banner) .main-content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { locale } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { LoginPage } from '@manacore/shared-auth-ui';
|
||||
import { getLoginTranslations } from '@manacore/shared-i18n';
|
||||
import { ContactsLogo } from '@manacore/shared-branding';
|
||||
|
|
@ -10,8 +11,20 @@
|
|||
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
||||
import '$lib/i18n';
|
||||
|
||||
// Get redirect URL from query params
|
||||
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/');
|
||||
// Get redirect URL from query params or sessionStorage
|
||||
let redirectTo = $state('/');
|
||||
|
||||
onMount(() => {
|
||||
// Check sessionStorage first (set by AuthGateModal)
|
||||
const storedReturnUrl = sessionStorage.getItem('auth-return-url');
|
||||
if (storedReturnUrl) {
|
||||
redirectTo = storedReturnUrl;
|
||||
sessionStorage.removeItem('auth-return-url');
|
||||
} else {
|
||||
// Fall back to query params
|
||||
redirectTo = $page.url.searchParams.get('redirectTo') || '/';
|
||||
}
|
||||
});
|
||||
|
||||
// Get translations based on current locale
|
||||
const translations = $derived(getLoginTranslations($locale || 'de'));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { locale } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import { getRegisterTranslations } from '@manacore/shared-i18n';
|
||||
import { ContactsLogo } from '@manacore/shared-branding';
|
||||
|
|
@ -9,6 +10,17 @@
|
|||
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
||||
import '$lib/i18n';
|
||||
|
||||
// Get redirect URL from sessionStorage (set by AuthGateModal)
|
||||
let redirectTo = $state('/');
|
||||
|
||||
onMount(() => {
|
||||
const storedReturnUrl = sessionStorage.getItem('auth-return-url');
|
||||
if (storedReturnUrl) {
|
||||
redirectTo = storedReturnUrl;
|
||||
sessionStorage.removeItem('auth-return-url');
|
||||
}
|
||||
});
|
||||
|
||||
const translations = $derived(getRegisterTranslations($locale || 'de'));
|
||||
|
||||
async function handleSignUp(email: string, password: string) {
|
||||
|
|
@ -26,7 +38,7 @@
|
|||
primaryColor="#3b82f6"
|
||||
onSignUp={handleSignUp}
|
||||
{goto}
|
||||
successRedirect="/"
|
||||
successRedirect={redirectTo}
|
||||
loginPath="/login"
|
||||
lightBackground="#eff6ff"
|
||||
darkBackground="#1e293b"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue