# Project export: cheap bananas!

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: find the best food deals anytime, anywhere, for you!
- Devpost: https://devpost.com/software/cheap-bananas
- GitHub: https://github.com/20ben/cheapbananas#
- Video: https://www.youtube.com/embed/k0CUaSM-UJA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — mepotato (16 commits), Matthew Kim (16 commits), Ben (8 commits)

## Devpost submission (written by the team)

### Inspiration

Starting university was the first time many of us began fully funding our meal and snack habits—and these expenses definitely add up. Both personal experience and the familiar stereotype that college students love free food inspired us to build a central platform to discover nearby restaurant and snack deals for anyone looking to try new food and save money.

### What it does

cheap bananas! locates free and discounted food, including BOGOs, special, seasonal, and new customer offers, near the user's location. We blended Bright Data's power to web scrape data in real time with Letta's persistent, shareable, and self-adaptive memory to create an app above other snack and meal apps by providing comprehensive, up-to-date, and intelligent recommendations that provides the most recent & local meal deals and events with food for users.

### How we built it

We used Letta's agent builder, running Claude Opus 4.1, to create our agents that use Bright Data to scrape the web. We used HTML, Python, and JavaScript to code the UI and link to Letta, and Google Maps API to access the user's location and support the interface.

### Challenges we ran into

Efficiency was one of the biggest hurdles when we built our platform. The need to link many services introduced significant latency, which exacerbated the event's overloaded wifi, leading to query timeouts and high webscraping response times. We explored different ways to divide and conquer query work including distributing the workload amongst multiple specialized agents simultaneously, unifying them with Letta's shared memory. We also spent time ironing out the links between the UI and the backend given the complex queries and agents needed.

### Accomplishments we're proud of

Integrating various apps to work together, especially giving Letta Bright Data's power to elevate our application's reach and accuracy was one of our greatest accomplishments. Integrating UI locational features, including multiple location displays and interactions is also a source of pride for us.

### What we learned

The breadth of knowledge needed for our project brought us to many new areas of knowledge, including ideation and brainstorming techniques, agentic project architecture/design, connecting services through various APIs, SDKs, and MCPs, and fluency with important UI design elements.

### What's next

for cheap bananas Our goals for cheap bananas! include increasing the range and efficiency of our queries by migrating to Bright Data SDK, as well as leveraging Letta's persistent memory to create more personalized recommendations.

## README (from the GitHub repository)

# cheapbananas

**Inspiration**

Starting university was the first time many of us began fully funding our meal and snack habits—and these expenses definitely add up. Both personal experience and the familiar stereotype that college students love free food inspired us to build a central platform to discover nearby restaurant and snack deals for anyone looking to try new food and save money.

**What it does**

cheap bananas! locates free and discounted food, including BOGOs, special, seasonal, and new customer offers, near the user's location. We blended Bright Data's power to web scrape data in real time with Letta's persistent, shareable, and self-adaptive memory to create an app above other snack and meal apps by providing comprehensive, up-to-date, and intelligent recommendations that provides the most recent & local meal deals and events with food for users.

**How we built it**

We used Letta's agent builder, running Claude Opus 4.1, to create our agents that use Bright Data to scrape the web. We used HTML, Python, and JavaScript to code the UI and link to Letta, and Google Maps API to access the user's location and support the interface.

**Challenges we ran into**

Efficiency was one of the biggest hurdles when we built our platform. The need to link many services introduced significant latency, which exacerbated the event's overloaded wifi, leading to query timeouts and high webscraping response times. We explored different ways to divide and conquer query work including distributing the workload amongst multiple specialized agents simultaneously, unifying them with Letta's shared memory. We also spent time ironing out the links between the UI and the backend given the complex queries and agents needed.

**Accomplishments that we're proud of**

Integrating various apps to work together, especially giving Letta Bright Data's power to elevate our application's reach and accuracy was one of our greatest accomplishments. Integrating UI locational features, including multiple location displays and interactions is also a source of pride for us.

**What we learned**

