Files
whats-the-point/frontend/src/pages/ChallengeList.jsx

178 lines
5.7 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import api from '../api';
export default function ChallengeList() {
const [challenges, setChallenges] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [showResults, setShowResults] = useState([]);
const [loading, setLoading] = useState(true);
const [creating, setCreating] = useState(false);
const [searchTimeout, setSearchTimeout] = useState(null);
useEffect(() => {
loadChallenges();
}, []);
const loadChallenges = async () => {
try {
const data = await api.getChallenges();
setChallenges(data.challenges);
} catch (err) {
console.error('Failed to load challenges:', err);
} finally {
setLoading(false);
}
};
const handleSearch = (query) => {
setSearchQuery(query);
// Clear previous timeout
if (searchTimeout) {
clearTimeout(searchTimeout);
}
if (query.trim().length < 2) {
setShowResults([]);
return;
}
// Debounce search by 1.5 seconds
const timeout = setTimeout(async () => {
try {
const data = await api.searchShows(query);
setShowResults(data.results || []);
} catch (err) {
console.error('Search failed:', err);
}
}, 1500);
setSearchTimeout(timeout);
};
const handleCreateChallenge = async (show) => {
setCreating(true);
try {
const coverImage = show.poster_path
? `https://image.tmdb.org/t/p/w500${show.poster_path}`
: null;
const result = await api.createChallenge({
title: show.title,
cover_image_url: coverImage,
tmdb_id: show.id,
media_type: show.media_type
});
setSearchQuery('');
setShowResults([]);
await loadChallenges();
} catch (err) {
alert('Failed to create challenge: ' + err.message);
} finally {
setCreating(false);
}
};
if (loading) {
return <div className="loading">Loading challenges...</div>;
}
return (
<div style={{ padding: '2rem 0' }}>
<div className="container">
<h1 style={{ marginBottom: '2rem' }}>My Challenges</h1>
{/* Search/Create */}
<div style={{ marginBottom: '2rem', position: 'relative' }}>
<input
type="text"
className="input"
placeholder="Search for a show or movie to create a challenge..."
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
/>
{showResults.length > 0 && (
<div style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
background: 'var(--bg-light)',
border: '1px solid var(--border)',
borderRadius: '0.5rem',
marginTop: '0.5rem',
maxHeight: '400px',
overflowY: 'auto',
zIndex: 10
}}>
{showResults.map(show => (
<div
key={show.id}
onClick={() => handleCreateChallenge(show)}
style={{
padding: '1rem',
borderBottom: '1px solid var(--border)',
cursor: 'pointer',
display: 'flex',
gap: '1rem',
alignItems: 'center'
}}
>
{show.poster_path && (
<img
src={`https://image.tmdb.org/t/p/w92${show.poster_path}`}
alt={show.title}
style={{ width: '46px', height: '69px', objectFit: 'cover', borderRadius: '0.25rem' }}
/>
)}
<div>
<div style={{ fontWeight: 500 }}>{show.title}</div>
<div style={{ fontSize: '0.875rem', color: 'var(--text-muted)' }}>
{show.media_type === 'tv' ? 'TV Show' : 'Movie'} {show.release_date?.substring(0, 4)}
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Challenge List */}
{challenges.length === 0 ? (
<div className="card" style={{ textAlign: 'center', padding: '3rem' }}>
<p style={{ color: 'var(--text-muted)' }}>No challenges yet. Search for a show above to create one!</p>
</div>
) : (
<div className="grid grid-2">
{challenges.map(challenge => (
<Link key={challenge.id} to={`/challenges/${challenge.id}`} style={{ textDecoration: 'none' }}>
<div className="card" style={{ height: '100%', display: 'flex', gap: '1rem' }}>
{challenge.cover_image_url && (
<img
src={challenge.cover_image_url}
alt={challenge.title}
style={{ width: '80px', height: '120px', objectFit: 'cover', borderRadius: '0.5rem' }}
/>
)}
<div style={{ flex: 1 }}>
<h3 style={{ marginBottom: '0.5rem' }}>{challenge.title}</h3>
<p style={{ fontSize: '0.875rem', color: 'var(--text-muted)' }}>
Created by {challenge.creator_username}
</p>
<p style={{ marginTop: '0.5rem', color: 'var(--primary)' }}>
{challenge.my_points} points
</p>
</div>
</div>
</Link>
))}
</div>
)}
</div>
</div>
);
}