Skip to content
Armand edited this page Dec 24, 2025 · 3 revisions

Adventure is near

That's a working title

Description: A straightforward mobile webpage for adventure services targeted at people on a city-trip.

1. Required pages

Core Pages

  1. Landing/Home Page - Introduces the service, shows value proposition, displays pricing
  2. City Selection Page - Users choose their destination city
  3. Questionnaire Page - Collects user preferences (budget, activity level, time, experience type)
  4. Payment Page - Handles the per-city payment
  5. Results/Activities Page - Displays the curated list of activities
  6. Activity Detail Page (optional but recommended) - Shows detailed information about a specific activity

Supporting Pages

  1. About/How It Works - Explains the service
  2. FAQ Page - Common questions
  3. Terms & Privacy - Legal requirements

2. Optimal User Flow & Payment Placement

Possible flows

Option A (Recommended - Payment Before Results):

Landing → City Selection → Questionnaire → Payment → Results

Why this works best:

  • Users are invested after filling out the questionnaire
  • They've spent time on preferences, creating commitment
  • Clear value proposition before payment (they know what they're getting)
  • Prevents free browsing of curated content

Option B (Alternative - Preview First):

Landing → City Selection → Questionnaire → Preview (3 activities) → Payment → Full Results

Benefits: Shows sample quality before payment Builds trust Higher conversion for hesitant users

Payment Strategy Recommendation

Option A with these enhancements:

  • Show pricing clearly on the landing page
  • Remind users of the price on the city selection page
  • After questionnaire completion, show a summary screen: "Your personalized guide for [City] is ready! Unlock [X] curated activities for $[price]"
  • This creates anticipation and justifies the payment

3. Questionnaire

Essential Questions

  1. City
  2. Budget - Ranges: $, $$, $$$, $$$$ or specific amounts
  3. Activity Level - Low/Moderate/High or Relaxed/Balanced/Active
  4. Available Time - Half day/Full day/Multiple days or specific hours
  5. Experience Type - Cultural/Nature/Entertainment/Food/Shopping/Nightlife/Adventure
  6. Travel Companions - Solo/Couple/Family with kids/Friends group

Optional Questions

This significantly affects recommendations

  1. Dietary Restrictions (if food is involved) - None/Vegetarian/Vegan/Gluten-free/Halal/Kosher
  2. Mobility Considerations - Fully mobile/Prefer easy access/Wheelchair accessible needed
  3. Interests (multi-select) - Art/History/Music/Sports/Photography/Local cuisine/Shopping/Wellness
  4. Pace Preference - Packed schedule/Balanced/Leisurely with downtime
  5. Previously Visited? - First time/Been
  6. Weather Preference - Indoor/Outdoor/Mix/No preference

4. Best Way to Present Activities

List View with Cards

Each activity card should include

  • Hero Image - Eye-catching photo/visual
  • Activity Name - Clear, descriptive title
  • Quick Stats Bar:
    • Duration (e.g., "2-3 hours")
    • Price range ($$$)
    • Activity level icon (walking shoe with intensity)
    • Best time (morning/afternoon/evening icon)
  • Short Description - 2-3 sentences max
  • Why We Picked This - Personalized note based on their preferences
  • Category Tags - Cultural, Outdoor, etc.
  • "View Details" button

Grouping Options

  • By Time of Day - Morning activities, Afternoon, Evening
  • By Priority - "Must-Do", "Highly Recommended", "If You Have Time"
  • By Category - Group similar experiences together
  • By Location - Neighborhood/district grouping

Interactive Features

  • Save/Favorite - Let users mark itineraries they're interested in
  • Map View Toggle - Show activities on a map
  • Filter/Sort - By price, duration, category, distance

Detail Page

  • Multiple photos
  • Full description
  • Exact address with map
  • Opening hours
  • Estimated cost
  • How to get there (transport options)
  • Insider tips
  • What to bring/wear
  • Booking information (if needed)
  • Similar activities

Mobile-Specific Considerations

  • Use vertical scrolling (natural for mobile)
  • Large, tappable cards
  • Sticky header with city name and filter button
  • Bottom navigation for easy thumb access
  • Swipeable image galleries
  • One-tap actions (save, share, get directions)

5. Payments

Flow Architecture

  • Use Stripe Checkout (recommended for simplicity) or Stripe Payment Element for custom UI
  • For per-city payments, create products in Stripe for each city or use dynamic pricing
  • Consider using Stripe Payment Links for the simplest implementation

Key Stripe Concepts to Implement

  • Products & Prices: Create a product for each city or a single product with variable pricing
  • Payment Intents: For custom checkout flows with more control
  • Webhooks: Critical for handling payment confirmations securely
  • Customer Objects: Store customer data for repeat purchases

Security Best Practices

  • Never process payments client-side only - always verify on the backend
  • Use Stripe's webhook signatures to verify events
  • Store Stripe customer IDs and payment IDs, not card details
  • Implement idempotency keys to prevent duplicate charges

Flow

User completes questionnaire -> Create Stripe Checkout Session (server-side via Supabase Edge Function) -> Redirect to Stripe Checkout -> User completes payment -> Stripe webhook notifies your backend -> Update Supabase database with payment status -> Redirect user to results page -> Verify payment status before showing activities

6. Supabase Database Structure

Tables

  1. users table
  • id (uuid, primary key)
  • email (text, unique)
  • created_at (timestamp)
  • stripe_customer_id (text, nullable)
  1. cities table
  • id (uuid, primary key)
  • name (text)
  • slug (text, unique)
  • price (numeric)
  • is_active (boolean)
  • created_at (timestamp)
  1. questionnaires table
  • id (uuid, primary key)
  • user_id (uuid, foreign key to users)
  • city_id (uuid, foreign key to cities)
  • budget (text)
  • activity_level (text)
  • available_time (text)
  • experience_types (text[] or jsonb)
  • travel_companions (text, nullable)
  • interests (text[] or jsonb)
  • created_at (timestamp)
  1. purchases table (Critical!)
  • id (uuid, primary key)
  • user_id (uuid, foreign key to users)
  • city_id (uuid, foreign key to cities)
  • questionnaire_id (uuid, foreign key to questionnaires)
  • stripe_payment_intent_id (text, unique)
  • stripe_checkout_session_id (text, nullable)
  • amount (numeric)
  • currency (text)
  • status (text) - 'pending', 'completed', 'failed', 'refunded'
  • paid_at (timestamp, nullable)
  • created_at (timestamp)
  1. activities table
  • id (uuid, primary key)
  • city_id (uuid, foreign key to cities)
  • name (text)
  • description (text)
  • short_description (text)
  • category (text)
  • budget_level (text) - '$', '$$', '$$$', '$$$$'
  • activity_level (text) - 'low', 'moderate', 'high'
  • duration_hours (numeric)
  • best_time_of_day (text[])
  • tags (text[] or jsonb)
  • image_url (text)
  • location (jsonb) - {address, lat, lng, neighborhood}
  • is_active (boolean)
  • priority_score (integer) - for ranking
  • created_at (timestamp)
  1. user_saved_activities table (Optional but recommended)
  • id (uuid, primary key)
  • user_id (uuid, foreign key to users)
  • activity_id (uuid, foreign key to activities)
  • purchase_id (uuid, foreign key to purchases)
  • created_at (timestamp)
  • UNIQUE(user_id, activity_id, purchase_id)

Key Implementation Considerations

  1. Access Control with Row Level Security (RLS)

-- Users can only see their own purchases CREATE POLICY "Users can view own purchases" ON purchases FOR SELECT USING (auth.uid() = user_id);

-- Users can only see activities for cities they've purchased CREATE POLICY "Users can view purchased city activities" ON activities FOR SELECT USING ( city_id IN ( SELECT city_id FROM purchases WHERE user_id = auth.uid() AND status = 'completed' ) );

  1. Supabase Edge Functions Needed

create-checkout-session - Creates Stripe checkout

// Called from frontend after questionnaire completion // Returns Stripe Checkout URL // Stores pending purchase in database

stripe-webhook - Handles Stripe events

// Listens for payment_intent.succeeded // Updates purchase status to 'completed' // Triggers any post-payment logic

get-activities - Returns curated activities

// Verifies user has paid for city // Filters activities based on questionnaire responses // Returns personalized activity list

  1. Payment Verification Flow

Always verify payment status server-side before showing activities Check purchases table for status = 'completed' AND city_id matches Don't rely on client-side state alone

  1. Handling Edge Cases

Duplicate payments: Use Stripe's idempotency keys Abandoned checkouts: Set expiration on checkout sessions (default 24 hours) Refunds: Update purchase status, consider time-based access revocation Multiple purchases: Allow users to purchase multiple cities, track separately

  1. Data Privacy & Compliance

Store minimal user data (email only if needed) Don't store payment card details (Stripe handles this) Implement data retention policies Add terms acceptance tracking in purchases table

  1. Performance Optimization

Index foreign keys (user_id, city_id, stripe_payment_intent_id) Cache activity results for common questionnaire combinations Use Supabase's built-in caching for frequently accessed data

  1. Testing Strategy

Use Stripe's test mode with test cards Create test purchases in Supabase Test webhook handling with Stripe CLI Verify RLS policies prevent unauthorized access