# Project export: DoGood

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: Cal Hacks 12.0
- Tagline: Bridging social good and self-improvement
- Devpost: https://devpost.com/software/dogood-gu7clt
- GitHub: https://github.com/Sam-T-G/calhacks25
- Video: https://www.youtube.com/embed/S5d_ydwTqGY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Sam.G (31 commits), calebnewtonusc (14 commits), Claude (13 commits)

## Devpost submission (written by the team)

### Inspiration

We live in an epidemic of burnout and disconnection and the online world effectively fills this gap with instant gratification through the dopamine of scrolling and easy rewards; our vision was to leverage these patterns to Do Good. Through trackable acts of kindness, productivity, and self-growth, DoGood guides users in building purpose while creating a tangible, real-world interest.

### What it does

DoGood will present opportunities for social good and self-improvement through four pages that have tasks that require photo verification: Serve: The first tab, Serve, connects you with ways to give back. You can find local volunteer opportunities, community service projects, or even respond to crisis-assistance alerts. There’s also an environmental mini-game that turns recycling and trash pickup into friendly local competitions where you log what you clean, earn XP, and see your positive impact grow. Be Productive: The Be Productive tab helps you make good use of your time. You can set focus timers for tasks, and the app sends gentle notifications to keep you accountable. It also uses smart tracking to remind you of activities you haven’t done in a while, like that personal project you left half-finished, or that hobby you love but forgot. Completing tasks or having a productive work session with the timer can also earn XP. Self-improve: The Self-Improve tab remembers your progress and gives personalized suggestions to earn XP, maybe journaling today, maybe reconnecting with an old friend. Over time, it becomes your personal coach for growth, mindfulness, and relationships. Shop: Finally, all this effort pays off, literally for good causes. Every task completed earns XP. You can spend your XP in the Shop to fund real charitable donations, from supporting food banks to environmental nonprofits. You’re improving yourself and your community at the same time. If a user opens the app and isn’t sure what part of doing good they want to prioritize for the day, they can speak with the “DoGood companion,” that can work with the user to narrow down goals and provide direction to a page that aligns with what the user can accomplish.

### How we built it

Once narrowing down the scope of what we wanted to accomplish for this version of DoGood, we began working on the frontend and UI/UX by describing the functionality and the vision for the four main buttons and debugging any faulty design through the site. We integrated this code to our GitHub repository with more frontend development made with Cursor and React. We finally integrated sponsors like Claude for the functionality and verification of all the features and LiveKit for the DoGood companion.

### Challenges we ran into

The biggest challenges we ran into was narrowing down the scope for this version of the app because the inspiration to DoGood could mean a lot of things pertaining to social good and self-improvement with endless possibilities. Another challenge we ran into was debugging tools including LiveKit and Interaction Co to be integrated into the app.

### Accomplishments we're proud of

Accomplishments that we are proud of are streamlining a clean UI/UX for different iterations of the app, leveraging Claude for verification, prompt engineering a LiveKit chatbox, and creating context API for knowledge transfer between different agents.

### What's next

The next steps for DoGood are giving it an aspect of community–having challenges within schools like USC or between different schools or group affiliations to encourage competition for the dopamine of earning XP. With a community page, users can organize their own service opportunities for everybody to partake in and encourage more proactivity when doing good. To accomplish this, we hope to partner with local nonprofit organizations from RCC, UC Berkeley, and USC to provide service and donation opportunities within the app.

## README (from the GitHub repository)

# DoGood App Features

This is a code bundle for DoGood App Features. The original project is available at https://www.figma.com/design/6sOViGx6ABV7U0aTZF4G6b/DoGood-App-Features. The fully functional site is found here: https://calhacks25-gamma.vercel.app/

## Project Structure

```
calhacks25/
├── src/               # React frontend application
├── api/               # Vercel serverless functions
├── voice-agent/       # LiveKit voice agent (Python)
└── ...
```

## Setup

### 1. Install Dependencies

**Frontend:**
```bash
npm i
```

**Voice Agent:**
```bash
cd voice-agent
uv sync
cd ..
```

### 2. Claude API - ⚠️ **Important: Requires Backend**

The app uses Claude AI to:

- Generate personalized community service activities
- Verify task completion through photo analysis

