Add search and fetch user account
This commit is contained in:
@@ -6,9 +6,10 @@ import { NeonButton, Input, GlassCard, StatsCard } from '@/components/ui'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useToast } from '@/store/toast'
|
||||
import { useConfirm } from '@/store/confirm'
|
||||
import { fuzzyFilter } from '@/utils/fuzzySearch'
|
||||
import {
|
||||
Plus, Trash2, Sparkles, Play, Loader2, Gamepad2, X, Save, Eye,
|
||||
ChevronDown, ChevronUp, Edit2, Check, CheckCircle, XCircle, Clock, User, ArrowLeft, Zap
|
||||
ChevronDown, ChevronUp, Edit2, Check, CheckCircle, XCircle, Clock, User, ArrowLeft, Zap, Search
|
||||
} from 'lucide-react'
|
||||
|
||||
export function LobbyPage() {
|
||||
@@ -85,6 +86,10 @@ export function LobbyPage() {
|
||||
// Start marathon
|
||||
const [isStarting, setIsStarting] = useState(false)
|
||||
|
||||
// Search
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [generateSearchQuery, setGenerateSearchQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [id])
|
||||
@@ -501,6 +506,7 @@ export function LobbyPage() {
|
||||
} else {
|
||||
setPreviewChallenges(result.challenges)
|
||||
setShowGenerateSelection(false)
|
||||
setGenerateSearchQuery('')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate challenges:', error)
|
||||
@@ -1343,6 +1349,7 @@ export function LobbyPage() {
|
||||
onClick={() => {
|
||||
setShowGenerateSelection(false)
|
||||
clearGameSelection()
|
||||
setGenerateSearchQuery('')
|
||||
}}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@@ -1376,6 +1383,25 @@ export function LobbyPage() {
|
||||
{/* Game selection */}
|
||||
{showGenerateSelection && (
|
||||
<div className="space-y-3">
|
||||
{/* Search in generation */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Поиск игры..."
|
||||
value={generateSearchQuery}
|
||||
onChange={(e) => setGenerateSearchQuery(e.target.value)}
|
||||
className="input w-full pl-10 pr-10 py-2 text-sm"
|
||||
/>
|
||||
{generateSearchQuery && (
|
||||
<button
|
||||
onClick={() => setGenerateSearchQuery('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300 transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<button
|
||||
onClick={selectAllGamesForGeneration}
|
||||
@@ -1390,36 +1416,48 @@ export function LobbyPage() {
|
||||
Снять выбор
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
{approvedGames.map((game) => {
|
||||
const isSelected = selectedGamesForGeneration.includes(game.id)
|
||||
const challengeCount = gameChallenges[game.id]?.length ?? game.challenges_count
|
||||
return (
|
||||
<button
|
||||
key={game.id}
|
||||
onClick={() => toggleGameSelection(game.id)}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border transition-all text-left ${
|
||||
isSelected
|
||||
? 'bg-accent-500/20 border-accent-500/50'
|
||||
: 'bg-dark-700/50 border-dark-600 hover:border-dark-500'
|
||||
}`}
|
||||
>
|
||||
<div className={`w-5 h-5 rounded flex items-center justify-center border-2 transition-colors ${
|
||||
isSelected
|
||||
? 'bg-accent-500 border-accent-500'
|
||||
: 'border-gray-500'
|
||||
}`}>
|
||||
{isSelected && <Check className="w-3 h-3 text-white" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-medium truncate">{game.title}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{challengeCount > 0 ? `${challengeCount} заданий` : 'Нет заданий'}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<div className="grid gap-2 max-h-64 overflow-y-auto custom-scrollbar">
|
||||
{(() => {
|
||||
const filteredGames = generateSearchQuery
|
||||
? fuzzyFilter(approvedGames, generateSearchQuery, (g) => g.title + ' ' + (g.genre || ''))
|
||||
: approvedGames
|
||||
|
||||
return filteredGames.length === 0 ? (
|
||||
<p className="text-center text-gray-500 py-4 text-sm">
|
||||
Ничего не найдено по запросу "{generateSearchQuery}"
|
||||
</p>
|
||||
) : (
|
||||
filteredGames.map((game) => {
|
||||
const isSelected = selectedGamesForGeneration.includes(game.id)
|
||||
const challengeCount = gameChallenges[game.id]?.length ?? game.challenges_count
|
||||
return (
|
||||
<button
|
||||
key={game.id}
|
||||
onClick={() => toggleGameSelection(game.id)}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border transition-all text-left ${
|
||||
isSelected
|
||||
? 'bg-accent-500/20 border-accent-500/50'
|
||||
: 'bg-dark-700/50 border-dark-600 hover:border-dark-500'
|
||||
}`}
|
||||
>
|
||||
<div className={`w-5 h-5 rounded flex items-center justify-center border-2 transition-colors ${
|
||||
isSelected
|
||||
? 'bg-accent-500 border-accent-500'
|
||||
: 'border-gray-500'
|
||||
}`}>
|
||||
{isSelected && <Check className="w-3 h-3 text-white" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-medium truncate">{game.title}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{challengeCount > 0 ? `${challengeCount} заданий` : 'Нет заданий'}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
)
|
||||
})}
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1574,7 +1612,7 @@ export function LobbyPage() {
|
||||
|
||||
{/* Games list */}
|
||||
<GlassCard>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-neon-500/20 flex items-center justify-center">
|
||||
<Gamepad2 className="w-5 h-5 text-neon-400" />
|
||||
@@ -1592,6 +1630,26 @@ export function LobbyPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Поиск игры..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="input w-full pl-10 pr-10"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300 transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add game form */}
|
||||
{showAddGame && (
|
||||
<div className="mb-6 p-4 glass rounded-xl space-y-3 border border-neon-500/20">
|
||||
@@ -1632,17 +1690,29 @@ export function LobbyPage() {
|
||||
|
||||
{/* Games */}
|
||||
{(() => {
|
||||
const visibleGames = isOrganizer
|
||||
const baseGames = isOrganizer
|
||||
? games.filter(g => g.status !== 'pending')
|
||||
: games.filter(g => g.status === 'approved' || (g.status === 'pending' && g.proposed_by?.id === user?.id))
|
||||
|
||||
const visibleGames = searchQuery
|
||||
? fuzzyFilter(baseGames, searchQuery, (g) => g.title + ' ' + (g.genre || ''))
|
||||
: baseGames
|
||||
|
||||
return visibleGames.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-dark-700 flex items-center justify-center">
|
||||
<Gamepad2 className="w-8 h-8 text-gray-600" />
|
||||
{searchQuery ? (
|
||||
<Search className="w-8 h-8 text-gray-600" />
|
||||
) : (
|
||||
<Gamepad2 className="w-8 h-8 text-gray-600" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-gray-400">
|
||||
{isOrganizer ? 'Пока нет игр. Добавьте игры, чтобы начать!' : 'Пока нет одобренных игр. Предложите свою!'}
|
||||
{searchQuery
|
||||
? `Ничего не найдено по запросу "${searchQuery}"`
|
||||
: isOrganizer
|
||||
? 'Пока нет игр. Добавьте игры, чтобы начать!'
|
||||
: 'Пока нет одобренных игр. Предложите свою!'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user