# Project export: Shopybara

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: TreeHacks 2025
- Tagline: Discover and shop furniture that matches your Spotify & Pinterest inspiration, powered by AI that understands your style.
- Devpost: https://devpost.com/software/shopybara
- GitHub: https://github.com/sabrinahu5/shopybara
- Demo: https://shopybara.vercel.app/
- Video: https://www.youtube.com/embed/8HNrSEgNzjg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Wency Suo (12 commits), Andrew Chen (11 commits), kimjanise (4 commits)

## Devpost submission (written by the team)

### Inspiration

Finding things that fit your style is hard, especially when there’s an overwhelming amount of options, but nothing that truly feels like “you”. Personal style is reflected in the way we dress, the music we listen to, and the aesthetics we’re drawn to; ultimately, it’s an extension of how we express ourselves. But style isn’t just about how we look-- it’s also about the spaces we live in. The Shopybara team’s vision is to empower self-expression in home design by leveraging your unique tastes and personal inspirations. We believe that personalization in homes extends far beyond traditional decor trends. Traditional interior design services rely on surface-level preferences such as colors or materials; moreover, existing e-commerce platforms lack personalization beyond traditional browsing history. Our team sees untapped potential in unique areas like music and visual inspirations, and we aim to bridge the gap between personal taste and home design by drawing from qualities unique to you.

### What it does

Our web application curates your personal aesthetic based on your music preferences and visual inspirations through platforms like Spotify and Pinterest. We then interpret and analyze this data to suggest personalized interior design recommendations (i.e. specific products, links, etc.) that truly reflect your individual style. Key features include the following: Taste Analysis Engine (processes personal data from Spotify and Pinterest to understand both emotional and visual preferences), Intelligent Shopping Assistant (leverages LLMs to provide curated product recommendations that match your unique style profile).

### How we built it

The platform is built with TypeScript and React on Next.js, hosted on Vercel for optimal performance. The frontend features Framer Motion for smooth animations, while Supabase handles user data storage and authentication. For data collection, we developed a custom Python scraper using Selenium to gather Pinterest data. The system integrates OpenAI's API for style analysis, processing inputs from various sources including Spotify, Amazon, and Pinterest APIs. This data is then used to power our product recommendation engine, which matches user preferences with relevant items.

### Challenges we ran into

Performance Bottlenecks: Our initial integration pipeline took too long to generate recommendations, creating a poor user experience. Each step (Pinterest scraping, OpenAI analysis, Amazon product search) added latency, requiring us to optimize our data pipeline. Data Access and Collection: Pinterest's API limitations led us to develop a custom web scraping solution using Selenium. This required careful implementation to reliably extract user board data while respecting rate limits. Integration Complexity: Coordinating data flow between multiple services (Spotify, Pinterest scraping, OpenAI, Amazon) became complex. Each API call added potential points of failure and increased response times. Prompt Engineering: We spent significant time refining our OpenAI API prompts to generate relevant product search terms from user preference data. Finding the right balance between specific and versatile recommendations required multiple iterations.

### Accomplishments we're proud of

Custom Data Collection Systems: Successfully built and deployed custom web scrapers for both Pinterest and Amazon, overcoming API limitations while ensuring reliable data extraction. This involved careful consideration of rate limiting, error handling, and data validation. End-to-End Deployment: Successfully deployed a full-stack application using Vercel, demonstrating our ability to ship a production-ready web application. We handled environment configuration, API integration, and continuous deployment effectively. Intuitive UI/UX Design: Created a clean, modern interface that simplifies the complex process of style analysis into an engaging user experience. Our design effectively presents AI-generated recommendations in an accessible way. Integration Architecture: Built a working pipeline that successfully coordinates multiple services (Spotify, Pinterest, OpenAI, Amazon) to deliver personalized recommendations. Despite the complexity, we maintained a clean architecture that can scale.

### What we learned

The importance of combining different aspects of personal taste for truly meaningful recommendations How to overcome API limitations through creative technical solutions The value of iterative development in creating a user-friendly experience

### What's next

We believe that Shopybara's potential is extensive in terms of scalability; we anticipate that our product’s capability to contribute unique insights to personalization is significant as our world becomes increasingly integrated with AI. In terms of commercialization, one direction could be transforming Shopybara from an Amazon-focused platform into a comprehensive discovery engine for anything by expanding our scraping technology to target areas of any market.

## README (from the GitHub repository)

# Shopybara

AI Curated Shopping Based on Your Spotify Listening


## Detected evidence (automated analysis)

Indexed codebase: 49 recognized source files, 115 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (57 of 57)

```
.env.example
.gitignore
components.json
eslint.config.mjs
next-env.d.ts
next.config.ts
package.json
postcss.config.mjs
README.md
scrapers/amazon_instance.py
scrapers/amazon.py
scrapers/img2txt.py
scrapers/pinterest_scraper.py
src/app/api/amazon/route.ts
src/app/api/combine-analysis/route.ts
src/app/api/pinterest/analyze/route.ts
src/app/api/spotify/analyze/route.ts
src/app/api/spotify/featured-track/route.ts
src/app/api/spotify/user-data/route.ts
src/app/auth/callback/route.ts
src/app/find/page.tsx
src/app/globals.css
src/app/home/page.tsx
src/app/home/PinterestModalWrapper.tsx
src/app/layout.tsx
src/app/lib/amazon.ts
src/app/lib/amazonDemos.ts
src/app/lib/data.ts
src/app/onboarding/page.tsx
src/app/page.tsx
src/app/types/amazonFinds.ts
src/app/types/spotify.ts
src/app/ui/fonts.ts
src/app/ui/Home/AmazonFindCard.tsx
src/app/ui/Home/AmazonFindCards.tsx
src/app/ui/Home/DirectionAwareHover.tsx
src/app/ui/Home/FlipWords.tsx
src/app/ui/Home/InfiniteMovingCards.tsx
src/app/ui/Home/Loader.tsx
src/app/ui/Home/Pin.tsx
src/app/ui/Home/PinterestModal.tsx
src/app/ui/Home/ScrollReveal.tsx
src/app/ui/Home/SpotifyAlbumDemo.tsx
src/app/ui/Home/TextGenerate.tsx
src/app/ui/NavBarAndFooter/Footer.tsx
src/app/ui/NavBarAndFooter/NavBar.tsx
src/app/ui/Product/StaticCard.tsx
src/components/ui/button.tsx
src/components/ui/carousel.tsx
src/env.mjs
src/lib/client-utils.ts
src/lib/schema.ts
src/lib/server-utils.ts
src/lib/utils.ts
src/middleware.ts
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @eslint/eslintrc@^3, @radix-ui/react-dropdown-menu@^2.0.6, @radix-ui/react-slot@^1.1.2, @supabase/auth-helpers-nextjs@^0.9.0, @supabase/auth-ui-react@^0.4.7, @supabase/auth-ui-shared@^0.1.8, @supabase/ssr@^0.1.0, @supabase/supabase-js@^2.48.1, @t3-oss/env-nextjs@^0.9.2, @types/node@^20, @types/react@^19, @types/react-dom@^19, cheerio@^1.0.0, class-variance-authority@^0.7.1, clsx@^2.1.1, embla-carousel-react@^8.5.2, eslint@^9, eslint-config-next@15.1.7, framer-motion@^12.4.3, lucide-react@^0.475.0, motion@^12.4.3, next@15.1.7, openai@^4.85.1, playwright@^1.50.1, postcss@^8, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^3.0.1, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5

### Recent commits (newest first)

- comments to figure out deployed error
- final changes
- correct onboarding flow
- c
- change redirects
- changes
- schema change
- change
- modal cancel
- change redirects
- frotend
- fix build errors and flag off
- flag
- shopybara theme
- clean up stuff for build
- lighter font
- small changes
- neo brute transformation
- get rid of lint during builds
- redirect logged in users to hom

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

### package.json

```
{
  "name": "test",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@radix-ui/react-dropdown-menu": "^2.0.6",
    "@supabase/ssr": "^0.1.0",
    "@supabase/auth-helpers-nextjs": "^0.9.0",
    "@supabase/auth-ui-react": "^0.4.7",
    "@supabase/auth-ui-shared": "^0.1.8",
    "@supabase/supabase-js": "^2.48.1",
    "@radix-ui/react-slot": "^1.1.2",
    "cheerio": "^1.0.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "embla-carousel-react": "^8.5.2",
    "framer-motion": "^12.4.3",
    "lucide-react": "^0.475.0",
    "motion": "^12.4.3",
    "next": "15.1.7",
    "openai": "^4.85.1",
    "playwright": "^1.50.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.0.1",
    "tailwindcss-animate": "^1.0.7",
    "@t3-oss/env-nextjs": "^0.9.2"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.1.7",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";
import { inter } from "./ui/fonts";
import NavBar from "./ui/NavBarAndFooter/NavBar";
import Footer from "./ui/NavBarAndFooter/Footer";

export const metadata: Metadata = {
  title: "shopybara",
  description:
    "Discover and shop furniture that matches your Spotify inspiration, powered by AI that understands your style.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>
        <NavBar />
        {children}
        <Footer />
      </body>
    </html>
  );
}
```

### src/app/page.tsx

```typescript
"use client";

import { createBrowserSupabaseClient } from "@/lib/client-utils";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import SpotifyAlbumDemo from "./ui/Home/SpotifyAlbumDemo";
import { InfiniteMovingCards } from "./ui/Home/InfiniteMovingCards";
import { WordRotate } from "./ui/Home/FlipWords";

const amazonFinds = [
  {
    id: "1",
    profile_id: "default",
    created_at: new Date(),
    title: "Modern Velvet Accent Chair",
    description:
      "Luxurious velvet armchair with gold-finished steel legs, perfect for living room corners",
    url_to_product: "https://amazon.com/sample-product-1",
    image_url: "https://images.unsplash.com/photo-1567538096630-e0c55bd6374c",
    price: "$299.99",
  },
  {
    id: "2",
    profile_id: "default",
    created_at: new Date(),
    title: "Minimalist Coffee Table",
    description: "Scandinavian-style wooden coffee table with white marble top",
    url_to_product: "https://amazon.com/sample-product-2",
    image_url: "https://images.unsplash.com/photo-1533090161767-e6ffed986c88",
    price: "$199.99",
  },
  {
    id: "3",
    profile_id: "default",
    created_at: new Date(),
    title: "Geometric Area Rug 5x7",
    description:
      "Contemporary geometric pattern rug in neutral tones, soft pile height",
    url_to_product: "https://amazon.com/sample-product-3",
    image_url:
      "https://plus.unsplash.com/premium_photo-1725570022235-31331a924383?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    price: "$129.99",
  },
  {
    id: "4",
    profile_id: "default",
    created_at: new Date(),
    title: "Industrial Pendant Light",
    description: "Matte black metal pendant light with exposed bulb design",
    url_to_product: "https://amazon.com/sample-product-4",
    image_url: "https://images.unsplash.com/photo-1524484485831-a92ffc0de03f",
    price: "$89.99",
  },
  {
    id: "5",
    profile_id: "default",
    created_at: new Date(),
    title: "Floating Wall Shelves Set",
    description: "Set of 3 wooden floating shelves with hidden brackets",
    url_to_product: "https://amazon.com/sample-product-5",
    image_url: "https://images.unsplash.com/photo-1532372320572-cda25653a26d",
    price: "$49.99",
  },
];

