mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 15:26:42 +02:00
feat(apps): migrate Presi, Picture, Inventar, NutriPhi, Planta, Storage to local-first
Add IndexedDB data layer (Dexie.js via @manacore/local-store) to 6 more apps,
bringing the total to 12/22 apps with local-first architecture.
For each app:
- Create local-store.ts with typed collections and sync config
- Create guest-seed.ts with onboarding data for guest mode
- Update layout with AuthGate allowGuest={true} + handleAuthReady()
- Add GuestWelcomeModal for first-visit experience
- Add @manacore/local-store dependency
App-specific changes:
- Presi: Rewrite decks store from API to IndexedDB, conditional share button
- Picture: Rewrite gallery + boards pages to read from IndexedDB
- Inventar: Replace manual auth $effect with AuthGate, keep localStorage stores
- NutriPhi: Add onReady handler to existing AuthGate
- Planta: Add allowGuest + sync init to existing AuthGate
- Storage: Add local store init to existing handleAuthReady
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f40de2966
commit
ce51fd5fe2
31 changed files with 1623 additions and 211 deletions
|
|
@ -50,6 +50,7 @@
|
|||
"@manacore/shared-help-types": "workspace:*",
|
||||
"@manacore/shared-help-ui": "workspace:*",
|
||||
"@manacore/shared-icons": "workspace:*",
|
||||
"@manacore/local-store": "workspace:*",
|
||||
"@manacore/shared-profile-ui": "workspace:*",
|
||||
"@manacore/shared-subscription-ui": "workspace:*",
|
||||
"@manacore/shared-tailwind": "workspace:*",
|
||||
|
|
|
|||
55
apps/nutriphi/apps/web/src/lib/data/guest-seed.ts
Normal file
55
apps/nutriphi/apps/web/src/lib/data/guest-seed.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Guest seed data for the NutriPhi app.
|
||||
*
|
||||
* Provides demo meals and default goals for the onboarding experience.
|
||||
*/
|
||||
|
||||
import type { LocalMeal, LocalGoal } from './local-store';
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
export const guestMeals: LocalMeal[] = [
|
||||
{
|
||||
id: 'meal-breakfast',
|
||||
date: today,
|
||||
mealType: 'breakfast',
|
||||
inputType: 'text',
|
||||
description: 'Haferflocken mit Banane und Honig',
|
||||
confidence: 0.9,
|
||||
nutrition: {
|
||||
calories: 380,
|
||||
protein: 10,
|
||||
carbohydrates: 68,
|
||||
fat: 8,
|
||||
fiber: 6,
|
||||
sugar: 24,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'meal-lunch',
|
||||
date: today,
|
||||
mealType: 'lunch',
|
||||
inputType: 'text',
|
||||
description: 'Vollkorn-Sandwich mit Avocado und Ei',
|
||||
confidence: 0.85,
|
||||
nutrition: {
|
||||
calories: 520,
|
||||
protein: 22,
|
||||
carbohydrates: 45,
|
||||
fat: 28,
|
||||
fiber: 8,
|
||||
sugar: 4,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const guestGoals: LocalGoal[] = [
|
||||
{
|
||||
id: 'default-goals',
|
||||
dailyCalories: 2000,
|
||||
dailyProtein: 60,
|
||||
dailyCarbs: 250,
|
||||
dailyFat: 65,
|
||||
dailyFiber: 30,
|
||||
},
|
||||
];
|
||||
83
apps/nutriphi/apps/web/src/lib/data/local-store.ts
Normal file
83
apps/nutriphi/apps/web/src/lib/data/local-store.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* NutriPhi — Local-First Data Layer
|
||||
*
|
||||
* Meals, nutrition, goals, and favorites stored locally.
|
||||
* AI analysis and recommendations remain server-side.
|
||||
*/
|
||||
|
||||
import { createLocalStore, type BaseRecord } from '@manacore/local-store';
|
||||
import { guestMeals, guestGoals } from './guest-seed';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────
|
||||
|
||||
export interface LocalMeal extends BaseRecord {
|
||||
date: string;
|
||||
mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack';
|
||||
inputType: 'photo' | 'text';
|
||||
description: string;
|
||||
portionSize?: string | null;
|
||||
confidence: number;
|
||||
nutrition?: {
|
||||
calories: number;
|
||||
protein: number;
|
||||
carbohydrates: number;
|
||||
fat: number;
|
||||
fiber: number;
|
||||
sugar: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface LocalGoal extends BaseRecord {
|
||||
dailyCalories: number;
|
||||
dailyProtein?: number | null;
|
||||
dailyCarbs?: number | null;
|
||||
dailyFat?: number | null;
|
||||
dailyFiber?: number | null;
|
||||
}
|
||||
|
||||
export interface LocalFavorite extends BaseRecord {
|
||||
name: string;
|
||||
description: string;
|
||||
mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack';
|
||||
nutrition: {
|
||||
calories: number;
|
||||
protein: number;
|
||||
carbohydrates: number;
|
||||
fat: number;
|
||||
fiber: number;
|
||||
sugar: number;
|
||||
};
|
||||
usageCount: number;
|
||||
}
|
||||
|
||||
// ─── Store ──────────────────────────────────────────────────
|
||||
|
||||
const SYNC_SERVER_URL = import.meta.env.PUBLIC_SYNC_SERVER_URL || 'http://localhost:3050';
|
||||
|
||||
export const nutriphiStore = createLocalStore({
|
||||
appId: 'nutriphi',
|
||||
collections: [
|
||||
{
|
||||
name: 'meals',
|
||||
indexes: ['date', 'mealType', '[date+mealType]'],
|
||||
guestSeed: guestMeals,
|
||||
},
|
||||
{
|
||||
name: 'goals',
|
||||
indexes: [],
|
||||
guestSeed: guestGoals,
|
||||
},
|
||||
{
|
||||
name: 'favorites',
|
||||
indexes: ['mealType', 'usageCount'],
|
||||
},
|
||||
],
|
||||
sync: {
|
||||
serverUrl: SYNC_SERVER_URL,
|
||||
},
|
||||
});
|
||||
|
||||
// Typed collection accessors
|
||||
export const mealCollection = nutriphiStore.collection<LocalMeal>('meals');
|
||||
export const goalCollection = nutriphiStore.collection<LocalGoal>('goals');
|
||||
export const favoriteCollection = nutriphiStore.collection<LocalFavorite>('favorites');
|
||||
|
|
@ -8,9 +8,13 @@
|
|||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { mealsStore } from '$lib/stores/meals.svelte';
|
||||
import { parseMealInput, formatParsedMealPreview } from '$lib/utils/meal-parser';
|
||||
import { SessionExpiredBanner, AuthGate } from '@manacore/shared-auth-ui';
|
||||
import { SessionExpiredBanner, AuthGate, GuestWelcomeModal } from '@manacore/shared-auth-ui';
|
||||
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import { nutriphiStore } from '$lib/data/local-store';
|
||||
let { children } = $props();
|
||||
|
||||
let showGuestWelcome = $state(false);
|
||||
|
||||
// QuickInputBar handlers - search recent meals
|
||||
async function handleSearch(query: string): Promise<QuickInputItem[]> {
|
||||
const q = query.toLowerCase();
|
||||
|
|
@ -38,6 +42,16 @@
|
|||
};
|
||||
}
|
||||
|
||||
async function handleAuthReady() {
|
||||
await nutriphiStore.initialize();
|
||||
if (authStore.isAuthenticated) {
|
||||
nutriphiStore.startSync(() => authStore.getValidToken());
|
||||
}
|
||||
if (!authStore.isAuthenticated && shouldShowGuestWelcome('nutriphi')) {
|
||||
showGuestWelcome = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreate(query: string): Promise<void> {
|
||||
if (!query.trim()) return;
|
||||
const parsed = parseMealInput(query);
|
||||
|
|
@ -60,7 +74,7 @@
|
|||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<AuthGate {authStore} {goto} allowGuest={true}>
|
||||
<AuthGate {authStore} {goto} allowGuest={true} onReady={handleAuthReady}>
|
||||
{#if $i18nLoading}
|
||||
<div class="flex min-h-screen items-center justify-center bg-background">
|
||||
<div
|
||||
|
|
@ -88,4 +102,13 @@
|
|||
{/if}
|
||||
<SessionExpiredBanner locale="de" loginHref="/login" />
|
||||
{/if}
|
||||
|
||||
<GuestWelcomeModal
|
||||
appId="nutriphi"
|
||||
visible={showGuestWelcome}
|
||||
onClose={() => (showGuestWelcome = false)}
|
||||
onLogin={() => goto('/login')}
|
||||
onRegister={() => goto('/register')}
|
||||
locale="de"
|
||||
/>
|
||||
</AuthGate>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue