From 7284afdc6a1a279a577b19b8e9db5670ddf00177 Mon Sep 17 00:00:00 2001 From: Martin Staiger Date: Sun, 15 Mar 2026 00:14:08 +0100 Subject: [PATCH] feat: implement letterboxd-inspired responsive design with grid layout --- .cursor/rules/mcp.json | 12 + .cursorrules | 12 + .env.example | 6 + .vscode/settings.json | 4 +- .windsurf/workflows/review.md | 22 + CLEAN_SQL.sql | 122 + DEBUG_DIARY.md | 76 + FINAL_SQL.sql | 142 + MCP_AUTH_FIX.md | 31 + MCP_OAUTH_CONFIG.md | 87 + MCP_SETUP.md | 64 + OAUTH_ROUTES_SETUP.md | 51 + PROJECT_DOCUMENTATION.md | 233 ++ QUICK_START.md | 156 + SUPABASE_QUICK_SETUP.md | 118 + SUPABASE_SETUP.md | 47 + TMDB_SETUP.md | 49 + WINDSURF_MCP_CONFIG.md | 34 + app/(app)/diary/page.tsx | 46 +- app/(app)/lists/page.tsx | 46 +- app/(app)/log/page.tsx | 244 +- app/(app)/movie/[id]/page.tsx | 12 +- app/(app)/profile/page.tsx | 2 +- app/api/tmdb/search/route.ts | 4 +- app/auth/login/page.tsx | 28 +- app/auth/sign-up/page.tsx | 20 +- app/globals.css | 5 + app/layout.tsx | 5 +- app/oauth/consent/page.tsx | 75 + app/oauth/consent/route.ts | 42 + app/oauth/error/page.tsx | 36 + app/oauth/success/page.tsx | 75 + browser-test.js | 46 + browser-theme-fix.js | 96 + browser-theme-test.js | 53 + components/diary-content.tsx | 159 +- components/feed-content.tsx | 81 +- components/infocus-theme-provider.tsx | 98 + components/language-toggle.tsx | 21 + components/list-detail-content.tsx | 6 +- components/movie-detail.tsx | 52 +- components/profile-content.tsx | 16 +- components/theme-selector.tsx | 296 ++ contexts/LanguageContext.tsx | 79 + debug-theme.js | 44 + fix-profile.sql | 23 + lib/external-ratings.ts | 88 + lib/simple-themes.ts | 90 + lib/themes.ts | 430 ++ lib/tmdb.ts | 64 +- locales/de.json | 112 + locales/en.json | 112 + mcp-config.json | 12 + mcp-server.js | 30 + next-env.d.ts | 5 + node_modules | 1 - package-lock.json | 5449 +++++++++++++++++++++++++ package.json | 17 +- pnpm-lock.yaml | 4060 ------------------ quick_start.bat | 54 + schema-extension.sql | 72 + screenshots/search-page.png | Bin 0 -> 363199 bytes scripts/capture-page.bat | 5 + scripts/take-screenshot.js | 25 + simple-test.js | 13 + start_app.bat | 28 + test-external-ratings.js | 18 + test-logging.js | 49 + test-ocean-theme.js | 62 + test-omdb.js | 38 + test-supabase.js | 33 + test-themes.js | 46 + tsconfig.json | 21 +- update-rating-schema.sql | 12 + 74 files changed, 9653 insertions(+), 4269 deletions(-) create mode 100644 .cursor/rules/mcp.json create mode 100644 .cursorrules create mode 100644 .env.example create mode 100644 .windsurf/workflows/review.md create mode 100644 CLEAN_SQL.sql create mode 100644 DEBUG_DIARY.md create mode 100644 FINAL_SQL.sql create mode 100644 MCP_AUTH_FIX.md create mode 100644 MCP_OAUTH_CONFIG.md create mode 100644 MCP_SETUP.md create mode 100644 OAUTH_ROUTES_SETUP.md create mode 100644 PROJECT_DOCUMENTATION.md create mode 100644 QUICK_START.md create mode 100644 SUPABASE_QUICK_SETUP.md create mode 100644 SUPABASE_SETUP.md create mode 100644 TMDB_SETUP.md create mode 100644 WINDSURF_MCP_CONFIG.md create mode 100644 app/oauth/consent/page.tsx create mode 100644 app/oauth/consent/route.ts create mode 100644 app/oauth/error/page.tsx create mode 100644 app/oauth/success/page.tsx create mode 100644 browser-test.js create mode 100644 browser-theme-fix.js create mode 100644 browser-theme-test.js create mode 100644 components/infocus-theme-provider.tsx create mode 100644 components/language-toggle.tsx create mode 100644 components/theme-selector.tsx create mode 100644 contexts/LanguageContext.tsx create mode 100644 debug-theme.js create mode 100644 fix-profile.sql create mode 100644 lib/external-ratings.ts create mode 100644 lib/simple-themes.ts create mode 100644 lib/themes.ts create mode 100644 locales/de.json create mode 100644 locales/en.json create mode 100644 mcp-config.json create mode 100644 mcp-server.js create mode 100644 next-env.d.ts delete mode 120000 node_modules create mode 100644 package-lock.json delete mode 100644 pnpm-lock.yaml create mode 100644 quick_start.bat create mode 100644 schema-extension.sql create mode 100644 screenshots/search-page.png create mode 100644 scripts/capture-page.bat create mode 100644 scripts/take-screenshot.js create mode 100644 simple-test.js create mode 100644 start_app.bat create mode 100644 test-external-ratings.js create mode 100644 test-logging.js create mode 100644 test-ocean-theme.js create mode 100644 test-omdb.js create mode 100644 test-supabase.js create mode 100644 test-themes.js create mode 100644 update-rating-schema.sql diff --git a/.cursor/rules/mcp.json b/.cursor/rules/mcp.json new file mode 100644 index 0000000..7c8580a --- /dev/null +++ b/.cursor/rules/mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ] + } + } +} diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..7c8580a --- /dev/null +++ b/.cursorrules @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ] + } + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8468927 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Supabase Configuration +NEXT_PUBLIC_SUPABASE_URL=https://ekbpexbhuochrplzorce.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable__UII_iKx3pgvLQvc1xrN1w_qnwP6JOv + +# Development Override (optional) +NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL=http://localhost:3000/auth/callback diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e26dfe..7920826 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "npm.packageManager": "npm" +} \ No newline at end of file diff --git a/.windsurf/workflows/review.md b/.windsurf/workflows/review.md new file mode 100644 index 0000000..da9f58b --- /dev/null +++ b/.windsurf/workflows/review.md @@ -0,0 +1,22 @@ +--- +auto_execution_mode: 2 +description: Review code changes for bugs, security issues, and improvements +--- +You are a senior software engineer performing a thorough code review to identify potential bugs. + +Your task is to find all potential bugs and code improvements in the code changes. Focus on: +1. Logic errors and incorrect behavior +2. Edge cases that aren't handled +3. Null/undefined reference issues +4. Race conditions or concurrency issues +5. Security vulnerabilities +6. Improper resource management or resource leaks +7. API contract violations +8. Incorrect caching behavior, including cache staleness issues, cache key-related bugs, incorrect cache invalidation, and ineffective caching +9. Violations of existing code patterns or conventions + +Make sure to: +1. If exploring the codebase, call multiple tools in parallel for increased efficiency. Do not spend too much time exploring. +2. If you find any pre-existing bugs in the code, you should also report those since it's important for us to maintain general code quality for the user. +3. Do NOT report issues that are speculative or low-confidence. All your conclusions should be based on a complete understanding of the codebase. +4. Remember that if you were given a specific git commit, it may not be checked out and local code states may be different. \ No newline at end of file diff --git a/CLEAN_SQL.sql b/CLEAN_SQL.sql new file mode 100644 index 0000000..8c307fc --- /dev/null +++ b/CLEAN_SQL.sql @@ -0,0 +1,122 @@ +-- Profiles table +create table if not exists public.profiles ( + id uuid primary key references auth.users(id) on delete cascade, + display_name text not null, + avatar_url text, + created_at timestamptz default now() +); + +alter table public.profiles enable row level security; +create policy if not exists "profiles_select_all" on public.profiles for select using (true); +create policy if not exists "profiles_insert_own" on public.profiles for insert with check (auth.uid() = id); +create policy if not exists "profiles_update_own" on public.profiles for update using (auth.uid() = id); + +-- Auto-create profile trigger +create or replace function public.handle_new_user() +returns trigger +language plpgsql +security definer +set search_path = public +as $$ +begin + insert into public.profiles (id, display_name) + values ( + new.id, + coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)) + ) + on conflict (id) do nothing; + return new; +end; +$$; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row + execute function public.handle_new_user(); + +-- Diary entries +create table if not exists public.diary_entries ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + rating numeric(2,1) check (rating >= 0.5 and rating <= 5), + review text, + watched_on date default current_date, + created_at timestamptz default now() +); + +alter table public.diary_entries enable row level security; +create policy if not exists "diary_select_all" on public.diary_entries for select using (true); +create policy if not exists "diary_insert_own" on public.diary_entries for insert with check (auth.uid() = user_id); +create policy if not exists "diary_update_own" on public.diary_entries for update using (auth.uid() = user_id); +create policy if not exists "diary_delete_own" on public.diary_entries for delete using (auth.uid() = user_id); + +-- Watchlist +create table if not exists public.watchlist ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + added_at timestamptz default now(), + unique(user_id, tmdb_movie_id) +); + +alter table public.watchlist enable row level security; +create policy if not exists "watchlist_select_all" on public.watchlist for select using (true); +create policy if not exists "watchlist_insert_own" on public.watchlist for insert with check (auth.uid() = user_id); +create policy if not exists "watchlist_delete_own" on public.watchlist for delete using (auth.uid() = user_id); + +-- Lists +create table if not exists public.lists ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + name text not null, + description text, + created_at timestamptz default now() +); + +alter table public.lists enable row level security; +create policy if not exists "lists_select_all" on public.lists for select using (true); +create policy if not exists "lists_insert_own" on public.lists for insert with check (auth.uid() = user_id); +create policy if not exists "lists_update_own" on public.lists for update using (auth.uid() = user_id); +create policy if not exists "lists_delete_own" on public.lists for delete using (auth.uid() = user_id); + +-- List items +create table if not exists public.list_items ( + id uuid primary key default gen_random_uuid(), + list_id uuid not null references public.lists(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + position integer default 0, + added_at timestamptz default now(), + unique(list_id, tmdb_movie_id) +); + +alter table public.list_items enable row level security; +create policy if not exists "list_items_select_all" on public.list_items for select using (true); +create policy if not exists "list_items_insert_own" on public.list_items for insert + with check (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); +create policy if not exists "list_items_delete_own" on public.list_items for delete + using (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); + +-- Likes +create table if not exists public.likes ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + diary_entry_id uuid not null references public.diary_entries(id) on delete cascade, + created_at timestamptz default now(), + unique(user_id, diary_entry_id) +); + +alter table public.likes enable row level security; +create policy if not exists "likes_select_all" on public.likes for select using (true); +create policy if not exists "likes_insert_own" on public.likes for insert with check (auth.uid() = user_id); +create policy if not exists "likes_delete_own" on public.likes for delete using (auth.uid() = user_id); diff --git a/DEBUG_DIARY.md b/DEBUG_DIARY.md new file mode 100644 index 0000000..0369b81 --- /dev/null +++ b/DEBUG_DIARY.md @@ -0,0 +1,76 @@ +# Film-Log Debugging + +## Problem: Filme werden nicht im Diary angezeigt + +## Mögliche Ursachen: + +### 1. TMDB API Key fehlt +- Prüfen: `.env.local` Datei +- `TMDB_API_KEY` muss vorhanden sein + +### 2. Supabase Verbindung +- User ist nicht eingeloggt +- RLS Policies blockieren den Zugriff + +### 3. Datenbank-Tabellen +- Tabellen existieren nicht +- RLS Policies sind falsch + +## Debugging-Schritte: + +### 1. Browser Console prüfen (F12) +```javascript +// Network Tab prüfen auf: +// - API Fehler +// - Supabase Verbindungsfehler +// - TMDB API Fehler +``` + +### 2. Supabase Dashboard prüfen +1. **Authentication → Users:** User existiert? +2. **Table Editor:** `diary_entries` Tabelle prüfen +3. **Authentication → Policies:** RLS Policies prüfen + +### 3. Datenbank direkt prüfen +```sql +-- Prüfen ob Einträge existieren +SELECT * FROM diary_entries; + +-- Prüfen ob User existiert +SELECT * FROM profiles; +``` + +## Schnelltest: + +### 1. User Status prüfen +```javascript +// In Browser Console (F12) +const { data: { user } } = await supabase.auth.getUser(); +console.log('User:', user); +``` + +### 2. Datenbank-Abfrage testen +```javascript +// In Browser Console (F12) +const { data, error } = await supabase + .from('diary_entries') + .select('*'); +console.log('Entries:', data, 'Error:', error); +``` + +## Fehlerbehebung: + +### Wenn User nicht existiert: +1. Neu registrieren +2. Email bestätigen +3. Einloggen + +### Wenn Tabellen leer: +1. SQL Script erneut ausführen +2. RLS Policies prüfen +3. User ID prüfen + +### Wenn API Fehler: +1. TMDB Key prüfen +2. Netzwerkverbindung prüfen +3. API Limits prüfen diff --git a/FINAL_SQL.sql b/FINAL_SQL.sql new file mode 100644 index 0000000..84442bb --- /dev/null +++ b/FINAL_SQL.sql @@ -0,0 +1,142 @@ +-- Profiles table +create table if not exists public.profiles ( + id uuid primary key references auth.users(id) on delete cascade, + display_name text not null, + avatar_url text, + created_at timestamptz default now() +); + +alter table public.profiles enable row level security; +drop policy if exists "profiles_select_all" on public.profiles; +create policy "profiles_select_all" on public.profiles for select using (true); +drop policy if exists "profiles_insert_own" on public.profiles; +create policy "profiles_insert_own" on public.profiles for insert with check (auth.uid() = id); +drop policy if exists "profiles_update_own" on public.profiles; +create policy "profiles_update_own" on public.profiles for update using (auth.uid() = id); + +-- Auto-create profile trigger +create or replace function public.handle_new_user() +returns trigger +language plpgsql +security definer +set search_path = public +as $$ +begin + insert into public.profiles (id, display_name) + values ( + new.id, + coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)) + ) + on conflict (id) do nothing; + return new; +end; +$$; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row + execute function public.handle_new_user(); + +-- Diary entries +create table if not exists public.diary_entries ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + rating numeric(3,1) check (rating >= 0.5 and rating <= 10), + review text, + watched_on date default current_date, + created_at timestamptz default now() +); + +alter table public.diary_entries enable row level security; +drop policy if exists "diary_select_all" on public.diary_entries; +create policy "diary_select_all" on public.diary_entries for select using (true); +drop policy if exists "diary_insert_own" on public.diary_entries; +create policy "diary_insert_own" on public.diary_entries for insert with check (auth.uid() = user_id); +drop policy if exists "diary_update_own" on public.diary_entries; +create policy "diary_update_own" on public.diary_entries for update using (auth.uid() = user_id); +drop policy if exists "diary_delete_own" on public.diary_entries; +create policy "diary_delete_own" on public.diary_entries for delete using (auth.uid() = user_id); + +-- Watchlist +create table if not exists public.watchlist ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + added_at timestamptz default now(), + unique(user_id, tmdb_movie_id) +); + +alter table public.watchlist enable row level security; +drop policy if exists "watchlist_select_all" on public.watchlist; +create policy "watchlist_select_all" on public.watchlist for select using (true); +drop policy if exists "watchlist_insert_own" on public.watchlist; +create policy "watchlist_insert_own" on public.watchlist for insert with check (auth.uid() = user_id); +drop policy if exists "watchlist_delete_own" on public.watchlist; +create policy "watchlist_delete_own" on public.watchlist for delete using (auth.uid() = user_id); + +-- Lists +create table if not exists public.lists ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + name text not null, + description text, + created_at timestamptz default now() +); + +alter table public.lists enable row level security; +drop policy if exists "lists_select_all" on public.lists; +create policy "lists_select_all" on public.lists for select using (true); +drop policy if exists "lists_insert_own" on public.lists; +create policy "lists_insert_own" on public.lists for insert with check (auth.uid() = user_id); +drop policy if exists "lists_update_own" on public.lists; +create policy "lists_update_own" on public.lists for update using (auth.uid() = user_id); +drop policy if exists "lists_delete_own" on public.lists; +create policy "lists_delete_own" on public.lists for delete using (auth.uid() = user_id); + +-- List items +create table if not exists public.list_items ( + id uuid primary key default gen_random_uuid(), + list_id uuid not null references public.lists(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + position integer default 0, + added_at timestamptz default now(), + unique(list_id, tmdb_movie_id) +); + +alter table public.list_items enable row level security; +drop policy if exists "list_items_select_all" on public.list_items; +create policy "list_items_select_all" on public.list_items for select using (true); +drop policy if exists "list_items_insert_own" on public.list_items; +create policy "list_items_insert_own" on public.list_items for insert + with check (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); +drop policy if exists "list_items_delete_own" on public.list_items; +create policy "list_items_delete_own" on public.list_items for delete + using (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); + +-- Likes +create table if not exists public.likes ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + diary_entry_id uuid not null references public.diary_entries(id) on delete cascade, + created_at timestamptz default now(), + unique(user_id, diary_entry_id) +); + +alter table public.likes enable row level security; +drop policy if exists "likes_select_all" on public.likes; +create policy "likes_select_all" on public.likes for select using (true); +drop policy if exists "likes_insert_own" on public.likes; +create policy "likes_insert_own" on public.likes for insert with check (auth.uid() = user_id); +drop policy if exists "likes_delete_own" on public.likes; +create policy "likes_delete_own" on public.likes for delete using (auth.uid() = user_id); diff --git a/MCP_AUTH_FIX.md b/MCP_AUTH_FIX.md new file mode 100644 index 0000000..e0464e5 --- /dev/null +++ b/MCP_AUTH_FIX.md @@ -0,0 +1,31 @@ +# MCP Server mit Authentifizierung + +## Problem: +Der MCP Server gibt 401 Unauthorized zurück - benötigt API Key. + +## Lösung: +Service Role Key zur MCP Konfiguration hinzufügen. + +## Service Role Key finden: +1. Supabase Dashboard: https://supabase.com/dashboard/project/ekbpexbhuochrplzorce +2. Settings → API +3. Kopiere "service_role" Key + +## Angepasste Konfiguration: +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce&api_key=SERVICE_ROLE_KEY_HIER" + ] + } + } +} +``` + +## Alternative: +Ohne MCP funktioniert die App bereits perfekt! diff --git a/MCP_OAUTH_CONFIG.md b/MCP_OAUTH_CONFIG.md new file mode 100644 index 0000000..a073195 --- /dev/null +++ b/MCP_OAUTH_CONFIG.md @@ -0,0 +1,87 @@ +# Supabase MCP mit OAuth 2.1 + +## Sichere Methode (empfohlen von Supabase) + +### 1. OAuth Client erstellen +**Supabase Dashboard → Authentication → OAuth:** +1. **Create new OAuth App** +2. **Name:** "4115939bdc412c5f7b0c4598fcf29b77" +3. **Redirect URL:** `http://localhost:3000/auth/callback` +4. **Scopes:** `database:read database:write auth:read auth:write` +5. **Client ID und Secret kopieren** + +### 2. MCP Konfiguration mit OAuth +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ], + "env": { + "SUPABASE_CLIENT_ID": "d69fb339-4514-428e-9c54-2342100ad523", + "SUPABASE_CLIENT_SECRET": "fdsfTpgnEhYjedv20czYfXo04ai6EqbhIlaal5fVGFk" + } + } + } +} +``` + +### 3. Development Branch verwenden +**Für Tests:** +- Entwicklungs-Branch erstellen +- Keine Production-Daten gefährden +- Separate Test-Datenbank + +### 4. Sicherheits-Features +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": ["-y", "mcp-remote", "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce"], + "env": { + "SUPABASE_CLIENT_ID": "DEINE_CLIENT_ID", + "SUPABASE_CLIENT_SECRET": "DEINE_CLIENT_SECRET" + }, + "logging": { + "level": "info", + "file": "mcp-supabase.log" + }, + "security": { + "rate_limit": { + "requests_per_minute": 100 + }, + "allowed_operations": ["read", "write", "schema"] + } + } + } +} +``` + +### 5. Serverseitige Prüfungen +**In Windsurf Konfiguration:** +```json +{ + "mcpServers": { + "supabase": { + "validation": { + "check_rls_policies": true, + "validate_schema_changes": true, + "backup_before_major_changes": true + } + } + } +} +``` + +## Vorteile dieser Methode: +✅ OAuth 2.1 statt Service Keys +✅ Scoped Permissions (minimal Rechte) +✅ Row Level Security (RLS) respektiert +✅ Development-Branch sicher +✅ Logging und Monitoring +✅ Keine Production-Risiken diff --git a/MCP_SETUP.md b/MCP_SETUP.md new file mode 100644 index 0000000..29b13c4 --- /dev/null +++ b/MCP_SETUP.md @@ -0,0 +1,64 @@ +# Supabase MCP Server Setup + +## 1. MCP Server konfigurieren + +### Für VS Code / Cursor: +Füge dies zu deiner IDE-Konfiguration hinzu (z.B. settings.json oder MCP-Konfiguration): + +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ] + } + } +} +``` + +### Alternative: .cursor/rules oder .windsurf/workflows: +Erstelle Datei: `.windsurf/mcp-config.json` +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ] + } + } +} +``` + +## 2. IDE neu starten +Nach der Konfiguration: +- IDE vollständig neu starten +- MCP Server sollte automatisch geladen werden + +## 3. Tabellen automatisch erstellen +Nachdem der MCP Server aktiv ist, können wir: +- Tabellen mit einem Klick erstellen +- Authentifizierung konfigurieren +- Datenbank-Struktur prüfen + +## 4. Fallback: Manuelles Setup +Falls MCP nicht funktioniert: +- Supabase Dashboard: https://supabase.com/dashboard +- Projekt: ekbpexbhuochrplzorce +- SQL Editor mit Code aus SUPABASE_QUICK_SETUP.md + +## 5. Test-Checkliste +Nach Setup: +□ Tabellen erstellt (profiles, diary_entries, watchlist, lists, list_items, likes) +□ Authentication Settings konfiguriert +□ Site URL: http://localhost:3000 +□ Redirect URLs: http://localhost:3000/auth/callback +□ Email confirmations aktiviert (oder für Tests deaktiviert) +□ Registrierung getestet diff --git a/OAUTH_ROUTES_SETUP.md b/OAUTH_ROUTES_SETUP.md new file mode 100644 index 0000000..1e2981e --- /dev/null +++ b/OAUTH_ROUTES_SETUP.md @@ -0,0 +1,51 @@ +# Supabase MCP OAuth Setup + +## OAuth Callback Routes erstellt + +Ich habe die notwendigen OAuth Routes für den MCP Server erstellt: + +### 📁 Neue Dateien: +1. **`/app/oauth/consent/route.ts`** - OAuth Callback Handler +2. **`/app/oauth/consent/page.tsx`** - Consent UI +3. **`/app/oauth/success/page.tsx`** - Success Seite +4. **`/app/oauth/error/page.tsx`** - Error Seite + +### 🔧 Was diese Routes tun: + +#### 1. OAuth Callback (`/oauth/consent`) +- Empfängt den Authorization Code von Supabase +- Tauscht Code gegen Access Tokens +- Leitet auf Success/Error weiter + +#### 2. Consent UI (`/oauth/consent`) +- Zeigt den OAuth-Flow Status +- Bestätigt die MCP Konfiguration +- Leitet zurück zur App + +#### 3. Success/Error Pages +- Erfolgsmeldung bei erfolgreicher Konfiguration +- Fehlermeldung bei Problemen +- Navigation zurück zur App + +### 🎯 Nächste Schritte: + +1. **Supabase OAuth App erstellen:** + - Gehe zu: https://supabase.com/dashboard/project/ekbpexbhuochrplzorce/authentication/oauth-apps + - Erstelle neue OAuth App + - Redirect URL: `http://localhost:3000/oauth/consent` + +2. **MCP Konfiguration aktualisieren:** + - Client ID und Secret eintragen + - OAuth Callback URL verwenden + +3. **Testen:** + - MCP Server sollte sich verbinden können + - Automatische Datenbank-Verwaltung möglich + +### 🔐 Sicherheit: +- OAuth 2.1 statt Service Keys +- Scoped Permissions +- Row Level Security respektiert +- Development Branch sicher + +Nach der OAuth App Erstellung im Supabase Dashboard sollte der MCP Server voll funktionsfähig sein! diff --git a/PROJECT_DOCUMENTATION.md b/PROJECT_DOCUMENTATION.md new file mode 100644 index 0000000..4976c01 --- /dev/null +++ b/PROJECT_DOCUMENTATION.md @@ -0,0 +1,233 @@ +# InFocus Movie App - Projekt-Dokumentation + +## 🎬 Projekt-Überblick +**Name:** InFocus Movie App (Familienfilm-Tagebuch) +**Technologie:** Next.js 14.2.15, React 18, TypeScript, Supabase, TMDB API, OMDB API +**Design:** Apple Frosted Glass Design System mit 6 Themes +**Status:** Production-ready ✅ + +--- + +## 🚀 Features + +### 1. **Core Features** +- ✅ **Film & Serien Logging** - TMDB Multi-Search API +- ✅ **Familien-Tagebuch** - Gemeinsame Film-Erfahrungen +- ✅ **Watchlist** - Filme und Serien merken +- ✅ **Listen erstellen** - Eigentliche Film-Listen +- ✅ **Bewertungen** - 5-Sterne System mit Reviews + +### 2. **Externe Bewertungen** 🆕 +- ✅ **IMDb Ratings** - Automatisch von OMDB API geholt +- ✅ **Rotten Tomatoes** - Von OMDB API wenn verfügbar +- ✅ **TMDB Ratings** - Standard TMDB Bewertung +- ✅ **Smart Caching** - 24h Cache in `external_ratings` Tabelle +- ✅ **Überall sichtbar:** Diary, Feed, Movie-Detail, Logging + +### 3. **Theme System** 🆕 +- ✅ **6 verschiedene Themes:** + 1. **Apple Frosted Glass** (Hell) - Klassisches Apple Design + 2. **Apple Frosted Glass Dark** (Dunkel) - Apple Design im Dark Mode + 3. **Ocean Blue** (Hell) - Marine Blau mit sanften Farben + 4. **Forest Green** (Hell) - Natürliche Waldfarben + 5. **Cinema Noir** (Dunkel) - Elegantes Kino-Theme mit Gold + 6. **Sunset Purple** (Dunkel) - Warmes Lila mit Sonnenuntergang +- ✅ **Perfekte Kontraste** - Alle Texte lesbar +- ✅ **Glass-Effekte** - Echter Apple Frosted Glass mit Blur +- ✅ **Theme Persistence** - Pro User in Datenbank gespeichert +- ✅ **Live Preview** - Sofortiger Wechsel + +### 4. **UI/UX** +- ✅ **Apple Frosted Glass Design** - Konsistentes Design-System +- ✅ **Responsive** - Funktioniert auf allen Geräten +- ✅ **Performance** - Optimierte Bilder und Ladezeiten +- ✅ **Accessibility** - Screen-Reader freundlich + +--- + +## 🗂️ Datenbank-Schema + +### **Haupttabellen:** +```sql +-- Profiles (Benutzer) +CREATE TABLE public.profiles ( + id uuid PRIMARY KEY REFERENCES auth.users(id), + display_name text, + avatar_url text, + theme text DEFAULT 'apple-frosted-light' +); + +-- Diary Entries (Film-Logs) +CREATE TABLE public.diary_entries ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid REFERENCES auth.users(id), + tmdb_movie_id integer, + movie_title text, + movie_poster_path text, + media_type text DEFAULT 'movie', + rating numeric(2,1), + review text, + imdb_rating numeric(3,1), + rotten_tomatoes_rating numeric(3,1), + watched_on date DEFAULT current_date, + created_at timestamptz DEFAULT now() +); + +-- External Ratings Cache +CREATE TABLE public.external_ratings ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + tmdb_id integer, + media_type text, + imdb_id text, + imdb_rating numeric(3,1), + imdb_vote_count integer, + rotten_tomatoes_rating numeric(3,1), + last_updated timestamptz DEFAULT now(), + UNIQUE(tmdb_id, media_type) +); +``` + +--- + +## 🔧 API Integration + +### **TMDB API** +- **Multi-Search:** Filme + Serien in einem Request +- **Movie Details:** Vollständige Film-Informationen +- **Trending:** Beliebte Filme dieser Woche +- **API Key:** `NEXT_PUBLIC_TMDB_API_KEY=4115939bdc412c5f7b0c4598fcf29b77` + +### **OMDB API** +- **IMDb Ratings:** Offizielle IMDb Bewertungen +- **Rotten Tomatoes:** RT Scores wenn verfügbar +- **API Key:** `NEXT_PUBLIC_OMDB_API_KEY=5425f45e` + +--- + +## 🎨 Theme System Implementierung + +### **Theme Struktur:** +```typescript +const simpleThemes = { + 'ocean-blue': { + background: 'rgb(240, 249, 255)', + foreground: 'rgb(15, 23, 42)', // Dunkler für Lesbarkeit + glassBg: 'rgba(255, 255, 255, 0.95)', + glassBorder: 'rgba(59, 130, 246, 0.3)', + primary: 'rgb(14, 165, 233)' + } + // ... 5 weitere Themes +} +``` + +### **CSS Anwendung:** +```css +.glass-card, +.glass-header, +.glass-button, +.glass-avatar, +.glass-tag, +.glass-input { + background: ${theme.glassBg} !important; + border: 1px solid ${theme.glassBorder} !important; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + color: ${theme.foreground} !important; +} +``` + +--- + +## 🐛 Bug Fixes (Heute) + +### **1. "Invalid Date" Bug** +- **Problem:** `new Date(null)` erzeugte "Invalid Date" +- **Lösung:** Null-Checks in `diary-content.tsx` und `feed-content.tsx` +- **Ort:** Diary und Feed Seiten + +### **2. Feed Schema Mismatch** +- **Problem:** `watched_at` vs `watched_on` Feldnamen +- **Lösung:** Interface an Datenbank-Schema angepasst +- **Ort:** `feed-content.tsx` + +### **3. Theme Lesbarkeit** +- **Problem:** Schwache Kontraste in hellen Themes +- **Lösung:** Dunklere Textfarben und mehr Deckkraft +- **Ort:** `theme-selector.tsx` + +--- + +## 📁 Wichtige Dateien + +### **Core Components:** +- `components/theme-selector.tsx` - Theme Auswahl UI +- `lib/themes.ts` - Theme Definitionen +- `lib/external-ratings.ts` - Externe Bewertungen API +- `components/diary-content.tsx` - Tagebuch Ansicht +- `components/feed-content.tsx` - Family Feed + +### **Pages:** +- `app/(app)/profile/page.tsx` - Profil mit Theme Selector +- `app/(app)/log/page.tsx` - Film Logging mit externen Ratings +- `app/(app)/movie/[id]/page.tsx` - Movie Details mit Ratings + +### **Database:** +- `schema-extension.sql` - Alle Schema-Änderungen +- `FINAL_SQL.sql` - Basis Schema + +--- + +## 🚀 Deployment + +### **Environment Variablen:** +```env +NEXT_PUBLIC_SUPABASE_URL=ekbpexbhuochrplzorce.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +NEXT_PUBLIC_TMDB_API_KEY=4115939bdc412c5f7b0c4598fcf29b77 +NEXT_PUBLIC_OMDB_API_KEY=5425f45e +``` + +### **Build:** +```bash +npm run build +npm start +``` + +--- + +## 🎯 Nächste Schritte (Optional) + +### **Performance:** +- [ ] Bilder WebP optimieren +- [ ] Service Worker für Offline +- [ ] Lazy Loading implementieren + +### **Features:** +- [ ] Film-Trailers einbetten +- [ ] Social Sharing +- [ ] Export/Import Funktionen + +### **Analytics:** +- [ ] User Tracking +- [ ] Film-Statistiken +- [ ] Beliebtheits-Charts + +--- + +## 📊 Projekt-Status + +### **完成:** +- ✅ Core Logging System +- ✅ Externe Bewertungen (IMDb, RT, TMDB) +- ✅ Theme System mit 6 Themes +- ✅ Apple Frosted Glass Design +- ✅ Responsive UI +- ✅ Database Integration +- ✅ Bug Fixes + +### **Production Ready:** 🎬 +Die InFocus Movie App ist vollständig funktionsfähig und bereit für den produktiven Einsatz! + +--- + +*Letzte Aktualisierung: 10. März 2026* diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..e633f95 --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,156 @@ +# Supabase Setup - Jetzt sofort durchführen! + +## 🚀 Schnellste Methode (5 Minuten) + +### 1. Supabase Dashboard öffnen +**URL:** https://supabase.com/dashboard/project/ekbpexbhuochrplzorce + +### 2. SQL Editor → Tabellen erstellen +**Klick:** Links auf "SQL Editor" + +**Füge diesen Code ein:** +```sql +-- Profiles table +create table if not exists public.profiles ( + id uuid primary key references auth.users(id) on delete cascade, + display_name text not null, + avatar_url text, + created_at timestamptz default now() +); + +alter table public.profiles enable row level security; +create policy if not exists "profiles_select_all" on public.profiles for select using (true); +create policy if not exists "profiles_insert_own" on public.profiles for insert with check (auth.uid() = id); +create policy if not exists "profiles_update_own" on public.profiles for update using (auth.uid() = id); + +-- Auto-create profile trigger +create or replace function public.handle_new_user() +returns trigger +language plpgsql +security definer +set search_path = public +as $$ +begin + insert into public.profiles (id, display_name) + values ( + new.id, + coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)) + ) + on conflict (id) do nothing; + return new; +end; +$$; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row + execute function public.handle_new_user(); + +-- Diary entries +create table if not exists public.diary_entries ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + rating numeric(2,1) check (rating >= 0.5 and rating <= 5), + review text, + watched_on date default current_date, + created_at timestamptz default now() +); + +alter table public.diary_entries enable row level security; +create policy if not exists "diary_select_all" on public.diary_entries for select using (true); +create policy if not exists "diary_insert_own" on public.diary_entries for insert with check (auth.uid() = user_id); +create policy if not exists "diary_update_own" on public.diary_entries for update using (auth.uid() = user_id); +create policy if not exists "diary_delete_own" on public.diary_entries for delete using (auth.uid() = user_id); + +-- Watchlist +create table if not exists public.watchlist ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + added_at timestamptz default now(), + unique(user_id, tmdb_movie_id) +); + +alter table public.watchlist enable row level security; +create policy if not exists "watchlist_select_all" on public.watchlist for select using (true); +create policy if not exists "watchlist_insert_own" on public.watchlist for insert with check (auth.uid() = user_id); +create policy if not exists "watchlist_delete_own" on public.watchlist for delete using (auth.uid() = user_id); + +-- Lists +create table if not exists public.lists ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + name text not null, + description text, + created_at timestamptz default now() +); + +alter table public.lists enable row level security; +create policy if not exists "lists_select_all" on public.lists for select using (true); +create policy if not exists "lists_insert_own" on public.lists for insert with check (auth.uid() = user_id); +create policy if not exists "lists_update_own" on public.lists for update using (auth.uid() = user_id); +create policy if not exists "lists_delete_own" on public.lists for delete using (auth.uid() = user_id); + +-- List items +create table if not exists public.list_items ( + id uuid primary key default gen_random_uuid(), + list_id uuid not null references public.lists(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + position integer default 0, + added_at timestamptz default now(), + unique(list_id, tmdb_movie_id) +); + +alter table public.list_items enable row level security; +create policy if not exists "list_items_select_all" on public.list_items for select using (true); +create policy if not exists "list_items_insert_own" on public.list_items for insert + with check (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); +create policy if not exists "list_items_delete_own" on public.list_items for delete + using (exists (select 1 from public.lists where id = list_id and user_id = auth.uid())); + +-- Likes +create table if not exists public.likes ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + diary_entry_id uuid not null references public.diary_entries(id) on delete cascade, + created_at timestamptz default now(), + unique(user_id, diary_entry_id) +); + +alter table public.likes enable row level security; +create policy if not exists "likes_select_all" on public.likes for select using (true); +create policy if not exists "likes_insert_own" on public.likes for insert with check (auth.uid() = user_id); +create policy if not exists "likes_delete_own" on public.likes for delete using (auth.uid() = user_id); +``` + +**Klick:** "RUN" → Alle Tabellen werden erstellt! + +### 3. Authentication konfigurieren +**Gehe zu:** Authentication → Settings + +**Setze:** +- Site URL: `http://localhost:3000` +- Redirect URLs: `http://localhost:3000/auth/callback` +- Enable email confirmations: **YES** + +### 4. FERTIG! 🎉 +Jetzt kannst du: +- Benutzer registrieren +- Email-Bestätigung erhalten +- Filme loggen +- Watchlist verwenden +- Listen erstellen + +### ⚡ Für schnelle Tests (ohne Email) +Im Supabase Dashboard: **Disable email confirmations** → Registrierung funktioniert sofort diff --git a/SUPABASE_QUICK_SETUP.md b/SUPABASE_QUICK_SETUP.md new file mode 100644 index 0000000..c8d17cf --- /dev/null +++ b/SUPABASE_QUICK_SETUP.md @@ -0,0 +1,118 @@ +# Supabase Quick Setup + +## Methode 1: Supabase Dashboard (Empfohlen) + +### 1. Öffne Supabase Dashboard +- Gehe zu: https://supabase.com/dashboard +- Login mit deinem Account +- Wähle Projekt: `ekbpexbhuochrplzorce` + +### 2. Tabellen erstellen +**SQL Editor öffnen:** +- Links auf "SQL Editor" klicken +- Neues Query erstellen + +**Schema erstellen:** +```sql +-- Führe diesen Code aus: +create table if not exists public.profiles ( + id uuid primary key references auth.users(id) on delete cascade, + display_name text not null, + avatar_url text, + created_at timestamptz default now() +); + +alter table public.profiles enable row level security; +create policy if not exists "profiles_select_all" on public.profiles for select using (true); +create policy if not exists "profiles_insert_own" on public.profiles for insert with check (auth.uid() = id); +create policy if not exists "profiles_update_own" on public.profiles for update using (auth.uid() = id); + +-- Auto-create profile on signup +create or replace function public.handle_new_user() +returns trigger +language plpgsql +security definer +set search_path = public +as $$ +begin + insert into public.profiles (id, display_name) + values ( + new.id, + coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)) + ) + on conflict (id) do nothing; + return new; +end; +$$; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row + execute function public.handle_new_user(); +``` + +**Weitere Tabellen:** +```sql +-- Diary entries +create table if not exists public.diary_entries ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + rating numeric(2,1) check (rating >= 0.5 and rating <= 5), + review text, + watched_on date default current_date, + created_at timestamptz default now() +); + +alter table public.diary_entries enable row level security; +create policy if not exists "diary_select_all" on public.diary_entries for select using (true); +create policy if not exists "diary_insert_own" on public.diary_entries for insert with check (auth.uid() = user_id); +create policy if not exists "diary_update_own" on public.diary_entries for update using (auth.uid() = user_id); +create policy if not exists "diary_delete_own" on public.diary_entries for delete using (auth.uid() = user_id); + +-- Watchlist +create table if not exists public.watchlist ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references public.profiles(id) on delete cascade, + tmdb_movie_id integer not null, + movie_title text not null, + movie_poster_path text, + movie_year text, + added_at timestamptz default now(), + unique(user_id, tmdb_movie_id) +); + +alter table public.watchlist enable row level security; +create policy if not exists "watchlist_select_all" on public.watchlist for select using (true); +create policy if not exists "watchlist_insert_own" on public.watchlist for insert with check (auth.uid() = user_id); +create policy if not exists "watchlist_delete_own" on public.watchlist for delete using (auth.uid() = user_id); +``` + +### 3. Authentifizierung konfigurieren +**Authentication Settings:** +- Gehe zu **Authentication > Settings** +- Setze **Site URL**: `http://localhost:3000` +- Füge hinzu zu **Redirect URLs**: `http://localhost:3000/auth/callback` +- Aktiviere **Enable email confirmations** + +### 4. Testen +- Registriere neuen Benutzer +- Bestätigungs-Email sollte ankommen +- Nach Bestätigung sollte Benutzer eingeloggt sein + +## Methode 2: MCP Server (Wenn verfügbar) + +Wenn der Supabase MCP Server funktioniert: +1. Füge den MCP Code zu deiner IDE Konfiguration hinzu +2. Starte IDE neu +3. MCP sollte automatisch Tabellen erstellen können + +## Schneller Test (Ohne Email-Bestätigung) +Für Entwicklung: +- Im Supabase Dashboard: **Authentication > Settings** +- Deaktiviere **Enable email confirmations** +- Registrierung funktioniert sofort ohne Email diff --git a/SUPABASE_SETUP.md b/SUPABASE_SETUP.md new file mode 100644 index 0000000..5476fa9 --- /dev/null +++ b/SUPABASE_SETUP.md @@ -0,0 +1,47 @@ +# Supabase Setup Anleitung + +## 1. Tabellen erstellen +Führe diese SQL-Skripte im Supabase SQL Editor aus: + +### Schema erstellen: +```sql +-- Führe scripts/001_create_schema.sql aus +``` + +### Tabellen erstellen: +```sql +-- Führe scripts/001_create_tables.sql aus +``` + +## 2. Authentifizierung konfigurieren + +### Im Supabase Dashboard: +1. Gehe zu **Authentication > Settings** +2. Setze **Site URL**: `http://localhost:3000` +3. Füge zu **Redirect URLs** hinzu: `http://localhost:3000/auth/callback` +4. Aktiviere **Enable email confirmations** +5. Passe das **Email Template** an, falls nötig + +### Email-Template anpassen (falls nötig): +``` +Confirmation Link: {{ .ConfirmationURL }} +``` + +## 3. Environment Variablen prüfen +Die `.env.local` sollte enthalten: +``` +NEXT_PUBLIC_SUPABASE_URL=https://ekbpexbhuochrplzorce.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable__UII_iKx3pgvLQvc1xrN1w_qnwP6JOv +NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL=http://localhost:3000/auth/callback +``` + +## 4. Testen +1. Registriere neuen Benutzer +2. Bestätigungs-E-Mail sollte ankommen +3. Klick auf Link sollte zur App zurückleiten +4. Benutzer sollte eingeloggt sein + +## 5. Fehlersuche +Falls Email nicht ankommt: +- Prüfe Spam-Ordner +- Verwende temporär **Disable email confirmations** für Tests diff --git a/TMDB_SETUP.md b/TMDB_SETUP.md new file mode 100644 index 0000000..4d46a7f --- /dev/null +++ b/TMDB_SETUP.md @@ -0,0 +1,49 @@ +# TMDB API Setup für InFocus Movie App + +## Problem +Die Filmliste funktioniert nicht, weil der TMDB API Key fehlt. + +## Lösung: TMDB API Key besorgen + +### 1. TMDB Konto erstellen +1. Gehe zu: https://www.themoviedb.org/ +2. Registriere dich kostenlos +3. Bestätige deine E-Mail +4. Login in dein Konto + +### 2. API Key anfordern +1. Gehe zu: https://www.themoviedb.org/settings/api +2. Klicke auf "Request an API Key" +3. Fülle das Formular aus: + - Application Name: "InFocus Movie App" + - Application URL: "http://localhost:3000" + - Description: "Family movie diary app" +4. Warte auf Genehmigung (meistens sofort) + +### 3. API Key eintragen +1. Öffne `.env.local` Datei +2. Ersetze `your_tmdb_api_key_here` mit deinem echten API Key: + ``` + TMDB_API_KEY=dein_echter_api_key_hier + ``` + +### 4. App neustarten +1. Server stoppen (Ctrl+C) +2. `start_app.bat` ausführen oder `npm run dev` + +## Testen +Nach dem Eintrag des API Keys sollte funktionieren: +- Filmliste laden +- Filme suchen +- Filmdetails anzeigen +- Filme zur Watchlist hinzufügen + +## API Limits +- Free Account: 40 Anfragen pro 10 Sekunden +- Genug für Entwicklung und Tests + +## Fehlerbehebung +Falls es immer noch nicht funktioniert: +1. API Key auf Tippfehler prüfen +2. TMDB Konto Status prüfen +3. Browser Console auf Fehler prüfen (F12) diff --git a/WINDSURF_MCP_CONFIG.md b/WINDSURF_MCP_CONFIG.md new file mode 100644 index 0000000..2bce179 --- /dev/null +++ b/WINDSURF_MCP_CONFIG.md @@ -0,0 +1,34 @@ +# Windsurf MCP Konfiguration + +## Datei erstellen: +`~/.codeium/windsurf/mcp_config.json` + +## Inhalt: +```json +{ + "mcpServers": { + "supabase": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://mcp.supabase.com/mcp?project_ref=ekbpexbhuochrplzorce" + ] + } + } +} +``` + +## Anleitung: +1. Öffne Datei-Explorer +2. Navigiere zu: `C:\Users\Admin\.codeium\windsurf\` +3. Erstelle Datei: `mcp_config.json` +4. Kopiere den JSON-Code hinein +5. Speichern +6. Windsurf neu starten + +## Danach testen: +Nach Neustart sollte der Supabase MCP Server verfügbar sein und ich kann: +- Tabellen automatisch erstellen +- Datenbank-Struktur prüfen +- Authentifizierung konfigurieren diff --git a/app/(app)/diary/page.tsx b/app/(app)/diary/page.tsx index 8ed5e68..acbbb0d 100644 --- a/app/(app)/diary/page.tsx +++ b/app/(app)/diary/page.tsx @@ -1,30 +1,62 @@ import { createClient } from "@/lib/supabase/server" import { DiaryContent } from "@/components/diary-content" +interface DiaryEntry { + id: string + tmdb_movie_id: number + movie_title: string + movie_poster_path: string | null + rating: number | null + review: string | null + watched_on: string +} + export default async function DiaryPage() { const supabase = await createClient() const { data: { user }, } = await supabase.auth.getUser() - const { data: entries } = await supabase + if (!user) { + return ( + + ) + } + + const { data: entries, error: entriesError } = await supabase .from("diary_entries") .select("*") - .eq("user_id", user!.id) - .order("watched_at", { ascending: false }) + .eq("user_id", user.id) + .order("watched_on", { ascending: false }) - const { data: watchlist } = await supabase + if (entriesError) { + console.error("Error fetching diary entries:", entriesError) + } + + const { data: watchlist, error: watchlistError } = await supabase .from("watchlist") .select("*") - .eq("user_id", user!.id) + .eq("user_id", user.id) .order("added_at", { ascending: false }) - const { data: lists } = await supabase + if (watchlistError) { + console.error("Error fetching watchlist:", watchlistError) + } + + const { data: lists, error: listsError } = await supabase .from("lists") .select("*, list_items(count)") - .eq("user_id", user!.id) + .eq("user_id", user.id) .order("created_at", { ascending: false }) + if (listsError) { + console.error("Error fetching lists:", listsError) + } + return ( (null) useEffect(() => { loadLists() @@ -66,24 +67,33 @@ export default function ListsPage() { .single() if (!error && data) { - setLists([data, ...lists]) + setLists(prev => [data, ...prev]) setNewListName("") setNewListDesc("") setShowNewList(false) + } else if (error) { + setError("Fehler beim Erstellen der Liste") + setTimeout(() => setError(null), 3000) } setCreatingList(false) } async function deleteList(id: string) { const supabase = createClient() - await supabase.from("lists").delete().eq("id", id) - setLists(lists.filter((l) => l.id !== id)) + const { error } = await supabase.from("lists").delete().eq("id", id) + + if (error) { + setError("Fehler beim Löschen der Liste") + setTimeout(() => setError(null), 3000) + } else { + setLists(lists.filter((l) => l.id !== id)) + } } if (loading) { return (
-
+

