Add discovery modes, personalization controls, taste profile page, updated pricing

- Discovery modes: Sonic Twin, Era Bridge, Deep Cuts, Rising Artists
- Discovery dial (Safe to Adventurous slider)
- Block genres/moods exclusion
- Thumbs down/dislike on recommendations
- My Taste page with Genre DNA breakdown, audio feature meters, listening personality
- Updated pricing: Free (5/week), Premium ($6.99/mo), Family ($12.99/mo coming soon)
- Weekly rate limiting instead of daily
- Alembic migration for new fields
This commit is contained in:
root
2026-03-31 00:21:58 -05:00
parent 789de25c1a
commit 1eea237c08
17 changed files with 898 additions and 113 deletions

View File

@@ -1,10 +1,11 @@
import { useState } from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Disc3, LayoutDashboard, ListMusic, Compass, Heart, Crown, Menu, X, LogOut, User } from 'lucide-react'
import { Disc3, LayoutDashboard, Fingerprint, ListMusic, Compass, Heart, Crown, Menu, X, LogOut, User } from 'lucide-react'
import { useAuth } from '../lib/auth'
const navItems = [
{ path: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ path: '/profile', label: 'My Taste', icon: Fingerprint },
{ path: '/playlists', label: 'Playlists', icon: ListMusic },
{ path: '/discover', label: 'Discover', icon: Compass },
{ path: '/saved', label: 'Saved', icon: Heart },

View File

@@ -1,13 +1,15 @@
import { Heart, ExternalLink, Music } from 'lucide-react'
import { Heart, ExternalLink, Music, ThumbsDown } from 'lucide-react'
import type { RecommendationItem } from '../lib/api'
interface Props {
recommendation: RecommendationItem
onToggleSave: (id: string) => void
onDislike?: (id: string) => void
saving?: boolean
disliking?: boolean
}
export default function RecommendationCard({ recommendation, onToggleSave, saving }: Props) {
export default function RecommendationCard({ recommendation, onToggleSave, onDislike, saving, disliking }: Props) {
return (
<div className="bg-white rounded-2xl border border-purple-100 shadow-sm hover:shadow-md transition-shadow overflow-hidden">
<div className="flex gap-4 p-5">
@@ -60,6 +62,24 @@ export default function RecommendationCard({ recommendation, onToggleSave, savin
/>
</button>
{onDislike && (
<button
onClick={() => onDislike(recommendation.id)}
disabled={disliking}
className={`p-2 rounded-full transition-colors cursor-pointer border-none ${
recommendation.disliked
? 'bg-charcoal-muted/20 text-charcoal'
: 'bg-gray-50 text-gray-400 hover:bg-gray-100 hover:text-charcoal-muted'
} ${disliking ? 'opacity-50 cursor-not-allowed' : ''}`}
title={recommendation.disliked ? 'Undo dislike' : 'Never recommend this type again'}
>
<ThumbsDown
className="w-4 h-4"
fill={recommendation.disliked ? 'currentColor' : 'none'}
/>
</button>
)}
{recommendation.bandcamp_url && (
<a
href={recommendation.bandcamp_url}