- Fork elmeg-demo codebase for multi-band support - Add data importer infrastructure with base class - Create band-specific importers: - phish.py: Phish.net API v5 - grateful_dead.py: Grateful Stats API - setlistfm.py: Dead & Company, Billy Strings (Setlist.fm) - Add spec-kit configuration for Gemini - Update README with supported bands and architecture
117 lines
4.2 KiB
TypeScript
117 lines
4.2 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { getApiUrl } from "@/lib/api-config"
|
|
|
|
interface SuggestNicknameDialogProps {
|
|
performanceId: number
|
|
songTitle: string
|
|
}
|
|
|
|
export function SuggestNicknameDialog({ performanceId, songTitle }: SuggestNicknameDialogProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const [nickname, setNickname] = useState("")
|
|
const [description, setDescription] = useState("")
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setLoading(true)
|
|
|
|
const token = localStorage.getItem("token")
|
|
if (!token) {
|
|
alert("You must be logged in to suggest a nickname.")
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(`${getApiUrl()}/nicknames/`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({
|
|
performance_id: performanceId,
|
|
nickname,
|
|
description
|
|
})
|
|
})
|
|
|
|
if (!res.ok) throw new Error("Failed to submit nickname")
|
|
|
|
alert("Nickname suggested! It will appear after moderator approval.")
|
|
setOpen(false)
|
|
setNickname("")
|
|
setDescription("")
|
|
} catch (err) {
|
|
console.error(err)
|
|
alert("Error submitting nickname")
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
<button className="opacity-0 group-hover:opacity-100 text-xs text-muted-foreground hover:text-primary transition-opacity ml-auto">
|
|
+ Suggest Nickname
|
|
</button>
|
|
</DialogTrigger>
|
|
<DialogContent className="sm:max-w-[425px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Suggest Nickname</DialogTitle>
|
|
<DialogDescription>
|
|
Suggest a "City Song" nickname for this performance of {songTitle}.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<form onSubmit={handleSubmit} className="grid gap-4 py-4">
|
|
<div className="grid grid-cols-4 items-center gap-4">
|
|
<Label htmlFor="nickname" className="text-right">
|
|
Nickname
|
|
</Label>
|
|
<Input
|
|
id="nickname"
|
|
value={nickname}
|
|
onChange={(e) => setNickname(e.target.value)}
|
|
className="col-span-3"
|
|
placeholder="e.g. Tahoe Tweezer"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-4 items-center gap-4">
|
|
<Label htmlFor="description" className="text-right">
|
|
Why?
|
|
</Label>
|
|
<Input
|
|
id="description"
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
className="col-span-3"
|
|
placeholder="Optional context..."
|
|
/>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button type="submit" disabled={loading}>
|
|
{loading ? "Submitting..." : "Submit Suggestion"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|