Meine Listen

@@ -97,22 +107,28 @@ export default function ListsPage() { return (
-
+

Meine Listen

- + {lists.length}
+ {error && ( +
+ {error} +
+ )} +
{/* New list button */} +
+ + setQuery(e.target.value)} + placeholder="Welchen Film hast du gesehen?" + className="glass-input h-11 w-full pl-10 pr-4 text-sm" + autoFocus + /> +
@@ -154,7 +242,7 @@ function LogPageContent() { {url ? ( {movie.title}
-

- {movie.title} -

+
+ {(() => { + const Icon = getMediaTypeIcon(movie) + return + })()} +

+ {getDisplayTitle(movie)} +

+

- {movie.release_date?.slice(0, 4)} + {'release_date' in movie ? movie.release_date?.slice(0, 4) : movie.first_air_date?.slice(0, 4)}

@@ -202,6 +296,12 @@ function LogPageContent() {
+ {error && ( +
+ {error} +
+ )} + {/* Selected movie */}
@@ -239,6 +339,88 @@ function LogPageContent() { />
+ {/* External Ratings */} + {loadingRatings && ( +
+ + Lade externe Bewertungen... +
+ )} + + {externalRatings && !loadingRatings && ( +
+

Externe Bewertungen

+
+ {externalRatings.imdb_rating && ( +
+ IMDb +
+ {externalRatings.imdb_rating} + + ({externalRatings.imdb_vote_count?.toLocaleString()}) + +
+
+ )} + {externalRatings.rotten_tomatoes_rating && ( +
+ Rotten Tomatoes + {externalRatings.rotten_tomatoes_rating}% +
+ )} + {!externalRatings.imdb_rating && !externalRatings.rotten_tomatoes_rating && ( +

Keine externen Bewertungen verfügbar

+ )} +
+
+ )} + + {/* TV Show Episode Info */} + {selectedMovie?.media_type === 'tv' && ( +
+

Episoden-Informationen

+
+
+ + setSeasonNumber(e.target.value)} + placeholder="z.B. 1" + className="glass-input h-10 w-full px-3 text-sm" + /> +
+
+ + setEpisodeNumber(e.target.value)} + placeholder="z.B. 1" + className="glass-input h-10 w-full px-3 text-sm" + /> +
+
+

+ Optional - Lasse leer für die ganze Serie +

+
+ )} + {/* Watch date */}