A comprehensive media tracking and analytics application built with Next.js, React, and Supabase. Track movies, TV shows, podcasts, and live theatre performances with detailed analytics, filtering, and metadata integration. Also includes a food & drinks tracking workspace with restaurant reviews and dining analytics.
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Configuration
- Project Structure
- Database Schema
- API Routes
- Core Concepts
- Usage
- Development
- Performance Optimizations
- Security Considerations
- Deployment
- Scripts
- Troubleshooting
- Contributing
- License
- Acknowledgments
Media Review is a full-stack web application that allows users to track and analyze their media consumption and dining experiences. The application is built with a modern tech stack focusing on performance, user experience, and scalability.
- Media Tracking: Track movies, TV shows, books, games, podcasts, and live theatre
- Food & Drinks Tracking: Track restaurant visits, meals, and dining experiences
- Analytics Dashboard: Comprehensive analytics with charts, KPIs, and trends
- AI-Powered Queries: Natural language queries using Google Gemini AI
- Metadata Integration: Automatic metadata fetching from OMDB and other sources
- Batch Operations: Bulk import, batch editing, and batch metadata fetching
- User Management: Admin panel for user approval and management
- Multiple Media Types: Track Movies, TV Shows, Books, Games, Podcasts, and Live Theatre
- Rich Metadata: Store title, genre, language, ratings, dates, prices, platforms, and more
- Status Management: Track status (Finished, Watching, On Hold, Dropped, Plan to Watch) with history
- Episode Tracking: Track individual episodes watched with timestamps
- Custom Ratings: Personal ratings alongside average ratings from external sources
- Date Tracking: Start and finish dates for each entry
- Poster Images: Automatic poster/image fetching and display
- IMDB Integration: Link entries to IMDB for additional metadata
- Restaurant Tracking: Track restaurant visits with location, cuisine, and ratings
- Meal Details: Record items ordered, prices, and dining dates
- Photo Gallery: Upload and manage photos of meals and restaurants
- Calendar View: Visual calendar view of dining history
- Return Visits: Track which restaurants you'd return to
- Cuisine Analytics: Analyze dining patterns by cuisine type
- KPI Metrics: Total items, time spent, pages read, money spent, average ratings
- Visual Charts:
- Spending trends by month and medium
- Time consumption (minutes/hours) by month
- Reading volume (pages) by month
- Rating distributions
- Counts by medium, language, genre, platform, status, and type
- Global Filtering: Filter analytics across all metrics simultaneously
- Time-based Analysis: Monthly breakdowns for all metrics
- Workspace-specific Analytics: Separate analytics for media and food workspaces
- Natural Language Queries: Ask questions in plain English (e.g., "How many movies did I watch in 2025?")
- Smart SQL Generation: AI converts questions to SQL queries automatically
- Intelligent Visualizations: Automatic chart selection based on query results
- Action Mode: AI can execute actions like updating entries or batch operations
- Safe Execution: SQL queries are validated and executed safely with RLS context
- Multi-criteria Filtering: Filter by type, status, medium, platform, language, genre, and date ranges
- Full-text Search: Search across titles and other fields
- URL-based Filters: Shareable filtered views via URL parameters
- Column Customization: Show/hide table columns with persistent preferences
- Sorting: Multi-column sorting with direction control
- Persistent Filters: Filters persist across page reloads
- Manual Entry: Comprehensive form for adding new entries
- CSV Import: Bulk import entries from CSV files with field mapping
- Batch Editing: Edit multiple entries simultaneously
- Entry Editing: Inline editing with dialog forms
- Entry Deletion: Safe deletion with confirmation
- Status History: Track status changes over time with timeline view
- Metadata Override: Selective field override when fetching metadata
- Dark/Light Mode: Theme toggle with system preference detection
- Responsive Design: Works on desktop, tablet, and mobile devices
- Toast Notifications: User-friendly feedback for all actions
- Loading States: Clear loading indicators during operations
- Error Handling: Graceful error handling with user-friendly messages
- Skeleton Screens: Loading placeholders for better perceived performance
- Image Optimization: Automatic image optimization and lazy loading
- Authentication: Email/password authentication via Supabase Auth
- User Approval System: Admin-controlled user approval workflow
- Admin Panel: Manage users, approve/reject requests, and view system stats
- Profile Management: User profiles with preferences and settings
- Row Level Security: Data isolation between users via RLS policies
┌─────────────────────────────────────────────────────────────┐
│ Client Browser │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ React UI │ │ Next.js App │ │ TanStack │ │
│ │ Components │ │ Router │ │ Table │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────┬───────────────────────────────────┘
│ HTTPS
│
┌───────────────────────▼───────────────────────────────────┐
│ Next.js Server │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ API Routes │ │ Middleware │ │ Server │ │
│ │ │ │ (Auth) │ │ Actions │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────┬───────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌─────▼──────┐
│ Supabase │ │ OMDB API │ │ Gemini AI│
│ PostgreSQL │ │ │ │ │
│ + Storage │ │ │ │ │
└──────────────┘ └─────────────┘ └───────────┘
The application follows a layered architecture with clear separation of concerns:
- Pages: Next.js App Router pages (
app/*/page.tsx) - Components: Reusable React components organized by feature
- UI Components: Base UI components from Radix UI primitives
- Client Components: Interactive components marked with
"use client"
- Server Actions: Server-side data mutations (
lib/actions.ts) - Custom Hooks: React hooks for data fetching and state management
- Utilities: Helper functions for parsing, filtering, and formatting
- Type Definitions: TypeScript types and interfaces
- Supabase Clients: Browser and server-side Supabase clients
- Database Types: Auto-generated TypeScript types from database schema
- Query Builders: Reusable query patterns
- REST Endpoints: API routes for external integrations
- Authentication: Auth callback handlers
- File Upload: Image upload handling
- Metadata Fetching: External API integrations
User Action → React Component → Custom Hook → Supabase Client → PostgreSQL
│
▼
User Interface ← React State ← Hook State ← Query Result ← Database Response
Example: Loading Media Entries
- User navigates to
/media MediaPagecomponent mountsuseMediaEntrieshook fetches data via Supabase client- Data flows back through hook state to component
- Component renders with data
User Action → Form Component → Server Action → Supabase Client → PostgreSQL
│
▼
Toast Notification ← Optimistic Update ← Success Response ← Database Update
Example: Creating an Entry
- User fills form and clicks "Save"
- Form calls
createEntryserver action - Server action validates data and inserts into database
- Success response triggers UI update and toast notification
- Component refetches data to show new entry
Login Request → Middleware → Supabase Auth → User Profile Check → Session Cookie
│
▼
Redirect to App ← Approval Check ← Profile Status ← Database Query
-
User Login:
- User enters email on login page
- System checks if user exists via
/api/auth/check-user - If exists, Supabase Auth sends magic link
- User clicks link, redirected to
/auth/callback
-
Callback Processing:
- Middleware validates session
- Checks user profile status (
pending,approved,rejected) - Only approved users can access the app
- Session cookie is set
-
Session Management:
- Middleware validates session on every request
- Unauthenticated users redirected to
/login - Session refreshed automatically by Supabase
- Row Level Security (RLS): All database tables have RLS policies
- User Isolation: Users can only access their own data
- Admin Routes: Protected by middleware checking
is_adminflag - Approval System: Users must be approved before accessing the app
- Service Role Key: Used server-side only, never exposed to client
- RLS Policies: Database-level security ensuring data isolation
- SQL Injection Prevention: Parameterized queries via Supabase
- XSS Protection: React's built-in escaping, sanitized inputs
- CSRF Protection: SameSite cookies, secure session handling
The application uses a hybrid state management approach:
- Fetched via custom hooks (
useMediaEntries,useFoodMetrics) - Cached in React Query-like pattern (manual implementation)
- Refetched on mutations or manual refresh
- Form state: Managed by React Hook Form
- UI state: Modal open/close, filters, selections
- URL state: Filters and search params via
nuqs(URL query state)
- LocalStorage: Theme preference, column preferences
- URL Params: Filters and search queries (shareable)
- Database: User preferences stored in
user_preferencestable
// Example: Filter state
URL Params → useMediaFilters hook → Filtered Data → Component Render
↑ │
└──────────────────────────────────────────────┘
(User updates filters)-
Next.js 16.1.1: React framework with App Router
- Server Components for better performance
- Server Actions for mutations
- Built-in API routes
- Image optimization
- Automatic code splitting
-
React 19.2.3: UI library
- Server Components
- Client Components for interactivity
- Concurrent features
-
TypeScript 5.7.2: Type safety and developer experience
- Supabase: Backend-as-a-Service
- PostgreSQL: Relational database
- Supabase Auth: Authentication service
- Supabase Storage: File storage for images
- Row Level Security: Database-level access control
- Real-time Subscriptions: (Available but not currently used)
- Tailwind CSS 3.4.17: Utility-first CSS framework
- Radix UI: Accessible component primitives
- Dialog, Dropdown, Select, Popover, Tabs, etc.
- Lucide React: Icon library
- Sonner: Toast notification library
- class-variance-authority: Component variant management
- tailwind-merge: Tailwind class merging utility
- React Hook Form: Form state management
- Zod: Schema validation (used in some forms)
- TanStack Table (React Table) 8.21.3: Powerful table component
- Sorting, filtering, column visibility
- Virtual scrolling support
- Recharts 3.6.0: Charting library
- Bar charts, pie charts, area charts, line charts
- date-fns 4.1.0: Date manipulation and formatting
- PapaParse 5.4.1: CSV parsing for imports
- Google Generative AI (@google/generative-ai):
- Natural language to SQL conversion
- AI-powered actions and queries
- next/image: Optimized image component
- react-easy-crop: Image cropping functionality
- heic-to: HEIC image format conversion
- nuqs: URL query state management (for shareable filters)
- cmdk: Command palette component
- embla-carousel-react: Carousel component
- ESLint: Code linting
- TypeScript: Type checking
- PostCSS: CSS processing
- Turbopack: Fast bundler (Next.js default)
- Node.js 18+ or Bun (recommended)
- A Supabase account and project
- (Optional) OMDB API key for metadata fetching
- (Optional) Google Gemini API key for AI features
-
Clone the repository:
git clone <repository-url> cd media-review
-
Install dependencies:
bun install
Minimal install (production dependencies only):
bun install --production
This keeps
node_modulessmaller (~450 MB vs ~515 MB with dev tools). -
Set up environment variables:
Create a
.env.localfile in the root directory:# Supabase Configuration (Required) NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key # External APIs (Optional) OMDB_API_KEY=your_omdb_api_key GOOGLE_GENERATIVE_AI_API_KEY=your_google_ai_key GEMINI_API_KEY=your_gemini_api_key GEMINI_MODEL_NAME=gemini-1.5-pro # Google Maps (Optional, for food workspace) NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_key
Important:
- The
SUPABASE_SERVICE_ROLE_KEYis required for user authentication checks - Never expose
SUPABASE_SERVICE_ROLE_KEYto the client - Find it in Supabase project settings under "API" → "Service Role Key"
- The
-
Set up the database:
- Create a Supabase project at supabase.com
- Run database migrations to create tables:
media_entriesmedia_status_historyfood_entriesuser_profilesuser_preferences
- Enable Row Level Security (RLS) on all tables
- Create RLS policies for user data isolation
- Set up the
execute_sql_queryfunction for AI queries
-
Run the development server:
bun run dev
-
Open the application: Navigate to http://localhost:3000 in your browser
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
✅ | Public Supabase project URL used by the browser client |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
✅ | Anonymous public key for the Supabase client |
SUPABASE_SERVICE_ROLE_KEY |
✅ | Server-side key for privileged Supabase calls (never expose in browser) |
OMDB_API_KEY |
❌ | Enables metadata lookup for movies and TV shows via OMDB |
GOOGLE_GENERATIVE_AI_API_KEY |
❌ | Legacy Google AI key (deprecated, use GEMINI_API_KEY) |
GEMINI_API_KEY |
❌ | Google Gemini API key for AI-powered queries |
GEMINI_MODEL_NAME |
❌ | Gemini model name (default: gemini-1.5-pro) |
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY |
❌ | Google Maps API key for restaurant location features |
- media_entries: Main table for media tracking
- media_status_history: History of status changes
- food_entries: Restaurant and meal tracking
- user_profiles: User approval and admin status
- user_preferences: User-specific preferences
Enable RLS on all tables and create policies:
-- Example: Media entries RLS policy
CREATE POLICY "Users can only see their own entries"
ON media_entries FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can only insert their own entries"
ON media_entries FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can only update their own entries"
ON media_entries FOR UPDATE
USING (auth.uid() = user_id);Create the execute_sql_query function for AI queries:
CREATE OR REPLACE FUNCTION execute_sql_query(query_text TEXT)
RETURNS JSON
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Only allow SELECT queries
IF NOT (query_text ~* '^\s*SELECT') THEN
RAISE EXCEPTION 'Only SELECT queries are allowed';
END IF;
-- Block dangerous keywords
IF query_text ~* '(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE)' THEN
RAISE EXCEPTION 'Query contains forbidden keywords';
END IF;
-- Execute query and return JSON
RETURN (SELECT json_agg(row_to_json(t)) FROM (
EXECUTE query_text
) t);
END;
$$;Key configurations in next.config.ts:
- Turbopack: Enabled for faster development
- Image Optimization: Remote patterns configured for Supabase and TMDB
- Package Optimization: Tree-shaking for large libraries
- Server Actions: Body size limit set to 10MB for file uploads
- Redirects: Legacy route redirects (
/movies→/media)
media-review/
├── app/ # Next.js App Router
│ ├── (auth)/ # Auth-related routes
│ │ ├── login/ # Login page
│ │ └── auth/ # Auth callbacks
│ │ └── callback/ # OAuth callback handler
│ ├── admin/ # Admin panel
│ │ ├── page.tsx # Admin dashboard
│ │ ├── users/ # User management
│ │ └── requests/ # Approval requests
│ ├── api/ # API routes
│ │ ├── auth/ # Authentication endpoints
│ │ │ └── check-user/ # User existence check
│ │ ├── metadata/ # Metadata fetching
│ │ │ └── search/ # Metadata search
│ │ ├── omdb/ # OMDB integration
│ │ ├── upload/ # File upload handler
│ │ ├── ai-query/ # AI query endpoint
│ │ ├── execute-actions/ # AI action execution
│ │ ├── clean-data/ # Data cleaning utilities
│ │ └── maps/ # Google Maps integration
│ ├── food/ # Food workspace
│ │ ├── page.tsx # Food entries list
│ │ ├── analytics/ # Food analytics
│ │ └── layout.tsx # Food workspace layout
│ ├── media/ # Media workspace
│ │ ├── page.tsx # Media entries list
│ │ ├── analytics/ # Media analytics
│ │ └── add/ # Add/edit entry
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page
│ └── globals.css # Global styles
├── components/ # React components
│ ├── admin/ # Admin components
│ │ ├── AdminLayout.tsx
│ │ ├── UsersTable.tsx
│ │ └── RequestsTable.tsx
│ ├── analytics/ # Analytics components
│ │ ├── AnalyticsCharts.tsx
│ │ ├── GlobalFilterBar.tsx
│ │ ├── KPIGrid.tsx
│ │ ├── FoodAnalyticsCharts.tsx
│ │ └── GenericAnalyticsDashboard.tsx
│ ├── auth/ # Auth components
│ │ ├── LoginForm.tsx
│ │ ├── SignupForm.tsx
│ │ └── OtpForm.tsx
│ ├── charts/ # Chart components
│ │ ├── SimpleBarChart.tsx
│ │ ├── SimplePieChart.tsx
│ │ └── AreaChartBase.tsx
│ ├── filter-components/ # Filter UI components
│ │ ├── MultiSelect.tsx
│ │ └── DateRangePicker.tsx
│ ├── form-inputs/ # Form input components
│ │ ├── RatingInput.tsx
│ │ ├── StarRatingInput.tsx
│ │ ├── MultiSelectInput.tsx
│ │ └── PlaceImageUpload.tsx
│ ├── import/ # CSV import components
│ │ ├── ImportFileUpload.tsx
│ │ ├── ImportPreviewTable.tsx
│ │ └── ImportFormatGuide.tsx
│ ├── landing/ # Landing page components
│ │ ├── Hero.tsx
│ │ ├── Features.tsx
│ │ ├── Navbar.tsx
│ │ └── Footer.tsx
│ ├── media/ # Media-specific components
│ │ ├── forms/ # Form sections
│ │ ├── WatchingSection.tsx
│ │ └── WatchedDiaryTable.tsx
│ ├── shared/ # Shared components
│ │ ├── EntityTable.tsx
│ │ ├── BatchEditDialog.tsx
│ │ └── StatusHistoryTimeline.tsx
│ ├── ui/ # Base UI components
│ │ ├── button.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ ├── table.tsx
│ │ └── ... (other Radix UI wrappers)
│ ├── ai-query-dialog.tsx # AI query interface
│ ├── ai-query-results.tsx # AI results display
│ ├── authenticated-layout.tsx # Main app layout
│ ├── food-add-dialog.tsx # Food entry dialog
│ ├── media-details-dialog.tsx # Media entry dialog
│ ├── media-table.tsx # Media table component
│ ├── page-header.tsx # Page header with actions
│ └── theme-provider.tsx # Theme context provider
├── hooks/ # Custom React hooks
│ ├── useMediaEntries.ts # Media entries data fetching
│ ├── useMediaMetrics.ts # Media analytics calculations
│ ├── useMediaFilters.ts # Filter state management
│ ├── useFoodMetrics.ts # Food analytics calculations
│ ├── useColumnPreferences.ts # Column visibility preferences
│ ├── useFileUpload.ts # File upload handling
│ └── useBatchMetadataFetch.ts # Batch metadata fetching
├── lib/ # Utility functions and types
│ ├── actions.ts # Server actions
│ ├── admin-actions.ts # Admin server actions
│ ├── database.types.ts # Database type definitions
│ ├── filter-types.ts # Filter logic and types
│ ├── parsing-utils.ts # Data parsing utilities
│ ├── types.ts # Type definitions and constants
│ ├── ai-query-schemas.ts # AI query schemas
│ └── supabase/ # Supabase client setup
│ ├── client.ts # Browser client
│ └── server.ts # Server client
├── middleware.ts # Next.js middleware (auth)
├── next.config.ts # Next.js configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── postcss.config.mjs # PostCSS configuration
├── eslint.config.mjs # ESLint configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── README.md # This file
Main table for media tracking entries.
| Column | Type | Description |
|---|---|---|
id |
uuid | Primary key |
user_id |
uuid | Foreign key to auth.users |
title |
text | Entry title (required) |
medium |
text | Movie, TV Show, Book, Game, Podcast, Live Theatre |
type |
text | Documentary, Variety, Reality, Scripted Live Action, Animation, Special, Audio |
status |
text | Finished, Watching, On Hold, Dropped, Plan to Watch |
genre |
text[] | Array of genres |
language |
text[] | Array of languages |
platform |
text | Streaming platform or source |
start_date |
date | When user started consuming |
finish_date |
date | When user finished |
my_rating |
numeric | User's personal rating |
average_rating |
numeric | Average rating from external source |
rating |
numeric | General rating field |
price |
numeric | Cost of the media |
length |
text | Duration or length |
episodes |
integer | Total episodes (for TV shows) |
episodes_watched |
integer | Episodes watched |
episode_history |
jsonb | Array of episode watch records |
last_watched_at |
timestamp | Last episode watch time |
poster_url |
text | URL to poster image |
imdb_id |
text | IMDB identifier |
season |
text | Season information |
time_taken |
text | Time taken to complete |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
Tracks status changes over time.
| Column | Type | Description |
|---|---|---|
id |
uuid | Primary key |
media_entry_id |
uuid | Foreign key to media_entries |
user_id |
uuid | Foreign key to auth.users |
old_status |
text | Previous status |
new_status |
text | New status |
changed_at |
timestamp | When status changed |
notes |
text | Optional notes about the change |
created_at |
timestamp | Creation timestamp |
Restaurant and meal tracking.
| Column | Type | Description |
|---|---|---|
id |
uuid | Primary key |
user_id |
uuid | Foreign key to auth.users |
restaurant_name |
text | Restaurant name |
cuisine |
text[] | Array of cuisine types |
location |
text | Restaurant location |
date |
date | Visit date |
rating |
numeric | User rating |
price |
numeric | Total cost |
items_ordered |
text[] | Array of items ordered |
would_return |
boolean | Would visit again |
notes |
text | Additional notes |
images |
text[] | Array of image URLs |
place_id |
text | Google Places ID |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
User approval and admin status.
| Column | Type | Description |
|---|---|---|
id |
uuid | Primary key |
user_id |
uuid | Foreign key to auth.users (unique) |
email |
text | User email |
status |
text | pending, approved, rejected |
is_admin |
boolean | Admin flag |
requested_at |
timestamp | When user requested access |
approved_at |
timestamp | When approved |
approved_by |
uuid | Admin who approved |
rejection_reason |
text | Reason for rejection |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
User-specific preferences.
| Column | Type | Description |
|---|---|---|
id |
uuid | Primary key |
user_id |
uuid | Foreign key to auth.users |
preference_key |
text | Preference name |
preference_value |
jsonb | Preference value (JSON) |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
Safely executes SELECT queries for AI features.
- Only allows SELECT queries
- Blocks INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, etc.
- Runs with user's RLS context
- Returns results as JSON array
Checks if current user is an admin.
- Returns boolean
- Uses RLS context
Method: POST
Purpose: Check if user exists and their approval status
Request Body:
{
"email": "user@example.com"
}Response:
{
"exists": true,
"approved": true,
"isAdmin": false
}Method: GET
Purpose: Fetch metadata for media entries
Query Parameters:
title: Media titlemedium: Media type (Movie, TV Show, etc.)year: Release year (optional)
Response: Metadata object with title, poster, ratings, etc.
Method: GET
Purpose: Search for media by title
Query Parameters:
query: Search querymedium: Media type filter
Response: Array of search results
Method: GET
Purpose: Direct OMDB API integration
Query Parameters: Standard OMDB API parameters
Method: POST
Purpose: Upload images to Supabase Storage
Request: FormData with image file
Response: Object with image URL
Method: POST
Purpose: Convert natural language to SQL and execute
Request Body:
{
"query": "How many movies did I watch in 2025?",
"workspace": "media"
}Response: Query results with visualization suggestions
Method: POST
Purpose: Execute AI-generated actions
Request Body:
{
"actions": [...],
"workspace": "media"
}Response: Execution results
Method: POST
Purpose: Clean and normalize data
Request Body: Data to clean
Response: Cleaned data
Method: GET
Purpose: Get Google Places details
Query Parameters:
place_id: Google Places ID
Response: Place details object
Each media entry represents a single item (book, movie, etc.). Status changes are stored in a separate history table (media_status_history) so you can visualize progress and trends over time. When an entry's status is updated:
- The main
media_entriesrecord is updated - A new row is inserted into
media_status_history - The UI updates to reflect the change
- Analytics recalculate automatically
The analytics dashboard uses a client-side aggregation approach:
- Data Fetching: Raw entries fetched from Supabase
- Filtering: Global filters applied to base dataset
- Aggregation: Metrics calculated in
useMediaMetricshook:- Counts: Items by medium, status, platform, language, genre, type
- Totals: Time spent, pages read, money spent
- Averages: Ratings by medium and overall
- Trends: Monthly breakdowns for charts
- Visualization: Charts rendered using Recharts
Global filters affect the base dataset before aggregation, ensuring all KPIs and charts stay synchronized.
Metadata fetch flows allow pulling external data and selectively applying it:
- Fetch: User triggers metadata fetch (OMDB, MyDramaList, etc.)
- Retrieve: External API called with title/year
- Override Dialog: User selects which fields to override
- Apply: Selected fields merged into entry form
- Save: Entry saved with enriched data
The "Smart Override" flow ensures you can keep your own values while still using fetched posters, plot summaries, or runtime details.
The application supports multiple workspaces:
- Media Workspace (
/media): Movies, TV shows, books, games, etc. - Food Workspace (
/food): Restaurant visits and dining experiences
Each workspace has:
- Separate data tables
- Independent analytics
- Workspace-specific components
- Shared UI patterns
For TV shows, the application tracks individual episodes:
- Episode History: JSON array of
{ episode: number, watched_at: timestamp } - Progress Tracking:
episodes_watchedcount - Last Watched:
last_watched_attimestamp - Visual Timeline: Episode watch history displayed in timeline
- Navigate to the Media workspace (
/media) - Click the "+" button in the header
- Enter the title and select the medium type
- Optionally click "Fetch Metadata" to pull data from OMDB
- Review fetched data and select fields to override
- Fill in additional details (genre, language, dates, ratings, etc.)
- Click "Save" to create the entry
- Navigate to the Media workspace
- Click "Import" in the header
- Upload a CSV file or paste CSV data
- Map CSV columns to database fields
- Preview the data and adjust mappings if needed
- Click "Import Entries" to bulk import
CSV Format Tips:
- Include at least
title,medium, andstatuscolumns - Dates should be in
YYYY-MM-DDformat - Arrays (genres, languages) can be comma-separated
- Ratings should be numeric values
- Navigate to Analytics (
/media/analyticsor/food/analytics) - Use the Global Filter Bar to filter data:
- Select mediums, statuses, platforms, etc.
- Set date ranges
- Apply multiple filters simultaneously
- View KPI Cards at the top:
- Total items, time spent, money spent, average ratings
- Explore Charts:
- Spending trends over time
- Time consumption by month
- Rating distributions
- Counts by various dimensions
- Click the ✨ (Sparkles) button in the page header
- Type a natural language question:
- "How many movies did I watch in 2025?"
- "Average rating by genre"
- "Total spent on games"
- Click "Analyze" to generate and execute the query
- View results with automatic visualization
- Expand "Generated SQL Query" to see the actual SQL
- Sort: Click column headers to sort
- Filter: Use filter bar or column filters
- Search: Use search box for full-text search
- Columns: Click column selector to show/hide columns
- Click the edit button on any entry
- Modify fields in the dialog
- Click "Save" to update
- Select multiple entries using checkboxes
- Click "Batch Edit" button
- Modify common fields
- Click "Save" to update all selected entries
- Click the delete button on an entry
- Confirm deletion in the dialog
- Entry is permanently removed
- Open a TV show entry
- Navigate to the "Watching" tab
- Click "Mark Episode Watched"
- Select episode number and date
- Episode is added to watch history
- View timeline of watched episodes
- Navigate to Food workspace (
/food) - Click "+" to add a restaurant visit
- Enter restaurant name and location
- Select cuisine types
- Add items ordered and price
- Upload photos (optional)
- Set rating and "Would Return" flag
- Save the entry
- Navigate to Admin panel (
/admin) - View User Requests tab:
- See pending approval requests
- Approve or reject users
- Add rejection reasons
- View Users tab:
- See all users
- Grant/revoke admin status
- View user activity
-
Start Development Server:
bun run dev
-
Make Changes:
- Edit files in
app/,components/,lib/, orhooks/ - Changes hot-reload automatically
- Edit files in
-
Test Changes:
- Navigate to affected pages
- Test functionality
- Check browser console for errors
-
Build for Production:
bun run build
-
Run Production Build:
bun run start
// Component file structure
"use client" // If using hooks or interactivity
import { useState } from "react"
import { ComponentProps } from "./types"
interface ComponentProps {
// Props interface
}
export function Component({ prop }: ComponentProps) {
// Component logic
return (
// JSX
)
}// lib/actions.ts
"use server"
import { createServerClient } from "@/lib/supabase/server"
export async function createEntry(data: CreateEntryInput) {
const supabase = await createServerClient()
// Server-side logic
return { success: true, data }
}// hooks/useMediaEntries.ts
"use client"
import { useState, useEffect } from "react"
import { createClient } from "@/lib/supabase/client"
export function useMediaEntries() {
const [entries, setEntries] = useState([])
// Hook logic
return { entries, loading, error }
}- Use Database Types: Import types from
lib/database.types.ts - Define Interfaces: Create interfaces for component props
- Type Server Actions: Use
ActionResponse<T>for server action returns - Avoid
any: Useunknownand type guards instead
- Tailwind First: Use Tailwind utility classes
- Component Variants: Use
class-variance-authorityfor component variants - Dark Mode: Use
dark:prefix for dark mode styles - Responsive: Use responsive prefixes (
sm:,md:,lg:)
While the project doesn't currently include automated tests, consider:
- Unit Tests: Test utility functions and hooks
- Integration Tests: Test API routes and server actions
- E2E Tests: Test critical user flows
- Component Tests: Test UI components in isolation
- Dynamic Imports: Heavy components loaded on demand
const Dialog = dynamic(() => import("@/components/dialog"))
- Route-based Splitting: Next.js automatically splits by route
- Library Optimization: Configured in
next.config.tsfor tree-shaking
- next/image: Automatic image optimization
- Lazy Loading: Images load as they enter viewport
- Format Conversion: AVIF and WebP formats supported
- Sizing: Responsive image sizes configured
- Server Components: Fetch data on server when possible
- Client-side Caching: Manual cache in hooks
- Optimistic Updates: Update UI before server confirmation
- Debouncing: Search and filter inputs debounced
- Tree Shaking: Unused code eliminated
- Package Optimization: Large libraries optimized in config
- Code Splitting: Routes and components split automatically
- Minification: Production builds minified
- Selective Fields: Only fetch needed columns
- Indexing: Database indexes on frequently queried fields
- Pagination: Consider pagination for large datasets
- RLS Efficiency: RLS policies optimized for performance
- Service Role Key: Never exposed to client
- Session Management: Secure cookie handling
- Magic Links: Passwordless authentication via Supabase
- Session Refresh: Automatic token refresh
- Row Level Security: Database-level access control
- User Isolation: Users can only access their own data
- Input Validation: Server-side validation for all inputs
- SQL Injection Prevention: Parameterized queries only
- Rate Limiting: Consider rate limiting for API routes
- CORS: Configured via Next.js headers
- Input Sanitization: Sanitize user inputs
- Error Messages: Don't expose sensitive info in errors
- File Type Validation: Only allow image types
- Size Limits: 10MB limit configured
- Storage Isolation: Files stored per user
- Virus Scanning: Consider adding virus scanning
- Never Commit:
.env.localin.gitignore - Public Prefix: Only
NEXT_PUBLIC_*vars exposed to client - Secret Management: Use secure secret management in production
-
Install Dependencies:
bun install --production
-
Build Application:
bun run build
-
Start Production Server:
bun run start
Set environment variables in your hosting platform:
- Vercel: Use Environment Variables in project settings
- Netlify: Use Site settings → Environment variables
- Other Platforms: Follow platform-specific instructions
- Create Migration Files: SQL files for schema changes
- Run Migrations: Execute in Supabase SQL editor
- Test Migrations: Test in development first
- Backup: Always backup before migrations
- Enable RLS: Ensure RLS is enabled on all tables
- Set Policies: Configure RLS policies for production
- Storage Buckets: Create storage buckets for file uploads
- API Keys: Rotate keys periodically
Consider setting up:
- Error Tracking: Sentry, LogRocket, etc.
- Analytics: Vercel Analytics, Google Analytics
- Performance Monitoring: Web Vitals tracking
- Database Monitoring: Supabase dashboard metrics
bun run dev- Start development server (with hot reload)bun run build- Build production bundlebun run start- Start production serverbun run lint- Run ESLint
Symptoms: Analytics page shows no data or incorrect metrics
Solutions:
- Check RLS policies allow reads for authenticated user
- Verify entries table is populated
- Check browser console for errors
- Verify filters aren't excluding all data
- Check
useMediaMetricshook calculations
Symptoms: "Failed to fetch metadata" errors
Solutions:
- Verify
OMDB_API_KEYis set in.env.local - Check OMDB API key is valid
- Check rate limits (OMDB has daily limits)
- Verify network connectivity
- Check API endpoint URLs
Symptoms: Image uploads fail or don't appear
Solutions:
- Verify Supabase storage bucket exists
- Check storage bucket permissions
- Verify file size is under 10MB
- Check file type is allowed (jpg, png, webp, etc.)
- Check Supabase storage policies
Symptoms: Login fails or users can't access app
Solutions:
- Verify
SUPABASE_SERVICE_ROLE_KEYis set - Check user profile exists in
user_profilestable - Verify user status is
approved - Check middleware is running correctly
- Verify Supabase Auth is configured
Symptoms: bun run build fails
Solutions:
- Check TypeScript errors:
bunx tsc --noEmit - Verify all environment variables are set
- Check for missing dependencies
- Clear
.nextfolder and rebuild - Check Node.js/Bun version compatibility
Symptoms: Slow page loads or laggy interactions
Solutions:
- Check bundle size in build output
- Verify images are optimized
- Check database query performance
- Enable React DevTools Profiler
- Check network tab for slow requests
- Browser Console: Check for JavaScript errors
- Network Tab: Inspect API requests and responses
- React DevTools: Inspect component state and props
- Supabase Logs: Check Supabase dashboard for database errors
- Next.js Logs: Check server logs for server-side errors
- Check Documentation: Review this README and code comments
- Search Issues: Check GitHub issues for similar problems
- Supabase Docs: Consult Supabase documentation
- Next.js Docs: Consult Next.js documentation
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Clone your fork
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Test thoroughly
- Commit your changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow existing code style
- Use TypeScript for type safety
- Add comments for complex logic
- Keep components focused and small
- Use meaningful variable names
- Use clear, descriptive messages
- Reference issues when applicable
- Follow conventional commits format
- Ensure code builds without errors
- Test all affected functionality
- Update documentation if needed
- Request review from maintainers
- Address feedback promptly
See LICENSE file for details.
- Built with Next.js
- UI components from Radix UI
- Styling with Tailwind CSS
- Database powered by Supabase
- Metadata from OMDB API
- AI powered by Google Gemini
- Icons from Lucide
- Charts from Recharts
Last Updated: February 2026