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
|
|
@ -78,6 +78,13 @@ contacts-api.mana.how {
|
||||||
reverse_proxy localhost:3015
|
reverse_proxy localhost:3015
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# LLM Playground
|
||||||
|
# ============================================
|
||||||
|
playground.mana.how {
|
||||||
|
reverse_proxy localhost:5090
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Monitoring & Analytics
|
# Monitoring & Analytics
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ const questionsSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fi
|
||||||
// Matrix icon (network/federated chat with purple gradient)
|
// Matrix icon (network/federated chat with purple gradient)
|
||||||
const matrixSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#matrixGrad)"/><circle cx="512" cy="400" r="80" fill="white"/><circle cx="340" cy="580" r="60" fill="white" fill-opacity="0.8"/><circle cx="684" cy="580" r="60" fill="white" fill-opacity="0.8"/><circle cx="420" cy="720" r="50" fill="white" fill-opacity="0.6"/><circle cx="604" cy="720" r="50" fill="white" fill-opacity="0.6"/><path d="M512 480V640M512 640L420 700M512 640L604 700" stroke="white" stroke-width="16" stroke-linecap="round"/><path d="M450 440L370 540M574 440L654 540" stroke="white" stroke-width="16" stroke-linecap="round"/><path d="M340 640L400 700M684 640L624 700" stroke="white" stroke-width="12" stroke-linecap="round" stroke-opacity="0.6"/><defs><linearGradient id="matrixGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#8b5cf6"/><stop offset="1" stop-color="#7c3aed"/></linearGradient></defs></svg>`;
|
const matrixSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#matrixGrad)"/><circle cx="512" cy="400" r="80" fill="white"/><circle cx="340" cy="580" r="60" fill="white" fill-opacity="0.8"/><circle cx="684" cy="580" r="60" fill="white" fill-opacity="0.8"/><circle cx="420" cy="720" r="50" fill="white" fill-opacity="0.6"/><circle cx="604" cy="720" r="50" fill="white" fill-opacity="0.6"/><path d="M512 480V640M512 640L420 700M512 640L604 700" stroke="white" stroke-width="16" stroke-linecap="round"/><path d="M450 440L370 540M574 440L654 540" stroke="white" stroke-width="16" stroke-linecap="round"/><path d="M340 640L400 700M684 640L624 700" stroke="white" stroke-width="12" stroke-linecap="round" stroke-opacity="0.6"/><defs><linearGradient id="matrixGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#8b5cf6"/><stop offset="1" stop-color="#7c3aed"/></linearGradient></defs></svg>`;
|
||||||
|
|
||||||
|
// Playground icon (code/terminal with cyan gradient)
|
||||||
|
const playgroundSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#playgroundGrad)"/><path d="M380 340L260 512L380 684" stroke="white" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/><path d="M644 340L764 512L644 684" stroke="white" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/><path d="M560 280L464 744" stroke="white" stroke-width="40" stroke-linecap="round"/><defs><linearGradient id="playgroundGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#06b6d4"/><stop offset="1" stop-color="#0891b2"/></linearGradient></defs></svg>`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App icons as data URLs
|
* App icons as data URLs
|
||||||
* Use these directly in <img src={APP_ICONS.memoro}> or CSS background-image
|
* Use these directly in <img src={APP_ICONS.memoro}> or CSS background-image
|
||||||
|
|
@ -94,6 +97,7 @@ export const APP_ICONS = {
|
||||||
inventory: svgToDataUrl(inventorySvg),
|
inventory: svgToDataUrl(inventorySvg),
|
||||||
questions: svgToDataUrl(questionsSvg),
|
questions: svgToDataUrl(questionsSvg),
|
||||||
matrix: svgToDataUrl(matrixSvg),
|
matrix: svgToDataUrl(matrixSvg),
|
||||||
|
playground: svgToDataUrl(playgroundSvg),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type AppIconId = keyof typeof APP_ICONS;
|
export type AppIconId = keyof typeof APP_ICONS;
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,18 @@ export const APP_BRANDING: Record<AppId, AppBranding> = {
|
||||||
logoStroke: true,
|
logoStroke: true,
|
||||||
logoStrokeWidth: 1.5,
|
logoStrokeWidth: 1.5,
|
||||||
},
|
},
|
||||||
|
playground: {
|
||||||
|
id: 'playground',
|
||||||
|
name: 'Playground',
|
||||||
|
tagline: 'LLM Playground',
|
||||||
|
primaryColor: '#06b6d4',
|
||||||
|
secondaryColor: '#22d3ee',
|
||||||
|
// Code/terminal icon for LLM playground
|
||||||
|
logoPath: 'M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5',
|
||||||
|
logoViewBox: '0 0 24 24',
|
||||||
|
logoStroke: true,
|
||||||
|
logoStrokeWidth: 1.5,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export {
|
||||||
QuestionsLogo,
|
QuestionsLogo,
|
||||||
SkillTreeLogo,
|
SkillTreeLogo,
|
||||||
PlantaLogo,
|
PlantaLogo,
|
||||||
|
PlaygroundLogo,
|
||||||
} from './logos';
|
} from './logos';
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|
|
||||||
13
packages/shared-branding/src/logos/PlaygroundLogo.svelte
Normal file
13
packages/shared-branding/src/logos/PlaygroundLogo.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import AppLogo from '../AppLogo.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { size = 55, color, class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AppLogo app="playground" {size} {color} class={className} />
|
||||||
|
|
@ -21,3 +21,4 @@ export { default as ClockLogo } from './ClockLogo.svelte';
|
||||||
export { default as QuestionsLogo } from './QuestionsLogo.svelte';
|
export { default as QuestionsLogo } from './QuestionsLogo.svelte';
|
||||||
export { default as SkillTreeLogo } from './SkillTreeLogo.svelte';
|
export { default as SkillTreeLogo } from './SkillTreeLogo.svelte';
|
||||||
export { default as PlantaLogo } from './PlantaLogo.svelte';
|
export { default as PlantaLogo } from './PlantaLogo.svelte';
|
||||||
|
export { default as PlaygroundLogo } from './PlaygroundLogo.svelte';
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,7 @@ export const APP_URLS: Record<AppIconId, { dev: string; prod: string }> = {
|
||||||
inventory: { dev: 'http://localhost:5188', prod: 'https://inventory.manacore.app' },
|
inventory: { dev: 'http://localhost:5188', prod: 'https://inventory.manacore.app' },
|
||||||
questions: { dev: 'http://localhost:5111', prod: 'https://questions.manacore.app' },
|
questions: { dev: 'http://localhost:5111', prod: 'https://questions.manacore.app' },
|
||||||
matrix: { dev: 'http://localhost:5180', prod: 'https://matrix.mana.how' },
|
matrix: { dev: 'http://localhost:5180', prod: 'https://matrix.mana.how' },
|
||||||
|
playground: { dev: 'http://localhost:5190', prod: 'https://playground.mana.how' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ export type AppId =
|
||||||
| 'inventory'
|
| 'inventory'
|
||||||
| 'questions'
|
| 'questions'
|
||||||
| 'skilltree'
|
| 'skilltree'
|
||||||
| 'planta';
|
| 'planta'
|
||||||
|
| 'playground';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App branding configuration
|
* App branding configuration
|
||||||
|
|
|
||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
|
|
@ -5361,9 +5361,24 @@ importers:
|
||||||
'@manacore/shared-auth':
|
'@manacore/shared-auth':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared-auth
|
version: link:../../packages/shared-auth
|
||||||
|
'@manacore/shared-auth-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shared-auth-ui
|
||||||
|
'@manacore/shared-branding':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shared-branding
|
||||||
|
'@manacore/shared-i18n':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shared-i18n
|
||||||
|
'@manacore/shared-icons':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shared-icons
|
||||||
marked:
|
marked:
|
||||||
specifier: ^17.0.0
|
specifier: ^17.0.0
|
||||||
version: 17.0.1
|
version: 17.0.1
|
||||||
|
svelte-i18n:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1(svelte@5.44.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-node':
|
'@sveltejs/adapter-node':
|
||||||
specifier: ^5.4.0
|
specifier: ^5.4.0
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@manacore/shared-auth": "workspace:*",
|
"@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;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
async signUp(email: string, password: string, name?: string) {
|
async signUp(email: string, password: string) {
|
||||||
const authService = getAuthService();
|
const authService = getAuthService();
|
||||||
if (!authService) throw new Error('Auth not initialized');
|
if (!authService) {
|
||||||
return authService.signUp(email, password, name);
|
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() {
|
async signOut() {
|
||||||
|
|
@ -102,4 +123,25 @@ export const authStore = {
|
||||||
if (!authService) return null;
|
if (!authService) return null;
|
||||||
return authService.getAppToken();
|
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();
|
let { children }: { children: Snippet } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
{@render children()}
|
||||||
class="min-h-screen flex items-center justify-center"
|
|
||||||
style="background-color: var(--color-bg);"
|
|
||||||
>
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,48 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
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 { 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') || '/');
|
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/');
|
||||||
|
|
||||||
onMount(async () => {
|
// Default to German translations
|
||||||
await authStore.initialize();
|
const translations = $derived(getLoginTranslations('de'));
|
||||||
if (authStore.isAuthenticated) {
|
|
||||||
goto(redirectTo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleSubmit(e: Event) {
|
// Read verification status from query params
|
||||||
e.preventDefault();
|
const verified = $derived($page.url.searchParams.get('verified') === 'true');
|
||||||
error = '';
|
const initialEmail = $derived($page.url.searchParams.get('email') || '');
|
||||||
isLoading = true;
|
|
||||||
|
|
||||||
try {
|
async function handleSignIn(email: string, password: string) {
|
||||||
const result = await authStore.signIn(email, password);
|
return authStore.signIn(email, password);
|
||||||
if (result.success) {
|
}
|
||||||
goto(redirectTo);
|
|
||||||
} else {
|
async function handleResendVerification(email: string) {
|
||||||
error = result.error || 'Login fehlgeschlagen';
|
return authStore.resendVerificationEmail(email);
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error = err instanceof Error ? err.message : 'Login fehlgeschlagen';
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<svelte:head>
|
||||||
class="w-full max-w-md p-8 rounded-xl border"
|
<title>{translations.title} | LLM Playground</title>
|
||||||
style="background-color: var(--color-surface); border-color: var(--color-border);"
|
</svelte:head>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{#if error}
|
<LoginPage
|
||||||
<div
|
appName="LLM Playground"
|
||||||
class="mb-4 p-3 rounded-lg text-sm"
|
logo={PlaygroundLogo}
|
||||||
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);"
|
primaryColor="#06b6d4"
|
||||||
>
|
onSignIn={handleSignIn}
|
||||||
{error}
|
onResendVerification={handleResendVerification}
|
||||||
</div>
|
{goto}
|
||||||
{/if}
|
enableGoogle={false}
|
||||||
|
enableApple={false}
|
||||||
<form onsubmit={handleSubmit} class="space-y-4">
|
successRedirect={redirectTo}
|
||||||
<div>
|
registerPath="/register"
|
||||||
<label
|
forgotPasswordPath="/forgot-password"
|
||||||
for="email"
|
lightBackground="#ecfeff"
|
||||||
class="block text-sm font-medium mb-1"
|
darkBackground="#083344"
|
||||||
style="color: var(--color-text-muted);">E-Mail</label
|
{translations}
|
||||||
>
|
{verified}
|
||||||
<input
|
{initialEmail}
|
||||||
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>
|
|
||||||
|
|
|
||||||
|
|
@ -1,129 +1,36 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
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';
|
import { authStore } from '$lib/stores/auth.svelte';
|
||||||
|
|
||||||
let email = $state('');
|
// Default to German translations
|
||||||
let password = $state('');
|
const translations = $derived(getRegisterTranslations('de'));
|
||||||
let name = $state('');
|
|
||||||
let error = $state('');
|
|
||||||
let success = $state(false);
|
|
||||||
let isLoading = $state(false);
|
|
||||||
|
|
||||||
async function handleSubmit(e: Event) {
|
async function handleSignUp(email: string, password: string) {
|
||||||
e.preventDefault();
|
return authStore.signUp(email, password);
|
||||||
error = '';
|
}
|
||||||
isLoading = true;
|
|
||||||
|
|
||||||
try {
|
async function handleResendVerification(email: string) {
|
||||||
const result = await authStore.signUp(email, password, name || undefined);
|
return authStore.resendVerificationEmail(email);
|
||||||
if (result.success) {
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
error = result.error || 'Registrierung fehlgeschlagen';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error = err instanceof Error ? err.message : 'Registrierung fehlgeschlagen';
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<svelte:head>
|
||||||
class="w-full max-w-md p-8 rounded-xl border"
|
<title>{translations.title} | LLM Playground</title>
|
||||||
style="background-color: var(--color-surface); border-color: var(--color-border);"
|
</svelte:head>
|
||||||
>
|
|
||||||
<h1 class="text-2xl font-bold mb-6" style="color: var(--color-text);">Registrieren</h1>
|
|
||||||
|
|
||||||
{#if success}
|
<RegisterPage
|
||||||
<div
|
appName="LLM Playground"
|
||||||
class="p-4 rounded-lg"
|
logo={PlaygroundLogo}
|
||||||
style="background-color: var(--color-success-bg, rgba(34, 197, 94, 0.1)); border: 1px solid var(--color-success, #22c55e);"
|
primaryColor="#06b6d4"
|
||||||
>
|
onSignUp={handleSignUp}
|
||||||
<p style="color: var(--color-success, #22c55e);">
|
onResendVerification={handleResendVerification}
|
||||||
Registrierung erfolgreich! Du kannst dich jetzt anmelden.
|
{goto}
|
||||||
</p>
|
successRedirect="/"
|
||||||
<a
|
loginPath="/login"
|
||||||
href="/login"
|
lightBackground="#ecfeff"
|
||||||
class="hover:underline mt-2 inline-block"
|
darkBackground="#083344"
|
||||||
style="color: var(--color-primary);">Zum Login</a
|
{translations}
|
||||||
>
|
/>
|
||||||
</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>
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue