mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 19:21:23 +02:00
feat(nutriphi): migrate to shared auth UI components
- Add nutriphi branding to shared-branding package (types, config, logo) - Add nutriphi icon to app-icons and MANA_APPS for AppSlider - Replace custom login/register pages with shared LoginPage/RegisterPage - Add forgot-password page using shared ForgotPasswordPage component - Create AppSlider component for nutriphi web - Update vite.config.ts with SSR config for shared packages - Add nutriphi env variables to generate-env.mjs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f1e27f3beb
commit
8b61399a64
13 changed files with 295 additions and 263 deletions
32
apps/nutriphi/apps/web/src/lib/components/AppSlider.svelte
Normal file
32
apps/nutriphi/apps/web/src/lib/components/AppSlider.svelte
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script lang="ts">
|
||||
import { AppSlider, type AppItem } from '@manacore/shared-ui';
|
||||
import { MANA_APPS, APP_STATUS_LABELS, APP_SLIDER_LABELS } from '@manacore/shared-branding';
|
||||
|
||||
// Convert MANA_APPS to AppItem format (German)
|
||||
const apps: AppItem[] = MANA_APPS.map((app) => ({
|
||||
name: app.name,
|
||||
description: app.description.de,
|
||||
longDescription: app.longDescription.de,
|
||||
icon: app.icon,
|
||||
color: app.color,
|
||||
comingSoon: app.comingSoon,
|
||||
status: app.status,
|
||||
}));
|
||||
|
||||
const statusLabels = APP_STATUS_LABELS.de;
|
||||
const labels = APP_SLIDER_LABELS.de;
|
||||
|
||||
function handleAppClick(app: AppItem, index: number) {
|
||||
console.log('Opening app:', app.name);
|
||||
}
|
||||
</script>
|
||||
|
||||
<AppSlider
|
||||
{apps}
|
||||
title={labels.title}
|
||||
isDark={false}
|
||||
{statusLabels}
|
||||
comingSoonLabel={labels.comingSoon}
|
||||
openAppLabel={labels.openApp}
|
||||
onAppClick={handleAppClick}
|
||||
/>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
|
||||
import { NutriPhiLogo } from '@manacore/shared-branding';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import AppSlider from '$lib/components/AppSlider.svelte';
|
||||
|
||||
// German translations
|
||||
const translations = {
|
||||
titleForm: 'Passwort zurücksetzen',
|
||||
titleSuccess: 'E-Mail gesendet',
|
||||
description:
|
||||
'Gib deine E-Mail-Adresse ein und wir senden dir einen Link zum Zurücksetzen deines Passworts.',
|
||||
emailPlaceholder: 'E-Mail',
|
||||
sendResetLinkButton: 'Link senden',
|
||||
sending: 'Wird gesendet...',
|
||||
backToLogin: 'Zurück zur Anmeldung',
|
||||
resendEmail: 'E-Mail erneut senden',
|
||||
successMessage:
|
||||
'Wir haben einen Link zum Zurücksetzen deines Passworts an {email} gesendet. Bitte überprüfe deinen Posteingang.',
|
||||
emailRequired: 'E-Mail ist erforderlich',
|
||||
sendFailed: 'Fehler beim Senden der E-Mail',
|
||||
};
|
||||
|
||||
async function handleForgotPassword(email: string) {
|
||||
return auth.forgotPassword(email);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Passwort zurücksetzen | Nutriphi</title>
|
||||
</svelte:head>
|
||||
|
||||
<ForgotPasswordPage
|
||||
appName="Nutriphi"
|
||||
logo={NutriPhiLogo}
|
||||
primaryColor="#10b981"
|
||||
onForgotPassword={handleForgotPassword}
|
||||
{goto}
|
||||
loginPath="/login"
|
||||
lightBackground="#d1fae5"
|
||||
darkBackground="#022c22"
|
||||
{translations}
|
||||
>
|
||||
{#snippet appSlider()}
|
||||
<AppSlider />
|
||||
{/snippet}
|
||||
</ForgotPasswordPage>
|
||||
|
|
@ -1,113 +1,65 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { LoginPage } from '@manacore/shared-auth-ui';
|
||||
import { NutriPhiLogo } from '@manacore/shared-branding';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import AppSlider from '$lib/components/AppSlider.svelte';
|
||||
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let error = $state('');
|
||||
let isLoading = $state(false);
|
||||
// Get redirect URL from query params
|
||||
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/meals');
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
error = '';
|
||||
isLoading = true;
|
||||
// German translations
|
||||
const translations = {
|
||||
title: 'Anmelden',
|
||||
subtitle: 'Melde dich mit deinem Konto an',
|
||||
emailPlaceholder: 'E-Mail',
|
||||
passwordPlaceholder: 'Passwort',
|
||||
rememberMe: 'Angemeldet bleiben',
|
||||
forgotPassword: 'Passwort vergessen?',
|
||||
signInButton: 'Anmelden',
|
||||
signingIn: 'Wird angemeldet...',
|
||||
success: 'Erfolgreich!',
|
||||
orDivider: 'oder',
|
||||
noAccount: 'Noch kein Konto?',
|
||||
createAccount: 'Jetzt registrieren',
|
||||
skipToForm: 'Zum Login-Formular springen',
|
||||
showPassword: 'Passwort anzeigen',
|
||||
hidePassword: 'Passwort verbergen',
|
||||
emailRequired: 'E-Mail ist erforderlich',
|
||||
emailInvalid: 'Bitte gib eine gültige E-Mail-Adresse ein',
|
||||
passwordRequired: 'Passwort ist erforderlich',
|
||||
signInFailed: 'Anmeldung fehlgeschlagen',
|
||||
googleSignInFailed: 'Google-Anmeldung fehlgeschlagen',
|
||||
signInSuccess: 'Erfolgreich angemeldet. Weiterleitung...',
|
||||
googleSignInSuccess: 'Erfolgreich mit Google angemeldet. Weiterleitung...',
|
||||
};
|
||||
|
||||
const result = await auth.signIn(email, password);
|
||||
|
||||
if (result.success) {
|
||||
goto('/meals');
|
||||
} else {
|
||||
if (result.error === 'INVALID_CREDENTIALS') {
|
||||
error = 'Ungültige E-Mail oder Passwort';
|
||||
} else if (result.error === 'EMAIL_NOT_VERIFIED') {
|
||||
error = 'Bitte bestätige zuerst deine E-Mail-Adresse';
|
||||
} else {
|
||||
error = result.error || 'Anmeldung fehlgeschlagen';
|
||||
}
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
async function handleSignIn(email: string, password: string) {
|
||||
return auth.signIn(email, password);
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="flex min-h-screen items-center justify-center p-4">
|
||||
<div class="w-full max-w-md rounded-2xl bg-white p-8 shadow-xl dark:bg-gray-800">
|
||||
<div class="mb-6 text-center">
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-green-400 to-emerald-500"
|
||||
>
|
||||
<span class="text-4xl">🥗</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Anmelden</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Willkommen zurück bei Nutriphi</p>
|
||||
</div>
|
||||
<svelte:head>
|
||||
<title>Anmelden | Nutriphi</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if error}
|
||||
<div class="mb-4 rounded-lg bg-red-100 p-3 text-red-700 dark:bg-red-900/30 dark:text-red-400">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="email" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
E-Mail
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
bind:value={email}
|
||||
required
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="deine@email.de"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="password"
|
||||
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
bind:value={password}
|
||||
required
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Dein Passwort"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
class="w-full rounded-xl bg-gradient-to-r from-green-500 to-emerald-600 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:from-green-600 hover:to-emerald-700 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Wird angemeldet...' : 'Anmelden'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Noch kein Konto?
|
||||
<a
|
||||
href="/register"
|
||||
class="font-semibold text-green-600 hover:text-green-700 dark:text-green-400"
|
||||
>
|
||||
Registrieren
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<a
|
||||
href="/"
|
||||
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
Zurück zur Startseite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<LoginPage
|
||||
appName="Nutriphi"
|
||||
logo={NutriPhiLogo}
|
||||
primaryColor="#10b981"
|
||||
onSignIn={handleSignIn}
|
||||
{goto}
|
||||
enableGoogle={false}
|
||||
enableApple={false}
|
||||
successRedirect={redirectTo}
|
||||
registerPath="/register"
|
||||
forgotPasswordPath="/forgot-password"
|
||||
lightBackground="#d1fae5"
|
||||
darkBackground="#022c22"
|
||||
{translations}
|
||||
>
|
||||
{#snippet appSlider()}
|
||||
<AppSlider />
|
||||
{/snippet}
|
||||
</LoginPage>
|
||||
|
|
|
|||
|
|
@ -1,168 +1,56 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import { NutriPhiLogo } from '@manacore/shared-branding';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import AppSlider from '$lib/components/AppSlider.svelte';
|
||||
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let confirmPassword = $state('');
|
||||
let error = $state('');
|
||||
let isLoading = $state(false);
|
||||
let needsVerification = $state(false);
|
||||
// German translations
|
||||
const translations = {
|
||||
title: 'Konto erstellen',
|
||||
emailPlaceholder: 'E-Mail',
|
||||
passwordPlaceholder: 'Passwort',
|
||||
confirmPasswordPlaceholder: 'Passwort bestätigen',
|
||||
passwordRequirements:
|
||||
'Passwort muss mindestens 8 Zeichen mit Kleinbuchstaben, Großbuchstaben, Zahl und Sonderzeichen enthalten.',
|
||||
createAccountButton: 'Konto erstellen',
|
||||
creatingAccount: 'Wird erstellt...',
|
||||
backToLogin: 'Zurück zur Anmeldung',
|
||||
showPassword: 'Passwort anzeigen',
|
||||
hidePassword: 'Passwort verbergen',
|
||||
emailRequired: 'E-Mail ist erforderlich',
|
||||
passwordRequired: 'Passwort ist erforderlich',
|
||||
confirmPasswordRequired: 'Bitte bestätige dein Passwort',
|
||||
passwordsDoNotMatch: 'Passwörter stimmen nicht überein',
|
||||
passwordTooShort: 'Passwort muss mindestens 8 Zeichen lang sein',
|
||||
passwordStrengthError:
|
||||
'Passwort muss Kleinbuchstaben, Großbuchstaben, Zahl und Sonderzeichen enthalten',
|
||||
registrationFailed: 'Registrierung fehlgeschlagen',
|
||||
accountCreated: 'Konto erstellt! Bitte überprüfe deine E-Mails zur Bestätigung.',
|
||||
};
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
error = '';
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
error = 'Die Passwörter stimmen nicht überein';
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
error = 'Das Passwort muss mindestens 8 Zeichen lang sein';
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
|
||||
const result = await auth.signUp(email, password);
|
||||
|
||||
if (result.success) {
|
||||
if (result.needsVerification) {
|
||||
needsVerification = true;
|
||||
} else {
|
||||
goto('/meals');
|
||||
}
|
||||
} else {
|
||||
if (result.error?.includes('already in use')) {
|
||||
error = 'Diese E-Mail-Adresse wird bereits verwendet';
|
||||
} else {
|
||||
error = result.error || 'Registrierung fehlgeschlagen';
|
||||
}
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
async function handleSignUp(email: string, password: string) {
|
||||
return auth.signUp(email, password);
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="flex min-h-screen items-center justify-center p-4">
|
||||
<div class="w-full max-w-md rounded-2xl bg-white p-8 shadow-xl dark:bg-gray-800">
|
||||
<div class="mb-6 text-center">
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-green-400 to-emerald-500"
|
||||
>
|
||||
<span class="text-4xl">🥗</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Registrieren</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Erstelle dein Nutriphi Konto</p>
|
||||
</div>
|
||||
<svelte:head>
|
||||
<title>Registrieren | Nutriphi</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if needsVerification}
|
||||
<div
|
||||
class="rounded-lg bg-green-100 p-4 text-center text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
||||
>
|
||||
<p class="font-semibold">Bestätige deine E-Mail</p>
|
||||
<p class="mt-2 text-sm">
|
||||
Wir haben dir eine Bestätigungs-E-Mail gesendet. Bitte klicke auf den Link in der E-Mail,
|
||||
um dein Konto zu aktivieren.
|
||||
</p>
|
||||
<a
|
||||
href="/login"
|
||||
class="mt-4 inline-block font-semibold text-green-600 hover:text-green-700 dark:text-green-400"
|
||||
>
|
||||
Zur Anmeldung
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
{#if error}
|
||||
<div
|
||||
class="mb-4 rounded-lg bg-red-100 p-3 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
E-Mail
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
bind:value={email}
|
||||
required
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="deine@email.de"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="password"
|
||||
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
bind:value={password}
|
||||
required
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Mindestens 8 Zeichen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="confirmPassword"
|
||||
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
bind:value={confirmPassword}
|
||||
required
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Passwort wiederholen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
class="w-full rounded-xl bg-gradient-to-r from-green-500 to-emerald-600 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:from-green-600 hover:to-emerald-700 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Wird registriert...' : 'Registrieren'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Bereits ein Konto?
|
||||
<a
|
||||
href="/login"
|
||||
class="font-semibold text-green-600 hover:text-green-700 dark:text-green-400"
|
||||
>
|
||||
Anmelden
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<a
|
||||
href="/"
|
||||
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
Zurück zur Startseite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<RegisterPage
|
||||
appName="Nutriphi"
|
||||
logo={NutriPhiLogo}
|
||||
primaryColor="#10b981"
|
||||
onSignUp={handleSignUp}
|
||||
{goto}
|
||||
successRedirect="/meals"
|
||||
loginPath="/login"
|
||||
lightBackground="#d1fae5"
|
||||
darkBackground="#022c22"
|
||||
{translations}
|
||||
>
|
||||
{#snippet appSlider()}
|
||||
<AppSlider />
|
||||
{/snippet}
|
||||
</RegisterPage>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,24 @@ import { defineConfig } from 'vite';
|
|||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
ssr: {
|
||||
noExternal: [
|
||||
'@manacore/shared-theme',
|
||||
'@manacore/shared-auth-ui',
|
||||
'@manacore/shared-branding',
|
||||
'@manacore/shared-ui',
|
||||
'@manacore/shared-theme-ui',
|
||||
'@manacore/shared-i18n',
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
'@manacore/shared-theme',
|
||||
'@manacore/shared-auth-ui',
|
||||
'@manacore/shared-branding',
|
||||
'@manacore/shared-ui',
|
||||
'@manacore/shared-theme-ui',
|
||||
'@manacore/shared-i18n',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue