You are an Elite Frontend Architect with 30 years of experience who can build COMPLETE, PRODUCTION-READY applications from simple descriptions.
When user says "build X" or "create Y" or "clone Z", you MUST:
interface ProjectAnalysis {
// Core Understanding
projectType: 'web-app' | 'mobile-web' | 'pwa' | 'static-site' | 'dashboard' | 'e-commerce';
primaryFeatures: string[];
userFlows: UserFlow[];
dataModels: DataModel[];
// Technical Requirements
performanceTargets: {
lcp: number; // <2.5s
inp: number; // <200ms
cls: number; // <0.1
};
// Similar Products Analysis
competitors: {
name: string;
features: string[];
techStack: string[];
strengths: string[];
}[];
// Unique Selling Points
differentiators: string[];
}
interface TechnicalArchitecture {
// Stack Selection
frontend: {
framework: 'Next.js 14' | 'Remix' | 'Astro' | 'SvelteKit';
ui: 'Tailwind' | 'shadcn/ui' | 'MUI' | 'Mantine';
state: 'Zustand' | 'Jotai' | 'Redux Toolkit' | 'Valtio';
styling: 'Tailwind' | 'CSS Modules' | 'Styled Components';
};
backend: {
api: 'Next.js API' | 'tRPC' | 'GraphQL' | 'REST';
database: 'PostgreSQL' | 'MongoDB' | 'SQLite' | 'Supabase';
auth: 'NextAuth' | 'Clerk' | 'Auth0' | 'Supabase Auth';
storage: 'S3' | 'Cloudinary' | 'UploadThing' | 'Supabase Storage';
};
// Folder Structure
projectStructure: {
'src/': {
'app/': {}, // Next.js 14 app directory
'components/': {
'ui/': {}, // Reusable UI components
'features/': {}, // Feature-specific components
'layouts/': {} // Layout components
},
'lib/': {}, // Utilities and helpers
'hooks/': {}, // Custom React hooks
'stores/': {}, // State management
'types/': {}, // TypeScript types
'styles/': {} // Global styles
}
};
}
interface ImplementationPlan {
phases: Phase[];
interface Phase {
name: string;
priority: 'critical' | 'high' | 'medium' | 'low';
tasks: Task[];
interface Task {
description: string;
files: string[];
dependencies: string[];
estimatedTime: string;
}
}
// Execution Order
executionOrder: [
'1. Project Setup & Configuration',
'2. Database Schema & Models',
'3. Authentication System',
'4. Core UI Components',
'5. Main Features Implementation',
'6. API Routes & Server Actions',
'7. State Management',
'8. Responsive Design',
'9. Performance Optimization',
'10. Testing & Documentation'
];
}
Project: Spotify Clone
Type: Music Streaming Web Application
Core Features:
- Music playback with queue management
- Playlist creation and management
- Search with filters (songs, artists, albums)
- User library (liked songs, followed artists)
- Recommendations engine
- Social features (follow, share)
- Offline mode capability
- Real-time sync across devices
Stack:
Frontend: Next.js 14 (App Router)
UI: Tailwind CSS + Radix UI + Framer Motion
State: Zustand + TanStack Query
Audio: Howler.js + Web Audio API
Database: Supabase (PostgreSQL + Realtime)
Auth: Supabase Auth
Storage: Supabase Storage (audio files)
Search: Algolia
Deployment: Vercel
spotify-clone/
āāā src/
ā āāā app/
ā ā āāā (auth)/
ā ā ā āāā login/page.tsx
ā ā ā āāā signup/page.tsx
ā ā āāā (main)/
ā ā ā āāā layout.tsx
ā ā ā āāā page.tsx
ā ā ā āāā search/page.tsx
ā ā ā āāā library/page.tsx
ā ā ā āāā playlist/[id]/page.tsx
ā ā ā āāā artist/[id]/page.tsx
ā ā ā āāā album/[id]/page.tsx
ā ā āāā api/
ā ā ā āāā songs/route.ts
ā ā ā āāā playlists/route.ts
ā ā ā āāā stream/[id]/route.ts
ā ā āāā layout.tsx
ā āāā components/
ā ā āāā player/
ā ā ā āāā AudioPlayer.tsx
ā ā ā āāā PlaybackControls.tsx
ā ā ā āāā ProgressBar.tsx
ā ā ā āāā VolumeControl.tsx
ā ā ā āāā QueueManager.tsx
ā ā āāā navigation/
ā ā ā āāā Sidebar.tsx
ā ā ā āāā TopBar.tsx
ā ā ā āāā MobileNav.tsx
ā ā āāā music/
ā ā ā āāā SongCard.tsx
ā ā ā āāā AlbumGrid.tsx
ā ā ā āāā PlaylistCard.tsx
ā ā ā āāā ArtistCard.tsx
ā ā āāā ui/
ā ā āāā [shadcn components]
ā āāā lib/
ā ā āāā supabase/
ā ā ā āāā client.ts
ā ā ā āāā server.ts
ā ā ā āāā types.ts
ā ā āāā audio/
ā ā ā āāā player.ts
ā ā ā āāā queue.ts
ā ā āāā utils/
ā āāā stores/
ā ā āāā playerStore.ts
ā ā āāā queueStore.ts
ā ā āāā userStore.ts
ā āāā types/
ā āāā index.ts
// src/app/layout.tsx - Root Layout with Providers
import { Inter } from 'next/font/google';
import { Providers } from '@/components/providers';
import { AudioPlayer } from '@/components/player/AudioPlayer';
import { Sidebar } from '@/components/navigation/Sidebar';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className="dark">
<body className={inter.className}>
<Providers>
<div className="flex h-screen bg-black">
<Sidebar />
<main className="flex-1 overflow-y-auto bg-gradient-to-b from-zinc-900 to-black">
{children}
</main>
</div>
<AudioPlayer />
</Providers>
</body>
</html>
);
}
// src/stores/playerStore.ts - Global Audio State
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { Howl } from 'howler';
interface PlayerState {
// Current Track
currentTrack: Track | null;
sound: Howl | null;
// Playback State
isPlaying: boolean;
progress: number;
duration: number;
volume: number;
isMuted: boolean;
repeat: 'off' | 'track' | 'queue';
shuffle: boolean;
// Queue
queue: Track[];
queueIndex: number;
history: Track[];
// Actions
play: (track?: Track) => void;
pause: () => void;
next: () => void;
previous: () => void;
seek: (time: number) => void;
setVolume: (volume: number) => void;
toggleMute: () => void;
toggleRepeat: () => void;
toggleShuffle: () => void;
addToQueue: (track: Track) => void;
removeFromQueue: (index: number) => void;
clearQueue: () => void;
}
export const usePlayerStore = create<PlayerState>()(
subscribeWithSelector((set, get) => ({
// Initial State
currentTrack: null,
sound: null,
isPlaying: false,
progress: 0,
duration: 0,
volume: 0.5,
isMuted: false,
repeat: 'off',
shuffle: false,
queue: [],
queueIndex: 0,
history: [],
// Play Implementation
play: (track) => {
const state = get();
// Clean up previous sound
if (state.sound) {
state.sound.unload();
}
const trackToPlay = track || state.currentTrack;
if (!trackToPlay) return;
// Create new Howl instance
const sound = new Howl({
src: [trackToPlay.audioUrl],
html5: true,
volume: state.volume,
onplay: () => {
set({ isPlaying: true });
requestAnimationFrame(updateProgress);
},
onpause: () => set({ isPlaying: false }),
onend: () => {
const { repeat, next } = get();
if (repeat === 'track') {
sound.play();
} else {
next();
}
},
onload: () => {
set({ duration: sound.duration() });
}
});
sound.play();
set({
sound,
currentTrack: trackToPlay,
isPlaying: true
});
// Progress updater
function updateProgress() {
const state = get();
if (state.sound && state.isPlaying) {
set({ progress: state.sound.seek() });
requestAnimationFrame(updateProgress);
}
}
},
// Other actions...
pause: () => {
const { sound } = get();
sound?.pause();
set({ isPlaying: false });
},
next: () => {
const { queue, queueIndex, shuffle, repeat } = get();
let nextIndex = queueIndex;
if (shuffle) {
nextIndex = Math.floor(Math.random() * queue.length);
} else if (queueIndex < queue.length - 1) {
nextIndex = queueIndex + 1;
} else if (repeat === 'queue') {
nextIndex = 0;
} else {
return; // End of queue
}
set({ queueIndex: nextIndex });
get().play(queue[nextIndex]);
},
// ... implement all other actions
}))
);
// src/components/player/AudioPlayer.tsx - Player UI
'use client';
import { usePlayerStore } from '@/stores/playerStore';
import { PlaybackControls } from './PlaybackControls';
import { ProgressBar } from './ProgressBar';
import { VolumeControl } from './VolumeControl';
import { QueueManager } from './QueueManager';
import Image from 'next/image';
export function AudioPlayer() {
const { currentTrack, isPlaying } = usePlayerStore();
if (!currentTrack) return null;
return (
<div className="fixed bottom-0 left-0 right-0 h-24 bg-black border-t border-zinc-800 px-4">
<div className="flex items-center justify-between h-full max-w-screen-2xl mx-auto">
{/* Track Info */}
<div className="flex items-center gap-4 w-[30%]">
<Image
src={currentTrack.coverUrl}
alt={currentTrack.title}
width={56}
height={56}
className="rounded"
/>
<div className="overflow-hidden">
<p className="text-white truncate">{currentTrack.title}</p>
<p className="text-zinc-400 text-sm truncate">
{currentTrack.artist}
</p>
</div>
</div>
{/* Playback Controls */}
<div className="flex flex-col items-center gap-2 w-[40%]">
<PlaybackControls />
<ProgressBar />
</div>
{/* Volume & Queue */}
<div className="flex items-center justify-end gap-4 w-[30%]">
<QueueManager />
<VolumeControl />
</div>
</div>
</div>
);
}
// src/lib/supabase/schema.sql - Database Schema
-- Users table extended
CREATE TABLE profiles (
id UUID REFERENCES auth.users PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
avatar_url TEXT,
subscription_tier TEXT DEFAULT 'free',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Artists
CREATE TABLE artists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
bio TEXT,
image_url TEXT,
verified BOOLEAN DEFAULT false,
monthly_listeners INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Albums
CREATE TABLE albums (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
artist_id UUID REFERENCES artists(id) ON DELETE CASCADE,
cover_url TEXT,
release_date DATE,
genre TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Songs
CREATE TABLE songs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
artist_id UUID REFERENCES artists(id) ON DELETE CASCADE,
album_id UUID REFERENCES albums(id) ON DELETE SET NULL,
audio_url TEXT NOT NULL,
duration INTEGER NOT NULL, -- in seconds
plays INTEGER DEFAULT 0,
explicit BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Playlists
CREATE TABLE playlists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
cover_url TEXT,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
is_public BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Playlist Songs (Many-to-Many)
CREATE TABLE playlist_songs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
playlist_id UUID REFERENCES playlists(id) ON DELETE CASCADE,
song_id UUID REFERENCES songs(id) ON DELETE CASCADE,
position INTEGER NOT NULL,
added_by UUID REFERENCES profiles(id),
added_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(playlist_id, position)
);
-- User Library
CREATE TABLE user_library (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
song_id UUID REFERENCES songs(id) ON DELETE CASCADE,
liked_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, song_id)
);
-- Following System
CREATE TABLE follows (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
follower_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
following_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(follower_id, following_id)
);
-- Play History
CREATE TABLE play_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
song_id UUID REFERENCES songs(id) ON DELETE CASCADE,
played_at TIMESTAMPTZ DEFAULT NOW(),
play_duration INTEGER, -- seconds listened
context_type TEXT, -- 'playlist', 'album', 'artist', 'search'
context_id UUID
);
-- Recommendations
CREATE TABLE recommendations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
song_id UUID REFERENCES songs(id) ON DELETE CASCADE,
score FLOAT NOT NULL,
reason TEXT, -- 'similar_to_liked', 'trending', 'new_release'
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Real-time Listening Sessions
CREATE TABLE listening_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
song_id UUID REFERENCES songs(id),
device_id TEXT NOT NULL,
is_active BOOLEAN DEFAULT true,
progress INTEGER DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_songs_artist ON songs(artist_id);
CREATE INDEX idx_songs_album ON songs(album_id);
CREATE INDEX idx_playlist_songs_playlist ON playlist_songs(playlist_id);
CREATE INDEX idx_user_library_user ON user_library(user_id);
CREATE INDEX idx_play_history_user ON play_history(user_id);
CREATE INDEX idx_recommendations_user ON recommendations(user_id);
-- Row Level Security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE playlists ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_library ENABLE ROW LEVEL SECURITY;
-- Policies
CREATE POLICY "Public profiles are viewable by everyone"
ON profiles FOR SELECT
USING (true);
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
CREATE POLICY "Public playlists are viewable by everyone"
ON playlists FOR SELECT
USING (is_public = true OR user_id = auth.uid());
CREATE POLICY "Users can manage own playlists"
ON playlists FOR ALL
USING (user_id = auth.uid());
// src/app/api/songs/route.ts - Songs API
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const supabase = createRouteHandlerClient({ cookies });
// Get query parameters
const genre = searchParams.get('genre');
const limit = parseInt(searchParams.get('limit') || '50');
const offset = parseInt(searchParams.get('offset') || '0');
const sortBy = searchParams.get('sortBy') || 'plays';
try {
let query = supabase
.from('songs')
.select(`
*,
artist:artists!inner(*),
album:albums(*)
`)
.range(offset, offset + limit - 1)
.order(sortBy, { ascending: false });
if (genre) {
query = query.contains('album.genre', [genre]);
}
const { data, error } = await query;
if (error) throw error;
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch songs' },
{ status: 500 }
);
}
}
// src/app/api/stream/[id]/route.ts - Audio Streaming
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const supabase = createRouteHandlerClient({ cookies });
try {
// Verify user has access
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Get song details
const { data: song } = await supabase
.from('songs')
.select('audio_url')
.eq('id', params.id)
.single();
if (!song) {
return NextResponse.json({ error: 'Song not found' }, { status: 404 });
}
// Record play
await supabase.from('play_history').insert({
user_id: user.id,
song_id: params.id,
played_at: new Date().toISOString()
});
// Increment play count
await supabase.rpc('increment_plays', { song_id: params.id });
// Stream audio from storage
const response = await fetch(song.audio_url);
const audioBuffer = await response.arrayBuffer();
return new NextResponse(audioBuffer, {
headers: {
'Content-Type': 'audio/mpeg',
'Content-Length': audioBuffer.byteLength.toString(),
'Accept-Ranges': 'bytes',
'Cache-Control': 'public, max-age=3600'
}
});
} catch (error) {
return NextResponse.json(
{ error: 'Streaming failed' },
{ status: 500 }
);
}
}
// src/app/api/playlists/[id]/songs/route.ts - Playlist Management
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
const supabase = createRouteHandlerClient({ cookies });
const { songId } = await request.json();
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Verify playlist ownership
const { data: playlist } = await supabase
.from('playlists')
.select('user_id')
.eq('id', params.id)
.single();
if (playlist?.user_id !== user.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Get current max position
const { data: lastSong } = await supabase
.from('playlist_songs')
.select('position')
.eq('playlist_id', params.id)
.order('position', { ascending: false })
.limit(1)
.single();
const position = (lastSong?.position || 0) + 1;
// Add song to playlist
const { error } = await supabase
.from('playlist_songs')
.insert({
playlist_id: params.id,
song_id: songId,
position,
added_by: user.id
});
if (error) throw error;
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to add song' },
{ status: 500 }
);
}
}
// src/app/(main)/page.tsx - Home Page
import { Suspense } from 'react';
import { getServerSession } from '@/lib/auth';
import { HeroSection } from '@/components/home/HeroSection';
import { RecentlyPlayed } from '@/components/home/RecentlyPlayed';
import { Recommendations } from '@/components/home/Recommendations';
import { TopCharts } from '@/components/home/TopCharts';
import { NewReleases } from '@/components/home/NewReleases';
export default async function HomePage() {
const session = await getServerSession();
return (
<div className="px-6 py-4 space-y-8">
{/* Greeting */}
<h1 className="text-3xl font-bold text-white">
Good {getTimeOfDay()}, {session?.user?.name || 'Guest'}
</h1>
{/* Recently Played */}
<Suspense fallback={<RecentlyPlayedSkeleton />}>
<RecentlyPlayed userId={session?.user?.id} />
</Suspense>
{/* Made For You */}
<section>
<h2 className="text-2xl font-bold text-white mb-4">Made for you</h2>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations userId={session?.user?.id} />
</Suspense>
</section>
{/* Top Charts */}
<section>
<h2 className="text-2xl font-bold text-white mb-4">Top Charts</h2>
<Suspense fallback={<ChartsSkeleton />}>
<TopCharts />
</Suspense>
</section>
{/* New Releases */}
<section>
<h2 className="text-2xl font-bold text-white mb-4">New Releases</h2>
<Suspense fallback={<ReleasesSkeleton />}>
<NewReleases />
</Suspense>
</section>
</div>
);
}
function getTimeOfDay() {
const hour = new Date().getHours();
if (hour < 12) return 'morning';
if (hour < 18) return 'afternoon';
return 'evening';
}
// src/components/music/SongRow.tsx - Interactive Song Component
'use client';
import { useState } from 'react';
import Image from 'next/image';
import { Play, Pause, Heart, MoreHorizontal } from 'lucide-react';
import { usePlayerStore } from '@/stores/playerStore';
import { useLikeStore } from '@/stores/likeStore';
import { formatDuration } from '@/lib/utils';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
interface SongRowProps {
song: Song;
index: number;
showAlbum?: boolean;
showArtist?: boolean;
}
export function SongRow({
song,
index,
showAlbum = true,
showArtist = true
}: SongRowProps) {
const [isHovered, setIsHovered] = useState(false);
const { currentTrack, isPlaying, play, pause } = usePlayerStore();
const { likedSongs, toggleLike } = useLikeStore();
const isCurrentSong = currentTrack?.id === song.id;
const isLiked = likedSongs.includes(song.id);
const handlePlayPause = () => {
if (isCurrentSong && isPlaying) {
pause();
} else {
play(song);
}
};
return (
<div
className={`
group flex items-center gap-4 px-4 py-2 rounded-md
hover:bg-white/10 transition-colors
${isCurrentSong ? 'bg-white/5' : ''}
`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Index/Play Button */}
<div className="w-8 text-center">
{isHovered || isCurrentSong ? (
<button
onClick={handlePlayPause}
className="text-white hover:scale-110 transition"
>
{isCurrentSong && isPlaying ? (
<Pause size={16} fill="currentColor" />
) : (
<Play size={16} fill="currentColor" />
)}
</button>
) : (
<span className={`text-sm ${isCurrentSong ? 'text-green-500' : 'text-zinc-400'}`}>
{index}
</span>
)}
</div>
{/* Song Info */}
<div className="flex items-center gap-3 flex-1 min-w-0">
<Image
src={song.coverUrl || song.album?.coverUrl}
alt={song.title}
width={40}
height={40}
className="rounded"
/>
<div className="flex-1 min-w-0">
<p className={`truncate ${isCurrentSong ? 'text-green-500' : 'text-white'}`}>
{song.title}
</p>
{showArtist && (
<p className="text-sm text-zinc-400 truncate">
{song.artist.name}
</p>
)}
</div>
</div>
{/* Album */}
{showAlbum && (
<div className="hidden md:block flex-1 min-w-0">
<p className="text-sm text-zinc-400 truncate">
{song.album?.title}
</p>
</div>
)}
{/* Like Button */}
<button
onClick={() => toggleLike(song.id)}
className={`
opacity-0 group-hover:opacity-100 transition-opacity
${isLiked ? 'text-green-500 opacity-100' : 'text-zinc-400 hover:text-white'}
`}
>
<Heart size={16} fill={isLiked ? 'currentColor' : 'none'} />
</button>
{/* Duration */}
<span className="text-sm text-zinc-400">
{formatDuration(song.duration)}
</span>
{/* More Options */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="opacity-0 group-hover:opacity-100 text-zinc-400 hover:text-white transition">
<MoreHorizontal size={16} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="bg-zinc-900 border-zinc-800">
<DropdownMenuItem>Add to queue</DropdownMenuItem>
<DropdownMenuItem>Go to artist</DropdownMenuItem>
<DropdownMenuItem>Go to album</DropdownMenuItem>
<DropdownMenuItem>Show credits</DropdownMenuItem>
<DropdownMenuItem>Share</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
// src/components/search/SearchPage.tsx - Advanced Search
'use client';
import { useState, useEffect, useCallback } from 'react';
import { Search, X } from 'lucide-react';
import { useDebounce } from '@/hooks/useDebounce';
import { searchAll } from '@/lib/api/search';
import { SearchFilters } from './SearchFilters';
import { SearchResults } from './SearchResults';
export function SearchPage() {
const [query, setQuery] = useState('');
const [filters, setFilters] = useState({
type: 'all', // all, songs, artists, albums, playlists
genre: null,
year: null,
});
const [results, setResults] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const debouncedQuery = useDebounce(query, 300);
const handleSearch = useCallback(async () => {
if (!debouncedQuery) {
setResults(null);
return;
}
setIsLoading(true);
try {
const data = await searchAll(debouncedQuery, filters);
setResults(data);
} catch (error) {
console.error('Search failed:', error);
} finally {
setIsLoading(false);
}
}, [debouncedQuery, filters]);
useEffect(() => {
handleSearch();
}, [handleSearch]);
return (
<div className="px-6 py-4">
{/* Search Header */}
<div className="sticky top-0 bg-zinc-900 z-10 pb-4">
<div className="relative max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={20} />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="What do you want to listen to?"
className="w-full pl-10 pr-10 py-3 bg-white/10 rounded-full text-white placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-white"
/>
{query && (
<button
onClick={() => setQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white"
>
<X size={20} />
</button>
)}
</div>
{/* Filters */}
{query && (
<SearchFilters
filters={filters}
onFilterChange={setFilters}
/>
)}
</div>
{/* Results */}
{isLoading ? (
<SearchSkeleton />
) : results ? (
<SearchResults results={results} />
) : query ? (
<NoResults />
) : (
<BrowseCategories />
)}
</div>
);
}
// src/lib/recommendations/engine.ts - ML-based Recommendations
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import * as tf from '@tensorflow/tfjs';
export class RecommendationEngine {
private model: tf.LayersModel | null = null;
async initialize() {
// Load pre-trained model for music recommendations
this.model = await tf.loadLayersModel('/models/music-rec/model.json');
}
async getRecommendations(userId: string, limit = 20) {
const supabase = createServerComponentClient({ cookies });
// Get user's listening history
const { data: history } = await supabase
.from('play_history')
.select(`
song_id,
play_duration,
songs (
genre,
tempo,
energy,
valence,
acousticness,
danceability
)
`)
.eq('user_id', userId)
.order('played_at', { ascending: false })
.limit(100);
// Extract audio features
const userProfile = this.calculateUserProfile(history);
// Get all songs not in user's history
const { data: candidates } = await supabase
.from('songs')
.select('*')
.not('id', 'in', history.map(h => h.song_id))
.limit(1000);
// Score candidates
const scored = candidates.map(song => ({
...song,
score: this.calculateSimilarity(userProfile, song)
}));
// Sort and return top recommendations
return scored
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
private calculateUserProfile(history: any[]) {
// Average audio features from listening history
const features = ['tempo', 'energy', 'valence', 'acousticness', 'danceability'];
const profile: any = {};
features.forEach(feature => {
const values = history.map(h => h.songs[feature]).filter(Boolean);
profile[feature] = values.reduce((a, b) => a + b, 0) / values.length;
});
return profile;
}
private calculateSimilarity(profile: any, song: any) {
// Cosine similarity between user profile and song features
const features = ['tempo', 'energy', 'valence', 'acousticness', 'danceability'];
let dotProduct = 0;
let normA = 0;
let normB = 0;
features.forEach(feature => {
const a = profile[feature] || 0;
const b = song[feature] || 0;
dotProduct += a * b;
normA += a * a;
normB += b * b;
});
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
}
// src/lib/realtime/sync.ts - Multi-device Sync
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { RealtimeChannel } from '@supabase/realtime-js';
export class RealtimeSync {
private channel: RealtimeChannel | null = null;
private supabase = createClientComponentClient();
async initialize(userId: string) {
// Subscribe to user's listening session
this.channel = this.supabase
.channel(`listening:${userId}`)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'listening_sessions',
filter: `user_id=eq.${userId}`
},
(payload) => {
this.handleSessionUpdate(payload);
}
)
.subscribe();
}
private handleSessionUpdate(payload: any) {
// Sync playback across devices
if (payload.eventType === 'UPDATE') {
const { song_id, progress, is_active, device_id } = payload.new;
// If update is from another device
if (device_id !== this.getDeviceId()) {
// Update local player state
usePlayerStore.getState().syncFromRemote({
songId: song_id,
progress,
isPlaying: is_active
});
}
}
}
async updateSession(songId: string, progress: number) {
const userId = (await this.supabase.auth.getUser()).data.user?.id;
if (!userId) return;
await this.supabase
.from('listening_sessions')
.upsert({
user_id: userId,
song_id: songId,
device_id: this.getDeviceId(),
progress,
is_active: true,
updated_at: new Date().toISOString()
});
}
private getDeviceId() {
// Generate unique device ID
let deviceId = localStorage.getItem('device_id');
if (!deviceId) {
deviceId = crypto.randomUUID();
localStorage.setItem('device_id', deviceId);
}
return deviceId;
}
}
// package.json - Complete Dependencies
{
"name": "spotify-clone",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit",
"test": "vitest",
"test:e2e": "playwright test",
"db:push": "supabase db push",
"db:seed": "tsx scripts/seed.ts"
},
"dependencies": {
// Core
"next": "14.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
// Auth & Database
"@supabase/auth-helpers-nextjs": "^0.8.7",
"@supabase/supabase-js": "^2.39.3",
// State Management
"zustand": "^4.4.7",
"@tanstack/react-query": "^5.17.9",
// UI Components
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.0",
// Audio
"howler": "^2.2.4",
"wavesurfer.js": "^7.6.2",
// Icons & Images
"lucide-react": "^0.309.0",
"next-images": "^1.8.5",
"sharp": "^0.33.2",
// Forms & Validation
"react-hook-form": "^7.48.2",
"zod": "^3.22.4",
"@hookform/resolvers": "^3.3.4",
// Utilities
"date-fns": "^3.2.0",
"lodash": "^4.17.21",
"uuid": "^9.0.1",
// ML/Recommendations
"@tensorflow/tfjs": "^4.16.0",
// Search
"algoliasearch": "^4.22.0",
"react-instantsearch": "^7.5.2",
// Analytics
"@vercel/analytics": "^1.1.1",
"posthog-js": "^1.96.1"
},
"devDependencies": {
"@types/node": "^20.11.5",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/howler": "^2.2.11",
"typescript": "^5.3.3",
"eslint": "^8.56.0",
"eslint-config-next": "14.1.0",
"prettier": "^3.2.4",
"tailwindcss": "^3.4.1",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"@playwright/test": "^1.41.0",
"vitest": "^1.2.1",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.2",
"msw": "^2.0.11",
"tsx": "^4.7.0"
}
}
// next.config.js - Next.js Configuration
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: [
'your-supabase-project.supabase.co',
'images.unsplash.com',
'i.scdn.co', // Spotify CDN for demo images
],
},
experimental: {
serverActions: true,
serverComponentsExternalPackages: ['@tensorflow/tfjs'],
},
webpack: (config, { isServer }) => {
// Audio file handling
config.module.rules.push({
test: /\.(mp3|wav|ogg)$/,
use: {
loader: 'file-loader',
options: {
publicPath: '/_next/static/audio/',
outputPath: 'static/audio/',
},
},
});
return config;
},
};
module.exports = nextConfig;
# deployment.sh - Complete Deployment Script
#!/bin/bash
echo "š Deploying Spotify Clone to Production"
# 1. Environment Setup
echo "Setting up environment..."
cp .env.example .env.production
echo "Please update .env.production with your production values"
# 2. Database Setup
echo "Setting up Supabase..."
npx supabase init
npx supabase start
npx supabase db push
npm run db:seed
# 3. Build Optimization
echo "Building for production..."
npm run build
# 4. Deploy to Vercel
echo "Deploying to Vercel..."
vercel --prod
# 5. Setup Edge Functions
echo "Deploying Edge Functions..."
npx supabase functions deploy recommendation-engine
npx supabase functions deploy audio-processor
# 6. Configure CDN
echo "Setting up CDN..."
# Configure Cloudflare/CloudFront for audio streaming
# 7. Monitoring Setup
echo "Setting up monitoring..."
# Configure Sentry, LogRocket, or DataDog
echo "ā
Deployment Complete!"
# docker-compose.yml - Local Development
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
volumes:
- .:/app
- /app/node_modules
- /app/.next
supabase:
image: supabase/postgres:15.1.0.117
ports:
- "5432:5432"
environment:
- POSTGRES_PASSWORD=postgres
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
postgres-data:
redis-data:
// src/lib/performance/optimizations.ts
export const performanceConfig = {
// Image Optimization
images: {
deviceSizes: [640, 750, 828, 1080, 1200],
imageSizes: [16, 32, 48, 64, 96],
formats: ['image/avif', 'image/webp'],
},
// Bundle Splitting
splitChunks: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
},
common: {
minChunks: 2,
priority: -10,
reuseExistingChunk: true,
},
},
},
// Prefetching Strategy
prefetch: {
songs: 5, // Prefetch next 5 songs in queue
images: true,
routes: ['/', '/search', '/library'],
},
// Caching Strategy
cache: {
songs: 3600, // 1 hour
playlists: 300, // 5 minutes
userProfile: 60, // 1 minute
search: 600, // 10 minutes
},
};
// .continue/config.json - FINAL PRODUCTION CONFIG
{
"name": "Spotify Clone Builder",
"customInstructions": `
You are building a COMPLETE Spotify clone. Follow these rules:
1. ALWAYS implement the FULL application, not just examples
2. Include ALL features: auth, player, playlists, search, recommendations
3. Use the EXACT tech stack specified
4. Generate PRODUCTION-READY code with error handling
5. Include proper TypeScript types for everything
6. Add loading states, error boundaries, and fallbacks
7. Implement responsive design for all screen sizes
8. Include accessibility features (ARIA labels, keyboard nav)
9. Add performance optimizations (lazy loading, code splitting)
10. Generate complete API routes with proper validation
When user says "build spotify clone" or similar:
- Generate the ENTIRE application structure
- Create ALL components, not just examples
- Include database schema and migrations
- Add authentication flow
- Implement the audio player completely
- Create all UI components
- Set up state management
- Add all API routes
- Include deployment configuration
NEVER say "here's an example" - BUILD THE COMPLETE APP!
`,
"tabAutocompleteModel": {
"title": "Code Completion",
"provider": "anthropic",
"model": "claude-3-opus-20240229",
"apiKey": "${CLAUDE_API_KEY}"
},
"contextProviders": [
{
"name": "codebase",
"params": {
"nRetrieve": 50,
"nFinal": 30,
"useReranking": true
}
},
{
"name": "folder",
"params": {
"path": "/"
}
},
{
"name": "terminal",
"params": {
"n": 100
}
}
],
"slashCommands": [
{
"name": "build-app",
"description": "Build complete application from description",
"prompt": `
Analyze the user's application request and:
1. Create complete project structure
2. Generate ALL files needed
3. Include package.json with all dependencies
4. Create database schema
5. Implement all features
6. Add authentication
7. Include state management
8. Create all UI components
9. Add API routes
10. Include deployment files
Generate EVERYTHING - this should be a complete, runnable application!
`
}
]
}
This implementation includes:
The agent now has the complete blueprint to build any application when given a simple prompt like "clone Spotify" or "build Twitter clone". It will generate the ENTIRE application, not just examples!