# Project export: EasyDentistry

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: CruzHacks 2025
- Tagline: EasyDentistry: Your personalized dental matchmaker — smarter, faster, and tailored to your smile.
- Devpost: https://devpost.com/software/healthistyou-easydentistry
- GitHub: https://github.com/Yingxuan-Selina-Bian/CruzHacksDentist
- Video: https://www.youtube.com/embed/O1PMXHoB6uY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Selina Bian (10 commits), Brandy Huang (4 commits), Madilyn Xu (2 commits)

## Devpost submission (written by the team)

### Inspiration

One of our teammates struggled to find an ideal dentist. She had to jump between multiple webpages — insurance portals, Google Maps, Yelp, etc. — just to make a decision.

### What it does

Our framework follows a 3-step flow: Information Gathering — via APIs and user input Filtering — generate a shortlist of suitable dentists Conversational Recommendation — use an LLM-powered chatbot for personalized suggestions 📍 Outputs a List of Dentists Based on User Input: Location, Type of dental service, Preferred distance range ✨ Each Dentist Entry Includes: Dentist/Dental business name, Ratings, Clinic location, Contact information, Distance from user, Interior image of the clinic, Embedded Google Map, Link to view the map in full

### Challenges we ran into

Expired API keys from earlier development Difficulty linking frontend and backend components

### Accomplishments we're proud of

Identifying a real, relatable need from personal experience Getting hands-on with Cursor, Create.xyz, and WordExpress for building websites and chatbots

### What's next

Make the chatbot smarter — ask more personalized, qualitative questions like “Are you anxious about dental visits?” or “Do you care more about gentleness or speed?” Add a text-to-voice feature to improve accessibility for broader audiences, supporting inclusivity and social justice Extend this idea in more physical and mental health application use cases

## README (from the GitHub repository)

# Personal Dentist Selection Tool
This project is a **{Personal Dentist Selection Toolkit}** that support physical well-being with accessible services.
With information gathering and chatbot functions, users can make decision on selection and appointment of dentist easier, saving their time on selecting suitable dentist and enhance experience.

