107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { listCoasters, deleteCoaster } from '../api/coaster'
|
|
import type { SavedCoaster } from '../types/api'
|
|
import styles from './CoasterListPanel.module.css'
|
|
|
|
interface Props {
|
|
challengeId: string
|
|
currentUsername: string | undefined
|
|
onLoad: (coaster: SavedCoaster) => void
|
|
refreshKey: number
|
|
/** Render as a top-bar dropdown instead of a sidebar panel */
|
|
menuMode?: boolean
|
|
}
|
|
|
|
export function CoasterListPanel({ challengeId, currentUsername, onLoad, refreshKey, menuMode }: Props) {
|
|
const [open, setOpen] = useState(false)
|
|
const [coasters, setCoasters] = useState<SavedCoaster[]>([])
|
|
|
|
useEffect(() => {
|
|
listCoasters(challengeId).then(setCoasters).catch(console.error)
|
|
}, [challengeId, refreshKey])
|
|
|
|
async function handleDelete(id: string) {
|
|
await deleteCoaster(id)
|
|
setCoasters(prev => prev.filter(c => c.id !== id))
|
|
}
|
|
|
|
if (menuMode) {
|
|
return (
|
|
<div className={styles.menuWrapper}>
|
|
<button className={styles.menuToggle} onClick={() => setOpen(o => !o)}>
|
|
Coasters ({coasters.length})
|
|
<span className={`${styles.toggleArrow}${open ? ` ${styles.open}` : ''}`}>▶</span>
|
|
</button>
|
|
{open && (
|
|
<div className={styles.menuDropdown}>
|
|
{coasters.length === 0 ? (
|
|
<p className={styles.empty}>No coasters yet.</p>
|
|
) : (
|
|
coasters.map(c => {
|
|
const isOwn = c.creator_username === currentUsername
|
|
return (
|
|
<div key={c.id} className={styles.row}>
|
|
<div className={styles.rowInfo}>
|
|
<span className={styles.rowName}>{c.name || 'Unnamed coaster'}</span>
|
|
<span className={`${styles.username}${isOwn ? ` ${styles.you}` : ''}`}>
|
|
@{c.creator_username}
|
|
</span>
|
|
</div>
|
|
<button className={styles.loadBtn} onClick={() => { onLoad(c); setOpen(false) }}>
|
|
Load
|
|
</button>
|
|
{isOwn && (
|
|
<button className={styles.deleteBtn} onClick={() => handleDelete(c.id)}>
|
|
Del
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
})
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className={styles.panel}>
|
|
<button className={styles.toggle} onClick={() => setOpen(o => !o)}>
|
|
<span>Coasters ({coasters.length})</span>
|
|
<span className={`${styles.toggleArrow}${open ? ` ${styles.open}` : ''}`}>▶</span>
|
|
</button>
|
|
|
|
{open && (
|
|
<div className={styles.body}>
|
|
{coasters.length === 0 ? (
|
|
<p className={styles.empty}>No coasters yet.</p>
|
|
) : (
|
|
coasters.map(c => {
|
|
const isOwn = c.creator_username === currentUsername
|
|
return (
|
|
<div key={c.id} className={styles.row}>
|
|
<div className={styles.rowInfo}>
|
|
<span className={styles.rowName}>{c.name || 'Unnamed coaster'}</span>
|
|
<span className={`${styles.username}${isOwn ? ` ${styles.you}` : ''}`}>
|
|
@{c.creator_username}
|
|
</span>
|
|
</div>
|
|
<button className={styles.loadBtn} onClick={() => onLoad(c)}>
|
|
Load
|
|
</button>
|
|
{isOwn && (
|
|
<button className={styles.deleteBtn} onClick={() => handleDelete(c.id)}>
|
|
Del
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
})
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|