mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:21:08 +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
|
|
@ -123,6 +123,23 @@ PICTURE_SUPABASE_ANON_KEY=your-supabase-anon-key
|
|||
PICTURE_GOOGLE_CLIENT_ID=
|
||||
PICTURE_APPLE_CLIENT_ID=
|
||||
|
||||
# ============================================
|
||||
# NUTRIPHI PROJECT
|
||||
# ============================================
|
||||
|
||||
NUTRIPHI_BACKEND_PORT=3002
|
||||
NUTRIPHI_DATABASE_URL=postgresql://nutriphi:nutriphi_dev_password@localhost:5435/nutriphi
|
||||
NUTRIPHI_APP_ID=nutriphi
|
||||
NUTRIPHI_GEMINI_API_KEY=your-gemini-api-key-here
|
||||
|
||||
# S3 Storage (Hetzner Object Storage)
|
||||
NUTRIPHI_S3_ENDPOINT=https://fsn1.your-objectstorage.com
|
||||
NUTRIPHI_S3_ACCESS_KEY_ID=your-access-key-id
|
||||
NUTRIPHI_S3_SECRET_ACCESS_KEY=your-secret-access-key
|
||||
NUTRIPHI_S3_BUCKET_NAME=nutriphi-meals
|
||||
NUTRIPHI_S3_REGION=fsn1
|
||||
NUTRIPHI_S3_PUBLIC_URL=https://nutriphi-meals.fsn1.your-objectstorage.com
|
||||
|
||||
# ============================================
|
||||
# ZITARE PROJECT
|
||||
# ============================================
|
||||
|
|
|
|||
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',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ const wisekeepSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fil
|
|||
// Moodlit icon (colorful gradient circle)
|
||||
const moodlitSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><rect x="103" y="103" width="819" height="819" rx="409.5" fill="white"/><g filter="url(#filter0)"><circle cx="611" cy="250" r="314" fill="#D10000"/></g><g filter="url(#filter1)"><circle cx="231" cy="393" r="314" fill="#D17A00"/></g><g filter="url(#filter2)"><circle cx="735" cy="625" r="314" fill="#FFEA00"/></g><g filter="url(#filter3)"><circle cx="409" cy="844" r="314" fill="#0033FF"/></g></g><rect x="107" y="107" width="811" height="811" rx="405.5" stroke="white" stroke-opacity="0.2" stroke-width="8"/><defs><filter id="filter0" x="-2" y="-363" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter1" x="-382" y="-220" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter2" x="122" y="12" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter3" x="-204" y="231" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><clipPath id="clip0"><rect x="103" y="103" width="819" height="819" rx="409.5" fill="white"/></clipPath></defs></svg>`;
|
||||
|
||||
// Nutriphi icon (nutrition/heart with gradient)
|
||||
const nutriphiSvg = `<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(#nutriGrad)"/><path d="M512 760C512 760 280 600 280 420C280 340 344 280 424 280C472 280 512 308 512 308C512 308 552 280 600 280C680 280 744 340 744 420C744 600 512 760 512 760Z" fill="white"/><path d="M512 280V200" stroke="white" stroke-width="24" stroke-linecap="round"/><path d="M512 200C512 200 560 160 600 180" stroke="white" stroke-width="24" stroke-linecap="round"/><defs><linearGradient id="nutriGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#10b981"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>`;
|
||||
|
||||
/**
|
||||
* App icons as data URLs
|
||||
* Use these directly in <img src={APP_ICONS.memoro}> or CSS background-image
|
||||
|
|
@ -60,6 +63,7 @@ export const APP_ICONS = {
|
|||
zitare: svgToDataUrl(zitareSvg),
|
||||
wisekeep: svgToDataUrl(wisekeepSvg),
|
||||
moodlit: svgToDataUrl(moodlitSvg),
|
||||
nutriphi: svgToDataUrl(nutriphiSvg),
|
||||
} as const;
|
||||
|
||||
export type AppIconId = keyof typeof APP_ICONS;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,19 @@ export const APP_BRANDING: Record<AppId, AppBranding> = {
|
|||
logoStroke: true,
|
||||
logoStrokeWidth: 1.5,
|
||||
},
|
||||
nutriphi: {
|
||||
id: 'nutriphi',
|
||||
name: 'Nutriphi',
|
||||
tagline: 'AI Nutrition Tracker',
|
||||
primaryColor: '#10b981',
|
||||
secondaryColor: '#34d399',
|
||||
// Heart with sparkle for healthy nutrition
|
||||
logoPath:
|
||||
'M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z',
|
||||
logoViewBox: '0 0 24 24',
|
||||
logoStroke: true,
|
||||
logoStrokeWidth: 1.5,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
13
packages/shared-branding/src/logos/NutriPhiLogo.svelte
Normal file
13
packages/shared-branding/src/logos/NutriPhiLogo.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="nutriphi" {size} {color} class={className} />
|
||||
|
|
@ -9,3 +9,4 @@ export { default as StorytellerLogo } from './StorytellerLogo.svelte';
|
|||
export { default as UloadLogo } from './UloadLogo.svelte';
|
||||
export { default as ChatLogo } from './ChatLogo.svelte';
|
||||
export { default as PresiLogo } from './PresiLogo.svelte';
|
||||
export { default as NutriPhiLogo } from './NutriPhiLogo.svelte';
|
||||
|
|
|
|||
|
|
@ -158,6 +158,22 @@ export const MANA_APPS: ManaApp[] = [
|
|||
comingSoon: true,
|
||||
status: 'planning',
|
||||
},
|
||||
{
|
||||
id: 'nutriphi',
|
||||
name: 'Nutriphi',
|
||||
description: {
|
||||
de: 'KI Ernährungstracker',
|
||||
en: 'AI Nutrition Tracker',
|
||||
},
|
||||
longDescription: {
|
||||
de: 'Tracke deine Ernährung mit KI-gestützter Foto-Analyse und erhalte detaillierte Nährwertinformationen.',
|
||||
en: 'Track your nutrition with AI-powered photo analysis and get detailed nutritional information.',
|
||||
},
|
||||
icon: APP_ICONS.nutriphi,
|
||||
color: '#10b981',
|
||||
comingSoon: false,
|
||||
status: 'development',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* App identifiers for branding
|
||||
*/
|
||||
export type AppId = 'memoro' | 'manacore' | 'manadeck' | 'maerchenzauber' | 'uload' | 'chat' | 'presi';
|
||||
export type AppId = 'memoro' | 'manacore' | 'manadeck' | 'maerchenzauber' | 'uload' | 'chat' | 'presi' | 'nutriphi';
|
||||
|
||||
/**
|
||||
* App branding configuration
|
||||
|
|
|
|||
|
|
@ -249,6 +249,34 @@ const APP_CONFIGS = [
|
|||
},
|
||||
},
|
||||
|
||||
// Nutriphi Backend (NestJS)
|
||||
{
|
||||
path: 'apps/nutriphi/apps/backend/.env',
|
||||
vars: {
|
||||
NODE_ENV: () => 'development',
|
||||
PORT: (env) => env.NUTRIPHI_BACKEND_PORT || '3002',
|
||||
DATABASE_URL: (env) => env.NUTRIPHI_DATABASE_URL,
|
||||
MANACORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
|
||||
GEMINI_API_KEY: (env) => env.NUTRIPHI_GEMINI_API_KEY,
|
||||
S3_ENDPOINT: (env) => env.NUTRIPHI_S3_ENDPOINT,
|
||||
S3_ACCESS_KEY_ID: (env) => env.NUTRIPHI_S3_ACCESS_KEY_ID,
|
||||
S3_SECRET_ACCESS_KEY: (env) => env.NUTRIPHI_S3_SECRET_ACCESS_KEY,
|
||||
S3_BUCKET_NAME: (env) => env.NUTRIPHI_S3_BUCKET_NAME,
|
||||
S3_REGION: (env) => env.NUTRIPHI_S3_REGION,
|
||||
S3_PUBLIC_URL: (env) => env.NUTRIPHI_S3_PUBLIC_URL,
|
||||
},
|
||||
},
|
||||
|
||||
// Nutriphi Web (SvelteKit)
|
||||
{
|
||||
path: 'apps/nutriphi/apps/web/.env',
|
||||
vars: {
|
||||
PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.NUTRIPHI_BACKEND_PORT || '3002'}`,
|
||||
PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
|
||||
PUBLIC_MIDDLEWARE_APP_ID: (env) => env.NUTRIPHI_APP_ID || 'nutriphi',
|
||||
},
|
||||
},
|
||||
|
||||
// Zitare Backend (NestJS)
|
||||
{
|
||||
path: 'apps/zitare/apps/backend/.env',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue