224 lines
8.4 KiB
TypeScript
224 lines
8.4 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import { useAuth } from "@/contexts/auth-context"
|
|
import { getApiUrl } from "@/lib/api-config"
|
|
|
|
interface Vertical {
|
|
id: number
|
|
name: string
|
|
slug: string
|
|
description: string | null
|
|
}
|
|
|
|
const StarIcon = ({ filled }: { filled: boolean }) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill={filled ? "currentColor" : "none"}
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="w-5 h-5"
|
|
>
|
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
|
</svg>
|
|
)
|
|
|
|
export function BandOnboarding({ onComplete }: { onComplete?: () => void }) {
|
|
const [verticals, setVerticals] = useState<Vertical[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [selectedBands, setSelectedBands] = useState<number[]>([])
|
|
const [headliners, setHeadliners] = useState<number[]>([])
|
|
const [step, setStep] = useState<"select" | "tier">("select")
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const { token } = useAuth()
|
|
const router = useRouter()
|
|
|
|
useEffect(() => {
|
|
async function fetchVerticals() {
|
|
try {
|
|
const res = await fetch(`${getApiUrl()}/verticals`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setVerticals(data)
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch verticals", error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
fetchVerticals()
|
|
}, [])
|
|
|
|
const toggleBand = (id: number) => {
|
|
setSelectedBands(prev =>
|
|
prev.includes(id) ? prev.filter(b => b !== id) : [...prev, id]
|
|
)
|
|
// Remove from headliners if deselected
|
|
if (headliners.includes(id)) {
|
|
setHeadliners(prev => prev.filter(h => h !== id))
|
|
}
|
|
}
|
|
|
|
const toggleHeadliner = (id: number) => {
|
|
setHeadliners(prev => {
|
|
if (prev.includes(id)) {
|
|
return prev.filter(h => h !== id)
|
|
}
|
|
if (prev.length >= 3) {
|
|
return prev // Limit 3
|
|
}
|
|
return [...prev, id]
|
|
})
|
|
}
|
|
|
|
const handleContinue = () => {
|
|
if (step === "select" && selectedBands.length > 0) {
|
|
setStep("tier")
|
|
} else {
|
|
handleSubmit()
|
|
}
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
setSubmitting(true)
|
|
try {
|
|
// Strategy:
|
|
// 1. Bulk add all as 'standard'
|
|
// 2. Update headliners to 'headliner'
|
|
|
|
const bulkRes = await fetch(`${getApiUrl()}/verticals/preferences/bulk`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({
|
|
vertical_ids: selectedBands,
|
|
display_mode: "standard"
|
|
}),
|
|
})
|
|
|
|
if (!bulkRes.ok) throw new Error("Failed to save preferences")
|
|
|
|
// Now set tiers
|
|
await Promise.all(headliners.map(async (vid) => {
|
|
await fetch(`${getApiUrl()}/verticals/preferences/${vid}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({
|
|
tier: "headliner",
|
|
priority: 100 // High priority
|
|
}),
|
|
})
|
|
}))
|
|
|
|
if (onComplete) {
|
|
onComplete()
|
|
} else {
|
|
router.push("/")
|
|
}
|
|
} catch (error) {
|
|
console.error("Error saving preferences", error)
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="text-muted-foreground">Loading bands...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-2xl mx-auto space-y-6">
|
|
<div className="text-center space-y-2">
|
|
<h1 className="text-3xl font-bold">
|
|
{step === "select" ? "Pick Your Bands" : "Who are your Headliners?"}
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
{step === "select"
|
|
? "Select the bands you follow. You can change this anytime."
|
|
: "Pick up to 3 favorites to feature on your home page."
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-8">
|
|
{verticals.map((vertical) => (
|
|
<button
|
|
key={vertical.id}
|
|
onClick={() => step === "select" ? toggleBand(vertical.id) : null}
|
|
disabled={step === "tier" && !selectedBands.includes(vertical.id)}
|
|
className={`
|
|
relative p-4 rounded-xl border-2 text-left transition-all
|
|
${step === "select"
|
|
? (selectedBands.includes(vertical.id)
|
|
? "border-primary bg-primary/10"
|
|
: "border-border hover:border-primary/50")
|
|
: (selectedBands.includes(vertical.id)
|
|
? "border-primary/50 opacity-100"
|
|
: "border-border opacity-20 grayscale")
|
|
}
|
|
${step === "tier" && selectedBands.includes(vertical.id) ? "cursor-default" : ""}
|
|
`}
|
|
>
|
|
<div className="font-bold">{vertical.name}</div>
|
|
<div className="text-sm text-muted-foreground">{vertical.description}</div>
|
|
|
|
{step === "tier" && selectedBands.includes(vertical.id) && (
|
|
<div
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
toggleHeadliner(vertical.id)
|
|
}}
|
|
className={`
|
|
absolute top-2 right-2 p-1 rounded-full cursor-pointer transition-colors z-10
|
|
${headliners.includes(vertical.id) ? "bg-yellow-500 text-black" : "bg-muted text-muted-foreground hover:bg-muted/80"}
|
|
`}
|
|
>
|
|
<StarIcon filled={headliners.includes(vertical.id)} />
|
|
</div>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center bg-card p-4 rounded-lg border">
|
|
<div className="text-sm font-medium">
|
|
{step === "select"
|
|
? `${selectedBands.length} selected`
|
|
: `${headliners.length}/3 Headliners selected`
|
|
}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
{step === "tier" && (
|
|
<Button variant="ghost" onClick={() => setStep("select")}>
|
|
Back
|
|
</Button>
|
|
)}
|
|
<Button
|
|
onClick={handleContinue}
|
|
disabled={submitting || (step === "select" && selectedBands.length === 0)}
|
|
size="lg"
|
|
>
|
|
{submitting ? "Saving..." : (step === "select" ? "Next: Choose Headliners" : "Finish")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|