Initial commit

This commit is contained in:
MrFastwind
2026-07-06 01:29:23 +02:00
commit b853aead8c
48 changed files with 5822 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
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>
)
}