**🚨 CORS Limitation:** Claude's API cannot be called directly from browsers due to security restrictions.

#### **Option A: Use Mock Data (Recommended for Testing)**

✅ Works out of the box - no setup needed!

- All features functional
- Perfect for development/demo
- No backend required

#### **Option B: Deploy with Backend (For Production)**

To use real Claude API:

1. **Deploy to Vercel** (Recommended):

   ```bash
   vercel deploy
   vercel secrets add anthropic-api-key sk-ant-your-key-here
   ```

2. **See `CORS_SOLUTION.md` for detailed setup instructions**

**Current Status:** App uses mock data (works perfectly!). Deploy to enable live Claude AI.

### 3. LiveKit Voice Agent Setup

The "DoGood Companion" voice assistant requires LiveKit configuration:

1. **Copy environment variables:**
   ```bash
   cp .env.example .env.local
   ```

2. **Add your LiveKit credentials to `.env.local`:**
   ```
   LIVEKIT_API_KEY=your_api_key
   LIVEKIT_API_SECRET=your_api_secret
   LIVEKIT_URL=wss://your-project.livekit.cloud
   ```

3. **Configure the voice agent:**
   ```bash
   cd voice-agent
   # Update .env.local with same LiveKit credentials
   ```

4. **See `voice-agent/SETUP.md` for detailed voice agent setup**

## Running the code

### Quick Start (Recommended)

Run everything with a single command:
```bash
./start-dev.sh
```

This will start:
- LiveKit voice agent (for DoGood Companion)
- API server (for LiveKit token generation)
- Vite development server (React frontend)

The frontend will be available at `http://localhost:3000`.

### Manual Start (Alternative)

If you prefer to run services separately:

**Terminal 1 - Voice Agent:**
```bash
cd voice-agent
uv run agent.py dev
```

**Terminal 2 - Frontend & API:**
```bash
npm run dev
```

## Features

### Dynamic Activity Generation

- Activities in the "Serve" section (currently uses mock data)
- Can be personalized based on user preferences
- Click "Refresh" to generate new activities
- ℹ️ **Requires backend for live Claude generation** (see CORS_SOLUTION.md)

### Photo Verification

- Click the camera button to open your device's camera
- Take a photo of your completed task
- Verification (currently in demo mode)
- Receive XP points for completed tasks
- ℹ️ **Requires backend for AI verification** (see CORS_SOLUTION.md)

### Voice Assistant (DoGood Companion)

- Click "Speak with DoGood Companion" to start a voice conversation
- Real-time audio interaction with AI assistant
- Visual feedback showing listening/thinking/speaking states
- Click "End Conversation" to close the assistant
- Each button press starts a fresh conversation
- ℹ️ **Requires LiveKit setup and running voice agent** (see voice-agent/SETUP.md)


## Detected evidence (automated analysis)

