# Project export: EXPLR

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: A webapp to suggest engaging activities in your city every day based on your interests. See what your friends are up to through their posts. Intended to help people find fun away from their phones.
- Devpost: https://devpost.com/software/explr-mlcpig
- GitHub: https://github.com/abouvel/quest
- Team: 1 GitHub contributor(s) — Austin Bouvel (11 commits)

## Devpost submission (written by the team)

### Inspiration

We were sitting in the Bay Area with free time and zero motivation. Even though there was stuff to do, nothing felt exciting. Most apps give you the same list of restaurants and landmarks with no context or personalization. We wanted something that would take the pressure off decision-making and make going out feel a little more like a mission. Otherwise, we would just spend time scrolling on our phones, wasting our time. That’s how EXPLR started, as an app that makes everyday plans feel like quests and to bring adults out to explore the real world.

### What it does

EXPLR gives you a daily, AI-generated quest based on your location, past activities, and personal preferences. These quests can be anything from trying a new cafe to checking out a nearby trail. You complete the quest by uploading a photo, and you can see your friends’ completed quests too. You build streaks, give feedback, and discover new places together. It considers whether you prefer museums to hiking, restaurants to camping, etc so it is part discovery tool, part daily motivator, and part social feed. Theres a leaderboard to build a competitive spirit amongst your friends to see who can explore the most days too.

### How we built it

Frontend: Built with Next.js, React, and TypeScript. Styled using Tailwind CSS and shadcn/ui. Backend & Database: Supabase handles PostgreSQL, authentication, and image storage. Supabase RLS policies enforce security. AI & Geolocation: Gemini AI generates quests based on user context. Google Maps API confirms the location is real and plots points on the global map. State Management: A custom global store keeps user quests in sync across components without re-fetching.

### Challenges we ran into

Authentication on the server: Supabase’s client didn’t carry user context server-side, causing row-level security issues. Fixed using a secure admin client with a SERVICE_ROLE_KEY. Duplicate AI results: Gemini occasionally repeated quest suggestions. We fixed it by passing full user quest history in the prompt and logging prompt data for debugging. Efficient social feed queries: We had to restructure our database joins to cleanly pull user, friend, and quest data together.

### Accomplishments we're proud of

We got a full AI-powered experience working from end to end, including user login, quest generation, image uploads, and a live social feed. And most importantly, it actually made us want to explore again. We plan to deploy this webapp after the hackathon and use it amongst our friends back at the University of Michigan.

### What we learned

How to use Supabase RLS and secure backend clients effectively How to debug AI prompt pipelines by logging intermediate data How to organize user-to-user relationships and content in relational databases

### What's next

Add IOS and Android full functionality Let users reroll quests or fine-tune preferences Build out ELO-style quest scoring and improve the leaderboard Add reactions, comments, and real-time friend updates Eventually partner with local businesses for sponsored quests

## README (from the GitHub repository)

# EXPLR - Location-Based Quest Generation App

EXPLR is a full-stack web application that generates personalized real-world quests based on user interests, location, weather, and quest history using Google's Agent Development Kit (ADK). It combines AI-powered activity generation with social features to help users explore their surroundings and share experiences with friends.

---

## TL;DR
EXPLR helps users discover new places and experiences by generating personalized quests using AI, location data, and weather. Users complete quests by submitting photos and track their progress through a social feed and leaderboard. Built with Next.js, FastAPI, Supabase, and Google Maps.

---

