Add search and fetch user account

This commit is contained in:
2025-12-20 00:17:58 +07:00
parent 13f484e726
commit 2d281d1c8c
5 changed files with 258 additions and 34 deletions

View File

@@ -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>
) : (