Indexed codebase: 103 recognized source files, 853 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Next.js (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (115 of 115)

```
.gitattributes
.gitignore
api/claude.js
api/context.js
api/livekit-token.js
CLAUDE_IMPLEMENTATION_FIXES.md
CORS_ISSUE_EXPLAINED.md
CORS_SOLUTION.md
DEPLOY_TO_VERCEL.md
deploy-package/.gitignore
deploy-package/api/claude.js
deploy-package/assets/index-C-BKiZuM.css
deploy-package/assets/index-D74J4AXT.js
deploy-package/index.html
deploy-package/vercel.json
dev-server.js
EXPERIMENTAL_NAVIGATION.md
FIX_SUMMARY.md
IMPLEMENTATION_STATUS.md
IMPLEMENTATION_SUMMARY.md
index.html
NAVIGATION_AUDIT_REPORT.md
NAVIGATION_DEBUG.md
ORCHESTRATION_IMPLEMENTATION.md
package.json
POKE_INTERACTION_INTEGRATION_PLAN.md
QUICK_START.md
README.md
REAL_TIME_ORCHESTRATION.md
SETUP_GUIDE.md
src/api/context.ts
src/App.tsx
src/Attributions.md
src/components/figma/ImageWithFallback.tsx
src/components/Home.tsx
src/components/PhotoVerification.tsx
src/components/ProductivitySection.tsx
src/components/SelfImproveSection.tsx
src/components/ServeSection.tsx
src/components/ShopSection.tsx
src/components/ui/accordion.tsx
src/components/ui/alert-dialog.tsx
src/components/ui/alert.tsx
src/components/ui/aspect-ratio.tsx
src/components/ui/avatar.tsx
src/components/ui/badge.tsx
src/components/ui/breadcrumb.tsx
src/components/ui/button.tsx
src/components/ui/calendar.tsx
src/components/ui/card.tsx
src/components/ui/carousel.tsx
src/components/ui/chart.tsx
src/components/ui/checkbox.tsx
src/components/ui/collapsible.tsx
src/components/ui/command.tsx
src/components/ui/context-menu.tsx
src/components/ui/dialog.tsx
src/components/ui/drawer.tsx
src/components/ui/dropdown-menu.tsx
src/components/ui/form.tsx
src/components/ui/hover-card.tsx
src/components/ui/input-otp.tsx
src/components/ui/input.tsx
src/components/ui/label.tsx
src/components/ui/menubar.tsx
src/components/ui/navigation-menu.tsx
src/components/ui/pagination.tsx
src/components/ui/popover.tsx
src/components/ui/progress.tsx
src/components/ui/radio-group.tsx
src/components/ui/resizable.tsx
src/components/ui/scroll-area.tsx
src/components/ui/select.tsx
src/components/ui/separator.tsx
src/components/ui/sheet.tsx
src/components/ui/sidebar.tsx
src/components/ui/skeleton.tsx
src/components/ui/slider.tsx
src/components/ui/sonner.tsx
src/components/ui/switch.tsx
src/components/ui/table.tsx
src/components/ui/tabs.tsx
src/components/ui/textarea.tsx
src/components/ui/toggle-group.tsx
src/components/ui/toggle.tsx
src/components/ui/tooltip.tsx
src/components/ui/use-mobile.ts
src/components/ui/utils.ts
src/components/UserStats.tsx
src/components/VoiceAssistant.tsx
src/config/userPreferences.example.ts
src/config/userPreferences.ts
src/custom.d.ts
src/guidelines/Guidelines.md
src/hooks/useContext.ts
src/index.css
src/main.tsx
src/services/claudeService.ts
src/services/contextService.ts
src/styles/globals.css
src/types/serve.ts
src/utils/imageUtils.ts
start-dev.sh
tsconfig.json
tsconfig.node.json
vercel.json
vite.config.ts
voice-agent/.gitignore
voice-agent/.python-version
voice-agent/agent.py
voice-agent/persona_schema.py
voice-agent/pyproject.toml
voice-agent/README.md
voice-agent/SETUP.md
voice-agent/uv.lock
```

### Dependencies

- package.json: @livekit/components-react@^2.9.15, @livekit/components-styles@^1.1.6, @radix-ui/react-accordion@^1.2.3, @radix-ui/react-alert-dialog@^1.1.6, @radix-ui/react-aspect-ratio@^1.1.2, @radix-ui/react-avatar@^1.1.3, @radix-ui/react-checkbox@^1.1.4, @radix-ui/react-collapsible@^1.1.3, @radix-ui/react-context-menu@^2.2.6, @radix-ui/react-dialog@^1.1.6, @radix-ui/react-dropdown-menu@^2.1.6, @radix-ui/react-hover-card@^1.1.6, @radix-ui/react-label@^2.1.2, @radix-ui/react-menubar@^1.1.6, @radix-ui/react-navigation-menu@^1.2.5, @radix-ui/react-popover@^1.1.6, @radix-ui/react-progress@^1.1.2, @radix-ui/react-radio-group@^1.2.3, @radix-ui/react-scroll-area@^1.2.3, @radix-ui/react-select@^2.1.6, @radix-ui/react-separator@^1.1.2, @radix-ui/react-slider@^1.2.3, @radix-ui/react-slot@^1.1.2, @radix-ui/react-switch@^1.1.3, @radix-ui/react-tabs@^1.1.3, @radix-ui/react-toggle@^1.1.2, @radix-ui/react-toggle-group@^1.1.2, @radix-ui/react-tooltip@^1.1.8, @types/node@^20.10.0, @types/react@^19.2.2, @types/react-dom@^19.2.2, @vitejs/plugin-react-swc@^3.10.2, class-variance-authority@^0.7.1, claude@^0.1.1, clsx@*, cmdk@^1.1.1, cors@^2.8.5, dotenv@^17.2.3, embla-carousel-react@^8.6.0, express@^5.1.0, input-otp@^1.4.2, livekit-client@^2.15.13, livekit-server-sdk@^2.14.0, lucide-react@^0.487.0, next-themes@^0.4.6, react@^18.3.1, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.55.0, react-resizable-panels@^2.1.7, recharts@^2.15.2, sonner@^2.0.3, tailwind-merge@*, typescript@^5.9.3, vaul@^1.1.2, vite@6.3.5
- voice-agent/pyproject.toml: httpx@>=0.27.0, livekit-agents[deepgram,openai,silero,turn-detector,cartesia,assemblyai]@>=1.2.15, livekit-plugins-noise-cancellation@~=0.2, python-dotenv@>=1.1.1

### Recent commits (newest first)

- Merge pull request #3 from Sam-T-G/python-integration
- implementation plan
- Update README.md
- Merge pull request #2 from Sam-T-G/python-integration
- Update ServeSection.tsx
- Merge pull request #1 from Sam-T-G/python-integration
- data configuration
- Update Home.tsx
- Update Home.tsx
- Update Home.tsx
- commit
- location relevance
- Update ServeSection.tsx
- progress
- ring and voice assistant fix
- Update ServeSection.tsx
- Update ServeSection.tsx
- Update ServeSection.tsx
- Update claudeService.ts
- debug

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

### NAVIGATION_DEBUG.md

```markdown
# Navigation Debug Analysis

## Issues Identified

### Issue #1: Data Channel Event Name

**Problem:** Using incorrect event name `"dataReceived"` in LiveKit
**Location:** `src/components/VoiceAssistant.tsx:282`
**Fix:** Change to `"data-received"` or use proper LiveKit SDK method

### Issue #2: Agent Override Method

**Problem:** `on_chat_received` may not be a valid Agent override method
**Location:** `voice-agent/agent.py:148`
**Fix:** Use `async def on_user_speech` instead

### Issue #3: Data Publishing Mismatch

**Problem:** Using `publish_data` but listener might not match
**Location:** `voice-agent/agent.py:140`
**Fix:** Verify data channel configuration

### Issue #4: Room Reference Not Updated

**Problem:** Room might not be set in Assistant initialization
**Location:** `voice-agent/agent.py:234`
**Fix:** Ensure room is available before sending data

### Issue #5: Navigation Type Safety

**Problem:** Casting page to Section type might fail
**Location:** `src/components/VoiceAssistant.tsx:44`
**Fix:** Validate page against Section type before navigation

## Testing Checklist

- [ ] Verify data channel events fire in console
- [ ] Check if Claude responses contain navigation JSON
- [ ] Test onNavigate callback is actually called
- [ ] Ensure room connection is established before data sends
- [ ] Validate navigation page against Section enum

```

### NAVIGATION_AUDIT_REPORT.md

```markdown
# Deep Navigation Audit Report

## Issues Found

### Issue #1: Incorrect Data Channel Event Name

**Problem:** Using `room.on("dataReceived")` but LiveKit SDK may use different event name
**File:** `src/components/VoiceAssistant.tsx:282`
**Fix:** Use `room.on(f.RoomEvent.DataReceived)` or check SDK docs

### Issue #2: Agent Override Method Not Called

**Problem:** `on_chat_received` method may not be the correct override name for Agent class
**File:** `voice-agent/agent.py:148`
**Evidence:** Cannot find `on_chat_received` in LiveKit agents docs
**Fix:** Use proper Agent callback method

### Issue #3: No Event Handler Registration

**Problem:** `room.on()` needs event name as first param - verifying correct syntax
**File:** `src/components/VoiceAssistant.tsx:282`
**Fix:** Verify correct event name from LiveKit SDK

### Issue #4: Missing Console Logs for Debugging

**Problem:** No logs to verify data is being sent/received
**Fix:** Add extensive logging throughout the flow

### Issue #5: Potential Timing Issue

**Problem:** Navigation happens while voice interface is still processing
**File:** Multiple files
**Fix:** Ensure navigation waits for data channel to be ready

## Testing Steps

1. Check browser console for any errors
2. Verify data channel events in network tab
3. Add debug logs to track message flow
4. Test if Claude is actually being called
5. Verify JSON parsing is working

## Proposed Fixes

1. Fix data channel event listening
2. Use correct Agent callback methods
3. Add comprehensive logging
4. Add error handling for data transmission
5. Test end-to-end flow

```

### package.json

```
{
      "name": "DoGood App Features",
      "version": "0.1.0",
      "private": true,
      "type": "module",
      "dependencies": {
            "@livekit/components-react": "^2.9.15",
            "@livekit/components-styles": "^1.1.6",
            "@radix-ui/react-accordion": "^1.2.3",
            "@radix-ui/react-alert-dialog": "^1.1.6",
            "@radix-ui/react-aspect-ratio": "^1.1.2",
            "@radix-ui/react-avatar": "^1.1.3",
            "@radix-ui/react-checkbox": "^1.1.4",
            "@radix-ui/react-collapsible": "^1.1.3",
            "@radix-ui/react-context-menu": "^2.2.6",
            "@radix-ui/react-dialog": "^1.1.6",
            "@radix-ui/react-dropdown-menu": "^2.1.6",
            "@radix-ui/react-hover-card": "^1.1.6",
            "@radix-ui/react-label": "^2.1.2",
            "@radix-ui/react-menubar": "^1.1.6",
            "@radix-ui/react-navigation-menu": "^1.2.5",
            "@radix-ui/react-popover": "^1.1.6",
            "@radix-ui/react-progress": "^1.1.2",
            "@radix-ui/react-radio-group": "^1.2.3",
            "@radix-ui/react-scroll-area": "^1.2.3",
            "@radix-ui/react-select": "^2.1.6",
            "@radix-ui/react-separator": "^1.1.2",
            "@radix-ui/react-slider": "^1.2.3",
            "@radix-ui/react-slot": "^1.1.2",
            "@radix-ui/react-switch": "^1.1.3",
            "@radix-ui/react-tabs": "^1.1.3",
            "@radix-ui/react-toggle": "^1.1.2",
            "@radix-ui/react-toggle-group": "^1.1.2",
            "@radix-ui/react-tooltip": "^1.1.8",
            "class-variance-authority": "^0.7.1",
            "claude": "^0.1.1",
            "clsx": "*",
            "cmdk": "^1.1.1",
            "embla-carousel-react": "^8.6.0",
            "input-otp": "^1.4.2",
            "livekit-client": "^2.15.13",
            "livekit-server-sdk": "^2.14.0",
            "lucide-react": "^0.487.0",
            "next-themes": "^0.4.6",
            "react": "^18.3.1",
            "react-day-picker": "^8.10.1",
            "react-dom": "^18.3.1",
            "react-hook-form": "^7.55.0",
            "react-resizable-panels": "^2.1.7",
            "recharts": "^2.15.2",
            "sonner": "^2.0.3",
            "tailwind-merge": "*",
            "vaul": "^1.1.2"
      },
      "devDependencies": {
            "@types/node": "^20.10.0",
            "@types/react": "^19.2.2",
            "@types/react-dom": "^19.2.2",
            "@vitejs/plugin-react-swc": "^3.10.2",
            "cors": "^2.8.5",
            "dotenv": "^17.2.3",
            "express": "^5.1.0",
            "typescript": "^5.9.3",
            "vite": "6.3.5"
      },
      "scripts": {
            "dev": "node dev-server.js & vite",
            "dev:api": "node dev-server.js",
            "dev:vite": "vite",
            "build": "vite build"
      }
}

```

### voice-agent/pyproject.toml

```
[project]
name = "voice-agent-workshop"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "livekit-agents[deepgram,openai,silero,turn-detector,cartesia,assemblyai]>=1.2.15",
    "livekit-plugins-noise-cancellation~=0.2",
    "python-dotenv>=1.1.1",
    "httpx>=0.27.0",
]

```

### src/main.tsx

```typescript

  import { createRoot } from "react-dom/client";
  import App from "./App.tsx";
  import "./index.css";

  createRoot(document.getElementById("root")!).render(<App />);
  
```

### src/App.tsx

```typescript
import { useState, useEffect } from "react";
import { Home } from "./components/Home";
import { ServeSection } from "./components/ServeSection";
import { ProductivitySection } from "./components/ProductivitySection";
import { SelfImproveSection } from "./components/SelfImproveSection";
import { ShopSection } from "./components/ShopSection";
import { UserStats } from "./components/UserStats";
import { VoiceAssistant } from "./components/VoiceAssistant";
import { Toaster } from "./components/ui/sonner";
import { userPreferences } from "./config/userPreferences";
import { contextService } from "./services/contextService";

export type Section =
	| "home"
	| "serve"
	| "productivity"
	| "self-improve"
	| "shop"
	| "stats";

export default function App() {
	const [currentSection, setCurrentSection] = useState<Section>("home");
	const [xpPoints, setXpPoints] = useState(2450);
	const [isVoiceAssistantActive, setIsVoiceAssistantActive] = useState(false);

	// Request location permission on mount
	useEffect(() => {
		if ("geolocation" in navigator) {
			navigator.geolocation.getCurrentPosition(
				(position) => {
					const location = {
						latitude: position.coords.latitude,
						longitude: position.coords.longitude,
					};
					// Store in context service
					contextService.updatePreferences({
						location: `${location.latitude}, ${location.longitude}`,
					});
					console.log("[App] Location obtained:", location);
				},
				(error) => {
					console.warn(
						"[App] Location permission denied or error:",
						error.message
					);
				},
				{
					enableHighAccuracy: false,
					timeout: 5000,
					maximumAge: 60000, // Cache for 1 minute
				}
			);
		}
	}, []);

	// Track page navigation
	useEffect(() => {
		const pageName =
			currentSection.charAt(0).toUpperCase() + currentSection.slice(1);
		contextService.trackPageVisit(pageName);
	}, [currentSection]);

	// Track voice assistant sessions
	useEffect(() => {
		if (isVoiceAssistantActive) {
			contextService.logActivity(
				"voice_session_started",
				"Started DoGood Companion voice session"
			);
		}
	}, [isVoiceAssistantActive]);

	const addXP = (points: number) => {
		setXpPoints((prev) => prev + points);
		const newTotal = xpPoints + points;
		contextService.getContext().totalXP = newTotal;
	};

	const spendXP = (points: number) => {
		if (xpPoints >= points) {
			setXpPoints((prev) => prev - points);
			return true;
		}
		return false;
	};

	// Handle voice navigation
	const handleVoiceNavigation = (section: Section) => {
		console.log("[App] Voice navigation to:", section);
		setCurrentSection(section);
	};

	// Handle voice-triggered actions from Claude orchestration
	const executeVoiceAction = (action: any) => {
		console.log("[App] Executing voice action:", action);

		switch (action.type) {
			case "generate_activities":
				// Activities will be auto-generated when navigating to serve section
				console.log("[App] Triggering activity generation");
				break;

			case "start_timer":
				// Navigate to productivity and timer will be available
				if (action.params?.minutes) {
					console.log(`[App] Starting ${action.params.minutes} minute timer`);
					setCurrentSection("productivity");
				}
				break;

			case "generate_self_improve":
				// Self-improve activities will be available on that page
				console.log("[App] Triggering self-improve generation");
				setCurrentSection("self-improve");
				break;

			case "update_preferences":
				// Context service handles this automatically
				console.log("[App] Preferences updated via context service");
				break;

			case "refresh_activities":
				// Force re-render of current section
				console.log("[App] Refreshing activities");
				break;

			default:
				console.log(`[App] Unknown action type: ${action.type}`);
		}
	};

	const renderSection = () => {
		switch (currentSection) {
			case "home":
				return (
					<Home
						xpPoints={xpPoints}
						onNavigate={handleVoiceNavigation}
						onVoiceAssistant={() => setIsVoiceAssistantActive(true)}
					/>
				);
			case "serve":
				return (
					<ServeSection
						onBack={() => setCurrentSection("home")}
						onEarnXP={addXP}
						userPreferences={userPreferences}
					/>
				);
			case "productivity":
				return (
					<ProductivitySection
						onBack={() => setCurrentSection("home")}
						onEarnXP={addXP}
					/>
				);
			case "self-improve":
				return (
					<SelfImproveSection
						onBack={() => setCurrentSection("home")}
						onEarnXP={addXP}
					/>
				);
			case "shop":
				return (
					<ShopSection
						xpPoints={xpPoints}
						onBack={() => setCurrentSection("home")}
						onSpendXP={spendXP}
					/>
				);
			case "stats":
				return (
					<UserStats
						xpPoints={xpPoints}
						onBack={() => setCurrentSection("home")}
					/>
				);
			default:
				return (
					<Home
						xpPoints={xpPoints}
						onNavigate={handleVoiceNavigation}
						onVoiceAssistant={() => setIsVoiceAssistantActive(true)}
					/>
				);
		}
	};

	return (
		<>
			<style>{`
				html, body {
					background-color: #E8DC93;
					overscroll-behavior: contain;
				}
			`}</style>
			<div
				className="min-h-screen relative"
				style={{ backgroundColor: "#E8DC93" }}>
				{renderSection()}
				<VoiceAssistant
					isActive={isVoiceAssistantActive}
					onClose={() => setIsVoiceAssistantActive(false)}
					onNavigate={handleVoiceNavigation}
					onExecuteAction={executeVoiceAction}
				/>
				<Toaster />
			</div>
		</>
	);
}

```

### index.html

```html

  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>DoGood App Features</title>
    </head>

    <body>
      <div id="root"></div>
      <script type="module" src="/src/main.tsx"></script>
    </body>
  </html>
  
```

### start-dev.sh

```shell
#!/bin/bash

# DoGood App - Development Startup Script
# This script starts all necessary services for local development

echo "🚀 Starting DoGood App Development Environment..."
echo ""

# Check if uv is installed
if ! command -v uv &> /dev/null; then
    echo "⚠️  uv is not installed. Installing uv..."
    curl -LsSf https://astral.sh/uv/install.sh | sh
    export PATH="$HOME/.local/bin:$PATH"
fi

# Start the LiveKit voice agent in the background
echo "🎤 Starting LiveKit Voice Agent..."
cd voice-agent
uv run agent.py dev &
AGENT_PID=$!
cd ..

# Wait a moment for the agent to initialize
sleep 3

# Start the API server and Vite dev server
echo "🌐 Starting API server and Vite..."
npm run dev

# Clean up on exit
trap "kill $AGENT_PID 2>/dev/null" EXIT

```

### dev-server.js

```javascript
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import { AccessToken } from "livekit-server-sdk";

// Load environment variables from .env.local first, then .env as fallback
dotenv.config({ path: ".env.local" });
dotenv.config({ path: ".env" });

const app = express();
app.use(cors());
app.use(express.json());

// In-memory session store for context
const sessionStore = new Map();

// Clean up old sessions periodically
setInterval(() => {
	const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
	for (const [sessionId, context] of sessionStore.entries()) {
		if (context.sessionStartTime < dayAgo) {
			sessionStore.delete(sessionId);
		}
	}
}, 60 * 60 * 1000); // Run every hour

app.get("/api/livekit-token", async (req, res) => {
	try {
		const apiKey = process.env.LIVEKIT_API_KEY;
		const apiSecret = process.env.LIVEKIT_API_SECRET;
		const wsUrl = process.env.LIVEKIT_URL;

		if (!apiKey || !apiSecret || !wsUrl) {
			throw new Error("LiveKit credentials not configured");
		}

		const roomName = req.query.roomName || `dogood-${Date.now()}`;
		const participantName =
			req.query.participantName || `user-${Math.floor(Math.random() * 10000)}`;

		// Get user context from query params
		const userContext = req.query.userContext || "";

		const at = new AccessToken(apiKey, apiSecret, {
			identity: participantName,
			ttl: "10m",
			// Store user context in token metadata for voice agent access
			metadata: JSON.stringify({
				userContext: userContext,
			}),
		});

		at.addGrant({
			room: roomName,
			roomJoin: true,
			canPublish: true,
			canSubscribe: true,
		});

		const token = await at.toJwt();

		res.status(200).json({
			token,
			wsUrl,
			roomName,
		});
	} catch (error) {
		console.error("Error generating LiveKit token:", error);
		res.status(500).json({
			error: "Failed to generate token",
			message: error.message,
		});
	}
});

// Context API endpoints
app.get("/api/context", async (req, res) => {
	const { sessionId } = req.query;

	if (!sessionId) {
		return res.status(400).json({ error: "sessionId is required" });
	}

	const context = sessionStore.get(sessionId);
	if (!context) {
		return res.status(404).json({ error: "Session not found" });
	}

	res.status(200).json(context);
});

app.post("/api/context", async (req, res) => {
	const { sessionId } = req.query;
	const context = req.body;

	if (!sessionId || !context) {
		return res
			.status(400)
			.json({ error: "sessionId and context are required" });
	}

	sessionStore.set(sessionId, context);
	console.log(`[Context] Stored context for session ${sessionId}`);
	res.status(200).json({ success: true, context });
});

const PORT = 3001;
app.listen(PORT, () => {
	console.log(`API server running on http://localhost:${PORT}`);
});