export default function LandingPage() {
  const supabase = createBrowserSupabaseClient();
  const router = useRouter();
  const words = ["Spotify", "Pinterest"];

  useEffect(() => {
    const checkUser = async () => {
      const {
        data: { user },
      } = await supabase.auth.getUser();
      if (user) {
        router.push("/home");
      }
    };

    checkUser();
  }, [supabase, router]);

  const handleSignUp = async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        redirectTo: `${window.location.origin}/auth/callback`,
        queryParams: {
          access_type: "offline",
          prompt: "consent",
        },
      },
    });

    if (error) {
      console.error("Sign up error:", error.message);
      return;
    }
  };

  return (
    <div className="dark:bg-gray-900">
      {/* Hero Section */}
      <section className="min-h-screen flex items-center">
        <div className="px-4 sm:px-6 lg:px-20 w-full">
          <div className="text-left max-w-3xl mx-auto">
            <h1 className="text-4xl sm:text-6xl font-bold text-gray-900 dark:text-white mb-6 leading-relaxed">
              <span>Your </span>
              <WordRotate words={words} /> <br />
              <span> Turned to Room Inspo</span>
            </h1>
            <p className="text-xl text-gray-600 dark:text-gray-300 mb-12 leading-relaxed">
              Discover and shop furniture that matches your Pinterest
              inspiration, powered by AI that understands your style.
            </p>

            <div className="flex flex-col sm:flex-row gap-4 justify-right mb-16">
              <button
                onClick={handleSignUp}
                className="px-8 py-0.5  border-2 border-black dark:border-white uppercase bg-white text-black transition duration-200 text-sm shadow-[1px_1px_rgba(0,0,0),2px_2px_rgba(0,0,0),3px_3px_rgba(0,0,0),4px_4px_rgba(0,0,0),5px_5px_0px_0px_rgba(0,0,0)] dark:shadow-[1px_1px_rgba(255,255,255),2px_2px_rgba(255,255,255),3px_3px_rgba(255,255,255),4px_4px_rgba(255,255,255),5px_5px_0px_0px_rgba(255,255,255)] "
              >
                Get Started
              </button>
            </div>
          </div>
          <div className="text-center max-w-3xl mx-auto">
            {/* Scroll down indicator */}
            <div className="animate-bounce">
              <p className="text-gray-600 dark:text-gray-400 mb-2">
                See how it works
              </p>
              <svg
                className="w-6 h-6 mx-auto text-gray-600 dark:text-gray-400"
                fill="none"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
              </svg>
            </div>
          </div>
        </div>
      </section>

      {/* Demo Section */}
      <section className="min-h-screen bg-gray-50 dark:bg-gray-800">
        <div className="w-full">
          <SpotifyAlbumDemo />
          <InfiniteMovingCards
            items={amazonFinds}
            direction="right"
            speed="fast"
          />
        </div>
      </section>
    </div>
  );
}

```

### src/app/find/page.tsx

```typescript
export default function Find() {
  return <div>hi</div>;
}

```

### src/app/home/page.tsx

```typescript
import AmazonFindCards from "../ui/Home/AmazonFindCards";
import { playfairDisplay } from "../ui/fonts";
import PinterestModalWrapper from "./PinterestModalWrapper";
import { createServerSupabaseClient } from "@/lib/server-utils";
import { redirect } from "next/navigation";

export default async function Home() {
  // Create supabase server component client and obtain user session from Supabase Auth
  const supabase = createServerSupabaseClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    redirect("/");
  }
  return (
    <div className="flex flex-col gap-6 px-20 py-4">
      <PinterestModalWrapper />
      <h1 className={`${playfairDisplay.className} antialiased text-xl`}>
        Here are your most recently saved list of curated items:
      </h1>
      <AmazonFindCards />
      <h1
        className={`${playfairDisplay.className} antialiased text-xl text-center pt-3`}
      >
        Want to find more items?
      </h1>
      <a href="/home?newUser=true" className="flex justify-center">
        <div className="w-[80px] px-4 py-2 b text-[#262626] bg-[#FFFFFF] border-gray-300 border rounded-md shadow-md">
          Yes!!!!
        </div>
      </a>
    </div>
  );
}

```

### src/app/onboarding/page.tsx

```typescript
'use client';