The breadth of knowledge needed for our project brought us to many new areas of knowledge, including ideation and brainstorming techniques, agentic project architecture/design, connecting services through various APIs, SDKs, and MCPs, and fluency with important UI design elements.

**What's next for cheap bananas**

Our goals for cheap bananas! include increasing the range and efficiency of our queries by migrating to Bright Data SDK, as well as leveraging Letta's persistent memory to create more personalized recommendations.

**Built With**

brightdata
css
flask
google-maps
html
javascript
letta
python
react

**Try it out:**

<u>Installing Node.js:</u>
[Node](https://nodejs.org/en/download)

<u>Installing Flask:</u>
[Flask](https://flask.palletsprojects.com/en/stable/installation/)

1. Change directory to backend folder in project directory
2. Download .venv folder: ```python3 -m venv .venv```
3. Activate .venv (virtual environment): ```. .venv/bin/activate```
4. Install required modules in the virtual environment: ```pip install -r requirements.txt```

<u>Running frontend:<u>
1. Change directory to the frontend folder
2. Run ```npm install``` to install the required modules
3. Run ```npm run dev``` to run the development website


## Detected evidence (automated analysis)

Indexed codebase: 90 recognized source files, 203 KB.
- CSS (language) — detected in the code
- Flask (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

## Codebase structure (from repository index)

### Files (97 of 97)

```
.gitignore
backend/asyncLettaConnection.py
backend/AsyncLettaMinion.py
backend/AsyncLettaReader.py
backend/flask-test.py
backend/hardcode.json
backend/letta-test-async.py
backend/letta-test-multitool.py
backend/letta-test.py
backend/letta-test2.py
backend/letta-tool-test.py
backend/lettaConnection.py
backend/LettaManager.py
backend/lettaMultiAgents.py
backend/package.json
backend/PlacesApi.py
backend/requirements.txt
backend/server.py
backend/test.py
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/oldindex.html
frontend/package.json
frontend/README.md
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/AppTest.test.tsx
frontend/src/components/ErrorBoundary.tsx
frontend/src/components/FloatingBanner.tsx
frontend/src/components/HomepagePlaceholder.tsx
frontend/src/components/MapComponent.tsx
frontend/src/components/ui/accordion.tsx
frontend/src/components/ui/alert-dialog.tsx
frontend/src/components/ui/alert.tsx
frontend/src/components/ui/aspect-ratio.tsx
frontend/src/components/ui/avatar.tsx
frontend/src/components/ui/badge.tsx
frontend/src/components/ui/breadcrumb.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/calendar.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/carousel.tsx
frontend/src/components/ui/chart.tsx
frontend/src/components/ui/checkbox.tsx
frontend/src/components/ui/collapsible.tsx
frontend/src/components/ui/command.tsx
frontend/src/components/ui/context-menu.tsx
frontend/src/components/ui/dialog.tsx
frontend/src/components/ui/drawer.tsx
frontend/src/components/ui/dropdown-menu.tsx
frontend/src/components/ui/form.tsx
frontend/src/components/ui/hover-card.tsx
frontend/src/components/ui/input-otp.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/label.tsx
frontend/src/components/ui/menubar.tsx
frontend/src/components/ui/navigation-menu.tsx
frontend/src/components/ui/pagination.tsx
frontend/src/components/ui/popover.tsx
frontend/src/components/ui/progress.tsx
frontend/src/components/ui/radio-group.tsx
frontend/src/components/ui/resizable.tsx
frontend/src/components/ui/scroll-area.tsx
frontend/src/components/ui/select.tsx
frontend/src/components/ui/separator.tsx
frontend/src/components/ui/sheet.tsx
frontend/src/components/ui/sidebar.tsx
frontend/src/components/ui/skeleton.tsx
frontend/src/components/ui/slider.tsx
frontend/src/components/ui/sonner.tsx
frontend/src/components/ui/switch.tsx
frontend/src/components/ui/table.tsx
frontend/src/components/ui/tabs.tsx
frontend/src/components/ui/textarea.tsx
frontend/src/components/ui/toggle-group.tsx
frontend/src/components/ui/toggle.tsx
frontend/src/components/ui/tooltip.tsx
frontend/src/hooks/use-app-config.ts
frontend/src/hooks/use-auth.ts
frontend/src/hooks/use-mcp-client.ts
frontend/src/hooks/use-mobile.ts
frontend/src/index.css
frontend/src/lib/auth-integration.ts
frontend/src/lib/jwt-utils.ts
frontend/src/lib/react-jsx-dev.js
frontend/src/lib/report-parent-window.ts
frontend/src/lib/types/creao-studio.ts
frontend/src/lib/utils.ts
frontend/src/main.jsx
frontend/src/MapView.jsx
frontend/src/routes/__root.tsx
frontend/src/routes/index.tsx
frontend/src/routes/jwt-debug.tsx
frontend/vite.config.js
README.md
requirements.txt
```

### Dependencies

- backend/package.json: leaflet@^1.9.4, react-leaflet@^5.0.0
- backend/requirements.txt: annotated-types@==0.7.0, anyio@==4.11.0, asyncio@==4.0.0, blinker@==1.9.0, certifi@==2025.10.5, charset-normalizer@==3.4.4, click@==8.3.0, colorama@==0.4.6, Flask@==3.1.2, flask-cors@==6.0.1, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, httpx-sse@==0.4.0, idna@==3.11, itsdangerous@==2.2.0, Jinja2@==3.1.6, letta-client@==0.1.324, MarkupSafe@==3.0.3, pydantic@==2.12.3, pydantic_core@==2.41.4, python-dotenv@==1.1.1, requests@==2.32.5, sniffio@==1.3.1, typing_extensions@==4.15.0, typing-inspection@==0.4.2, urllib3@==2.5.0, Werkzeug@==3.1.3
- frontend/package.json: @eslint/js@^9.36.0, @types/react@^19.1.16, @types/react-dom@^19.1.9, @vitejs/plugin-react@^5.0.4, eslint@^9.36.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.22, globals@^16.4.0, leaflet@^1.9.4, react@^19.1.1, react-dom@^19.1.1, react-leaflet@^5.0.0, vite@^7.1.7
- requirements.txt: blinker@==1.9.0, certifi@==2025.10.5, charset-normalizer@==3.4.4, click@==8.3.0, colorama@==0.4.6, Flask@==3.1.2, idna@==3.11, itsdangerous@==2.2.0, Jinja2@==3.1.6, MarkupSafe@==3.0.3, requests@==2.32.5, urllib3@==2.5.0, Werkzeug@==3.1.3

### Recent commits (newest first)

- Update README.md
- Update README.md
- Merge branch 'main' of https://github.com/20ben/cheapbananas
- finished multiple return for AsyncReader in 2 element case
- DISPLAY DEALS!!
- Delete .env
- done
- Merge branch 'main' of https://github.com/20ben/cheapbananas
- Integrated letta with flask
- cleaning
- super small formatting change
- Create PlacesApi.cpython-314.pyc
- Adding Letta -> React
- updated AsyncMinion instructions, letta-test2.py is now template for api
- Merge branch 'main' of https://github.com/20ben/cheapbananas
- Disabled search bar while fetching data and added deals section to sidebar
- corrected letta-test2
- Merge branch 'main' of https://github.com/20ben/cheapbananas
- Fixed a lot of stuff!
- Merge branch 'main' of https://github.com/20ben/cheapbananas

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

### requirements.txt

```
blinker==1.9.0
certifi==2025.10.5
charset-normalizer==3.4.4
click==8.3.0
colorama==0.4.6
Flask==3.1.2
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.3
requests==2.32.5
urllib3==2.5.0
Werkzeug==3.1.3

```

### backend/package.json

```
{
  "dependencies": {
    "leaflet": "^1.9.4",
    "react-leaflet": "^5.0.0"
  }
}

```

### backend/requirements.txt

```
annotated-types==0.7.0
anyio==4.11.0
asyncio==4.0.0
blinker==1.9.0
certifi==2025.10.5
charset-normalizer==3.4.4
click==8.3.0
colorama==0.4.6
Flask==3.1.2
flask-cors==6.0.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
httpx-sse==0.4.0
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.6
letta-client==0.1.324
MarkupSafe==3.0.3
pydantic==2.12.3
pydantic_core==2.41.4
python-dotenv==1.1.1
requests==2.32.5
sniffio==1.3.1
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.5.0
Werkzeug==3.1.3

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "leaflet": "^1.9.4",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-leaflet": "^5.0.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.36.0",
    "@types/react": "^19.1.16",
    "@types/react-dom": "^19.1.9",
    "@vitejs/plugin-react": "^5.0.4",
    "eslint": "^9.36.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.22",
    "globals": "^16.4.0",
    "vite": "^7.1.7"
  }
}

```

### backend/server.py

```python
# Filename - server.py

# Import flask and datetime module for showing date and time
from flask import Flask, request, jsonify, Response, stream_with_context
from flask_cors import CORS, cross_origin  # allows frontend to call backend
import datetime
from PlacesApi import getNearbyRestaurants
import time
from LettaManager import LettaManager
import asyncio
import json

x = datetime.datetime.now()

# Initializing flask app
app = Flask(__name__)

# Allow all routes to be accessed from any origin (development)
CORS(app)
CORS(app, origins=["http://localhost:5173"])


# Route for sending data
@app.route('/api/data')
def output_deals():

    # Letta agent call here...
    # fake_data = {
    # 'name': "Insomnia Cookies",
    # 'lat': 37.8, 
    # 'lng': -122.448,
    # 'deals': [
    #     {
    #         "Deal Type": "Double Dozen Deal",
    #         "Description": "24 Classic Cookies",
    #         "Price/Discount": "$22",
    #         "Availability": "Pickup Only",
    #         "Source": "https://instagram.com"
    #     },
    #     {
    #         "Deal Type": "Cookiewich",
    #         "Description": "Ice Cream Sandwich",
    #         "Price/Discount": "$4",
    #         "Availability": "Pickup Only",
    #         "Source": "https://instagram.com"
    #     },
    #     {
    #         "Deal Type": "Free Cookie On Signup",
    #         "Description": "Free classic cookie on signup",
    #         "Price/Discount": "Free on signup",
    #         "Availability": "Online Order",
    #         "Source": "https://instagram.com"
    #     }]
    # }

    fake_data = [
        {'lat': 37.800000, 'lng': -122.448000},
        {'lat': 37.805000, 'lng': -122.464000},
        {'lat': 37.810000, 'lng': -122.478000},
    ]


    # Returning an api for showing in  reactjs
    return jsonify(fake_data)

# Route for receiving data from frontend... tbd on frontend side

@app.route('/submit_location', methods=['POST'])
@cross_origin(origin="http://localhost:5173")
def submit_location():
    data = request.get_json()  # Get JSON data from request
    
    # Process the data (here we just print it)
    print("Received data:", data)

    result = None
    nearbyRestaurants = getNearbyRestaurants(data.get("lat"), data.get("lng"))
    if nearbyRestaurants:

        restas = [r[:2] for r in nearbyRestaurants]
        
        #manager = LettaManager()
        #result = asyncio.run(manager.process_restaurants(restas))
        result = None

        with open("hardcode.json", "r") as f:
            result = json.load(f)

        nearbyRestaurants.sort(key=lambda x: x[0])


    # Step 1: Append deals to each restaurant if they exist
    merged = []
    if nearbyRestaurants and result:
        for r in nearbyRestaurants:
            name = r[0]
            deals = result.get(name, [])  # get deals if they exist, else empty list
            merged.append(r + [deals])  # append deals as a new element at the end

    # # merged now contains the restaurants with deals at the end
    # print(merged)
    print(merged)
    return jsonify(merged)

# Running app
if __name__ == '__main__':
    app.run(debug=True)
```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### frontend/src/App.jsx

```javascript
// import { useState, useEffect } from 'react'
// import reactLogo from './assets/react.svg'
// import viteLogo from '/vite.svg'
// import './App.css'


import React from 'react'
import ReactDOM from 'react-dom/client'

// import MapView from './MapView.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <MapView />
  </React.StrictMode>
)

export default function App() {

  return (
    <div>
      <h1>CheapBananas Map</h1>
      <MapView />
    </div>
  );
}


// function App() {
//   const [count, setCount] = useState(0)

//   const [data, setdata] = useState({
//         name: "",
//         age: 0,
//         date: "",
//         programming: "",
//     });

//   useEffect(() => {
//     // Fetch data from the Flask backend
//     fetch('/data')
//       .then(response => response.json())
//       .then(data => {
//         setdata({
//           name: data.Name,
//           age: data.Age,
//           date: data.Date,
//           programming: data.programming
//         });
//       })
//       .catch(error => console.error('Error fetching data:', error));
//   }, []); // Empty dependency array means this runs once when component mounts


//   return (
//     <>
//       <h1>cheap bananas</h1>
//       <div className="card">
//         <button onClick={() => setCount((count) => count + 1)}>
//           count is {count}
//         </button>
//         <p>{data.name}</p>
//         <p>{data.age}</p>
//         <p>{data.date}</p>
//         <p>{data.programming}</p>
//       </div>
//     </>
//   )
// }

// export default App

```

### frontend/src/routes/index.tsx

```typescript
import { MapComponent, type MapLocation } from "@/components/MapComponent";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { createFileRoute } from "@tanstack/react-router";
import { Clock, MapPin, Navigation, Star } from "lucide-react";
import { useState } from "react";

export const Route = createFileRoute("/")({
	component: App,
});

// Location data type structure compatible with Flask backend integration
type Location = {
	id: string;
	name: string;
	category: string;
	address: string;
	rating: number;
	hours: string;
	imageUrl: string;
};

// Dummy JSON data for locations
const locationData: Location[] = [
	{
		id: "1",
		name: "Blue Bottle Coffee",
		category: "Cafe",
		address: "66 Mint St, San Francisco, CA 94103",
		rating: 4.5,
		hours: "Open · Closes 6:00 PM",
		imageUrl:
			"https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=100&h=100&fit=crop",
	},
	{
		id: "2",
		name: "Golden Gate Park",
		category: "Park",
		address: "Golden Gate Park, San Francisco, CA 94122",
		rating: 4.8,
		hours: "Open 24 hours",
		imageUrl:
			"https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=100&h=100&fit=crop",
	},
	{
		id: "3",
		name: "Ferry Building Marketplace",
		category: "Shopping",
		address: "1 Ferry Building, San Francisco, CA 94111",
		rating: 4.6,
		hours: "Open · Closes 7:00 PM",
		imageUrl:
			"https://images.unsplash.com/photo-1534452203293-494d7ddbf7e0?w=100&h=100&fit=crop",
	},
	{
		id: "4",
		name: "Tartine Bakery",
		category: "Bakery",
		address: "600 Guerrero St, San Francisco, CA 94110",
		rating: 4.7,
		hours: "Open · Closes 5:00 PM",
		imageUrl:
			"https://images.unsplash.com/photo-1509440159596-0249088772ff?w=100&h=100&fit=crop",
	},
	{
		id: "5",
		name: "Exploratorium",
		category: "Museum",
		address: "Pier 15, San Francisco, CA 94111",
		rating: 4.6,
		hours: "Open · Closes 5:00 PM",
		imageUrl:
			"https://images.unsplash.com/photo-1560264280-88b68371db39?w=100&h=100&fit=crop",
	},
	{
		id: "6",
		name: "Fisherman's Wharf",
		category: "Tourist Attraction",
		address: "Jefferson St, San Francisco, CA 94133",
		rating: 4.4,
		hours: "Open 24 hours",
		imageUrl:
			"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=100&h=100&fit=crop",
	},
	{
		id: "7",
		name: "Zazie Restaurant",
		category: "Restaurant",
		address: "941 Cole St, San Francisco, CA 94117",
		rating: 4.5,
		hours: "Open · Closes 9:00 PM",
		imageUrl:
			"https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=100&h=100&fit=crop",
	},
	{
		id: "8",
		name: "Alamo Square Park",
		category: "Park",
		address: "Hayes St & Steiner St, San Francisco, CA 94117",
		rating: 4.7,
		hours: "Open 24 hours",
		imageUrl:
			"https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=100&h=100&fit=crop",
	},
];

function App() {
	// State management: Track which location is currently selected (null when none selected)
	const [selectedLocation, setSelectedLocation] = useState<Location | null>(
		null,
	);

	// State for map markers (locations returned from backend)
	const [mapMarkers, setMapMarkers] = useState<MapLocation[]>([]);

	// State for search input
	const [searchQuery, setSearchQuery] = useState("");

	/**
	 * Click handler for location list items
	 * Sets the selected location and triggers the detail panel to slide out
	 * @param location - The location object that was clicked
	 */
	const handleLocationClick = (location: Location) => {
		setSelectedLocation(location);
	};

	/**
	 * Close handler for detail panel
	 * Resets selected location to null, triggering slide-out animation
	 */
	const handleCloseDetail = () => {
		setSelectedLocation(null);
	};

	/**
	 * Handler for location search from map
	 * Called when user searches for a location or uses current location
	 */
	const handleLocationSearch = (location: MapLocation) => {
		// Here you would typically call your backend API
		// For now, we'll just add a sample marker
		console.log("Location searched:", location);

		// Example: Add sample markers around the searched location
		const sampleMarkers: MapLocation[] = [
			{ lat: location.lat + 0.001, lng: location.lng + 0.001 },
			{ lat: location.lat - 0.001, lng: location.lng - 0.001 },
			{ lat: location.lat + 0.002, lng: location.lng - 0.001 },
		];

		setMapMarkers(sampleMarkers);
	};

	return (
		<div className="flex h-screen bg-gray-100">
			{/* Sidebar: Fixed-width scrollable location list */}
			<div className="w-96 bg-white shadow-lg flex flex-col">
				{/* Search bar header */}
				<div className="p-4 border-b border-gray-200">
					<div className="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-lg">
						<MapPin className="w-5 h-5 text-gray-400" />
						<input
							type="text"
							placeholder="Search for places..."
							className="flex-1 bg-transparent outline-none text-sm"
							value={searchQuery}
							onChange={(e) => setSearchQuery(e.target.value)}
						/>
					</div>
				</div>

				{/* Scrollable location list with smooth overflow behavior */}
				<ScrollArea className="flex-1">
					<div className="divide-y divide-gray-200">
						{locationData.map((location) => (
							<LocationCard
								key={location.id}
								location={location}
								isSelected={selectedLocation?.id === location.id}
								onClick={() => handleLocationClick(location)}
							/>
						))}
					</div>
				</ScrollArea>
			</div>

			{/* Slide-out detail panel: Appears to the right of sidebar when location is selected */}
			<DetailPanel location={selectedLocation} onClose={handleCloseDetail} />

			{/* Interactive Google Maps area */}
			<div className="flex-1 relative">
				<MapComponent
					onLocationSearch={handleLocationSearch}
					markers={mapMarkers}
				/>
			</div>
		</div>
	);
}

/**
 * LocationCard Component
 * Displays individual location in the list with name, category badge, and thumbnail
 * Handles hover states and se
[truncated — 4124 more characters]
```

### backend/flask-test.py

```python
from flask import Flask

app = Flask(__name__)

@app.route("/")
def cheap_bananas():
    return "<p>cheap bananas project</p>"
```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/data': {
        target: 'http://127.0.0.1:5000',
        changeOrigin: true,
      }
    }
  }
})

```

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