```

### vite.config.ts

```typescript

  import { defineConfig } from 'vite';
  import react from '@vitejs/plugin-react-swc';
  import path from 'path';

  export default defineConfig({
    plugins: [react()],
    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
      alias: {
        'vaul@1.1.2': 'vaul',
        'sonner@2.0.3': 'sonner',
        'recharts@2.15.2': 'recharts',
        'react-resizable-panels@2.1.7': 'react-resizable-panels',
        'react-hook-form@7.55.0': 'react-hook-form',
        'react-day-picker@8.10.1': 'react-day-picker',
        'next-themes@0.4.6': 'next-themes',
        'lucide-react@0.487.0': 'lucide-react',
        'input-otp@1.4.2': 'input-otp',
        'embla-carousel-react@8.6.0': 'embla-carousel-react',
        'cmdk@1.1.1': 'cmdk',
        'class-variance-authority@0.7.1': 'class-variance-authority',
        '@radix-ui/react-tooltip@1.1.8': '@radix-ui/react-tooltip',
        '@radix-ui/react-toggle@1.1.2': '@radix-ui/react-toggle',
        '@radix-ui/react-toggle-group@1.1.2': '@radix-ui/react-toggle-group',
        '@radix-ui/react-tabs@1.1.3': '@radix-ui/react-tabs',
        '@radix-ui/react-switch@1.1.3': '@radix-ui/react-switch',
        '@radix-ui/react-slot@1.1.2': '@radix-ui/react-slot',
        '@radix-ui/react-slider@1.2.3': '@radix-ui/react-slider',
        '@radix-ui/react-separator@1.1.2': '@radix-ui/react-separator',
        '@radix-ui/react-select@2.1.6': '@radix-ui/react-select',
        '@radix-ui/react-scroll-area@1.2.3': '@radix-ui/react-scroll-area',
        '@radix-ui/react-radio-group@1.2.3': '@radix-ui/react-radio-group',
        '@radix-ui/react-progress@1.1.2': '@radix-ui/react-progress',
        '@radix-ui/react-popover@1.1.6': '@radix-ui/react-popover',
        '@radix-ui/react-navigation-menu@1.2.5': '@radix-ui/react-navigation-menu',
        '@radix-ui/react-menubar@1.1.6': '@radix-ui/react-menubar',
        '@radix-ui/react-label@2.1.2': '@radix-ui/react-label',
        '@radix-ui/react-hover-card@1.1.6': '@radix-ui/react-hover-card',
        '@radix-ui/react-dropdown-menu@2.1.6': '@radix-ui/react-dropdown-menu',
        '@radix-ui/react-dialog@1.1.6': '@radix-ui/react-dialog',
        '@radix-ui/react-context-menu@2.2.6': '@radix-ui/react-context-menu',
        '@radix-ui/react-collapsible@1.1.3': '@radix-ui/react-collapsible',
        '@radix-ui/react-checkbox@1.1.4': '@radix-ui/react-checkbox',
        '@radix-ui/react-avatar@1.1.3': '@radix-ui/react-avatar',
        '@radix-ui/react-aspect-ratio@1.1.2': '@radix-ui/react-aspect-ratio',
        '@radix-ui/react-alert-dialog@1.1.6': '@radix-ui/react-alert-dialog',
        '@radix-ui/react-accordion@1.2.3': '@radix-ui/react-accordion',
        '@': path.resolve(__dirname, './src'),
      },
    },
    build: {
      target: 'esnext',
      outDir: 'build',
    },
    server: {
      port: 3000,
      open: true,
      proxy: {
        '/api': {
          target: 'http://localhost:3001',
          changeOrigin: true,
        },
      },
    },
  });
```

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