From 8def989ed9e79459c2af102ee3a26725a6e1860c Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 16 Apr 2026 15:18:50 +0200 Subject: [PATCH] chore: global ToastContainer, migrate inline toasts, delete SETUP.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ToastContainer.svelte to (app) layout β€” renders toasts from the central toast.svelte store (stacked, auto-dismiss, color-coded by type). Previously the store existed but had no renderer. Migrate inline toast implementations to the central store: - SyncSection: showToast() β†’ toast.success/error(), strip DOM + CSS - Credits ListView: same migration, remove showToast + inline toast - Profile ListView: same migration, remove showToast + inline toast Delete apps/mana/apps/web/SETUP.md β€” completely outdated (references Supabase, teams, organizations β€” all removed long ago). Real docs live in CLAUDE.md and docs/LOCAL_DEVELOPMENT.md. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/mana/apps/web/SETUP.md | 364 ------------------ .../src/lib/components/ToastContainer.svelte | 85 ++++ .../settings/sections/SyncSection.svelte | 32 +- .../src/lib/modules/credits/ListView.svelte | 63 +-- .../src/lib/modules/profile/ListView.svelte | 40 +- .../apps/web/src/routes/(app)/+layout.svelte | 3 + 6 files changed, 107 insertions(+), 480 deletions(-) delete mode 100644 apps/mana/apps/web/SETUP.md create mode 100644 apps/mana/apps/web/src/lib/components/ToastContainer.svelte diff --git a/apps/mana/apps/web/SETUP.md b/apps/mana/apps/web/SETUP.md deleted file mode 100644 index e26263f92..000000000 --- a/apps/mana/apps/web/SETUP.md +++ /dev/null @@ -1,364 +0,0 @@ -# Mana Web - Setup Guide - -## πŸŽ‰ Project Created Successfully! - -A brand new SvelteKit application has been created in the `mana-web` folder, separate from the existing React Native `mana_app`. - -## βœ… What's Been Implemented - -### Core Infrastructure - -- βœ… SvelteKit 2.x with Svelte 5 (Runes) -- βœ… TypeScript configuration -- βœ… Tailwind CSS styling -- βœ… Supabase authentication integration -- βœ… Server-side hooks for auth management -- βœ… Environment configuration - -### Authentication System - -- βœ… Login page (`/login`) -- βœ… Registration page (`/register`) -- βœ… Protected routes with automatic redirects -- βœ… Server-side session management -- βœ… SSR-safe Supabase client setup - -### Dashboard - -- βœ… Main dashboard with stats display -- βœ… Available mana/credits tracking -- βœ… Organization and team counts -- βœ… Quick action links -- βœ… Responsive design - -### UI Components - -- βœ… Button component (primary, secondary, danger, ghost variants) -- βœ… Card component -- βœ… Input component with validation -- βœ… Responsive navigation with mobile menu - -## πŸ“ Project Structure - -``` -mana-web/ -β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ routes/ -β”‚ β”‚ β”œβ”€β”€ (auth)/ # Public auth routes -β”‚ β”‚ β”‚ β”œβ”€β”€ login/ # Login page -β”‚ β”‚ β”‚ └── register/ # Registration page -β”‚ β”‚ β”œβ”€β”€ (app)/ # Protected app routes -β”‚ β”‚ β”‚ └── dashboard/ # Main dashboard -β”‚ β”‚ β”œβ”€β”€ +layout.svelte # Root layout -β”‚ β”‚ β”œβ”€β”€ +layout.ts # Client-side layout load -β”‚ β”‚ β”œβ”€β”€ +layout.server.ts # Server-side layout load -β”‚ β”‚ └── +page.svelte # Home page (redirects) -β”‚ β”œβ”€β”€ lib/ -β”‚ β”‚ β”œβ”€β”€ components/ -β”‚ β”‚ β”‚ └── ui/ # Reusable UI components -β”‚ β”‚ β”œβ”€β”€ server/ # Server-only utilities -β”‚ β”‚ β”œβ”€β”€ stores/ # Svelte stores -β”‚ β”‚ β”œβ”€β”€ types/ # TypeScript types -β”‚ β”‚ └── utils/ # Utility functions -β”‚ β”œβ”€β”€ hooks.server.ts # Server hooks (auth middleware) -β”‚ β”œβ”€β”€ app.css # Global Tailwind styles -β”‚ β”œβ”€β”€ app.d.ts # TypeScript declarations -β”‚ └── app.html # HTML template -β”œβ”€β”€ static/ # Static assets -β”œβ”€β”€ tests/ # Test files (future) -β”œβ”€β”€ .env # Environment variables -β”œβ”€β”€ .env.example # Environment template -β”œβ”€β”€ svelte.config.js # SvelteKit configuration -β”œβ”€β”€ vite.config.ts # Vite configuration -β”œβ”€β”€ tsconfig.json # TypeScript configuration -└── package.json # Dependencies -``` - -## πŸš€ Getting Started - -### 1. Configure Environment Variables - -Update `.env` with your actual Supabase credentials: - -```bash -# Get these from your Supabase project settings -PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co -PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here -``` - -### 2. Install Dependencies (if not already done) - -```bash -cd mana-web -pnpm install -``` - -### 3. Start Development Server - -```bash -pnpm dev -``` - -Visit `http://localhost:5173` - -### 4. Database Setup - -The app expects the same Supabase database schema as the React Native app. Ensure you have these tables: - -- `profiles` - User profile data -- `organizations` - Organization entities -- `teams` - Team entities -- `team_members` - Team membership -- `user_roles` - Role assignments -- `roles` - Role definitions -- `credit_transactions` - Credit history - -## πŸ“ Next Steps - -### Immediate (Must Do) - -1. **Update .env file** with real Supabase credentials -2. **Test login/registration** flow -3. **Verify database schema** matches expected structure - -### Short-Term Features to Add - -1. **Organizations Management** - - List organizations page - - Organization detail page - - Create organization form - -2. **Teams Management** - - List teams page - - Team detail page with members - - Create team form - - Add/remove team members - -3. **Credit/Mana Transfer** - - Send mana page - - Credit transaction history - - Balance tracking - -4. **Settings Page** - - Profile management - - Theme toggle (light/dark) - - Account settings - -### Medium-Term Enhancements - -1. **Real-time Updates** - - Supabase realtime subscriptions - - Live credit balance updates - - Team activity notifications - -2. **Enhanced UI/UX** - - Loading skeletons - - Error boundaries - - Toast notifications - - Optimistic UI updates - -3. **Testing** - - Vitest unit tests - - Playwright E2E tests - - Component testing - -4. **Performance** - - Image optimization - - Code splitting - - Caching strategies - - Performance monitoring - -## πŸ”§ Available Scripts - -```bash -# Development -pnpm dev # Start dev server - -# Building -pnpm build # Build for production -pnpm preview # Preview production build - -# Type Checking -pnpm check # Run TypeScript checks -pnpm check:watch # Watch mode - -# Code Quality -pnpm format # Format code with Prettier -pnpm lint # Lint code - -# Testing -pnpm test # Run unit tests -pnpm test:ui # Run tests with UI -pnpm test:e2e # Run E2E tests -``` - -## 🎨 Design System - -### Colors - -- Primary: `#0055FF` (blue-600) -- Background Light: `#FFFFFF` -- Background Dark: `#121212` -- Text Light: `#1F2937` -- Text Dark: `#F9FAFB` - -### Components - -All components support: - -- Dark mode (automatic via system preference) -- TypeScript props -- Tailwind CSS classes -- Accessibility features - -### Responsive Breakpoints - -- `sm`: 640px -- `md`: 768px -- `lg`: 1024px -- `xl`: 1280px -- `2xl`: 1536px - -## πŸ” Authentication Flow - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ User visits site β”‚ -β”‚ ↓ β”‚ -β”‚ hooks.server.ts checks session β”‚ -β”‚ ↓ β”‚ -β”‚ Has session? β†’ Go to /dashboard β”‚ -β”‚ ↓ β”‚ -β”‚ No session? β†’ Go to /login β”‚ -β”‚ ↓ β”‚ -β”‚ User logs in β”‚ -β”‚ ↓ β”‚ -β”‚ Supabase creates session β”‚ -β”‚ ↓ β”‚ -β”‚ Redirect to /dashboard β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - -## πŸ“š Key Technologies - -| Technology | Version | Purpose | -| ------------ | ------- | ------------- | -| SvelteKit | 2.48.4 | Web framework | -| Svelte | 5.43.3 | UI components | -| TypeScript | 5.9.3 | Type safety | -| Tailwind CSS | 3.4.18 | Styling | -| Supabase | 2.79.0 | Backend/Auth | -| Vite | 6.4.1 | Build tool | -| Playwright | 1.56.1 | E2E testing | -| Vitest | 3.2.4 | Unit testing | - -## πŸ†š Comparison: React Native vs SvelteKit - -| Feature | React Native (mana_app) | SvelteKit (mana-web) | -| ---------- | ----------------------- | -------------------- | -| Platform | Mobile (iOS/Android) | Web (Browser) | -| Routing | Expo Router | File-based routing | -| State | useState/Context | Svelte Runes/$state | -| Styling | NativeWind | Tailwind CSS | -| Auth | Supabase | Supabase (same) | -| Database | Supabase | Supabase (same) | -| Build | Expo | Vite | -| Deployment | App Stores | Vercel/Netlify | - -## 🚨 Common Issues & Solutions - -### Issue: Type errors on first run - -**Solution**: Run `pnpm run check` to sync types - -### Issue: Port already in use - -**Solution**: Change port in `vite.config.ts` or kill process on port 5173 - -### Issue: Supabase auth not working - -**Solution**: - -1. Check `.env` variables are correct -2. Verify Supabase project is active -3. Check browser console for errors - -### Issue: Dark mode not switching - -**Solution**: Check system dark mode preference, or implement manual toggle - -## πŸ“– Documentation & Resources - -### SvelteKit - -- [SvelteKit Docs](https://svelte.dev/docs/kit) -- [Svelte 5 Tutorial](https://svelte.dev/tutorial/svelte/welcome) - -### Supabase - -- [Supabase Docs](https://supabase.com/docs) -- [Supabase Auth](https://supabase.com/docs/guides/auth) - -### Tailwind CSS - -- [Tailwind Docs](https://tailwindcss.com/docs) -- [Tailwind UI](https://tailwindui.com) - -## πŸ’‘ Development Tips - -1. **Use Runes ($state, $derived, $effect)** - Modern Svelte 5 reactivity -2. **Server-side data loading** - Use `+page.server.ts` for secure data fetching -3. **Progressive enhancement** - Forms work without JavaScript -4. **Type safety** - Leverage generated types from SvelteKit -5. **Component composition** - Use snippets for flexible component APIs - -## ✨ What Makes This Different - -Unlike a migration, this is a **clean, modern implementation** that: - -- βœ… Follows 2025 SvelteKit best practices -- βœ… Uses latest Svelte 5 features (Runes) -- βœ… Implements SSR-safe authentication -- βœ… Provides better developer experience -- βœ… Enables easy deployment to web platforms -- βœ… Maintains compatibility with same backend - -## 🎯 Success Criteria - -The project is ready for development when: - -- [x] Project structure created -- [x] Dependencies installed -- [x] TypeScript configured -- [x] Tailwind CSS working -- [x] Authentication pages created -- [x] Dashboard implemented -- [ ] Environment variables configured with real credentials -- [ ] Database schema verified -- [ ] First successful login completed - -## 🀝 Contributing - -When adding new features: - -1. Create components in `src/lib/components/` -2. Add pages in appropriate route groups -3. Use server-side data loading for security -4. Follow existing code patterns -5. Test in both light and dark modes -6. Add TypeScript types - -## πŸ“ž Support - -For issues or questions: - -- Check SvelteKit docs -- Review Supabase documentation -- Check browser console for errors -- Verify environment variables - ---- - -**Status**: βœ… Core application structure complete and ready for feature development! - -**Next**: Configure Supabase credentials and start building organization/team management features. diff --git a/apps/mana/apps/web/src/lib/components/ToastContainer.svelte b/apps/mana/apps/web/src/lib/components/ToastContainer.svelte new file mode 100644 index 000000000..c0d481427 --- /dev/null +++ b/apps/mana/apps/web/src/lib/components/ToastContainer.svelte @@ -0,0 +1,85 @@ + + +{#if toast.toasts.length > 0} +
+ {#each toast.toasts as t (t.id)} +
+ {t.message} + +
+ {/each} +
+{/if} + + diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/SyncSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/SyncSection.svelte index b78f725ae..19d0bff22 100644 --- a/apps/mana/apps/web/src/lib/components/settings/sections/SyncSection.svelte +++ b/apps/mana/apps/web/src/lib/components/settings/sections/SyncSection.svelte @@ -3,6 +3,7 @@ import { syncBilling } from '$lib/stores/sync-billing.svelte'; import { creditsService, type CreditBalance } from '$lib/api/credits'; import type { BillingInterval } from '$lib/api/sync'; + import { toast } from '$lib/stores/toast.svelte'; import { onMount } from 'svelte'; const SYNC_PRICES: Record = { @@ -22,10 +23,6 @@ let error = $state(null); let selectedInterval = $state('monthly'); - // Toast - let toastMessage = $state(null); - let toastType = $state<'success' | 'error'>('success'); - onMount(async () => { await Promise.all([syncBilling.load(), loadBalance()]); selectedInterval = syncBilling.interval; @@ -45,10 +42,10 @@ try { await syncBilling.activate(selectedInterval); await loadBalance(); - showToast('Cloud Sync aktiviert!', 'success'); + toast.success('Cloud Sync aktiviert!'); } catch (e) { error = e instanceof Error ? e.message : 'Aktivierung fehlgeschlagen'; - showToast(error, 'error'); + toast.error(error); } finally { loading = false; } @@ -63,7 +60,7 @@ showToast('Cloud Sync deaktiviert', 'success'); } catch (e) { error = e instanceof Error ? e.message : 'Deaktivierung fehlgeschlagen'; - showToast(error, 'error'); + toast.error(error); } finally { loading = false; } @@ -78,7 +75,7 @@ showToast(`Intervall auf ${INTERVAL_LABELS[selectedInterval]} geΓ€ndert`, 'success'); } catch (e) { error = e instanceof Error ? e.message : 'Γ„nderung fehlgeschlagen'; - showToast(error, 'error'); + toast.error(error); } finally { loading = false; } @@ -95,14 +92,6 @@ year: 'numeric', }); } - - function showToast(message: string, type: 'success' | 'error') { - toastMessage = message; - toastType = type; - setTimeout(() => { - toastMessage = null; - }, 4000); - }
@@ -267,14 +256,3 @@
{/if} - - -{#if toastMessage} -
- {toastMessage} -
-{/if} diff --git a/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte b/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte index 1eed84f96..8985eec34 100644 --- a/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte @@ -34,6 +34,7 @@ } from '@mana/credits'; import { ManaEvents } from '@mana/shared-utils/analytics'; import { authStore } from '$lib/stores/auth.svelte'; + import { toast } from '$lib/stores/toast.svelte'; // ── Credits state ────────────────────────────────────── let balance = $state(null); @@ -58,8 +59,6 @@ ); let costFilter = $state<'all' | 'ai' | 'premium'>('all'); let processingPackageId = $state(null); - let toastMessage = $state(null); - let toastType = $state<'success' | 'error'>('success'); // ── Derived pricing data ─────────────────────────────── const allOperations = $derived( @@ -139,10 +138,10 @@ const canceled = params.get('canceled'); if (success === 'true') { - showToast('Zahlung erfolgreich!', 'success'); + toast.success('\1'); history.replaceState({}, '', '/'); } else if (canceled === 'true') { - showToast('Kauf wurde abgebrochen', 'error'); + toast.error('Kauf wurde abgebrochen'); history.replaceState({}, '', '/'); } @@ -251,10 +250,7 @@ const result = await creditsService.initiatePurchase(pkg.id); window.location.href = result.checkoutUrl; } catch (e) { - showToast( - e instanceof Error ? e.message : 'Fehler beim Erstellen der Checkout-Session', - 'error' - ); + toast.error(e instanceof Error ? e.message : 'Fehler beim Erstellen der Checkout-Session'); } finally { processingPackageId = null; } @@ -285,7 +281,7 @@ const { url } = await subscriptionsService.createCheckout(plan.id, billingInterval); window.location.href = url; } catch (e) { - showToast(e instanceof Error ? e.message : 'Fehler beim Checkout', 'error'); + toast.error(e instanceof Error ? e.message : 'Fehler beim Checkout'); } finally { processingPlanId = null; } @@ -297,7 +293,7 @@ const { url } = await subscriptionsService.openPortal(); window.location.href = url; } catch (e) { - showToast(e instanceof Error ? e.message : 'Fehler beim Billing-Portal', 'error'); + toast.error(e instanceof Error ? e.message : 'Fehler beim Billing-Portal'); } finally { openingPortal = false; } @@ -308,10 +304,10 @@ cancelingSub = true; try { await subscriptionsService.cancelSubscription(); - showToast('Abonnement wird zum Ende der Laufzeit gekΓΌndigt', 'success'); + toast.success('\1'); await loadData(); } catch (e) { - showToast(e instanceof Error ? e.message : 'Fehler beim KΓΌndigen', 'error'); + toast.error(e instanceof Error ? e.message : 'Fehler beim KΓΌndigen'); } finally { cancelingSub = false; } @@ -321,20 +317,14 @@ reactivatingSub = true; try { await subscriptionsService.reactivateSubscription(); - showToast('Abonnement wurde reaktiviert', 'success'); + toast.success('\1'); await loadData(); } catch (e) { - showToast(e instanceof Error ? e.message : 'Fehler beim Reaktivieren', 'error'); + toast.error(e instanceof Error ? e.message : 'Fehler beim Reaktivieren'); } finally { reactivatingSub = false; } } - - function showToast(message: string, type: 'success' | 'error') { - toastMessage = message; - toastType = type; - setTimeout(() => (toastMessage = null), 4000); - }
@@ -714,10 +704,6 @@ {/if}
-{#if toastMessage} -
{toastMessage}
-{/if} - diff --git a/apps/mana/apps/web/src/lib/modules/profile/ListView.svelte b/apps/mana/apps/web/src/lib/modules/profile/ListView.svelte index d52300444..578ada2c2 100644 --- a/apps/mana/apps/web/src/lib/modules/profile/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/profile/ListView.svelte @@ -5,6 +5,7 @@ import { onMount } from 'svelte'; import { profileService, type UserProfile as ApiUserProfile } from '$lib/api/profile'; import { authStore } from '$lib/stores/auth.svelte'; + import { toast } from '$lib/stores/toast.svelte'; import { goto } from '$app/navigation'; import { EditProfileModal, @@ -24,7 +25,6 @@ let showEditModal = $state(false); let showPasswordModal = $state(false); let showDeleteModal = $state(false); - let toastMessage = $state(null); onMount(async () => { try { @@ -45,23 +45,18 @@ function handleProfileUpdate(user: ApiUserProfile) { apiProfile = user; - showToast('Profil erfolgreich aktualisiert'); + toast.success('Profil erfolgreich aktualisiert'); } function handlePasswordChange() { - showToast('Passwort erfolgreich geΓ€ndert'); + toast.success('Passwort erfolgreich geΓ€ndert'); } async function handleAccountDeleted() { - showToast('Konto wird gelΓΆscht...'); + toast.info('Konto wird gelΓΆscht...'); await authStore.signOut(); goto('/login'); } - - function showToast(message: string) { - toastMessage = message; - setTimeout(() => (toastMessage = null), 3000); - }
@@ -155,10 +150,6 @@ onSuccess={handleAccountDeleted} /> -{#if toastMessage} -
{toastMessage}
-{/if} - diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index 7aa625230..733c96375 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import type { Component, Snippet } from 'svelte'; + import ToastContainer from '$lib/components/ToastContainer.svelte'; import { onDestroy, setContext, tick } from 'svelte'; import { createReminderScheduler } from '@mana/shared-stores'; import { todoReminderSource } from '$lib/modules/todo/reminder-source'; @@ -1063,6 +1064,8 @@ {/if} + +