- Add src/middleware.ts for global route proection
- Whitelist public routes (/, /auth/*, /splits/[slug])
- Add redirect logic to Home page for returning users
- Fix minor lint issues in Home page
- Replace StatsDashboard with new AnalyticsDashboard component
- Add Recharts charts: Category Pie, ABV Area, Age Bar, Top Distilleries Bar, Price vs Quality Scatter
- Update fetching logic to include tasting ratings for analysis
- Enhance UI with new KPI cards and dark mode styling
- Create /admin/sessions page showing all sessions from all users
- Stats: total sessions, active, hosts, participants, tastings
- Filter by host, status (active/ended)
- Show session duration, participant count, tasting count
- Add 'All Sessions' link to admin dashboard
- Replace next/image with native img tags in admin bottles, splits, tastings
- Remove hardcoded Supabase hostname from next.config.mjs
- Native img works with any hostname without config changes on deploy
- Fixed fetch handler that could return undefined instead of Response
- Changed from stale-while-revalidate to network-first with cache fallback
- Always return proper 503 Response when offline and no cache available
- Bump cache version to v21 to force SW update
- Create /admin/bottles page with comprehensive bottle overview
- Show stats: total bottles, total users, avg rating, top distilleries
- AdminBottlesList with search, filter by user/category, sorting
- Display bottle images, ratings, user info, and dates
- Add 'All Bottles' link to admin dashboard
Override Tailwind zinc scale with brighter values for improved text contrast on dark backgrounds. Targets older users who may have difficulty reading gray-on-black text.
- zinc-500: #71717a → #8a8a95 (+20% brightness)
- zinc-600: #52525b → #6b6b75 (+25% brightness)
- zinc-400/700 also adjusted proportionally
- Install @sentry/nextjs
- Add sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts
- Conditional initialization based on GLITCHTIP_DSN env variable
- Add /api/glitchtip-tunnel route to bypass ad blockers
- Update next.config.mjs with withSentryConfig wrapper
- Integrate Sentry.captureException in error.tsx and global-error.tsx
- Support env vars: GLITCHTIP_DSN, NEXT_PUBLIC_GLITCHTIP_DSN, GLITCHTIP_URL, etc.
- Rename 'Events' to 'Tastings' in nav
- Sessions page: Create, list, delete sessions with sticky header
- Buddies page: Add, search, delete buddies with linked/unlinked sections
- Both pages match new home view design language
- Cascade OCR now saves a log entry even when no text is detected
- Logs ocrMethod as 'text_detector' or 'not_supported' for debugging
- Helps identify when browsers block the TextDetector API
- Added public discovery section for active splits on the landing page
- Refactored split detail page for guest support and login redirects
- Extracted SplitCard component for reuse
- Consolidated RLS policies for bottles and tastings to resolve permission errors
- Added unified SQL consolidation script for RLS and naming fixes
- Enhanced service logging for better database error diagnostics
- Removed hardcoded 100 credits from lazy initialization
- Now looks up user's subscription plan and uses monthly_credits
- Set total_purchased to 0 (previously incorrectly set to 100)
- Fallback to 10 credits if no plan found (starter default)
New trigger creates:
- Profile (username from metadata)
- Subscription (starter plan by default)
- Credits (from plan's monthly_credits, not hardcoded)
Includes RLS policies for self-insert fallback
- Simplified trigger to only create profile
- Added exception handling to prevent user creation failures
- Subscription created client-side after successful signup
New component in header shows:
- Subscription plan badge (Starter/Bronze/Silver/Gold with icons)
- AI credits balance with sparkle icon
Also includes SQL migration for user_subscriptions RLS fix
Registration form now includes:
- Username field (required, unique, validated)
- Full name field (optional)
- Auto-validates username format and availability
- Auto-creates 'starter' subscription on signup
Login form unchanged (email + password only)
New: src/lib/distillery-matcher.ts
- normalizeDistillery(): Fuzzy matches AI responses against distilleries.json
- cleanBottleName(): Removes distillery from bottle name to avoid duplication
- normalizeWhiskyData(): Combined helper for both operations
Example transformations:
- 'ARDNAHOE DISTILLERY CO LTD' → 'Ardnahoe'
- 'Laphroaig 10 Year Old' → '10 Year Old' (with distillery in separate field)
Integration:
- gemini-vision.ts now normalizes results after AI response
- Enables consistent distillery names for enrichment cache
Caches AI enrichment results per distillery to save API calls:
- New table: enrichment_cache (distillery, tags, hit_count)
- New service: cache-enrichment.ts (get, save, increment, stats)
- enrich-data.ts checks cache before AI query
- Saves to cache after successful AI response
- Returns cached: true/false flag for transparency
Benefits:
- 0 API cost for repeated distillery scans
- Near-instant response for cached distilleries
- Shared across all users
- Email displayed as user[at]domain with onClick handler
- Phone displayed split with dashes
- All contact info clickable but not easily scraped
- Ready to fill in at src/app/impressum/page.tsx
Onboarding Tutorial:
- 5-step walkthrough for new users
- Welcome, Scan, Taste, Sessions, Ready steps
- Skippable, stores completion in localStorage
- Beautiful full-screen overlay with animations
Empty States:
- SessionList: Visual empty state with icon and description
- BuddyList: Visual empty state with icon and description
- Reusable EmptyState component ready for more usage
Layout: Added OnboardingTutorial and CookieBanner
- Updated profile-actions.ts to use username column
- Updated ProfileForm.tsx to use username
- Updated settings page to pass username
- Matches database schema (profiles.username)
Cookie Banner:
- GDPR-compliant consent banner
- Shows on first visit, stores consent in localStorage
- Two options: Accept All / Only Essential
- Dark theme with smooth animations
Settings Page (/settings):
- Profile form: display name editing
- Password change with validation
- Cookie settings info section
- Privacy links and account info
New files:
- src/hooks/useCookieConsent.ts
- src/components/CookieBanner.tsx
- src/components/ProfileForm.tsx
- src/components/PasswordChangeForm.tsx
- src/services/profile-actions.ts
- src/app/settings/page.tsx
- bulk-scan.ts: Now uses OpenRouter for batch analysis
- scan-label.ts: Now uses OpenRouter with Gemini fallback
- analyze-bottle.ts: Now uses OpenRouter with Gemini fallback
- All AI calls now respect AI_PROVIDER env variable
- Uses Nebius/FP8 provider preferences consistently
- Unified logging: [FunctionName] Using provider: openrouter/gemini
- enrichData now uses same AI provider switch as vision
- Both scan and enrichment use OpenRouter by default
- Added retry logic for rate limits
- Uses Nebius/FP8 provider preferences
- Logs which provider is being used
- Added OPENROUTER_PROVIDER_PREFERENCES config
- Prioritizes Nebius provider for better availability/speed
- Uses FP8 quantization for quality/speed balance
- Falls back to other providers if Nebius unavailable
- Retries up to 3 times with exponential backoff (2s, 4s, 8s)
- Also handles 503 service unavailable errors
- Logs retry attempts for debugging
- Only retries rate limit errors, other errors fail immediately
- Editor opens immediately with placeholder data
- AI analyzes label in background while user can already edit
- Blue 'KI analysiert...' banner shows when AI is still working
- User edits preserved when AI results arrive (dirty field tracking)
- Model/provider shown in admin perf overlay
- Renamed 'CLOUD' to 'AI' in perf display
- Added openrouter.ts with provider configuration
- Default provider: OpenRouter with google/gemma-3-27b-it
- Switch to Gemini via AI_PROVIDER=gemini in .env.local
- Both providers use same credit system
- OpenRouter uses OpenAI-compatible API
To switch providers, set in .env.local:
AI_PROVIDER=openrouter # default
AI_PROVIDER=gemini # Google Gemini