## Demo Video
[Watch the demo video](https://youtu.be/O1PMXHoB6uY)

## Interface & Example
![image](https://github.com/user-attachments/assets/8d80abec-84d1-4ed2-9caf-57f3a1b92ee1)
![image](https://github.com/user-attachments/assets/58caab88-8253-462f-b682-832b24d50649)

## Features
📍 Output a list of dentists based on your input:
- Your location
- Type of dental service
- Preferred distance range

✨ Each dentist section includes:
- Dentist name
- Ratings
- Clinic location
- Contact information
- Distance from your location
- Interior image of the clinic
- Embedded Google Map
- Link to view the map in detail


## Installation & Setup
Follow these steps to get the project running locally:

1. Clone the repository

```
git clone https://github.com/Yingxuan-Selina-Bian/CruzHacksDentist.git
cd CruzHacksDentist
```
2. Create and activate a virtual environment

```
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
```
3. Install the required packages

```
pip install -r requirements.txt
```
4. Set up environment variables: 
Create a .env file in the root directory and add your Gemini API key:

```
touch .env
```
Then edit .env and add your API keys.




## License
This project is licensed under the Non-Commercial Public License (NCPL). This means:

✅ You can:
- Use this software for personal purposes
- Modify the software
- Share the software with others
- Contribute to the project

❌ You cannot:
- Use this software for commercial purposes
- Sell this software or any modifications of it
- Remove or modify this license notice
- All API keys and services used in this project require separate licenses and terms of service agreements.


## Detected evidence (automated analysis)

Indexed codebase: 36 recognized source files, 126 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (38 of 38)

```
.github/workflows/webpack.yml
jsconfig.json
next.config.js
package.json
postcss.config.js
README.md
src/api/analyze-dentist-reviews/route.js
src/api/analyze-reviews/route.js
src/api/generate-dentist-summary/route.js
src/api/generate-recommendation/route.js
src/api/generate-summary/route.js
src/api/get-dentist-reviews/route.js
src/api/get-sfmta-route/route.js
src/api/google-places-search/route.js
src/api/recommendations/route.js
src/api/rename/route.js
src/api/search-dentists/route.js
src/api/settings/route.js
src/api/sfmta-lines/route.js
src/api/sfmta-patterns/route.js
src/api/sfmta-stop/route.js
src/api/yelp-api/route.js
src/api/yelp-search/route.js
src/app/_/backup/page.jsx
src/app/_/business-locations/page.jsx
src/app/_/dentist-finder/page.jsx
src/app/_/recommendations/page.jsx
src/app/_/settings/page.jsx
src/app/api-settings/page.jsx
src/app/globals.css
src/app/layout.js
src/app/new-page/page.jsx
src/app/page.jsx
src/components/sfmta-lines-grid.jsx
src/components/SmartChatbot.jsx
src/middleware.js
src/utilities/runtime-helpers.js
tailwind.config.js
```

### Dependencies

- package.json: autoprefixer@^10.0.1, next@14.0.4, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.3.0

### Recent commits (newest first)

- Update README.md
- Update README.md
- Update README.md
- Add files via upload
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Create webpack.yml
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update layout.js

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

### package.json

```
{
  "name": "create-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    
    "react": "^18",
    "react-dom": "^18",
    "next": "14.0.4"
  },
  "devDependencies": {
    "autoprefixer": "^10.0.1",
    "postcss": "^8",
    "tailwindcss": "^3.3.0"
  }
}
```

### src/app/layout.js

```javascript
import SmartChatbot from '../components/SmartChatbot'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create.xyz App',
  description: 'Generated by create.xyz',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {children}
        <SmartChatbot />
      </body>
    </html>
  )  
}

```

### src/app/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  const [location, setLocation] = useState("");
  const [suggestions, setSuggestions] = useState([]);
  const [service, setService] = useState("");
  const [distance, setDistance] = useState("5");
  const [dentists, setDentists] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [savedDentists, setSavedDentists] = useState([]);
  const [dentistImages, setDentistImages] = useState({});
  const [mapView, setMapView] = useState(null);
  const [dentistSummaries, setDentistSummaries] = useState({});
  const [loadingSummaries, setLoadingSummaries] = useState({});

  const handleLocationInput = async (input) => {
    setLocation(input);
    if (input.length > 2) {
      try {
        const response = await fetch(
          `/integrations/google-place-autocomplete/autocomplete/json?input=${encodeURIComponent(
            input
          )}&radius=50000`
        );
        if (!response.ok) throw new Error("Failed to fetch suggestions");
        const data = await response.json();
        setSuggestions(data.predictions);
      } catch (err) {
        console.error("Error fetching suggestions:", err);
        setSuggestions([]);
      }
    } else {
      setSuggestions([]);
    }
  };

  const fetchAdditionalImages = async (dentist) => {
    try {
      const query = `${dentist.name} dental office ${dentist.formatted_address}`;
      const response = await fetch(
        `/integrations/image-search/imagesearch?q=${encodeURIComponent(query)}`
      );
      if (!response.ok) throw new Error("Failed to fetch images");
      const data = await response.json();
      return data.items?.[0]?.originalImageUrl;
    } catch (err) {
      console.error("Error fetching additional images:", err);
      return null;
    }
  };

  const searchDentists = async () => {
    setLoading(true);
    setError(null);
    try {
      const geocodeResponse = await fetch(
        `/integrations/local-business-data/search?query=${encodeURIComponent(
          location
        )}&limit=1`
      );
      if (!geocodeResponse.ok) throw new Error("Failed to geocode location");
      const geocodeData = await geocodeResponse.json();
      const userLocation = geocodeData.data?.[0];

      if (!userLocation?.latitude || !userLocation?.longitude) {
        throw new Error("Could not determine location coordinates");
      }

      const query = `Dentist ${service}`;
      const response = await fetch(
        `/integrations/local-business-data/search?query=${encodeURIComponent(
          query
        )}&lat=${userLocation.latitude}&lng=${userLocation.longitude}&radius=${
          distance * 1609.34
        }&limit=10`
      );

      if (!response.ok) throw new Error("Failed to search for dentists");
      const data = await response.json();

      const dentistsWithDistance = data.data
        .map((dentist) => {
          const distance = calculateDistance(
            userLocation.latitude,
            userLocation.longitude,
            dentist.latitude,
            dentist.longitude
          );
          return { ...dentist, distance };
        })
        .filter((dentist) => dentist.distance <= parseInt(distance));

      setDentists(dentistsWithDistance);

      const imagePromises = dentistsWithDistance.map(async (dentist) => {
        if (!dentist.photos_sample?.[0]?.photo_url) {
          const additionalImage = await fetchAdditionalImages(dentist);
          if (additionalImage) {
            setDentistImages((prev) => ({
              ...prev,
              [dentist.place_id]: additionalImage,
            }));
          }
        }
      });

      await Promise.all(imagePromises);
    } catch (err) {
      console.error("Error:", err);
      setError("Failed to find dentists. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  const calculateDistance = (lat1, lon1, lat2, lon2) => {
    const R = 3959;
    const dLat = ((lat2 - lat1) * Math.PI) / 180;
    const dLon = ((lon2 - lon1) * Math.PI) / 180;
    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos((lat1 * Math.PI) / 180) *
        Math.cos((lat2 * Math.PI) / 180) *
        Math.sin(dLon / 2) *
        Math.sin(dLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  };

  const generateQuickSummary = async (dentist) => {
    if (dentistSummaries[dentist.place_id]) return;

    setLoadingSummaries((prev) => ({ ...prev, [dentist.place_id]: true }));
    try {
      const response = await fetch("/integrations/google-gemini-1-5/", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: [
            {
              role: "system",
              content:
                "You are a dental practice analyst. Provide a brief, factual summary of this dental practice.",
            },
            {
              role: "user",
              content: `Summarize this dental practice in 2-3 short sentences, focusing on location, services, and key highlights from reviews:
            Name: ${dentist.name}
            Address: ${dentist.formatted_address}
            Rating: ${dentist.rating} (${dentist.review_count} reviews)
            Distance: ${dentist.distance?.toFixed(1)} miles
            ${
              dentist.reviews
                ? `Reviews: ${dentist.reviews.map((r) => r.text).join(" ")}`
                : ""
            }`,
            },
          ],
          json_schema: {
            name: "quick_summary",
            schema: {
              type: "object",
              properties: {
                summary: { type: "string" },
              },
              required: ["summary"],
              additionalProperties: false,
            },
          },
        }),
      });

      if (!response.ok) throw new Error("Failed to generate summary");
      const result 
[truncated — 11582 more characters]
```

### src/app/api-settings/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  return <div></div>;
}

export default MainComponent;
```

### src/app/new-page/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  const [location, setLocation] = useState("");
  const [suggestions, setSuggestions] = useState([]);
  const [service, setService] = useState("");
  const [distance, setDistance] = useState("5");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [apiKeys, setApiKeys] = useState(null);
  const [selectedDentist, setSelectedDentist] = useState(null);

  useEffect(() => {
    const fetchApiKeys = async () => {
      try {
        const response = await fetch("/api/settings", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ method: "GET" }),
        });
        if (!response.ok) throw new Error("Failed to fetch API keys");
        const data = await response.json();
        setApiKeys(data);
      } catch (err) {
        console.error("Error fetching API keys:", err);
        setError("Please configure your API keys in the settings page");
      }
    };
    fetchApiKeys();
  }, []);

  const handleLocationInput = async (input) => {
    setLocation(input);
    if (input.length > 2) {
      try {
        const response = await fetch(
          `/integrations/google-place-autocomplete/autocomplete/json?input=${encodeURIComponent(
            input
          )}&radius=50000`
        );
        if (!response.ok) throw new Error("Failed to fetch suggestions");
        const data = await response.json();
        setSuggestions(data.predictions);
      } catch (err) {
        console.error("Error fetching suggestions:", err);
      }
    } else {
      setSuggestions([]);
    }
  };

  const searchDentists = async () => {
    setLoading(true);
    setError(null);
    try {
      const yelpResponse = await fetch("/api/rename", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          term: `dentist ${service}`,
          location: location,
        }),
      });

      if (!yelpResponse.ok) throw new Error("Failed to fetch dental practices");
      const yelpData = await yelpResponse.json();

      const dentistsWithReviews = await Promise.all(
        yelpData.businesses.map(async (business) => {
          const reviewsResponse = await fetch("/api/rename", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ businessId: business.id }),
          });
          const reviewsData = await reviewsResponse.json();

          const summaryResponse = await fetch("/api/generate-summary", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              name: business.name,
              rating: business.rating,
              reviewCount: business.review_count,
              services: service,
              address: business.address,
              reviews: reviewsData.reviews || [],
            }),
          });
          const summaryData = await summaryResponse.json();

          return {
            ...business,
            reviews: reviewsData.reviews || [],
            summary: summaryData,
          };
        })
      );

      setResults(dentistsWithReviews);
    } catch (err) {
      console.error(err);
      setError("Failed to fetch dentist recommendations");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="min-h-screen bg-gray-50 p-4 md:p-8">
      <div className="max-w-7xl mx-auto">
        <h1 className="text-3xl font-bold mb-6 text-blue-800">
          Find Your Perfect Dentist
        </h1>

        <div className="bg-white rounded-lg shadow p-6 mb-8">
          <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
            <div className="relative">
              <label className="block text-sm font-medium mb-2">Location</label>
              <input
                type="text"
                value={location}
                onChange={(e) => handleLocationInput(e.target.value)}
                placeholder="Enter your location"
                className="w-full p-2 border rounded"
              />
              {suggestions.length > 0 && (
                <div className="absolute z-10 w-full bg-white border rounded-md mt-1 shadow-lg">
                  {suggestions.map((suggestion, index) => (
                    <div
                      key={index}
                      className="p-2 hover:bg-gray-100 cursor-pointer"
                      onClick={() => {
                        setLocation(suggestion.description);
                        setSuggestions([]);
                      }}
                    >
                      {suggestion.description}
                    </div>
                  ))}
                </div>
              )}
            </div>

            <div>
              <label className="block text-sm font-medium mb-2">
                Service Needed
              </label>
              <select
                value={service}
                onChange={(e) => setService(e.target.value)}
                className="w-full p-2 border rounded"
              >
                <option value="">Select service</option>
                <option value="general">General Dentistry</option>
                <option value="cosmetic">Cosmetic Dentistry</option>
                <option value="orthodontics">Orthodontics</option>
                <option value="pediatric">Pediatric Dentistry</option>
                <option value="emergency">Emergency Dental Care</option>
              </select>
            </div>

            <div>
              <label className="block text-sm font-medium mb-2">Distance</label>
              <select
                value={distance}
                onChange={(e) => setDistance(e.target.value)}
                className="w-full p-2 border rounded"
              >
               
[truncated — 3778 more characters]
```

### src/app/_/settings/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  const [keys, setKeys] = useState({
    googleMaps: "",
    yelp: "",
  });
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    setSuccess(false);

    try {
      const response = await fetch("/api/settings", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(keys),
      });

      if (!response.ok) {
        throw new Error("Failed to save API keys");
      }

      setSuccess(true);
    } catch (err) {
      console.error("Error saving API keys:", err);
      setError("Failed to save API keys. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setKeys((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  return (
    <div className="min-h-screen bg-gray-50 p-4 md:p-8">
      <div className="max-w-2xl mx-auto">
        <div className="bg-white rounded-lg shadow p-6">
          <h1 className="text-2xl font-bold text-blue-800 mb-6">
            API Settings
          </h1>

          <form onSubmit={handleSubmit} className="space-y-6">
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-2">
                Google Maps API Key
              </label>
              <input
                type="password"
                name="googleMaps"
                value={keys.googleMaps}
                onChange={handleChange}
                className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                placeholder="Enter Google Maps API Key"
              />
              <p className="mt-1 text-sm text-gray-500">
                Used for maps and location services
              </p>
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-2">
                Yelp API Key
              </label>
              <input
                type="password"
                name="yelp"
                value={keys.yelp}
                onChange={handleChange}
                className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                placeholder="Enter Yelp API Key"
              />
              <p className="mt-1 text-sm text-gray-500">
                Used for business information and reviews
              </p>
            </div>

            {error && (
              <div className="p-3 bg-red-50 text-red-700 rounded">{error}</div>
            )}

            {success && (
              <div className="p-3 bg-green-50 text-green-700 rounded">
                API keys saved successfully!
              </div>
            )}

            <button
              type="submit"
              disabled={loading}
              className="w-full bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
            >
              {loading ? "Saving..." : "Save API Keys"}
            </button>
          </form>

          <div className="mt-8 border-t pt-6">
            <h2 className="text-lg font-medium text-gray-900 mb-4">
              Security Note
            </h2>
            <p className="text-sm text-gray-600">
              Your API keys are encrypted before being stored. Never share your
              API keys or expose them in client-side code. If you suspect your
              keys have been compromised, please regenerate them immediately
              from their respective platforms.
            </p>
          </div>
        </div>
      </div>
    </div>
  );
}

export default MainComponent;
```

### src/app/_/dentist-finder/page.jsx

```javascript
"use client";
import React from "react";

import { useHandleStreamResponse } from "../utilities/runtime-helpers";

function MainComponent() {
  const [location, setLocation] = useState("");
  const [selectedDentist, setSelectedDentist] = useState(null);
  const [suggestions, setSuggestions] = useState([]);
  const [dentists, setDentists] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [transitDirections, setTransitDirections] = useState(null);
  const [reviewAnalysis, setReviewAnalysis] = useState(null);
  const [streamingMessage, setStreamingMessage] = useState("");
  const handleStreamResponse = useHandleStreamResponse({
    onChunk: setStreamingMessage,
    onFinish: (message) => {
      setStreamingMessage("");
      setReviewAnalysis(message);
    },
  });

  const handleLocationInput = async (input) => {
    setLocation(input);
    if (input.length > 2) {
      try {
        const response = await fetch(
          `/integrations/google-place-autocomplete/autocomplete/json?input=${encodeURIComponent(
            input
          )}&radius=50000`
        );
        if (!response.ok) throw new Error("Failed to fetch suggestions");
        const data = await response.json();
        setSuggestions(data.predictions);
      } catch (err) {
        console.error("Error fetching suggestions:", err);
        setSuggestions([]);
      }
    } else {
      setSuggestions([]);
    }
  };

  const searchDentists = async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(
        `/integrations/local-business-data/search?query=${encodeURIComponent(
          "dentist near " + location
        )}&limit=10&business_status=OPEN`
      );

      if (!response.ok) throw new Error("Failed to fetch dentists");
      const data = await response.json();
      setDentists(data.data || []);
    } catch (err) {
      console.error("Error searching dentists:", err);
      setError("Failed to search for dentists. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  const getTransitDirections = async (dentist) => {
    try {
      const response = await fetch(`/api/sfmta-route`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          destination: `${dentist.latitude},${dentist.longitude}`,
          origin: location,
        }),
      });
      if (!response.ok) throw new Error("Failed to get transit directions");
      const data = await response.json();
      setTransitDirections(data);
    } catch (err) {
      console.error("Error getting transit directions:", err);
      setError("Failed to get transit directions");
    }
  };

  const analyzeReviews = async (dentist) => {
    try {
      const response = await fetch("/api/analyze-reviews", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ place_id: dentist.place_id }),
      });
      if (!response.ok) throw new Error("Failed to analyze reviews");
      const data = await response.json();
      setReviewAnalysis(data);
    } catch (err) {
      console.error("Error analyzing reviews:", err);
      setError("Failed to analyze reviews");
    }
  };

  const selectDentist = async (dentist) => {
    setSelectedDentist(dentist);
    await Promise.all([getTransitDirections(dentist), analyzeReviews(dentist)]);
  };

  return (
    <div className="min-h-screen bg-gray-50 p-4 md:p-8">
      <div className="max-w-6xl mx-auto">
        <h1 className="text-3xl font-bold text-blue-800 mb-6">
          Find a Dentist Near You
        </h1>

        <div className="bg-white rounded-lg shadow p-6 mb-8">
          <div className="relative">
            <input
              type="text"
              value={location}
              onChange={(e) => handleLocationInput(e.target.value)}
              placeholder="Enter your location"
              className="w-full p-3 border rounded-lg"
            />
            {suggestions.length > 0 && (
              <div className="absolute z-10 w-full bg-white border rounded-lg mt-1 shadow-lg">
                {suggestions.map((suggestion, index) => (
                  <div
                    key={index}
                    className="p-3 hover:bg-gray-100 cursor-pointer"
                    onClick={() => {
                      setLocation(suggestion.description);
                      setSuggestions([]);
                    }}
                  >
                    {suggestion.description}
                  </div>
                ))}
              </div>
            )}
            <button
              onClick={searchDentists}
              disabled={!location || loading}
              className="mt-4 bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
            >
              {loading ? "Searching..." : "Find Dentists"}
            </button>
          </div>
        </div>

        {error && (
          <div className="bg-red-50 text-red-700 p-4 rounded-lg mb-6">
            {error}
          </div>
        )}

        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          <div className="space-y-4">
            {dentists.map((dentist) => (
              <div
                key={dentist.place_id}
                className="bg-white rounded-lg shadow p-6 hover:shadow-lg transition-shadow"
              >
                <h2 className="text-xl font-semibold text-blue-800">
                  {dentist.name}
                </h2>
                <p className="text-gray-600">{dentist.formatted_address}</p>
                <div className="mt-2">
                  <span className="text-yellow-500">
                    {"★".repeat(Math.round(dentist.rating))}
                  </span>
                  <span className="text-gray-600">
                    {" "}
                    ({dentist.review_count} reviews)
                 
[truncated — 2616 more characters]
```

### src/app/_/backup/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  const [location, setLocation] = useState("");
  const [suggestions, setSuggestions] = useState([]);
  const [service, setService] = useState("");
  const [distance, setDistance] = useState("5");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [selectedDentist, setSelectedDentist] = useState(null);

  const handleLocationInput = async (input) => {
    setLocation(input);
    if (input.length > 2) {
      try {
        const response = await fetch(
          `/integrations/google-place-autocomplete/autocomplete/json?input=${encodeURIComponent(
            input
          )}&radius=50000`
        );
        if (!response.ok) throw new Error("Failed to fetch suggestions");
        const data = await response.json();
        setSuggestions(data.predictions);
      } catch (err) {
        console.error("Error fetching suggestions:", err);
      }
    } else {
      setSuggestions([]);
    }
  };

  const searchDentists = async () => {
    setLoading(true);
    setError(null);
    try {
      const query = `Dentist in ${location}`;
      const response = await fetch(
        `/integrations/local-business-data/search?query=${encodeURIComponent(
          query
        )}&limit=10`
      );

      if (!response.ok) {
        throw new Error("Failed to search for dentists");
      }

      const data = await response.json();
      if (data.status !== "OK") {
        throw new Error("Failed to get results");
      }

      setResults(
        data.data.map((dentist) => ({
          name: dentist.name,
          address: dentist.full_address,
          rating: dentist.rating,
          details: {
            phone: dentist.phone_number,
            website: dentist.website,
            total_reviews: dentist.review_count,
          },
          photos: dentist.photos_sample,
          analysis: {
            specialties: dentist.subtypes || [],
            summary: dentist.about?.summary || "",
            highlights: [],
          },
        }))
      );
    } catch (err) {
      console.error("Error:", err);
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="min-h-screen bg-gray-50 p-4 md:p-8">
      <div className="max-w-7xl mx-auto">
        <h1 className="text-3xl font-bold mb-6 text-blue-800">
          Find Your Perfect Dentist
        </h1>

        <div className="bg-white rounded-lg shadow p-6 mb-8">
          <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
            <div className="relative">
              <label className="block text-sm font-medium mb-2">Location</label>
              <input
                type="text"
                value={location}
                onChange={(e) => handleLocationInput(e.target.value)}
                placeholder="Enter your location"
                className="w-full p-2 border rounded"
              />
              {suggestions.length > 0 && (
                <div className="absolute z-10 w-full bg-white border rounded-md mt-1 shadow-lg">
                  {suggestions.map((suggestion, index) => (
                    <div
                      key={index}
                      className="p-2 hover:bg-gray-100 cursor-pointer"
                      onClick={() => {
                        setLocation(suggestion.description);
                        setSuggestions([]);
                      }}
                    >
                      {suggestion.description}
                    </div>
                  ))}
                </div>
              )}
            </div>

            <div>
              <label className="block text-sm font-medium mb-2">
                Service Needed
              </label>
              <select
                value={service}
                onChange={(e) => setService(e.target.value)}
                className="w-full p-2 border rounded"
              >
                <option value="">Select service</option>
                <option value="general">General Dentistry</option>
                <option value="cosmetic">Cosmetic Dentistry</option>
                <option value="orthodontics">Orthodontics</option>
                <option value="pediatric">Pediatric Dentistry</option>
                <option value="emergency">Emergency Dental Care</option>
              </select>
            </div>

            <div>
              <label className="block text-sm font-medium mb-2">Distance</label>
              <select
                value={distance}
                onChange={(e) => setDistance(e.target.value)}
                className="w-full p-2 border rounded"
              >
                <option value="5">Within 5 miles</option>
                <option value="10">Within 10 miles</option>
                <option value="20">Within 20 miles</option>
                <option value="30">Within 30 miles</option>
              </select>
            </div>
          </div>

          <button
            onClick={searchDentists}
            disabled={!location || !service || loading}
            className="mt-4 bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
          >
            {loading ? "Searching..." : "Find Dentists"}
          </button>
        </div>

        {error && (
          <div className="bg-red-50 text-red-700 p-4 rounded mb-6">{error}</div>
        )}

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {results.map((dentist, index) => (
            <div key={index} className="bg-white rounded-lg shadow p-6">
              <div className="flex justify-between items-start">
                <div>
                  <h2 className="text-xl font-semibold text-blue-800">
                    {dentist.name}
                  </h2>
                  <p className="text-gray-600">{dentist.address}</p>
                </div>
        
[truncated — 2767 more characters]
```

### src/app/_/recommendations/page.jsx

```javascript
"use client";
import React from "react";

function MainComponent() {
  const [recommendations, setRecommendations] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [showForm, setShowForm] = useState(false);
  const [newRecommendation, setNewRecommendation] = useState({
    name: "",
    address: "",
    website: "",
    phone: "",
    reason: "",
    rating: "",
    category: "General Dentistry",
  });

  useEffect(() => {
    fetchRecommendations();
  }, []);

  const fetchRecommendations = async () => {
    setLoading(true);
    try {
      const response = await fetch("/api/recommendations");
      if (!response.ok) {
        throw new Error("Failed to fetch recommendations");
      }
      const data = await response.json();
      setRecommendations(Array.isArray(data) ? data : []);
    } catch (err) {
      console.error("Error fetching recommendations:", err);
      setError("Failed to load recommendations");
      setRecommendations([]);
    } finally {
      setLoading(false);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    try {
      const response = await fetch("/api/recommendations", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(newRecommendation),
      });

      if (!response.ok) {
        throw new Error("Failed to add recommendation");
      }

      await fetchRecommendations();
      setShowForm(false);
      setNewRecommendation({
        name: "",
        address: "",
        website: "",
        phone: "",
        reason: "",
        rating: "",
        category: "General Dentistry",
      });
    } catch (err) {
      console.error("Error adding recommendation:", err);
      setError("Failed to add recommendation");
    } finally {
      setLoading(false);
    }
  };

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setNewRecommendation((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  return (
    <div className="min-h-screen bg-gray-50 p-4 md:p-8">
      <div className="max-w-6xl mx-auto">
        <div className="flex justify-between items-center mb-6">
          <h1 className="text-3xl font-bold text-blue-800">
            Business Recommendations
          </h1>
          <button
            onClick={() => setShowForm(!showForm)}
            className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
          >
            {showForm ? "Cancel" : "Add New Recommendation"}
          </button>
        </div>

        {error && (
          <div className="bg-red-50 text-red-700 p-4 rounded mb-6">{error}</div>
        )}

        {showForm && (
          <div className="bg-white rounded-lg shadow p-6 mb-8">
            <h2 className="text-xl font-semibold mb-4">
              Add New Recommendation
            </h2>
            <form
              onSubmit={handleSubmit}
              className="grid grid-cols-1 md:grid-cols-2 gap-4"
            >
              <div>
                <label className="block text-sm font-medium mb-2">
                  Business Name
                </label>
                <input
                  type="text"
                  name="name"
                  value={newRecommendation.name}
                  onChange={handleInputChange}
                  className="w-full p-2 border rounded"
                  required
                />
              </div>

              <div>
                <label className="block text-sm font-medium mb-2">
                  Category
                </label>
                <select
                  name="category"
                  value={newRecommendation.category}
                  onChange={handleInputChange}
                  className="w-full p-2 border rounded"
                >
                  <option value="General Dentistry">General Dentistry</option>
                  <option value="Orthodontics">Orthodontics</option>
                  <option value="Periodontics">Periodontics</option>
                  <option value="Cosmetic Dentistry">Cosmetic Dentistry</option>
                  <option value="Pediatric Dentistry">
                    Pediatric Dentistry
                  </option>
                </select>
              </div>

              <div>
                <label className="block text-sm font-medium mb-2">
                  Address
                </label>
                <input
                  type="text"
                  name="address"
                  value={newRecommendation.address}
                  onChange={handleInputChange}
                  className="w-full p-2 border rounded"
                  required
                />
              </div>

              <div>
                <label className="block text-sm font-medium mb-2">
                  Website
                </label>
                <input
                  type="url"
                  name="website"
                  value={newRecommendation.website}
                  onChange={handleInputChange}
                  className="w-full p-2 border rounded"
                />
              </div>

              <div>
                <label className="block text-sm font-medium mb-2">Phone</label>
                <input
                  type="tel"
                  name="phone"
                  value={newRecommendation.phone}
                  onChange={handleInputChange}
                  className="w-full p-2 border rounded"
                />
              </div>

              <div>
                <label className="block text-sm font-medium mb-2">
                  Rating (1-5)
                </label>
                <input
                  type="number"
                  name="rating"
                  min="1"
                  max="5"
                  value={newRecommendation.rating}
                  onChange={handleInputChange}
       
[truncated — 4054 more characters]
```

### postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
```

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