## Project Links
- Live Demo: [quest-production-a5ff.up.railway.app](https://quest-production-a5ff.up.railway.app/)
- Devpost: [devpost.com/software/explr-mlcpig](https://devpost.com/software/explr-mlcpig)

---

## What This Project Does
- Personalized quest generation using multi-agent AI workflows
- Quest suggestions factor in user interests, real-time weather, and location
- Interactive quest map with Google Maps pinning and location validation
- Social feed to share completed quests with friends
- Leaderboard tracking user activity and streaks

---

## Multi-Agent Quest Generation Pipeline
1. Summarizer Agent – Analyzes user interests and quest history
2. Weather-Time Agent – Uses coordinates and weather API to suggest viable activities
3. Search Agent – Finds nearby places that fit the activity
4. Reformatter Agent – Finalizes quest object and metadata

---

## Tech Stack
Frontend:
- Next.js 15.2.4 (App Router)
- Tailwind CSS, shadcn/ui, Radix UI
- Supabase Auth
- Google Maps API

Backend:
- FastAPI (Python 3.11)
- Google ADK (multi-agent)
- Supabase (PostgreSQL)
- Weather API

DevOps:
- Docker
- Railway (deployment)

---

## Quick Start
### Frontend
```bash
cd app/
npm install
npm run dev
```
Visit: http://localhost:3000

### Backend
```bash
pip install -r requirements.txt
uvicorn lib.fastapi_server:app --host 0.0.0.0 --port 8000 --reload
```
Visit: http://localhost:8000

### Full Stack with Docker
```bash
docker-compose up --build
```

---

## Sample Quest Output
```json
{
  "final_quest": {
    "title": "Visit Local Art Museum",
    "description": "Philadelphia Museum of Art: See world-class exhibits...",
    "locationName": "Philadelphia Museum of Art",
    "address": "2600 Benjamin Franklin Pkwy, Philadelphia, PA 19130",
    "coords": { "lat": 39.9656, "lng": -75.1809 },
    "validated": true,
    "location": { "name": "...", "rating": 4.5, "placeId": "..." }
  }
}
```

---

## API Reference
### POST /quests
Generates a new quest for a user

### GET /admin
Health check for backend / DB status

Docs available at:
- Swagger: http://localhost:8000/docs
- OpenAPI: http://localhost:8000/openapi.json

---

## Development Notes
- Use TypeScript and functional components in frontend
- Backend agents follow a consistent four-step pipeline
- Quests are stored as validated JSON objects
- Add CORS middleware for frontend-backend integration

---

## Troubleshooting
| Problem                 | Solution                          |
|------------------------|-----------------------------------|
| Ports 3000/8000 taken  | Free ports or change config       |
| .env missing vars      | Define all required keys          |
| CORS errors            | Add proper middleware in FastAPI  |
| Map not rendering      | Enable Google Maps billing and key  |

---



## Detected evidence (automated analysis)

Indexed codebase: 106 recognized source files, 398 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 124)

```
.dockerignore
.gitignore
add-daily-completion-migration.sql
add-likes-comments-migration.sql
add-quest-coordinates-migration.sql
add-user-coordinates.sql
add-users-rls-policies.sql
ai-debug.log
ai.js
API_Documentation.txt
api/index.py
app/api/generate-quest/route.ts
app/dashboard/page.tsx
app/friends/page.tsx
app/globals.css
app/layout.tsx
app/leaderboard/page.tsx
app/map/page.tsx
app/page.tsx
app/preferences/page.tsx
app/quest/page.tsx
complete-setup.sql
components.json
components/navigation.tsx
components/theme-provider.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
cursorRef/ADK_pipeline.txt
cursorRef/API_Documentation.txt
cursorRef/googleAgents.txt
debug-map-query-detailed.js
debug-quest-completion.js
docker-compose.yaml
docker.md
Dockerfile
e
FRONTEND.md
googlemaps.txt
hooks/use-mobile.tsx
hooks/use-toast.ts
hooks/useAuth.ts
lib/fastapi_server.py
lib/globalQuestStore.ts
lib/googleMaps.js
lib/mapsApi.js
lib/multiagent/__inti__.py
lib/multiagent/agent.py
lib/multiagent/maps_api.py
lib/multiagent/state.py
lib/supabaseClient.js
lib/supabaseUtils.js
lib/tools.py
lib/userService.js
lib/utils.ts
next.config.mjs
package.json
postcss.config.mjs
presentation-notes.txt
quests-rls-policies.sql
README.md
requirements.txt
setup-storage.js
sqlFormat.txt
start.sh
storage-rls-policies.sql
styles/globals.css
supabase-schema.sql
tailwind.config.ts
test-ai.js
test-map-integration.js
test-quest-generation.js
test-supabase-integration.js
[4 more files omitted for size]
```

### Dependencies

- package.json: @google/generative-ai@^0.24.1, @googlemaps/js-api-loader@^1.16.8, @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@latest, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@latest, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@latest, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @react-google-maps/api@^2.20.7, @supabase/supabase-js@^2.50.0, @types/google.maps@^3.58.1, @types/node@^22, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@3.6.0, dotenv@^16.5.0, embla-carousel-react@8.5.1, input-otp@1.4.1, lucide-react@^0.454.0, next@15.2.4, next-themes@^0.4.4, postcss@^8.5, react@^19, react-day-picker@9.4.3, react-dom@^19, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@2.15.0, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5, vaul@^1.1.2, zod@^3.24.1
- requirements.txt: asyncio, backports.zoneinfo, fastapi, google-adk, google-genai, requests, supabase, uvicorn

### Recent commits (newest first)

- Update README.md
- Update README.md
- readMe stuff
- edit readMe
- work
- edits
- fixing coordinate issue
- frontend stuff. sending coords
- fix plz
- fixed docker
- fixed requests
- edits
- fixed supabase import
- deleted files with old imports
- fixed error
- edited api endpoint typo
- got rid of memory issue
- added memory limit
- edited package.json
- edited dockerfile

## Key source files (fetched from GitHub, selected and truncated for size)

### docker.md

```markdown
# Docker Setup and Deployment

This document covers Docker deployment for the Quest backend application.

## Docker Configuration

The application uses a Python 3.11-slim base image optimized for the FastAPI backend.

### Dockerfile Breakdown

```dockerfile
FROM python:3.11-slim      # Lightweight Python runtime
WORKDIR /app               # Set container working directory
COPY requirements.txt ./   # Copy Python dependencies
RUN pip install --no-cache-dir -r requirements.txt  # Install dependencies
COPY lib/ ./lib/          # Copy backend logic
COPY api/ ./api/          # Copy API modules
EXPOSE 8000               # Expose FastAPI port
CMD ["uvicorn", "lib.fastapi_server:app", "--host", "0.0.0.0", "--port", "8000"]
```

## Environment Setup

Create a `.env` file with required variables:

```bash
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
```

## Build and Run

### Single Container (Backend Only)

Build the Docker image:
```bash
docker build -t quest-backend .
```

Run the container:
```bash
docker run -p 8000:8000 --env-file .env quest-backend
```

### Docker Compose (Development)

The `docker-compose.yaml` configuration supports both frontend and backend:

```yaml
version: '3.8'
services:
  web:
    build: .
    container_name: dev-app
    ports:
      - "8000:8000"  # FastAPI backend
      - "3000:3000"  # Next.js frontend
    volumes:
      - ./:/app      # Hot reload support
    env_file:
      - .env
    command: ["./start.sh"]
```

Start with Docker Compose:
```bash
docker-compose up --build
```

## Production Deployment

### Backend-Only Production Build

For production deployment of just the backend:

```bash
# Build production image
docker build -t quest-backend:prod .

# Run production container
docker run -d \
  --name quest-backend-prod \
  -p 8000:8000 \
  --env-file .env \
  --restart unless-stopped \
  quest-backend:prod
```

### Health Checks

Add health check to Dockerfile for production:

```dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/admin || exit 1
```

## Container Management

### View logs:
```bash
docker logs quest-backend-prod
```

### Execute commands in container:
```bash
docker exec -it quest-backend-prod bash
```

### Stop and remove:
```bash
docker stop quest-backend-prod
docker rm quest-backend-prod
```

## API Access

Once running, the FastAPI backend is available at:
- **Development**: http://localhost:8000
- **API Documentation**: http://localhost:8000/docs
- **OpenAPI Schema**: http://localhost:8000/openapi.json

### Test Endpoints

```bash
# Health check
curl http://localhost:8000/admin

# Generate quest (POST request)
curl -X POST http://localhost:8000/quests \
  -H "Content-Type: application/json" \
  -d '{
    "user": {"interests": ["hiking"], "location": "SF", "preference": "outdoor"},
    "questTitles": [],
    "userId": "test123",
 
[truncated — 522 more characters]
```

### FRONTEND.md

```markdown
# EXPLR Frontend Documentation

A Next.js-based frontend for the location-based quest generation application.

## Overview

The frontend is built with Next.js 15, TypeScript, and modern React patterns. It provides a complete user interface for authentication, quest generation, social features, and user management.

## Tech Stack

- **Next.js 15.2.4** - React framework with App Router
- **React 19** - Latest React with concurrent features
- **TypeScript** - Type safety and developer experience
- **Tailwind CSS** - Utility-first styling framework
- **shadcn/ui** - High-quality UI component library
- **Radix UI** - Unstyled, accessible UI primitives
- **Supabase** - Authentication and database client
- **Google Maps API** - Location services and mapping
- **Lucide React** - Modern icon library

## Project Structure

```
app/
├── layout.tsx              # Root layout
├── page.tsx               # Landing page with auth
├── globals.css            # Global styles
├── api/
│   └── generate-quest/
│       └── route.ts       # Quest generation API route
├── dashboard/
│   └── page.tsx          # Quest feed and social dashboard
├── friends/
│   └── page.tsx          # Friend management
├── leaderboard/
│   └── page.tsx          # User rankings
├── map/
│   └── page.tsx          # Interactive quest map
├── preferences/
│   └── page.tsx          # User preferences setup
└── quest/
    └── page.tsx          # Quest generation and completion

components/
├── navigation.tsx         # App navigation component
├── theme-provider.tsx     # Theme context provider
└── ui/                   # shadcn/ui components
    ├── button.tsx
    ├── card.tsx
    ├── input.tsx
    └── ... (30+ UI components)

hooks/
├── useAuth.ts            # Authentication hook
├── use-mobile.tsx        # Mobile detection
└── use-toast.ts          # Toast notifications

lib/
├── supabase.js           # Supabase client
├── supabaseClient.js     # Additional Supabase utilities
├── supabaseUtils.js      # Database utility functions
├── utils.ts              # General utilities
└── globalQuestStore.ts   # Quest state management
```

## Key Features

### Authentication System
- **Landing Page** (`app/page.tsx`) - Login/signup with tabs
- **User Management** - Supabase auth integration
- **Profile Setup** - Preferences flow for new users
- **Protected Routes** - Authentication guards

### Quest System
- **Quest Generation** (`app/quest/page.tsx`) - AI-powered quest creation
- **Quest Completion** - Photo upload and feedback
- **Quest History** - Personal quest tracking
- **Location Integration** - Google Maps API for quest locations

### Social Features
- **Dashboard** (`app/dashboard/page.tsx`) - Social feed of completed quests
- **Friends System** (`app/friends/page.tsx`) - Friend management
- **Leaderboard** (`app/leaderboard/page.tsx`) - User rankings and streaks
- **Like/Comment System** - Social interactions

### Map Integration
- **Interactive Map** (`app/map/page.tsx`) - Visual quest explora
[truncated — 3453 more characters]
```

### requirements.txt

```
fastapi
supabase
uvicorn
requests
google-adk
google-genai
# zoneinfo is included in Python 3.9+, use backports.zoneinfo for older versions
backports.zoneinfo; python_version<'3.9'
asyncio 
```

### docker-compose.yaml

```yaml
version: '3.8'
services:

  web:
    build: .
    container_name: dev-app
    ports:
      - "8000:8000"
      - "3000:3000"
    volumes:
      - ./:/app
    env_file:
      - .env
    command: ["./start.sh"] 
```

### Dockerfile

```
# Use an official Node.js runtime as a base image
FROM node:20-slim

# Set working directory
WORKDIR /app

# Copy package files first for caching
COPY package.json ./
COPY package-lock.json ./
RUN npm install

# Copy the rest of the application
COPY . .

# Expose port 3000 (Next.js)
EXPOSE 3000

# Start Next.js in production mode — build happens at runtime
CMD ["sh", "-c", "npm run build && npm start"]
```

### package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "NODE_ENV=production next build",
    "dev": "NODE_ENV=development next dev",
    "lint": "NODE_ENV=development next lint",
    "start": "NODE_ENV=production next start -p $PORT"
  },
  "dependencies": {
    "@google/generative-ai": "^0.24.1",
    "@googlemaps/js-api-loader": "^1.16.8",
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "latest",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "latest",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "latest",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "@react-google-maps/api": "^2.20.7",
    "@supabase/supabase-js": "^2.50.0",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "3.6.0",
    "dotenv": "^16.5.0",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "lucide-react": "^0.454.0",
    "next": "15.2.4",
    "next-themes": "^0.4.4",
    "react": "^19",
    "react-day-picker": "9.4.3",
    "react-dom": "^19",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/google.maps": "^3.58.1",
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "^5"
  }
}
```

### api/index.py

```python
import sys
import os

# Ensure the lib directory is in the Python path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))

from multiagent.state import app 
```

### app/layout.tsx

```typescript
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'v0 App',
  description: 'Created with v0',
  generator: 'v0.dev',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

```

### app/page.tsx

```typescript
"use client"

import type React from "react"

import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import Link from "next/link"
import { useAuth } from "@/hooks/useAuth"
import { profileUtils } from "@/lib/supabaseUtils"

export default function LandingPage() {
  const { user, loading, signIn, signUp, isAuthenticated } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [username, setUsername] = useState("")
  const [mounted, setMounted] = useState(false)
  const [authError, setAuthError] = useState("")
  const [successMessage, setSuccessMessage] = useState("")
  const [hasCompletedPreferences, setHasCompletedPreferences] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  useEffect(() => {
    if (user && mounted) {
      // Check if user has completed preferences
      checkUserPreferences()
    }
  }, [user, mounted])

  const checkUserPreferences = async () => {
    if (!user) return;
    
    try {
      const { data: userData } = await profileUtils.getProfile(user.id)
      // Check if user has a location set (either in location_description or preference_tags.location)
      const hasLocation = userData && (
        userData.location_description || 
        (userData.preference_tags && userData.preference_tags.location)
      )
      setHasCompletedPreferences(hasLocation)
    } catch (error) {
      console.error('Error checking preferences:', error)
      setHasCompletedPreferences(false)
    }
  }

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    setAuthError("")
    
    const { error } = await signIn(email, password)
    if (error) {
      setAuthError(error)
    } else {
      // Redirect to appropriate page after successful login
      if (hasCompletedPreferences) {
        window.location.href = "/dashboard"
      } else {
        window.location.href = "/preferences"
      }
    }
  }

  const handleSignup = async (e: React.FormEvent) => {
    e.preventDefault()
    setAuthError("")
    setSuccessMessage("")
    
    const { error } = await signUp(email, password, username)
    if (error) {
      setAuthError(error)
    } else {
      // Show clear success message about email confirmation
      setSuccessMessage("Account created successfully! Please check your email and click the confirmation link to continue. You'll be able to sign in after confirming your email.")
      // Don't automatically switch to login tab - let user see the message
      // Clear the form
      setEmail("")
      setPassword("")
      setUsername("")
    }
  }

  // Show loading state until mounted to prevent hydration issues
  if (!mounted || loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50 flex items-center justify-center">
        <div className="text-center">
          <h1 className="text-5xl font-bold text-gray-900 mb-4">EXPLR</h1>
          <p className="text-xl text-gray-600">Loading...</p>
        </div>
      </div>
    )
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50">
      <div className="container mx-auto px-4 py-16">
        <div className="text-center mb-12">
          <h1 className="text-5xl font-bold text-gray-900 mb-4">EXPLR</h1>
          <p className="text-xl text-gray-600 mb-8">Discover daily adventures based on your location and preferences</p>
        </div>

        <div className="max-w-md mx-auto">
          <Tabs defaultValue="login" className="w-full">
            <TabsList className="grid w-full grid-cols-2">
              <TabsTrigger value="login">Login</TabsTrigger>
              <TabsTrigger value="signup">Sign Up</TabsTrigger>
            </TabsList>

            <TabsContent value="login">
              <Card>
                <CardHeader>
                  <CardTitle>Login</CardTitle>
                  <CardDescription>Enter your credentials to access your quests</CardDescription>
                </CardHeader>
                <CardContent>
                  <form onSubmit={handleLogin} className="space-y-4">
                    {authError && (
                      <div className="text-red-600 text-sm bg-red-50 p-2 rounded">
                        {authError}
                      </div>
                    )}
                    {successMessage && (
                      <div className="text-green-600 text-sm bg-green-50 p-2 rounded">
                        {successMessage}
                      </div>
                    )}
                    <div>
                      <Label htmlFor="email">Email</Label>
                      <Input
                        id="email"
                        type="email"
                        value={email}
                        onChange={(e) => {
                          setEmail(e.target.value)
                          setSuccessMessage("")
                        }}
                        required
                      />
                    </div>
                    <div>
                      <Label htmlFor="password">Password</Label>
                      <Input
                        id="password"
                        type="password"
                        value={password}
                        onChange={(e) => {
                          setPassword(e.target.value)
                          setSuccessMessage("")
                        }}
                        required
                      />
                    </div>
                    <Button type="submit" className="w-full" disabled={loading}>
                      {loading ? "Logging in..." : "Login"}
                    </Button>
      
[truncated — 2062 more characters]
```

### app/preferences/page.tsx

```typescript
"use client"

import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  Coffee,
  Mountain,
  Camera,
  Utensils,
  Music,
  Book,
  Palette,
  Gamepad2,
  Dumbbell,
  ShoppingBag,
  TreePine,
  Building,
  MapPin,
} from "lucide-react"
import { useAuth } from "@/hooks/useAuth"
import { userUtils } from "@/lib/supabaseUtils"

interface UserPreferences {
  location: string
  interests: string[]
}

const interestOptions = [
  { id: "food", label: "Food & Dining", icon: Utensils, color: "bg-orange-100 text-orange-800" },
  { id: "nature", label: "Nature & Outdoors", icon: TreePine, color: "bg-green-100 text-green-800" },
  { id: "culture", label: "Arts & Culture", icon: Palette, color: "bg-purple-100 text-purple-800" },
  { id: "fitness", label: "Fitness & Sports", icon: Dumbbell, color: "bg-blue-100 text-blue-800" },
  { id: "photography", label: "Photography", icon: Camera, color: "bg-pink-100 text-pink-800" },
  { id: "music", label: "Music & Entertainment", icon: Music, color: "bg-indigo-100 text-indigo-800" },
  { id: "shopping", label: "Shopping", icon: ShoppingBag, color: "bg-yellow-100 text-yellow-800" },
  { id: "coffee", label: "Coffee & Cafes", icon: Coffee, color: "bg-amber-100 text-amber-800" },
  { id: "books", label: "Books & Learning", icon: Book, color: "bg-teal-100 text-teal-800" },
  { id: "gaming", label: "Gaming & Tech", icon: Gamepad2, color: "bg-cyan-100 text-cyan-800" },
  { id: "adventure", label: "Adventure Sports", icon: Mountain, color: "bg-red-100 text-red-800" },
  { id: "architecture", label: "Architecture", icon: Building, color: "bg-gray-100 text-gray-800" },
]

export default function PreferencesPage() {
  const { user, loading, isAuthenticated } = useAuth()
  const [saving, setSaving] = useState(false)
  const [preferences, setPreferences] = useState<UserPreferences>({
    location: "",
    interests: [],
  })

  useEffect(() => {
    // Check if user is authenticated
    if (!loading && !isAuthenticated) {
      window.location.href = "/"
    }
  }, [loading, isAuthenticated])

  const handleInterestToggle = (interestId: string) => {
    setPreferences((prev) => ({
      ...prev,
      interests: prev.interests.includes(interestId)
        ? prev.interests.filter((id) => id !== interestId)
        : [...prev.interests, interestId],
    }))
  }

  const handleSavePreferences = async () => {
    if (!user) return
    
    setSaving(true)
    try {
      // Convert preferences to preference_tags format with default values for removed fields
      const preferenceTags = {
        location: preferences.location,
        interests: preferences.interests,
        bio: "",
        // Set default values for removed fields
        activityTypes: ["Restaurants & Bars", "Local Markets", "Scenic Viewpoints"],
        difficultyPreference: 50,
        timePreference: ["Morning (9-12 PM)", "Afternoon (12-5 PM)", "Evening (5-8 PM)"],
        budgetRange: [20, 100],
        indoorOutdoorPreference: 50,
        socialPreference: 50,
        explorationRadius: 5,
        questFrequency: "daily",
      }

      // Save to Supabase
      const { error } = await userUtils.updatePreferences(user.id, preferenceTags)
      
      if (error) {
        console.error('Error saving preferences:', error)
        alert('Error saving preferences. Please try again.')
        return
      }

      // Redirect to dashboard
      window.location.href = "/dashboard"
    } catch (error) {
      console.error('Error saving preferences:', error)
      alert('Error saving preferences. Please try again.')
    } finally {
      setSaving(false)
    }
  }

  const canProceed = () => {
    return preferences.location.trim() !== "" && preferences.interests.length >= 3
  }

  if (loading) {
    return (
      <div className="min-h-screen bg-gray-50 flex items-center justify-center">
        <div className="text-center">
          <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600 mx-auto"></div>
          <p className="mt-4 text-gray-600">Loading...</p>
        </div>
      </div>
    )
  }

  if (!isAuthenticated) {
    return (
      <div className="min-h-screen bg-gray-50 flex items-center justify-center">
        <div className="text-center">
          <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600 mx-auto"></div>
          <p className="mt-4 text-gray-600">Redirecting to login...</p>
        </div>
      </div>
    )
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50">
      <div className="container mx-auto px-4 py-8">
        <div className="max-w-2xl mx-auto">
          {/* Header */}
          <div className="text-center mb-8">
            <h1 className="text-3xl font-bold text-gray-900 mb-2">Set Up Your Quest Preferences</h1>
            <p className="text-gray-600">Help us create the perfect adventures for you</p>
          </div>

          {/* Main Content */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>Location & Interests</CardTitle>
              <CardDescription>Tell us where you are and what you're interested in</CardDescription>
            </CardHeader>
            <CardContent className="space-y-6">
              <div>
                <Label htmlFor="location" className="text-base font-semibold">
                  Where are you located?
                </Label>
                <p className="text-sm text-gray-600 mb-3">This helps us find quests near you</p>
                <div className="relative">
                  <MapPin className="absolute left-3 top-3 w-4 h-4 text-gray-400" />
                  <Input
                    id="location"
                    placeholder="Enter your city or zip code"
           
[truncated — 2380 more characters]
```

[97 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]