import { useEffect, useState, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { createBrowserSupabaseClient } from '@/lib/client-utils';

function generateCodeVerifier(length: number) {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (let i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

async function generateCodeChallenge(codeVerifier: string) {
  const data = new TextEncoder().encode(codeVerifier);
  const digest = await window.crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode.apply(null, [...new Uint8Array(digest)]))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function OnboardingContent() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const code = searchParams.get('code');
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!code) {
      redirectToAuthCodeFlow();
    } else {
      handleCallback(code);
    }
  }, [code]);

  async function redirectToAuthCodeFlow() {
    const verifier = generateCodeVerifier(128);
    const challenge = await generateCodeChallenge(verifier);
    
    localStorage.setItem("verifier", verifier);

    const params = new URLSearchParams();
    params.append("client_id", process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID!);
    params.append("response_type", "code");
    params.append("redirect_uri", process.env.NEXT_PUBLIC_APP_URL!);
    params.append("scope", "user-read-private user-read-email user-top-read");
    params.append("code_challenge_method", "S256");
    params.append("code_challenge", challenge);

    window.location.href = `https://accounts.spotify.com/authorize?${params.toString()}`;
  }

  async function handleCallback(code: string) {
    try {
      const verifier = localStorage.getItem("verifier");
      const params = new URLSearchParams();
      params.append("client_id", process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID!);
      params.append("grant_type", "authorization_code");
      params.append("code", code);
      params.append("redirect_uri", process.env.NEXT_PUBLIC_APP_URL!);
      params.append("code_verifier", verifier!);

      const result = await fetch("https://accounts.spotify.com/api/token", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: params
      });

      const data = await result.json();
      
      if (data.error) {
        throw new Error(data.error);
      }

      const analysisResponse = await fetch('/api/spotify/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ access_token: data.access_token })
      });

      const analysisData = await analysisResponse.json();

      // Store both analysis and token
      localStorage.setItem('spotifyAnalysis', JSON.stringify(analysisData.data));
      localStorage.setItem('spotifyToken', data.access_token);

            // Update user metadata to indicate completed onboarding
      const supabase = createBrowserSupabaseClient();
      await supabase.auth.updateUser({
        data: { has_completed_onboarding: true }
      });

      // If successful, redirect to home
      router.push('/home?newUser=true');
    } catch (error) {
      console.error('Error:', error);
      setError(error instanceof Error ? error.message : 'Failed to connect to Spotify');
    }
  }

  if (error) {
    return (
      <div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
        <div className="text-center p-8">
          <h1 className="text-2xl font-bold text-red-600 mb-4">Error</h1>
          <p className="text-gray-700 dark:text-gray-300 mb-4">{error}</p>
          <button
            onClick={() => window.location.href = '/onboarding'}
            className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700"
          >
            Try Again
          </button>
        </div>
      </div>
    );
  }

  return null;
}

export default function Onboarding() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OnboardingContent />
    </Suspense>
  );
}

```

### src/app/auth/callback/route.ts

```typescript
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");

  if (code) {
    const cookieStore = await cookies();
    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          get(name: string) {
            return cookieStore.get(name)?.value;
          },
          set(name: string, value: string, options: CookieOptions) {
            cookieStore.set({ name, value, ...options });
          },
          remove(name: string, options: CookieOptions) {
            cookieStore.delete({ name, ...options });
          },
        },
      }
    );

    const {
      data: { user },
      error,
    } = await supabase.auth.exchangeCodeForSession(code);

    if (!error && user) {
      // Check if user has completed onboarding
      const hasCompletedOnboarding = user.user_metadata.has_completed_onboarding;

      // Create response with cookies
      const response = NextResponse.redirect(
        hasCompletedOnboarding ? `${origin}/home` : `${origin}/onboarding`
      );

      // Copy over the cookies from the supabase response
      const supabaseCookies = cookieStore.getAll();
      supabaseCookies.forEach((cookie) => {
        response.cookies.set(cookie.name, cookie.value, cookie);
      });

      return response;
    }
  }

  // return the user to an error page with instructions
  return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}

```

### src/app/api/amazon/route.ts

```typescript
import { NextResponse } from "next/server";
import { chromium } from "playwright";

export async function POST(request: Request) {
  try {
    const { searchTerms } = await request.json();

    if (!Array.isArray(searchTerms)) {
      return NextResponse.json(
        { error: "searchTerms must be an array" },
        { status: 400 }
      );
    }

    const items: string[] = [];
    const browser = await chromium.launch({
      headless: true
    });

    const context = await browser.newContext();
    const page = await context.newPage();
    
    // Set a global timeout for navigation
    page.setDefaultNavigationTimeout(5000);
    // Set a global timeout for other operations
    page.setDefaultTimeout(5000);
    
    await page.goto('https://www.amazon.com/');

    for (const searchTerm of searchTerms) {
      try {
        await page.fill('#twotabsearchtextbox', searchTerm);
        await page.press('#twotabsearchtextbox', 'Enter');
        
        // Reduced timeout for network idle and selector wait
        await Promise.race([
          page.waitForLoadState('networkidle', { timeout: 3000 }),
          page.waitForSelector('div[data-component-type="s-search-result"]', { timeout: 3000 })
        ]);

        // Reduced delay
        await page.waitForTimeout(1000);

        const firstProduct = await page.locator('div[data-component-type="s-search-result"] a.a-link-normal').first();
        
        if (firstProduct) {
          const href = await firstProduct.getAttribute('href');
          if (href) {
            const itemUrl = `https://www.amazon.com${href}`;
            items.push(itemUrl);
            console.log(`Found URL for ${searchTerm}: ${itemUrl}`);
          } else {
            console.log(`No link found for ${searchTerm}`);
          }
        } else {
          console.log(`No results found for ${searchTerm}`);
        }
      } catch (searchError) {
        console.error(`Error searching for "${searchTerm}":`, searchError);
        continue;
      }
    }

    await browser.close();
    return NextResponse.json({ items });
  } catch (error) {
    console.error('Amazon scraping error:', error);
    return NextResponse.json(
      { error: 'Failed to scrape Amazon' },
      { status: 500 }
    );
  }
} 
```

### src/app/api/spotify/user-data/route.ts

```typescript
import { NextResponse } from 'next/server';

async function getTopItems(token: string, type: 'artists' | 'tracks') {
  const response = await fetch(
    `https://api.spotify.com/v1/me/top/${type}?limit=10&time_range=medium_term`,
    {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }
  );
  return response.json();
}

export async function POST(request: Request) {
  try {
    const { access_token } = await request.json();

    const [topArtists, topTracks] = await Promise.all([
      getTopItems(access_token, 'artists'),
      getTopItems(access_token, 'tracks'),
    ]);

    // Log the data to console for now
    console.log('Top Artists:', topArtists);
    console.log('Top Tracks:', topTracks);

    return NextResponse.json({
      success: true,
      data: {
        topArtists: topArtists.items,
        topTracks: topTracks.items,
      },
    });
  } catch (error) {
    console.error('Error fetching user data:', error);
    return NextResponse.json(
      { error: 'Failed to fetch user data' },
      { status: 500 }
    );
  }
} 
```

### src/app/api/spotify/featured-track/route.ts

```typescript
import { NextResponse } from 'next/server';

async function getSpotifyAccessToken() {
  const basic = Buffer.from(`${process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID}:${process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_SECRET}`).toString('base64');
  
  const response = await fetch('https://accounts.spotify.com/api/token', {
    method: 'POST',
    headers: {
      Authorization: `Basic ${basic}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: 'grant_type=client_credentials',
  });

  const data = await response.json();
  return data.access_token;
}

export async function GET() {
  try {
    const accessToken = await getSpotifyAccessToken();
    
    // Using the same fetch pattern as your existing routes
    const response = await fetch(
      'https://api.spotify.com/v1/browse/new-releases?limit=1',
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );
    
    const data = await response.json();
    const track = {
      name: data.albums.items[0].name,
      artists: data.albums.items[0].artists,
      album: {
        images: data.albums.items[0].images,
        name: data.albums.items[0].name
      }
    };
    
    return NextResponse.json(track);
  } catch (error) {
    console.error('Error:', error);
    return NextResponse.json({ error: 'Failed to fetch track' }, { status: 500 });
  }
} 
```

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