81 lines
3.8 KiB
TypeScript
81 lines
3.8 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { modlistsApi } from '../api/client'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { useRef, useState } from 'react'
|
|
|
|
export default function Modlists() {
|
|
const qc = useQueryClient()
|
|
const navigate = useNavigate()
|
|
const fileRef = useRef<HTMLInputElement>(null)
|
|
const { data: modlists, isLoading } = useQuery({ queryKey: ['modlists'], queryFn: modlistsApi.list })
|
|
const [showCreate, setShowCreate] = useState(false)
|
|
const [name, setName] = useState('')
|
|
|
|
const createMut = useMutation({
|
|
mutationFn: () => modlistsApi.create(name),
|
|
onSuccess: (res) => { qc.invalidateQueries({ queryKey: ['modlists'] }); setShowCreate(false); setName(''); navigate(`/modlists/${res.id}`) },
|
|
})
|
|
|
|
const deleteMut = useMutation({
|
|
mutationFn: (id: string) => modlistsApi.delete(id),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['modlists'] }),
|
|
})
|
|
|
|
const duplicateMut = useMutation({
|
|
mutationFn: (id: string) => {
|
|
const orig = modlists?.find(m => m.id === id)
|
|
return modlistsApi.duplicate(id, `${orig?.name ?? 'copy'} (copy)`)
|
|
},
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['modlists'] }),
|
|
})
|
|
|
|
const importMut = useMutation({
|
|
mutationFn: (file: File) => modlistsApi.importHtml(file),
|
|
onSuccess: (res) => { qc.invalidateQueries({ queryKey: ['modlists'] }); navigate(`/modlists/${res.id}`) },
|
|
})
|
|
|
|
const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (file) importMut.mutate(file)
|
|
e.target.value = ''
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<input ref={fileRef} type="file" accept=".html" onChange={handleImport} className="hidden" />
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h2 className="text-2xl font-semibold">Modlists</h2>
|
|
<div className="flex gap-2">
|
|
<button onClick={() => fileRef.current?.click()} disabled={importMut.isPending} className="px-4 py-2 bg-amber-700 hover:bg-amber-600 disabled:opacity-50 rounded-md text-sm font-medium">Import HTML</button>
|
|
<button onClick={() => setShowCreate(!showCreate)} className="px-4 py-2 bg-blue-600 hover:bg-blue-500 rounded-md text-sm font-medium">+ New Modlist</button>
|
|
</div>
|
|
</div>
|
|
|
|
{showCreate && (
|
|
<div className="mb-6 p-4 bg-neutral-900 border border-neutral-800 rounded-lg flex gap-3">
|
|
<input value={name} onChange={e => setName(e.target.value)} placeholder="Modlist name" className="bg-neutral-800 border border-neutral-700 rounded px-3 py-2 text-sm flex-1" />
|
|
<button onClick={() => createMut.mutate()} disabled={!name || createMut.isPending} className="px-4 py-2 bg-green-700 hover:bg-green-600 disabled:opacity-50 rounded text-sm">Create</button>
|
|
</div>
|
|
)}
|
|
|
|
{isLoading && <p className="text-neutral-500">Loading...</p>}
|
|
|
|
<div className="space-y-2">
|
|
{modlists?.map(m => (
|
|
<div key={m.id} className="flex items-center justify-between p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
|
|
<div>
|
|
<Link to={`/modlists/${m.id}`} className="font-medium hover:text-blue-400 transition-colors">{m.name}</Link>
|
|
<span className="text-xs text-neutral-500 ml-2">{m.mod_count} mods</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 text-sm">
|
|
<button onClick={() => duplicateMut.mutate(m.id)} className="text-neutral-400 hover:text-white">Duplicate</button>
|
|
<button onClick={() => deleteMut.mutate(m.id)} className="text-red-500 hover:text-red-400">Delete</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{modlists?.length === 0 && <p className="text-neutral-500 text-sm">No modlists yet.</p>}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|