mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
feat(playground): integrate shared auth UI for consistent login experience
- Add PlaygroundLogo to shared-branding package - Add playground to APP_BRANDING, APP_ICONS, and APP_URLS - Replace custom login/register pages with shared-auth-ui components - Update authStore with resendVerificationEmail and improved signUp - Add Caddy reverse proxy entry for playground.mana.how Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2777f604fd
commit
8525020e8a
14 changed files with 169 additions and 226 deletions
|
|
@ -25,6 +25,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-auth": "workspace:*",
|
||||
"marked": "^17.0.0"
|
||||
"@manacore/shared-auth-ui": "workspace:*",
|
||||
"@manacore/shared-branding": "workspace:*",
|
||||
"@manacore/shared-i18n": "workspace:*",
|
||||
"@manacore/shared-icons": "workspace:*",
|
||||
"marked": "^17.0.0",
|
||||
"svelte-i18n": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,10 +82,31 @@ export const authStore = {
|
|||
return result;
|
||||
},
|
||||
|
||||
async signUp(email: string, password: string, name?: string) {
|
||||
async signUp(email: string, password: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) throw new Error('Auth not initialized');
|
||||
return authService.signUp(email, password, name);
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available', needsVerification: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const sourceAppUrl = browser ? window.location.origin : undefined;
|
||||
const result = await authService.signUp(email, password, undefined, sourceAppUrl);
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
|
||||
}
|
||||
|
||||
if (result.needsVerification) {
|
||||
return { success: true, needsVerification: true };
|
||||
}
|
||||
|
||||
// Auto sign in after successful signup
|
||||
const signInResult = await this.signIn(email, password);
|
||||
return { ...signInResult, needsVerification: false };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage, needsVerification: false };
|
||||
}
|
||||
},
|
||||
|
||||
async signOut() {
|
||||
|
|
@ -102,4 +123,25 @@ export const authStore = {
|
|||
if (!authService) return null;
|
||||
return authService.getAppToken();
|
||||
},
|
||||
|
||||
async resendVerificationEmail(email: string) {
|
||||
const authService = getAuthService();
|
||||
if (!authService) {
|
||||
return { success: false, error: 'Auth not available' };
|
||||
}
|
||||
|
||||
try {
|
||||
const sourceAppUrl = typeof window !== 'undefined' ? window.location.origin : undefined;
|
||||
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Failed to resend verification email' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,9 +3,4 @@
|
|||
let { children }: { children: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="min-h-screen flex items-center justify-center"
|
||||
style="background-color: var(--color-bg);"
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
{@render children()}
|
||||
|
|
|
|||
|
|
@ -1,109 +1,48 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { LoginPage } from '@manacore/shared-auth-ui';
|
||||
import { getLoginTranslations } from '@manacore/shared-i18n';
|
||||
import { PlaygroundLogo } from '@manacore/shared-branding';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let error = $state('');
|
||||
let isLoading = $state(false);
|
||||
|
||||
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/');
|
||||
|
||||
onMount(async () => {
|
||||
await authStore.initialize();
|
||||
if (authStore.isAuthenticated) {
|
||||
goto(redirectTo);
|
||||
}
|
||||
});
|
||||
// Default to German translations
|
||||
const translations = $derived(getLoginTranslations('de'));
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
error = '';
|
||||
isLoading = true;
|
||||
// Read verification status from query params
|
||||
const verified = $derived($page.url.searchParams.get('verified') === 'true');
|
||||
const initialEmail = $derived($page.url.searchParams.get('email') || '');
|
||||
|
||||
try {
|
||||
const result = await authStore.signIn(email, password);
|
||||
if (result.success) {
|
||||
goto(redirectTo);
|
||||
} else {
|
||||
error = result.error || 'Login fehlgeschlagen';
|
||||
}
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Login fehlgeschlagen';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
async function handleSignIn(email: string, password: string) {
|
||||
return authStore.signIn(email, password);
|
||||
}
|
||||
|
||||
async function handleResendVerification(email: string) {
|
||||
return authStore.resendVerificationEmail(email);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="w-full max-w-md p-8 rounded-xl border"
|
||||
style="background-color: var(--color-surface); border-color: var(--color-border);"
|
||||
>
|
||||
<h1 class="text-2xl font-bold mb-6" style="color: var(--color-text);">LLM Playground</h1>
|
||||
<p class="mb-6" style="color: var(--color-text-muted);">
|
||||
Melde dich an, um den Playground zu nutzen.
|
||||
</p>
|
||||
<svelte:head>
|
||||
<title>{translations.title} | LLM Playground</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if error}
|
||||
<div
|
||||
class="mb-4 p-3 rounded-lg text-sm"
|
||||
style="background-color: var(--color-error-bg, rgba(239, 68, 68, 0.1)); border: 1px solid var(--color-error, #ef4444); color: var(--color-error, #ef4444);"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium mb-1"
|
||||
style="color: var(--color-text-muted);">E-Mail</label
|
||||
>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
bind:value={email}
|
||||
required
|
||||
class="w-full px-4 py-2 rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="password"
|
||||
class="block text-sm font-medium mb-1"
|
||||
style="color: var(--color-text-muted);">Passwort</label
|
||||
>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
bind:value={password}
|
||||
required
|
||||
class="w-full px-4 py-2 rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
class="w-full py-2 px-4 font-medium rounded-lg transition-colors disabled:opacity-50"
|
||||
style="background-color: var(--color-primary); color: white;"
|
||||
>
|
||||
{isLoading ? 'Wird angemeldet...' : 'Anmelden'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm" style="color: var(--color-text-muted);">
|
||||
Noch kein Konto? <a
|
||||
href="/register"
|
||||
class="hover:underline"
|
||||
style="color: var(--color-primary);">Registrieren</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<LoginPage
|
||||
appName="LLM Playground"
|
||||
logo={PlaygroundLogo}
|
||||
primaryColor="#06b6d4"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={handleResendVerification}
|
||||
{goto}
|
||||
enableGoogle={false}
|
||||
enableApple={false}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
forgotPasswordPath="/forgot-password"
|
||||
lightBackground="#ecfeff"
|
||||
darkBackground="#083344"
|
||||
{translations}
|
||||
{verified}
|
||||
{initialEmail}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,129 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import { getRegisterTranslations } from '@manacore/shared-i18n';
|
||||
import { PlaygroundLogo } from '@manacore/shared-branding';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let name = $state('');
|
||||
let error = $state('');
|
||||
let success = $state(false);
|
||||
let isLoading = $state(false);
|
||||
// Default to German translations
|
||||
const translations = $derived(getRegisterTranslations('de'));
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
error = '';
|
||||
isLoading = true;
|
||||
async function handleSignUp(email: string, password: string) {
|
||||
return authStore.signUp(email, password);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authStore.signUp(email, password, name || undefined);
|
||||
if (result.success) {
|
||||
success = true;
|
||||
} else {
|
||||
error = result.error || 'Registrierung fehlgeschlagen';
|
||||
}
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Registrierung fehlgeschlagen';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
async function handleResendVerification(email: string) {
|
||||
return authStore.resendVerificationEmail(email);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="w-full max-w-md p-8 rounded-xl border"
|
||||
style="background-color: var(--color-surface); border-color: var(--color-border);"
|
||||
>
|
||||
<h1 class="text-2xl font-bold mb-6" style="color: var(--color-text);">Registrieren</h1>
|
||||
<svelte:head>
|
||||
<title>{translations.title} | LLM Playground</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if success}
|
||||
<div
|
||||
class="p-4 rounded-lg"
|
||||
style="background-color: var(--color-success-bg, rgba(34, 197, 94, 0.1)); border: 1px solid var(--color-success, #22c55e);"
|
||||
>
|
||||
<p style="color: var(--color-success, #22c55e);">
|
||||
Registrierung erfolgreich! Du kannst dich jetzt anmelden.
|
||||
</p>
|
||||
<a
|
||||
href="/login"
|
||||
class="hover:underline mt-2 inline-block"
|
||||
style="color: var(--color-primary);">Zum Login</a
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
{#if error}
|
||||
<div
|
||||
class="mb-4 p-3 rounded-lg text-sm"
|
||||
style="background-color: var(--color-error-bg, rgba(239, 68, 68, 0.1)); border: 1px solid var(--color-error, #ef4444); color: var(--color-error, #ef4444);"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium mb-1"
|
||||
style="color: var(--color-text-muted);">Name (optional)</label
|
||||
>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
class="w-full px-4 py-2 rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium mb-1"
|
||||
style="color: var(--color-text-muted);">E-Mail</label
|
||||
>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
bind:value={email}
|
||||
required
|
||||
class="w-full px-4 py-2 rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="password"
|
||||
class="block text-sm font-medium mb-1"
|
||||
style="color: var(--color-text-muted);">Passwort</label
|
||||
>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
bind:value={password}
|
||||
required
|
||||
minlength="8"
|
||||
class="w-full px-4 py-2 rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--color-bg); border: 1px solid var(--color-border); color: var(--color-text);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
class="w-full py-2 px-4 font-medium rounded-lg transition-colors disabled:opacity-50"
|
||||
style="background-color: var(--color-primary); color: white;"
|
||||
>
|
||||
{isLoading ? 'Wird registriert...' : 'Registrieren'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm" style="color: var(--color-text-muted);">
|
||||
Bereits ein Konto? <a
|
||||
href="/login"
|
||||
class="hover:underline"
|
||||
style="color: var(--color-primary);">Anmelden</a
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<RegisterPage
|
||||
appName="LLM Playground"
|
||||
logo={PlaygroundLogo}
|
||||
primaryColor="#06b6d4"
|
||||
onSignUp={handleSignUp}
|
||||
onResendVerification={handleResendVerification}
|
||||
{goto}
|
||||
successRedirect="/"
|
||||
loginPath="/login"
|
||||
lightBackground="#ecfeff"
|
||||
darkBackground="#083344"
|
||||
{translations}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue