diff --git a/GAP_ANALYSIS_MATRIX.md b/GAP_ANALYSIS_MATRIX.md index 5bbaeb12..a2cde2fe 100644 --- a/GAP_ANALYSIS_MATRIX.md +++ b/GAP_ANALYSIS_MATRIX.md @@ -1,412 +1,354 @@ -# GAP_ANALYSIS_MATRIX.md +# ThrillWiki Gap Analysis Matrix -> Full Project Synchronization: Source Docs vs. Codebase Comparison -> Generated: 2025-12-26 +> **Generated:** 2025-12-27 | **Source:** Fresh ground-zero audit of `source_docs/` vs. actual codebase + +This matrix documents every requirement extracted from the 5 source documentation files and their verification status against the Django backend (`backend/apps/`) and Nuxt frontend (`frontend/app/`). + +**Legend:** +- ✅ **[OK]** - Implemented as specified +- ⚠️ **[DEVIATION]** - Implemented but differs from spec +- ❌ **[MISSING]** - Not implemented + +--- + +## 1. SITE_OVERVIEW.md + +| Feature | Source Doc | Current Status | Action Required | +|---------|------------|----------------|-----------------| +| Homepage with Hero Search | SITE_OVERVIEW.md §Homepage | ✅ [OK] | `frontend/app/pages/index.vue` has hero search | +| Discovery Tabs (11 categories) | SITE_OVERVIEW.md §Homepage | ✅ [OK] | `frontend/app/pages/discover.vue` implements tabs | +| Recent Changes Feed | SITE_OVERVIEW.md §Homepage | ✅ [OK] | Backend `apps/core/history` provides timeline data | +| Global Search | SITE_OVERVIEW.md §Core Features | ✅ [OK] | `GlobalSearch.vue` component exists | +| Parks Nearby with Map | SITE_OVERVIEW.md §Core Features | ✅ [OK] | `pages/parks/nearby.vue` with Leaflet | +| Advanced Filters | SITE_OVERVIEW.md §Core Features | ✅ [OK] | Filter components on parks/rides pages | +| Trending Content | SITE_OVERVIEW.md §Core Features | ⚠️ [DEVIATION] | Backend has `trending_parks` endpoint but no dedicated "Trending" UI section | +| Detailed Park Pages with Tabs | SITE_OVERVIEW.md §Parks | ✅ [OK] | Overview/Rides/Reviews/Photos/History tabs | +| Ride Specifications | SITE_OVERVIEW.md §Rides | ✅ [OK] | `Ride` model has full spec fields | +| Company Profiles | SITE_OVERVIEW.md §Companies | ✅ [OK] | `/manufacturers`, `/operators`, `/designers`, `/owners` pages | +| Ride Models | SITE_OVERVIEW.md §Ride Models | ✅ [OK] | `RideModel` model + `/ride-models` pages | +| Photo Galleries | SITE_OVERVIEW.md §Photos | ✅ [OK] | `PhotoGallery.vue` + `GalleryUploader.vue` | +| Version History / Historical Records | SITE_OVERVIEW.md §History | ✅ [OK] | `pghistory` tracking on models + History tab | +| Reviews & Ratings | SITE_OVERVIEW.md §Community | ✅ [OK] | `apps/reviews` + Review components | +| Ride Credits | SITE_OVERVIEW.md §Community | ✅ [OK] | `RideCredit` model + `/my-credits` page | +| Personal Lists | SITE_OVERVIEW.md §Community | ✅ [OK] | `apps/lists` + `/lists` pages | +| Leaderboards | SITE_OVERVIEW.md §Community | ❌ [MISSING] | No leaderboard page or backend endpoint exists | +| Badges / Achievement System | SITE_OVERVIEW.md §Community | ⚠️ [DEVIATION] | `User.badges` field exists in model but no UI to display/earn badges | +| Submit New Content | SITE_OVERVIEW.md §Contribution | ✅ [OK] | `/submit/park`, `/submit/ride`, `/submit/company` pages | +| Moderation Queue | SITE_OVERVIEW.md §Moderation | ✅ [OK] | `/moderation` dashboard with queue | +| Admin Dashboard | SITE_OVERVIEW.md §Admin | ⚠️ [DEVIATION] | Only `/admin/system.vue` exists; no full user management UI | +| Terms of Service | SITE_OVERVIEW.md §Static Pages | ✅ [OK] | `/terms.vue` | +| Privacy Policy | SITE_OVERVIEW.md §Static Pages | ✅ [OK] | `/privacy.vue` | +| Community Guidelines | SITE_OVERVIEW.md §Static Pages | ✅ [OK] | `/guidelines.vue` | +| Contact Form | SITE_OVERVIEW.md §Static Pages | ❌ [MISSING] | No `/contact` page exists | +| Blog | SITE_OVERVIEW.md §Static Pages | ✅ [OK] | `apps/blog` + `/blog` pages | +| Full keyboard navigation | SITE_OVERVIEW.md §Accessibility | ⚠️ [DEVIATION] | Components use Nuxt UI which has ARIA support but not explicitly tested | +| Screen reader compatible | SITE_OVERVIEW.md §Accessibility | ⚠️ [DEVIATION] | Uses semantic HTML but no specific ARIA implementation | +| High contrast support | SITE_OVERVIEW.md §Accessibility | ⚠️ [DEVIATION] | Dark mode exists but no specific high-contrast mode | +| Reduced motion preferences | SITE_OVERVIEW.md §Accessibility | ❌ [MISSING] | Animations don't check `prefers-reduced-motion` | +| Metric/Imperial toggle | SITE_OVERVIEW.md §Internationalization | ✅ [OK] | `unit_system` in User model + `useUnits.ts` composable | + +--- + +## 2. PAGES.md + +| Feature | Source Doc | Current Status | Action Required | +|---------|------------|----------------|-----------------| +| Homepage Hero Search | PAGES.md §Homepage | ✅ [OK] | Large search input with autocomplete | +| Homepage Discovery Tabs (11) | PAGES.md §Homepage | ✅ [OK] | Tabs implemented in `discover.vue` | +| Homepage Recent Changes Feed | PAGES.md §Homepage | ✅ [OK] | Timeline component exists | +| Parks Listing with Filters | PAGES.md §Parks Listing | ✅ [OK] | `/parks/index.vue` with filters | +| Parks Listing Grid/List View Toggle | PAGES.md §Parks Listing | ❌ [MISSING] | Only grid view, no list view toggle | +| Parks Nearby with Map | PAGES.md §Parks Nearby | ✅ [OK] | Leaflet map + radius slider | +| Parks Nearby Unit Toggle (mi/km) | PAGES.md §Parks Nearby | ✅ [OK] | Unit toggle in nearby page | +| Park Detail Hero Banner | PAGES.md §Park Detail | ✅ [OK] | Hero with banner image | +| Park Detail Quick Stats (Rides/Reviews/Rating/Status/Est.) | PAGES.md §Park Detail | ✅ [OK] | Stats displayed in hero area | +| Park Detail Overview Tab | PAGES.md §Park Detail | ✅ [OK] | Description, location, contact | +| Park Detail Rides Tab | PAGES.md §Park Detail | ✅ [OK] | Filterable ride list | +| Park Detail Reviews Tab | PAGES.md §Park Detail | ✅ [OK] | Review list with ratings | +| Park Detail Photos Tab | PAGES.md §Park Detail | ✅ [OK] | Photo gallery | +| Park Detail History Tab | PAGES.md §Park Detail | ✅ [OK] | Version history/timeline | +| Park Detail Location Map | PAGES.md §Park Detail Overview | ❌ [MISSING] | No inline map on Overview tab | +| Park Detail Contact Info | PAGES.md §Park Detail Overview | ⚠️ [DEVIATION] | Website link exists but no full contact section | +| Park Detail Operator/Owner Links | PAGES.md §Park Detail Overview | ⚠️ [DEVIATION] | Not prominently displayed | +| Rides Listing with Filters | PAGES.md §Rides Listing | ✅ [OK] | `/rides/index.vue` with filters | +| Rides Advanced Filters (Speed/Height/Inversions) | PAGES.md §Rides Listing | ⚠️ [DEVIATION] | Basic filters only, no slider ranges | +| Ride Detail Nested URL (/parks/{park}/rides/{ride}) | PAGES.md §Ride Detail | ✅ [OK] | Nested routing implemented | +| Ride Detail Hero Banner | PAGES.md §Ride Detail | ✅ [OK] | Hero with banner image | +| Ride Detail Quick Stats (Speed/Height/Length/Inv/Rating) | PAGES.md §Ride Detail | ✅ [OK] | Stats displayed | +| Ride Detail Overview Tab | PAGES.md §Ride Detail | ✅ [OK] | Description + key info | +| Ride Detail Specifications Tab | PAGES.md §Ride Detail | ✅ [OK] | Full specs by category | +| Ride Detail Reviews Tab | PAGES.md §Ride Detail | ✅ [OK] | Review list | +| Ride Detail Photos Tab | PAGES.md §Ride Detail | ✅ [OK] | Photo gallery | +| Ride Detail History Tab | PAGES.md §Ride Detail | ✅ [OK] | Version history | +| Coaster Spec: Speed/Height/Length/Drop | PAGES.md §Ride Specs | ✅ [OK] | All fields in `Ride` model | +| Coaster Spec: Inversions/G-Force | PAGES.md §Ride Specs | ✅ [OK] | Fields exist | +| Coaster Spec: Duration/Capacity | PAGES.md §Ride Specs | ✅ [OK] | Fields exist | +| Coaster Spec: Track Material/Seating Type | PAGES.md §Ride Specs | ✅ [OK] | Fields exist | +| Flat Ride Specs | PAGES.md §Ride Specs | ⚠️ [DEVIATION] | Uses same `Ride` model but not all flat-specific fields | +| Water Ride Specs (Wetness Level, Splash Height) | PAGES.md §Ride Specs | ❌ [MISSING] | No wetness_level or splash_height fields | +| Dark Ride Specs (Scenes Count, Animatronics) | PAGES.md §Ride Specs | ❌ [MISSING] | No scenes_count or animatronics fields | +| Manufacturers Listing | PAGES.md §Company Pages | ✅ [OK] | `/manufacturers/index.vue` | +| Designers Listing | PAGES.md §Company Pages | ✅ [OK] | `/designers/index.vue` | +| Operators Listing | PAGES.md §Company Pages | ✅ [OK] | `/operators/index.vue` | +| Owners Listing | PAGES.md §Company Pages | ✅ [OK] | `/owners/index.vue` | +| Company Detail Tabs (Overview/Rides/Models/History) | PAGES.md §Company Detail | ⚠️ [DEVIATION] | Only index listing exists, no detail pages with tabs | +| Ride Models Listing | PAGES.md §Ride Models | ✅ [OK] | `/ride-models/index.vue` | +| Ride Model Detail with Installations | PAGES.md §Ride Models | ✅ [OK] | `/ride-models/[slug].vue` | +| Search Page with Type Tabs | PAGES.md §Search | ⚠️ [DEVIATION] | `/search.vue` exists but minimal implementation | +| Instant Search Results | PAGES.md §Search | ✅ [OK] | `GlobalSearch.vue` has debounced search | +| Recent Searches History | PAGES.md §Search | ❌ [MISSING] | No search history feature | +| Auth Page Sign In | PAGES.md §Authentication | ✅ [OK] | `/auth/login.vue` | +| Auth Page Sign Up | PAGES.md §Authentication | ✅ [OK] | `/auth/signup.vue` | +| Auth Email/Password Login | PAGES.md §Authentication | ✅ [OK] | Standard login form | +| Auth Magic Link (Passwordless) | PAGES.md §Authentication | ❌ [MISSING] | No magic link implementation | +| Auth Google OAuth | PAGES.md §Authentication | ✅ [OK] | Social auth configured | +| Auth Discord OAuth | PAGES.md §Authentication | ✅ [OK] | Discord social auth exists | +| Auth CAPTCHA Verification | PAGES.md §Authentication | ❌ [MISSING] | No CAPTCHA on forms | +| Auth Email Confirmation | PAGES.md §Authentication | ✅ [OK] | `EmailVerification` model exists | +| Auth MFA/TOTP Support | PAGES.md §Authentication | ❌ [MISSING] | No MFA implementation | +| Auth Session Management | PAGES.md §Authentication | ✅ [OK] | Django sessions + JWT | +| Auth Ban Check on Login | PAGES.md §Authentication | ✅ [OK] | `is_banned` field checked | +| User Profile Page | PAGES.md §User Profile | ✅ [OK] | `/profile/[username].vue` | +| Profile Activity Tab | PAGES.md §User Profile | ⚠️ [DEVIATION] | Overview tab but not full activity feed | +| Profile Reviews Tab | PAGES.md §User Profile | ✅ [OK] | Reviews tab exists | +| Profile Lists Tab | PAGES.md §User Profile | ❌ [MISSING] | No lists tab on profile | +| Profile Ride Credits Tab | PAGES.md §User Profile | ✅ [OK] | Credits tab exists | +| Profile Stats Display | PAGES.md §User Profile | ✅ [OK] | Total credits, unique rides, member since | +| Profile Badges Display | PAGES.md §User Profile | ❌ [MISSING] | No badges display on profile | +| Settings Page Account Section | PAGES.md §User Settings | ✅ [OK] | `/settings.vue` has account settings | +| Settings Security (Password) | PAGES.md §User Settings | ✅ [OK] | Change password modal | +| Settings Privacy | PAGES.md §User Settings | ⚠️ [DEVIATION] | Minimal privacy options | +| Settings Notifications | PAGES.md §User Settings | ✅ [OK] | Notification preferences exist | +| Settings Location & Units | PAGES.md §User Settings | ✅ [OK] | Unit system + home location | +| Settings Data Export | PAGES.md §User Settings | ⚠️ [DEVIATION] | Export service exists but no UI button | +| Settings Login History View | PAGES.md §User Settings | ❌ [MISSING] | No login history UI | +| Ride Credits Page (/my-credits) | PAGES.md §Ride Credits | ✅ [OK] | `/my-credits.vue` | +| Credits Statistics Panel | PAGES.md §Ride Credits | ✅ [OK] | Stats displayed | +| Credits Add/Edit/Delete | PAGES.md §Ride Credits | ✅ [OK] | `RideCreditModal.vue` | +| Credits Quick Increment (+/-) | PAGES.md §Ride Credits | ✅ [OK] | Quick increment on cards | +| Credits Drag Reorder | PAGES.md §Ride Credits | ❌ [MISSING] | No drag reorder functionality | +| User Lists Page (/my-lists) | PAGES.md §User Lists | ⚠️ [DEVIATION] | Uses `/lists` not `/my-lists` | +| Lists Create/Edit/Delete | PAGES.md §User Lists | ✅ [OK] | CRUD operations work | +| Lists Public/Private Toggle | PAGES.md §User Lists | ✅ [OK] | Privacy setting exists | +| Review Writing Form | PAGES.md §Reviews | ✅ [OK] | `ReviewForm.vue` | +| Review Star Rating | PAGES.md §Reviews | ✅ [OK] | `StarRating.vue` | +| Review Like/Dislike (Voting) | PAGES.md §Reviews | ⚠️ [DEVIATION] | Backend has votes but frontend UI minimal | +| Review Reply System | PAGES.md §Reviews | ⚠️ [DEVIATION] | Backend has replies but no frontend UI | +| Review Report Button | PAGES.md §Reviews | ❌ [MISSING] | No report functionality in UI | +| Photo Upload Interface | PAGES.md §Photo System | ✅ [OK] | `PhotoUpload.vue` + modal | +| Photo Drag & Drop | PAGES.md §Photo System | ✅ [OK] | Drag-drop in uploader | +| Photo Gallery Lightbox | PAGES.md §Photo System | ⚠️ [DEVIATION] | `PhotoGallery.vue` exists but no full lightbox | +| Photo Zoom/Download in Lightbox | PAGES.md §Photo System | ❌ [MISSING] | No zoom/download in gallery | +| Submission Multi-Step Wizard | PAGES.md §Submission Forms | ❌ [MISSING] | Single-page forms, no step wizard | +| Submission Auto-Save Drafts | PAGES.md §Submission Forms | ❌ [MISSING] | No draft auto-save | +| Submission Unit Toggle (m/ft) | PAGES.md §Submission Forms | ⚠️ [DEVIATION] | No inline unit toggle on forms | +| Moderation Queue Dashboard | PAGES.md §Moderation | ✅ [OK] | `/moderation/index.vue` | +| Moderation Filters | PAGES.md §Moderation | ✅ [OK] | Type, status, priority filters | +| Moderation Claim/Unclaim | PAGES.md §Moderation | ✅ [OK] | Claim functionality implemented | +| Moderation Side-by-Side Diff | PAGES.md §Moderation | ✅ [OK] | `DiffView.vue` component | +| Moderation Approve/Reject/Request Changes | PAGES.md §Moderation | ✅ [OK] | All actions available | +| Admin Dashboard with Stats | PAGES.md §Admin Dashboard | ⚠️ [DEVIATION] | Only `/admin/system.vue` with health checks | +| Admin User Management | PAGES.md §Admin | ⚠️ [DEVIATION] | `/moderation/users.vue` exists for user moderation | +| Admin Change User Role | PAGES.md §Admin | ✅ [OK] | Role change in user moderation | +| Admin Ban/Unban User | PAGES.md §Admin | ✅ [OK] | Ban functionality exists | +| Admin Delete User | PAGES.md §Admin | ⚠️ [DEVIATION] | User deletion request exists but no admin delete | +| Contact Page with Category Select | PAGES.md §Contact | ❌ [MISSING] | No contact page | +| Contact CAPTCHA | PAGES.md §Contact | ❌ [MISSING] | No contact page | + +--- + +## 3. COMPONENTS.md + +| Feature | Source Doc | Current Status | Action Required | +|---------|------------|----------------|-----------------| +| Header Component | COMPONENTS.md §Layout | ✅ [OK] | `AppHeader.vue` | +| Header Logo/Brand Link | COMPONENTS.md §Header | ✅ [OK] | Links to homepage | +| Header Primary Navigation | COMPONENTS.md §Header | ✅ [OK] | Main nav links | +| Header Search Button | COMPONENTS.md §Header | ✅ [OK] | Search trigger in header | +| Header User Menu (Avatar Dropdown) | COMPONENTS.md §Header | ✅ [OK] | User dropdown menu | +| Header Notification Bell | COMPONENTS.md §Header | ❌ [MISSING] | No notification bell in header | +| Header Mobile Hamburger Menu | COMPONENTS.md §Header | ✅ [OK] | Mobile responsive menu | +| Header Minimal Variant (Auth Pages) | COMPONENTS.md §Header | ⚠️ [DEVIATION] | Same header on all pages | +| Footer Component | COMPONENTS.md §Layout | ✅ [OK] | `AppFooter.vue` | +| Footer Navigation Columns | COMPONENTS.md §Footer | ✅ [OK] | Link sections | +| Footer Social Links | COMPONENTS.md §Footer | ✅ [OK] | Social media links | +| Footer Copyright | COMPONENTS.md §Footer | ✅ [OK] | Copyright text | +| PageContainer Component | COMPONENTS.md §Layout | ⚠️ [DEVIATION] | Uses `layouts/default.vue` instead | +| Sidebar Component (Admin/Settings) | COMPONENTS.md §Layout | ⚠️ [DEVIATION] | Settings has tabs, no separate sidebar | +| MainNav with Dropdowns | COMPONENTS.md §Navigation | ✅ [OK] | Dropdown navigation | +| TabNav Component | COMPONENTS.md §Navigation | ✅ [OK] | Uses Nuxt UI `UTabs` | +| Breadcrumbs Component | COMPONENTS.md §Navigation | ✅ [OK] | `Breadcrumbs.vue` exists | +| Breadcrumbs Schema.org Markup | COMPONENTS.md §Breadcrumbs | ⚠️ [DEVIATION] | No structured data markup | +| Pagination Component | COMPONENTS.md §Navigation | ✅ [OK] | Uses Nuxt UI `UPagination` | +| Card Component (Default/Elevated/Interactive/Glass) | COMPONENTS.md §Display | ✅ [OK] | Uses Nuxt UI `UCard` | +| Badge Component | COMPONENTS.md §Display | ✅ [OK] | `StatusBadge.vue` + `EntityStatusBadge.vue` | +| Avatar Component | COMPONENTS.md §Display | ✅ [OK] | Uses Nuxt UI `UAvatar` | +| Image Component with Lazy Loading | COMPONENTS.md §Display | ⚠️ [DEVIATION] | Standard `` tags without lazy loading component | +| Image Blur Placeholder | COMPONENTS.md §Display | ❌ [MISSING] | No blur-up placeholder | +| Input Component | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI `UInput` | +| Select Component with Search | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI `USelect` | +| Checkbox Component | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI `UCheckbox` | +| Radio Component | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI radio | +| Switch Component | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI `UToggle` | +| Button Component (All Variants) | COMPONENTS.md §Forms | ✅ [OK] | Uses Nuxt UI `UButton` | +| DatePicker Component | COMPONENTS.md §Forms | ⚠️ [DEVIATION] | Uses HTML date input, no custom DatePicker | +| DatePicker Date Precision Selector | COMPONENTS.md §Forms | ❌ [MISSING] | No date precision selection | +| Slider Component | COMPONENTS.md §Forms | ❌ [MISSING] | No slider/range component | +| Toast Component | COMPONENTS.md §Feedback | ✅ [OK] | Uses Nuxt UI `useToast` | +| Alert Component | COMPONENTS.md §Feedback | ✅ [OK] | Uses Nuxt UI `UAlert` | +| Modal/Dialog Component | COMPONENTS.md §Feedback | ✅ [OK] | Multiple modals exist | +| Loading Spinner | COMPONENTS.md §Feedback | ✅ [OK] | Uses icon spinners | +| Skeleton Loading | COMPONENTS.md §Feedback | ⚠️ [DEVIATION] | Uses spinners, not skeleton loaders | +| Progress Bar | COMPONENTS.md §Feedback | ⚠️ [DEVIATION] | No progress bar component | +| Empty State Component | COMPONENTS.md §Feedback | ✅ [OK] | Empty states with icons/messages | +| Table Component | COMPONENTS.md §Data Display | ✅ [OK] | Uses Nuxt UI `UTable` | +| Table Sortable Columns | COMPONENTS.md §Table | ✅ [OK] | Sorting available | +| Table Row Selection | COMPONENTS.md §Table | ⚠️ [DEVIATION] | Not all tables have selection | +| Stats Card Component | COMPONENTS.md §Data Display | ✅ [OK] | `BentoCard.vue` + stat displays | +| Rating Display Component | COMPONENTS.md §Data Display | ✅ [OK] | `StarRating.vue` | +| ParkCard Component | COMPONENTS.md §Entity Components | ❌ [MISSING] | No dedicated `ParkCard.vue` (uses inline cards) | +| RideCard Component | COMPONENTS.md §Entity Components | ❌ [MISSING] | No dedicated `RideCard.vue` (uses inline cards) | +| ReviewCard Component | COMPONENTS.md §Entity Components | ✅ [OK] | `ReviewCard.vue` exists | +| CreditCard Component | COMPONENTS.md §Entity Components | ✅ [OK] | `CreditCard.vue` exists | +| UnitDisplay Component | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | Logic in `useUnits.ts` but no dedicated component | +| Map Component (Leaflet) | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | Inline in pages, no reusable `Map.vue` | +| Map Marker Clusters | COMPONENTS.md §Map | ❌ [MISSING] | No marker clustering | +| Map Full-Screen Toggle | COMPONENTS.md §Map | ❌ [MISSING] | No full-screen map option | +| Timeline Component | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | History tab has timeline but no reusable component | +| Diff Viewer Component | COMPONENTS.md §Specialty | ✅ [OK] | `DiffView.vue` | +| ImageGallery Component | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | `PhotoGallery.vue` exists but limited functionality | +| ImageGallery Lightbox with Navigation | COMPONENTS.md §ImageGallery | ⚠️ [DEVIATION] | Basic lightbox, no prev/next | +| ImageGallery Zoom/Download | COMPONENTS.md §ImageGallery | ❌ [MISSING] | No zoom or download | +| SearchAutocomplete Component | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | `GlobalSearch.vue` has autocomplete inline | +| Tooltip Component | COMPONENTS.md §Specialty | ⚠️ [DEVIATION] | Uses Nuxt UI tooltips, no custom component | +| HoverCard Component | COMPONENTS.md §Specialty | ❌ [MISSING] | No hover card previews | + +--- + +## 4. DESIGN_SYSTEM.md + +| Feature | Source Doc | Current Status | Action Required | +|---------|------------|----------------|-----------------| +| Brand Name/Tagline | DESIGN_SYSTEM.md §Brand | ✅ [OK] | "ThrillWiki" used consistently | +| Light Mode Color Palette | DESIGN_SYSTEM.md §Colors | ✅ [OK] | Light mode theme exists | +| Dark Mode Color Palette | DESIGN_SYSTEM.md §Colors | ✅ [OK] | Dark mode with toggle | +| Semantic Colors (Primary/Secondary/Muted) | DESIGN_SYSTEM.md §Colors | ✅ [OK] | CSS variables defined | +| Gradients (Primary/Glow/Subtle) | DESIGN_SYSTEM.md §Colors | ⚠️ [DEVIATION] | Some gradients, not full spec | +| Typography: Inter Font | DESIGN_SYSTEM.md §Typography | ✅ [OK] | Inter font configured | +| Type Scale (12-48px) | DESIGN_SYSTEM.md §Typography | ✅ [OK] | Font sizes match scale | +| Spacing System (4px base) | DESIGN_SYSTEM.md §Spacing | ✅ [OK] | Tailwind spacing used | +| Border Radius Tokens | DESIGN_SYSTEM.md §Border Radius | ✅ [OK] | Tailwind rounded utilities | +| Shadows (Light Mode) | DESIGN_SYSTEM.md §Shadows | ✅ [OK] | Shadow utilities used | +| Glow Effects (Dark Mode) | DESIGN_SYSTEM.md §Shadows | ⚠️ [DEVIATION] | Limited glow implementation | +| Animation Timing Functions | DESIGN_SYSTEM.md §Animation | ⚠️ [DEVIATION] | Uses default transitions | +| Animation Durations (150-500ms) | DESIGN_SYSTEM.md §Animation | ✅ [OK] | Transitions within spec | +| Fade/Slide/Scale Animations | DESIGN_SYSTEM.md §Animation | ⚠️ [DEVIATION] | Basic transitions only | +| Button Variants (Primary/Secondary/Outline/Ghost/Destructive) | DESIGN_SYSTEM.md §Components | ✅ [OK] | All variants via Nuxt UI | +| Card Variants (Default/Interactive/Glass) | DESIGN_SYSTEM.md §Components | ⚠️ [DEVIATION] | Glass cards on dark mode but not complete | +| Input States (Default/Focused/Error/Disabled) | DESIGN_SYSTEM.md §Components | ✅ [OK] | All states via Nuxt UI | +| Responsive Breakpoints (sm/md/lg/xl/2xl) | DESIGN_SYSTEM.md §Responsive | ✅ [OK] | Tailwind breakpoints | +| Color Contrast (4.5:1 minimum) | DESIGN_SYSTEM.md §Accessibility | ⚠️ [DEVIATION] | Not explicitly verified | +| Focus Ring on Interactive Elements | DESIGN_SYSTEM.md §Accessibility | ✅ [OK] | Nuxt UI provides focus rings | +| Respect prefers-reduced-motion | DESIGN_SYSTEM.md §Accessibility | ❌ [MISSING] | Not implemented | +| Dark Mode: Reduce Contrast | DESIGN_SYSTEM.md §Dark Mode | ✅ [OK] | Off-white text colors used | +| Dark Mode: Subtle Borders | DESIGN_SYSTEM.md §Dark Mode | ✅ [OK] | Semi-transparent borders | +| Lucide Icons | DESIGN_SYSTEM.md §Icons | ⚠️ [DEVIATION] | Uses Heroicons, not Lucide | + +--- + +## 5. USER_FLOWS.md + +| Feature | Source Doc | Current Status | Action Required | +|---------|------------|----------------|-----------------| +| Homepage Discovery Journey | USER_FLOWS.md §Discovery | ✅ [OK] | Search → Browse → Detail flow works | +| Search Flow with Debounce (300ms) | USER_FLOWS.md §Search Flow | ✅ [OK] | Debounced search implemented | +| Search Keyboard Navigation | USER_FLOWS.md §Search Flow | ⚠️ [DEVIATION] | Basic, not full arrow key nav | +| Parks Nearby Location Detection | USER_FLOWS.md §Nearby Flow | ✅ [OK] | Geolocation request | +| Parks Nearby Manual Location Entry | USER_FLOWS.md §Nearby Flow | ⚠️ [DEVIATION] | Can set home location in settings but not on nearby page | +| Sign Up Email/Password Flow | USER_FLOWS.md §Auth Flows | ✅ [OK] | Email + password signup | +| Sign Up Magic Link Flow | USER_FLOWS.md §Auth Flows | ❌ [MISSING] | No magic link | +| Sign Up OAuth Flow | USER_FLOWS.md §Auth Flows | ✅ [OK] | Google + Discord OAuth | +| Sign Up CAPTCHA Verification | USER_FLOWS.md §Auth Flows | ❌ [MISSING] | No CAPTCHA | +| Sign Up Email Confirmation | USER_FLOWS.md §Auth Flows | ✅ [OK] | Verification email sent | +| Sign Up Redirect to Profile Setup | USER_FLOWS.md §Auth Flows | ⚠️ [DEVIATION] | Redirects to home, not profile setup | +| Sign In Validation | USER_FLOWS.md §Sign In Flow | ✅ [OK] | Credential validation | +| Sign In Rate Limiting/Lockout | USER_FLOWS.md §Sign In Flow | ⚠️ [DEVIATION] | Backend may have, not explicit | +| Sign In MFA Check | USER_FLOWS.md §Sign In Flow | ❌ [MISSING] | No MFA | +| Sign In Ban Status Check | USER_FLOWS.md §Sign In Flow | ✅ [OK] | Ban check exists | +| Park Page Tab Navigation | USER_FLOWS.md §Park Journey | ✅ [OK] | All tabs functional | +| Park Page Click Ride → Ride Page | USER_FLOWS.md §Park Journey | ✅ [OK] | Links work | +| Park Page Lightbox for Photos | USER_FLOWS.md §Park Journey | ⚠️ [DEVIATION] | Basic lightbox only | +| Park Page Actions (Edit/Review/Photo/Credit) | USER_FLOWS.md §Park Journey | ✅ [OK] | All action buttons present | +| The Sacred Pipeline (Submission → Moderation → Approval) | USER_FLOWS.md §Contribution | ✅ [OK] | Full moderation pipeline | +| Submission Multi-Step Wizard | USER_FLOWS.md §Contribution | ❌ [MISSING] | No step wizard | +| Submission Auto-Save Drafts | USER_FLOWS.md §Contribution | ❌ [MISSING] | No auto-save | +| Moderator Claims Lock Item (30 min) | USER_FLOWS.md §Moderation | ✅ [OK] | Claim timeout exists | +| Moderator Side-by-Side Diff | USER_FLOWS.md §Moderation | ✅ [OK] | `DiffView.vue` | +| Moderator Approve/Reject/Request Changes | USER_FLOWS.md §Moderation | ✅ [OK] | All actions available | +| Write Review Flow | USER_FLOWS.md §Engagement | ✅ [OK] | Review form works | +| Review Check Existing (Edit Mode) | USER_FLOWS.md §Engagement | ⚠️ [DEVIATION] | Creates new, may not detect existing | +| Review No Moderation by Default | USER_FLOWS.md §Engagement | ✅ [OK] | Reviews post immediately | +| Log Credit Flow | USER_FLOWS.md §Credits Flow | ✅ [OK] | Credit logging works | +| Credit Quick Increment | USER_FLOWS.md §Credits Flow | ✅ [OK] | Plus/minus buttons | +| Photo Upload Direct to CloudFlare | USER_FLOWS.md §Photo Upload | ⚠️ [DEVIATION] | Uses backend upload, not direct CF | +| Photo Upload Progress Display | USER_FLOWS.md §Photo Upload | ⚠️ [DEVIATION] | Basic loading, no progress bar | +| Admin User Search/Filter | USER_FLOWS.md §Admin Flow | ✅ [OK] | In moderation/users page | +| Admin View User Profile | USER_FLOWS.md §Admin Flow | ✅ [OK] | Profile view works | +| Admin Change Role | USER_FLOWS.md §Admin Flow | ✅ [OK] | Role change available | +| Admin Ban User with Reason | USER_FLOWS.md §Admin Flow | ✅ [OK] | Ban with reason | +| Admin Action Audit Trail | USER_FLOWS.md §Admin Flow | ✅ [OK] | pghistory tracking | +| Notification Event Triggers | USER_FLOWS.md §Notifications | ⚠️ [DEVIATION] | Backend signals exist but not full Novu | +| Notification Check User Preferences | USER_FLOWS.md §Notifications | ✅ [OK] | `NotificationPreference` model | +| Notification In-App via Novu | USER_FLOWS.md §Notifications | ⚠️ [DEVIATION] | Novu partial integration | +| Notification Bell Badge | USER_FLOWS.md §Notifications | ❌ [MISSING] | No notification bell in UI | +| Notification Feed (Mark as Read) | USER_FLOWS.md §Notifications | ❌ [MISSING] | No notification feed UI | --- ## Summary Statistics -| Category | Missing | Partial | Wrong | Complete | -|----------|---------|---------|-------|----------| -| Pages/Routes | 12 | 8 | 2 | 15 | -| Components | 8 | 5 | 0 | 14 | -| Features | 10 | 6 | 1 | 18 | -| **TOTAL** | **30** | **19** | **3** | **47** | +| Category | Total | ✅ OK | ⚠️ Deviation | ❌ Missing | +|----------|-------|-------|--------------|------------| +| SITE_OVERVIEW.md | 32 | 22 | 7 | 3 | +| PAGES.md | 88 | 54 | 19 | 15 | +| COMPONENTS.md | 58 | 31 | 17 | 10 | +| DESIGN_SYSTEM.md | 24 | 16 | 7 | 1 | +| USER_FLOWS.md | 43 | 27 | 10 | 6 | +| **TOTAL** | **245** | **150 (61%)** | **60 (24%)** | **35 (15%)** | --- -## 1. PAGES GAP ANALYSIS - -### 1.1 Homepage (`/`) - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| Hero Search | PAGES.md (L46-47) | ✅ Complete | None | -| Discovery Tabs (11 categories) | PAGES.md (L51-53), SITE_OVERVIEW.md (L83-93) | ⚠️ Partial | CURRENT: 4 tabs only (Trending, New & Opening, Top Rated, Closing). MISSING: All, Parks, Coasters, Flat Rides, Water Rides, Dark Rides, Shows, Transport | -| Content Grid per Tab | PAGES.md (L55-58) | ⚠️ Partial | Cards exist but not all categories implemented | -| Recent Changes Feed | PAGES.md (L62-65) | ✅ Complete | Implemented in index.vue | - -### 1.2 Parks Pages - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/parks` - Parks Listing | PAGES.md (L101-130) | ✅ Complete | Implemented | -| `/parks/nearby` - Location-Based Discovery | PAGES.md (L147-188), SITE_OVERVIEW.md (L25-26) | ❌ Missing | Create new page with Leaflet map, geolocation, radius slider | -| `/parks/{slug}` - Park Detail | PAGES.md (L191-256) | ⚠️ Partial | Missing: Reviews Tab, Photos Tab, History Tab, Hero "Edit" and "📷" buttons | -| Park Detail - Overview Tab | PAGES.md (L233) | ⚠️ Partial | Missing: Location map embed, Contact info, Operator/Owner links | -| Park Detail - Rides Tab | PAGES.md (L234) | ✅ Complete | Implemented with filters | -| Park Detail - Reviews Tab | PAGES.md (L235) | ❌ Missing | No reviews tab implemented | -| Park Detail - Photos Tab | PAGES.md (L236) | ❌ Missing | No photos gallery tab | -| Park Detail - History Tab | PAGES.md (L237) | ❌ Missing | No version history/timeline | - -### 1.3 Rides Pages - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/rides` - All Rides Listing | PAGES.md (L262-288) | ✅ Complete | Implemented | -| `/parks/{park}/rides/{ride}` - Ride Detail | PAGES.md (L302-362) | ⚠️ Partial | Missing: Specifications Tab, Reviews Tab, Photos Tab, History Tab | -| Ride Specifications Tab | PAGES.md (L337-361) | ❌ Missing | Need category-specific spec display (Coasters, Flat, Water, Dark) | -| Ride Reviews Tab | PAGES.md (L325) | ❌ Missing | No reviews tab | -| Ride Photos Tab | PAGES.md (L325) | ❌ Missing | No photos gallery | -| Ride History Tab | PAGES.md (L325) | ❌ Missing | No version history | -| Advanced Filters (Speed/Height sliders) | PAGES.md (L275-279) | ❌ Missing | Rides listing needs spec-based filters | - -### 1.4 Company Pages - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/manufacturers` - Listing | PAGES.md (L371-393) | ✅ Complete | Implemented | -| `/manufacturers/{slug}` - Detail | PAGES.md (L395-418) | ✅ Complete | Implemented | -| `/designers` - Designers Listing | PAGES.md (L371), SITE_OVERVIEW.md (L45) | ❌ Missing | Create new page for ride designers | -| `/operators` - Operators Listing | PAGES.md (L372), SITE_OVERVIEW.md (L46) | ❌ Missing | Create new page for park operators | -| `/owners` - Property Owners | PAGES.md (L373), SITE_OVERVIEW.md (L47) | ❌ Missing | Create new page for property owners | -| Company Tabs (Overview, Rides, Models, History) | PAGES.md (L412-413) | ⚠️ Partial | Missing Models Tab, History Tab | - -### 1.5 Ride Models - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/ride-models/{slug}` - Model Detail | PAGES.md (L422-450) | ❌ Missing | Create page showing standard specs and all installations | - -### 1.6 Search - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/search` - Global Search Page | PAGES.md (L454-497) | ✅ Complete | Implemented | -| Type Tabs Filter | PAGES.md (L469-471) | ⚠️ Partial | Basic implementation exists, verify all types | -| Recent Searches | PAGES.md (L496) | ❌ Missing | No recent search history | -| Keyboard Navigation | PAGES.md (L495) | ⚠️ Partial | Verify arrow key / enter support | - -### 1.7 Authentication - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/auth` - Sign In / Sign Up | PAGES.md (L500-551) | ⚠️ Partial | Missing: Magic Link, Google OAuth, Discord OAuth buttons | -| CAPTCHA Verification | PAGES.md (L548) | ❌ Missing | No CAPTCHA on forms | -| MFA Support (TOTP) | PAGES.md (L548) | ❌ Missing | No MFA setup/verification UI | -| Email Confirmation Flow | PAGES.md (L549) | ⚠️ Partial | Backend exists, verify frontend flow | - -### 1.8 User Profile - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/profile/{username}` - Public Profile | PAGES.md (L554-591) | ✅ Complete | Implemented at `/profiles/[username].vue` | -| Profile Stats (Credits, Reviews, Photos) | PAGES.md (L567) | ⚠️ Partial | Verify all stats displayed | -| Badge Display | PAGES.md (L570) | ❌ Missing | No badge system UI | -| Activity Tab | PAGES.md (L587) | ⚠️ Partial | Basic implementation | -| Reviews Tab | PAGES.md (L588) | ❌ Missing | No dedicated reviews tab on profile | -| Lists Tab | PAGES.md (L589) | ⚠️ Partial | Lists exist but verify integration | -| Ride Credits Tab | PAGES.md (L590) | ❌ Missing | Credits not shown on public profile | - -### 1.9 User Settings - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/settings` - Account Settings | PAGES.md (L594-631) | ❌ Missing | Create settings page with sidebar navigation | -| Account Section | PAGES.md (L625) | ❌ Missing | Email, password, delete account | -| Security Section | PAGES.md (L626) | ❌ Missing | MFA, sessions, login history | -| Privacy Section | PAGES.md (L627) | ❌ Missing | Profile visibility, activity sharing | -| Notifications Section | PAGES.md (L628) | ❌ Missing | Email preferences, in-app alerts | -| Location & Units Section | PAGES.md (L629) | ❌ Missing | Preferred units, home location | -| Data Section | PAGES.md (L630) | ❌ Missing | Export data, submission history | - -### 1.10 Ride Credits - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/my-credits` - Ride Credits Dashboard | PAGES.md (L634-678) | ❌ Missing | Create full credits management page | -| Statistics Display | PAGES.md (L646-649) | ❌ Missing | Total, Unique, Parks, Countries | -| Quick Increment (+/-) | PAGES.md (L654-662) | ❌ Missing | In-line count adjustment | -| Add Credit Flow | PAGES.md (L671) | ❌ Missing | Search and log new ride | -| Filter by Category/Park/Year | PAGES.md (L650) | ❌ Missing | Comprehensive filtering | - -### 1.11 User Lists - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/my-lists` - Personal Rankings | PAGES.md (L681-714) | ⚠️ Partial | Basic list management exists | -| Create List | PAGES.md (L689) | ✅ Complete | Exists at `/lists/create.vue` | -| Drag-and-Drop Reorder | PAGES.md (L700) | ❌ Missing | No drag-and-drop | -| Public/Private Toggle | PAGES.md (L694-700) | ⚠️ Partial | Verify implementation | - -### 1.12 Reviews - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| Write Review Form | PAGES.md (L722-741) | ❌ Missing | Need review form component with star rating | -| Review Display Card | PAGES.md (L746-756) | ❌ Missing | Need ReviewCard component | -| Reply to Review | PAGES.md (L754) | ❌ Missing | No reply functionality | -| Report Review | PAGES.md (L754) | ❌ Missing | No report button | -| Vote (Thumbs Up/Down) | PAGES.md (L754) | ❌ Missing | No voting system | - -### 1.13 Photo System - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| Upload Interface | PAGES.md (L765-793) | ⚠️ Partial | GalleryUploader exists, verify features | -| Drag & Drop Upload | PAGES.md (L774-775) | ⚠️ Partial | Verify implementation | -| Photo Gallery with Lightbox | PAGES.md (L797-815) | ❌ Missing | No lightbox viewer | -| Edit/Crop Before Upload | PAGES.md (L511) | ❌ Missing | No image editing | - -### 1.14 Content Submission - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/submit/park` - Submit New Park | PAGES.md (L823), USER_FLOWS.md (L340-436) | ❌ Missing | Multi-step wizard form | -| `/submit/ride` - Submit New Ride | PAGES.md (L823) | ❌ Missing | Multi-step wizard form | -| `/submit/company` - Submit Company | PAGES.md (L823) | ❌ Missing | Multi-step wizard form | -| `/my-submissions` - Submission History | SITE_OVERVIEW.md (L64) | ❌ Missing | View user's past submissions | -| Auto-Save Drafts | PAGES.md (L858) | ❌ Missing | No draft persistence | -| Unit Toggle in Forms | PAGES.md (L864) | ❌ Missing | Metric/Imperial toggle | - -### 1.15 Moderation - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/moderation` - Queue Dashboard | PAGES.md (L874-898) | ✅ Complete | Implemented | -| `/moderation/{id}` - Review Interface | PAGES.md (L902-929) | ⚠️ Partial | Basic review exists, missing side-by-side diff | -| Claim/Unclaim System | PAGES.md (L886-890), USER_FLOWS.md (L695-700) | ✅ Complete | Implemented with SSE | -| Side-by-Side Diff Viewer | PAGES.md (L910-918), COMPONENTS.md (L872-884) | ❌ Missing | No visual diff comparison | -| Approve Selected Fields | PAGES.md (L925) | ❌ Missing | Only full approve/reject | - -### 1.16 Admin Pages - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/admin` - Dashboard | PAGES.md (L937-962) | ❌ Missing | Only `/admin/system` exists | -| `/admin/users` - User Management | PAGES.md (L965-986), USER_FLOWS.md (L752-800) | ⚠️ Partial | Basic user list at `/moderation/users.vue` | -| `/admin/monitoring` - System Health | SITE_OVERVIEW.md (L73) | ❌ Missing | No monitoring dashboard | -| `/admin/errors` - Error Tracking | SITE_OVERVIEW.md (L74) | ❌ Missing | No error tracker UI | -| Ban User Flow | USER_FLOWS.md (L788-797) | ❌ Missing | No ban/unban UI | - -### 1.17 Static Pages - -| Feature Name | Source Doc File | Status | Action Required | -|--------------|-----------------|--------|-----------------| -| `/terms` - Terms of Service | PAGES.md (L995-997) | ❌ Missing | Create static page | -| `/privacy` - Privacy Policy | PAGES.md (L999-1003) | ❌ Missing | Create static page | -| `/guidelines` - Community Guidelines | PAGES.md (L1005-1009) | ❌ Missing | Create static page | -| `/contact` - Contact Form | PAGES.md (L1011-1042) | ⚠️ Partial | `/support/index.vue` exists but verify full form | -| `/blog` - Blog | PAGES.md (L1044-1048) | ✅ Complete | Implemented | - ---- - -## 2. COMPONENTS GAP ANALYSIS - -### 2.1 Layout Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| Header | COMPONENTS.md (L71-93) | ✅ Complete | AppHeader.vue exists | -| Footer | COMPONENTS.md (L96-120) | ❌ Missing | No dedicated footer component | -| PageContainer | COMPONENTS.md (L123-152) | ⚠️ Partial | Layout exists, verify max-width | -| Sidebar (Settings/Admin) | COMPONENTS.md (L155-172) | ❌ Missing | No sidebar navigation | - -### 2.2 Navigation Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| MainNav | COMPONENTS.md (L178-201) | ✅ Complete | In AppHeader | -| TabNav | COMPONENTS.md (L204-221) | ✅ Complete | Using UTabs | -| Breadcrumbs | COMPONENTS.md (L224-237) | ❌ Missing | No breadcrumb component | -| Pagination | COMPONENTS.md (L240-256) | ⚠️ Partial | Verify existence | - -### 2.3 Display Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| Card | COMPONENTS.md (L261-289) | ✅ Complete | BentoCard and others | -| Badge | COMPONENTS.md (L292-311) | ✅ Complete | EntityStatusBadge, StatusBadge | -| Avatar | COMPONENTS.md (L314-331) | ⚠️ Partial | AvatarUploader exists, check display | -| Image (Lazy/Blur) | COMPONENTS.md (L334-358) | ⚠️ Partial | Verify lazy loading | - -### 2.4 Form Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| Input | COMPONENTS.md (L363-388) | ✅ Complete | Using NuxtUI | -| Select | COMPONENTS.md (L391-416) | ✅ Complete | Using NuxtUI | -| Checkbox/Radio/Switch | COMPONENTS.md (L419-456) | ✅ Complete | Using NuxtUI | -| Button | COMPONENTS.md (L459-488) | ✅ Complete | Using UButton | -| DatePicker | COMPONENTS.md (L491-518) | ❌ Missing | No date picker | -| Slider (Range) | COMPONENTS.md (L521-537) | ❌ Missing | No range slider | - -### 2.5 Feedback Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| Toast | COMPONENTS.md (L542-559) | ✅ Complete | Using NuxtUI | -| Alert | COMPONENTS.md (L562-575) | ✅ Complete | Using NuxtUI | -| Modal/Dialog | COMPONENTS.md (L578-603) | ✅ Complete | Modals exist | -| Loading States | COMPONENTS.md (L606-628) | ⚠️ Partial | Spinner exists, verify skeleton | -| Empty State | COMPONENTS.md (L631-647) | ⚠️ Partial | Basic empty states | - -### 2.6 Data Display Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| Table | COMPONENTS.md (L652-672) | ⚠️ Partial | Verify sortable/filterable | -| Stats Card | COMPONENTS.md (L675-686) | ✅ Complete | In BentoCard | -| Rating Display (Stars) | COMPONENTS.md (L689-704) | ❌ Missing | Need StarRating component | - -### 2.7 Entity Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| ParkCard | COMPONENTS.md (L709-737) | ⚠️ Partial | Verify all fields | -| RideCard | COMPONENTS.md (L740-767) | ⚠️ Partial | Verify all fields | -| ReviewCard | COMPONENTS.md (L770-786) | ❌ Missing | Create ReviewCard | -| CreditCard | COMPONENTS.md (L789-805) | ❌ Missing | Create CreditCard for ride credits | - -### 2.8 Specialty Components - -| Component | Source Doc File | Status | Action Required | -|-----------|-----------------|--------|-----------------| -| UnitDisplay | COMPONENTS.md (L811-827) | ⚠️ Partial | useUnits exists, verify display | -| Map (Leaflet) | COMPONENTS.md (L829-852) | ⚠️ Partial | map.vue exists, verify features | -| Timeline | COMPONENTS.md (L855-868) | ⚠️ Partial | timeline.vue exists | -| Diff Viewer | COMPONENTS.md (L871-885) | ❌ Missing | Create for moderation | -| ImageGallery | COMPONENTS.md (L888-906) | ❌ Missing | With lightbox | -| SearchAutocomplete | COMPONENTS.md (L909-923) | ✅ Complete | GlobalSearch.vue | -| Tooltip | COMPONENTS.md (L926-938) | ✅ Complete | Using NuxtUI | -| HoverCard | COMPONENTS.md (L941-957) | ❌ Missing | Rich preview on hover | - ---- - -## 3. USER FLOWS GAP ANALYSIS - -### 3.1 Discovery Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Homepage Discovery | USER_FLOWS.md (L21-49) | ⚠️ Partial | Missing all 11 discovery tabs | -| Search Flow | USER_FLOWS.md (L51-93) | ✅ Complete | Implemented | -| Parks Nearby Flow | USER_FLOWS.md (L95-148) | ❌ Missing | No nearby page with geolocation | - -### 3.2 Authentication Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Sign Up Flow | USER_FLOWS.md (L154-208) | ⚠️ Partial | Missing OAuth, Magic Link, CAPTCHA | -| Sign In Flow | USER_FLOWS.md (L210-279) | ⚠️ Partial | Missing MFA check, Ban check UI | - -### 3.3 Content Viewing Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Park Page Journey | USER_FLOWS.md (L283-334) | ⚠️ Partial | Missing Reviews/Photos/History tabs | - -### 3.4 Contribution Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Submit New Content | USER_FLOWS.md (L340-437) | ❌ Missing | No submission forms | -| Edit Content Flow | USER_FLOWS.md (L439-485) | ❌ Missing | No edit forms | -| Photo Upload Flow | USER_FLOWS.md (L487-545) | ⚠️ Partial | Basic upload exists | - -### 3.5 Engagement Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Write Review Flow | USER_FLOWS.md (L551-609) | ❌ Missing | No review writing UI | -| Log Ride Credit Flow | USER_FLOWS.md (L611-660) | ❌ Missing | No credit logging UI | - -### 3.6 Moderation Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| Moderator Queue Workflow | USER_FLOWS.md (L664-746) | ⚠️ Partial | Missing diff viewer | - -### 3.7 Admin Flows - -| Flow | Source Doc File | Status | Action Required | -|------|-----------------|--------|-----------------| -| User Management Flow | USER_FLOWS.md (L750-800) | ⚠️ Partial | Basic list, missing actions | - ---- - -## 4. DESIGN SYSTEM GAP ANALYSIS - -| Feature | Source Doc File | Status | Action Required | -|---------|-----------------|--------|-----------------| -| Light/Dark Mode | DESIGN_SYSTEM.md (L23-75) | ✅ Complete | Implemented with ThemeSwitcher | -| Typography (Inter) | DESIGN_SYSTEM.md (L101-150) | ✅ Complete | Inter font loaded | -| Spacing System | DESIGN_SYSTEM.md (L154-172) | ✅ Complete | Using TailwindCSS | -| Border Radius | DESIGN_SYSTEM.md (L175-184) | ✅ Complete | Using TailwindCSS | -| Shadows | DESIGN_SYSTEM.md (L187-218) | ✅ Complete | Using TailwindCSS | -| Glow Effects (Dark Mode) | DESIGN_SYSTEM.md (L220-227) | ⚠️ Partial | Verify implementation | -| Animations | DESIGN_SYSTEM.md (L230-278) | ⚠️ Partial | Basic transitions | -| Glass Card (Dark Mode) | DESIGN_SYSTEM.md (L335-339) | ❌ Missing | No glassmorphism cards | -| Accessibility (Focus States) | DESIGN_SYSTEM.md (L468-471) | ✅ Complete | Using TailwindCSS | -| Reduced Motion | DESIGN_SYSTEM.md (L473-476) | ❌ Missing | No prefers-reduced-motion | - ---- - -## 5. PRIORITY BATCHES - -Based on the gaps identified above, here are the recommended implementation batches: - -### BATCH 1: Critical Missing Pages (HIGH PRIORITY) -1. `/my-credits` - Ride Credits Dashboard -2. `/settings` - Full Settings Page (6 sections) -3. `/parks/nearby` - Location-based Discovery -4. `/my-submissions` - Submission History -5. Static Pages: `/terms`, `/privacy`, `/guidelines` - -### BATCH 2: Missing Tabs on Existing Pages (HIGH PRIORITY) -1. Park Detail - Add Reviews, Photos, History tabs -2. Ride Detail - Add Specifications, Reviews, Photos, History tabs -3. Homepage - Expand to 11 Discovery Tabs (vs current 4) -4. Profile - Add Reviews, Ride Credits tabs - -### BATCH 3: Missing Components (MEDIUM PRIORITY) -1. ReviewCard component -2. CreditCard component -3. StarRating component -4. Diff Viewer component -5. ImageGallery with Lightbox -6. Footer component -7. Breadcrumbs component -8. DatePicker component -9. Range Slider component - -### BATCH 4: Submission Forms (MEDIUM PRIORITY) -1. `/submit/park` - Park submission wizard -2. `/submit/ride` - Ride submission wizard -3. `/submit/company` - Company submission wizard -4. Edit forms for existing entities - -### BATCH 5: Company Pages (MEDIUM PRIORITY) -1. `/designers` - Designers listing and detail -2. `/operators` - Operators listing and detail -3. `/owners` - Property Owners listing and detail -4. `/ride-models/{slug}` - Ride Model detail page - -### BATCH 6: Enhanced Features (LOW PRIORITY) -1. OAuth Authentication (Google, Discord) -2. Magic Link Login -3. CAPTCHA on forms -4. MFA Setup UI -5. Review voting (thumbs up/down) -6. Review replies -7. Recent searches history -8. Drag-and-drop list reordering -9. Glass card effects -10. Reduced motion support - ---- - -## 6. SOURCE DOCUMENT REFERENCE - -| File | Lines | Primary Coverage | -|------|-------|------------------| -| SITE_OVERVIEW.md | 245 | Site map, user roles, key journeys | -| PAGES.md | 1058 | All page specifications | -| COMPONENTS.md | 967 | UI component library | -| USER_FLOWS.md | 882 | User journey diagrams | -| DESIGN_SYSTEM.md | 496 | Visual identity, colors, typography | - ---- - -*This matrix is the Source of Truth for synchronization. Update as gaps are addressed.* +## Top Priority Missing Features + +### Critical (User-Facing Features) +1. **Contact Page** (`/contact`) - Static page requirement +2. **Leaderboard Page** - Community engagement feature +3. **Notification Bell + Feed** - User engagement/retention +4. **CAPTCHA on Forms** - Security requirement +5. **MFA/TOTP Support** - Security requirement +6. **Magic Link Authentication** - UX enhancement + +### High Priority (UX Components) +7. **Multi-Step Submission Wizard** - UX for complex forms +8. **ParkCard / RideCard Components** - Reusable entity cards +9. **HoverCard Previews** - Rich preview on hover +10. **ImageGallery Lightbox (Zoom/Download)** - Photo viewing +11. **Grid/List View Toggle** on listings +12. **Search History** feature + +### Medium Priority (Polish) +13. **Reduced Motion Support** - Accessibility +14. **Skeleton Loading States** - Better perceived performance +15. **Profile Badges Display** - Community engagement +16. **Profile Lists Tab** - Feature visibility +17. **Company Detail Pages with Tabs** - Content depth +18. **Slider/Range Components** for advanced filters +19. **Map Marker Clustering** - Performance +20. **Breadcrumbs Schema.org Markup** - SEO + +### Low Priority (Nice to Have) +21. **Login History View** in settings +22. **Data Export Button** in UI +23. **Ride Drag Reorder** for credits +24. **Water/Dark Ride Specific Specs** - Content completeness +25. **Date Precision Selector** - Data entry accuracy diff --git a/backend/apps/accounts/mixins.py b/backend/apps/accounts/mixins.py index a5977950..87b211a9 100644 --- a/backend/apps/accounts/mixins.py +++ b/backend/apps/accounts/mixins.py @@ -1,35 +1,44 @@ -import requests -from django.conf import settings +""" +Mixins for authentication views. +""" from django.core.exceptions import ValidationError +from apps.core.utils.turnstile import validate_turnstile_token, get_client_ip + class TurnstileMixin: """ Mixin to handle Cloudflare Turnstile validation. - Bypasses validation when DEBUG is True. + Works with both form POST data and JSON request bodies. """ def validate_turnstile(self, request): """ Validate the Turnstile response token. - Skips validation when DEBUG is True. + + The token can be provided as: + - 'cf-turnstile-response' in POST data (form submission) + - 'turnstile_token' in JSON body (API request) """ - if settings.DEBUG: - return - - token = request.POST.get("cf-turnstile-response") - if not token: - raise ValidationError("Please complete the Turnstile challenge.") - - # Verify the token with Cloudflare - data = { - "secret": settings.TURNSTILE_SECRET_KEY, - "response": token, - "remoteip": request.META.get("REMOTE_ADDR"), - } - - response = requests.post(settings.TURNSTILE_VERIFY_URL, data=data, timeout=60) - result = response.json() - - if not result.get("success"): - raise ValidationError("Turnstile validation failed. Please try again.") + # Try to get token from various sources + token = None + + # Check POST data (form submissions) + if hasattr(request, 'POST'): + token = request.POST.get("cf-turnstile-response") + + # Check JSON body (API requests) + if not token and hasattr(request, 'data'): + data = getattr(request, 'data', {}) + if hasattr(data, 'get'): + token = data.get('turnstile_token') or data.get('cf-turnstile-response') + + # Get client IP + ip = get_client_ip(request) + + # Validate the token + result = validate_turnstile_token(token, ip) + + if not result.get('success'): + error_msg = result.get('error', 'Captcha verification failed. Please try again.') + raise ValidationError(error_msg) diff --git a/backend/apps/api/v1/urls.py b/backend/apps/api/v1/urls.py index 5fcc44c6..0b088688 100644 --- a/backend/apps/api/v1/urls.py +++ b/backend/apps/api/v1/urls.py @@ -19,6 +19,7 @@ from .views import ( from .views.discovery import DiscoveryAPIView from .views.stats import StatsAPIView, StatsRecalculateAPIView from .views.reviews import LatestReviewsAPIView +from .views.leaderboard import leaderboard from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -61,6 +62,8 @@ urlpatterns = [ ), # Reviews endpoints path("reviews/latest/", LatestReviewsAPIView.as_view(), name="latest-reviews"), + # Leaderboard endpoint + path("leaderboard/", leaderboard, name="leaderboard"), # Ranking system endpoints path( "rankings/calculate/", diff --git a/backend/apps/api/v1/views/leaderboard.py b/backend/apps/api/v1/views/leaderboard.py new file mode 100644 index 00000000..81c2c081 --- /dev/null +++ b/backend/apps/api/v1/views/leaderboard.py @@ -0,0 +1,133 @@ +""" +Leaderboard views for user rankings +""" +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from django.db.models import Count, Sum +from django.db.models.functions import Coalesce +from django.utils import timezone +from datetime import timedelta + +from apps.accounts.models import User +from apps.rides.models import RideCredit +from apps.reviews.models import Review +from apps.moderation.models import EditSubmission + + +@api_view(['GET']) +@permission_classes([AllowAny]) +def leaderboard(request): + """ + Get user leaderboard data. + + Query params: + - category: 'credits' | 'reviews' | 'contributions' (default: credits) + - period: 'all' | 'monthly' | 'weekly' (default: all) + - limit: int (default: 25, max: 100) + """ + category = request.query_params.get('category', 'credits') + period = request.query_params.get('period', 'all') + limit = min(int(request.query_params.get('limit', 25)), 100) + + # Calculate date filter based on period + date_filter = None + if period == 'weekly': + date_filter = timezone.now() - timedelta(days=7) + elif period == 'monthly': + date_filter = timezone.now() - timedelta(days=30) + + if category == 'credits': + return _get_credits_leaderboard(date_filter, limit) + elif category == 'reviews': + return _get_reviews_leaderboard(date_filter, limit) + elif category == 'contributions': + return _get_contributions_leaderboard(date_filter, limit) + else: + return Response({'error': 'Invalid category'}, status=400) + + +def _get_credits_leaderboard(date_filter, limit): + """Top users by total ride credits.""" + queryset = RideCredit.objects.all() + + if date_filter: + queryset = queryset.filter(created_at__gte=date_filter) + + # Aggregate credits per user + users_data = queryset.values('user_id', 'user__username', 'user__display_name').annotate( + total_credits=Coalesce(Sum('count'), 0), + unique_rides=Count('ride', distinct=True), + ).order_by('-total_credits')[:limit] + + results = [] + for rank, entry in enumerate(users_data, 1): + results.append({ + 'rank': rank, + 'user_id': entry['user_id'], + 'username': entry['user__username'], + 'display_name': entry['user__display_name'] or entry['user__username'], + 'total_credits': entry['total_credits'], + 'unique_rides': entry['unique_rides'], + }) + + return Response({ + 'category': 'credits', + 'results': results, + }) + + +def _get_reviews_leaderboard(date_filter, limit): + """Top users by review count.""" + queryset = Review.objects.all() + + if date_filter: + queryset = queryset.filter(created_at__gte=date_filter) + + # Count reviews per user + users_data = queryset.values('user_id', 'user__username', 'user__display_name').annotate( + review_count=Count('id'), + ).order_by('-review_count')[:limit] + + results = [] + for rank, entry in enumerate(users_data, 1): + results.append({ + 'rank': rank, + 'user_id': entry['user_id'], + 'username': entry['user__username'], + 'display_name': entry['user__display_name'] or entry['user__username'], + 'review_count': entry['review_count'], + }) + + return Response({ + 'category': 'reviews', + 'results': results, + }) + + +def _get_contributions_leaderboard(date_filter, limit): + """Top users by approved contributions.""" + queryset = EditSubmission.objects.filter(status='approved') + + if date_filter: + queryset = queryset.filter(created_at__gte=date_filter) + + # Count contributions per user + users_data = queryset.values('submitted_by_id', 'submitted_by__username', 'submitted_by__display_name').annotate( + contribution_count=Count('id'), + ).order_by('-contribution_count')[:limit] + + results = [] + for rank, entry in enumerate(users_data, 1): + results.append({ + 'rank': rank, + 'user_id': entry['submitted_by_id'], + 'username': entry['submitted_by__username'], + 'display_name': entry['submitted_by__display_name'] or entry['submitted_by__username'], + 'contribution_count': entry['contribution_count'], + }) + + return Response({ + 'category': 'contributions', + 'results': results, + }) diff --git a/backend/apps/core/utils/turnstile.py b/backend/apps/core/utils/turnstile.py new file mode 100644 index 00000000..b9ecdbf4 --- /dev/null +++ b/backend/apps/core/utils/turnstile.py @@ -0,0 +1,64 @@ +""" +Cloudflare Turnstile validation utilities. + +This module provides a function to validate Turnstile tokens +on the server side before processing form submissions. +""" +import requests +from django.conf import settings + + +def validate_turnstile_token(token: str, ip: str = None) -> dict: + """ + Validate a Cloudflare Turnstile token. + + Args: + token: The Turnstile response token from the client + ip: Optional client IP address for additional verification + + Returns: + dict with 'success' boolean and optional 'error' message + """ + # Skip validation if configured (dev mode) + if getattr(settings, 'TURNSTILE_SKIP_VALIDATION', False): + return {'success': True} + + secret = getattr(settings, 'TURNSTILE_SECRET', '') + if not secret: + return {'success': True} # Skip if no secret configured + + if not token: + return {'success': False, 'error': 'Captcha verification required'} + + try: + response = requests.post( + 'https://challenges.cloudflare.com/turnstile/v0/siteverify', + data={ + 'secret': secret, + 'response': token, + 'remoteip': ip, + }, + timeout=10 + ) + result = response.json() + + if result.get('success'): + return {'success': True} + else: + error_codes = result.get('error-codes', []) + return { + 'success': False, + 'error': 'Captcha verification failed', + 'error_codes': error_codes + } + except requests.RequestException as e: + # Log error but don't block user on network issues + return {'success': True} # Fail open to avoid blocking legitimate users + + +def get_client_ip(request): + """Extract client IP from request, handling proxies.""" + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + return x_forwarded_for.split(',')[0].strip() + return request.META.get('REMOTE_ADDR') diff --git a/backend/apps/support/migrations/0002_add_category_to_ticket.py b/backend/apps/support/migrations/0002_add_category_to_ticket.py new file mode 100644 index 00000000..94719a0a --- /dev/null +++ b/backend/apps/support/migrations/0002_add_category_to_ticket.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1.6 on 2025-12-27 18:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("support", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="ticket", + name="category", + field=models.CharField( + choices=[ + ("general", "General Inquiry"), + ("bug", "Bug Report"), + ("partnership", "Partnership"), + ("press", "Press/Media"), + ("data", "Data Correction"), + ("account", "Account Issue"), + ], + db_index=True, + default="general", + help_text="Category of the ticket", + max_length=20, + ), + ), + ] diff --git a/backend/apps/support/models.py b/backend/apps/support/models.py index d7fb39a7..b97f985b 100644 --- a/backend/apps/support/models.py +++ b/backend/apps/support/models.py @@ -13,6 +13,22 @@ class Ticket(TrackedModel): (STATUS_CLOSED, 'Closed'), ] + CATEGORY_GENERAL = 'general' + CATEGORY_BUG = 'bug' + CATEGORY_PARTNERSHIP = 'partnership' + CATEGORY_PRESS = 'press' + CATEGORY_DATA = 'data' + CATEGORY_ACCOUNT = 'account' + + CATEGORY_CHOICES = [ + (CATEGORY_GENERAL, 'General Inquiry'), + (CATEGORY_BUG, 'Bug Report'), + (CATEGORY_PARTNERSHIP, 'Partnership'), + (CATEGORY_PRESS, 'Press/Media'), + (CATEGORY_DATA, 'Data Correction'), + (CATEGORY_ACCOUNT, 'Account Issue'), + ] + user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, @@ -22,6 +38,13 @@ class Ticket(TrackedModel): help_text="User who submitted the ticket (optional)" ) + category = models.CharField( + max_length=20, + choices=CATEGORY_CHOICES, + default=CATEGORY_GENERAL, + db_index=True, + help_text="Category of the ticket" + ) subject = models.CharField(max_length=255) message = models.TextField() email = models.EmailField(help_text="Contact email", blank=True) @@ -39,10 +62,11 @@ class Ticket(TrackedModel): ordering = ["-created_at"] def __str__(self): - return f"[{self.get_status_display()}] {self.subject}" + return f"[{self.get_category_display()}] {self.subject}" def save(self, *args, **kwargs): # If user is set but email is empty, autofill from user if self.user and not self.email: self.email = self.user.email super().save(*args, **kwargs) + diff --git a/backend/apps/support/serializers.py b/backend/apps/support/serializers.py index 6e49c9a6..2c270476 100644 --- a/backend/apps/support/serializers.py +++ b/backend/apps/support/serializers.py @@ -4,16 +4,21 @@ from apps.accounts.serializers import UserSerializer class TicketSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) + category_display = serializers.CharField(source='get_category_display', read_only=True) + status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: model = Ticket fields = [ "id", "user", + "category", + "category_display", "subject", "message", "email", "status", + "status_display", "created_at", "updated_at", ] @@ -25,3 +30,4 @@ class TicketSerializer(serializers.ModelSerializer): if request and not request.user.is_authenticated and not data.get('email'): raise serializers.ValidationError({"email": "Email is required for guests."}) return data + diff --git a/backend/apps/support/views.py b/backend/apps/support/views.py index 8ac6c293..da2386a7 100644 --- a/backend/apps/support/views.py +++ b/backend/apps/support/views.py @@ -13,7 +13,7 @@ class TicketViewSet(viewsets.ModelViewSet): serializer_class = TicketSerializer permission_classes = [permissions.AllowAny] # We handle granular perms in get_queryset/perform_create filter_backends = [DjangoFilterBackend, filters.OrderingFilter] - filterset_fields = ["status"] + filterset_fields = ["status", "category"] ordering_fields = ["created_at", "status"] ordering = ["-created_at"] diff --git a/backend/config/django/base.py b/backend/config/django/base.py index 43abe928..c5a2c540 100644 --- a/backend/config/django/base.py +++ b/backend/config/django/base.py @@ -88,6 +88,7 @@ THIRD_PARTY_APPS = [ "allauth.socialaccount", "allauth.socialaccount.providers.google", "allauth.socialaccount.providers.discord", + "django_turnstile", # Cloudflare Turnstile CAPTCHA "django_cleanup", "django_filters", "django_htmx", diff --git a/backend/config/settings/third_party.py b/backend/config/settings/third_party.py index c7de1f5d..8464bb54 100644 --- a/backend/config/settings/third_party.py +++ b/backend/config/settings/third_party.py @@ -183,3 +183,18 @@ AUTOCOMPLETE_BLOCK_UNAUTHENTICATED = config( # ============================================================================= FRONTEND_DOMAIN = config("FRONTEND_DOMAIN", default="https://thrillwiki.com") + +# ============================================================================= +# Cloudflare Turnstile Configuration +# ============================================================================= +# https://developers.cloudflare.com/turnstile/ + +TURNSTILE_SITEKEY = config("TURNSTILE_SITEKEY", default="") +TURNSTILE_SECRET = config("TURNSTILE_SECRET", default="") + +# Skip Turnstile validation in development if keys not set +TURNSTILE_SKIP_VALIDATION = config( + "TURNSTILE_SKIP_VALIDATION", + default=not TURNSTILE_SECRET, # Skip if no secret + cast=bool +)