a
This commit is contained in:
@@ -5,7 +5,7 @@ import type { Marathon, Assignment, Game, ActiveEvent, SwapCandidate, MySwapRequ
|
||||
import { NeonButton, GlassCard, StatsCard } from '@/components/ui'
|
||||
import { SpinWheel } from '@/components/SpinWheel'
|
||||
import { EventBanner } from '@/components/EventBanner'
|
||||
import { Loader2, Upload, X, Gamepad2, ArrowLeftRight, Check, XCircle, Clock, Send, Trophy, Users, ArrowLeft, AlertTriangle, Zap, Flame, Target } from 'lucide-react'
|
||||
import { Loader2, Upload, X, Gamepad2, ArrowLeftRight, Check, XCircle, Clock, Send, Trophy, Users, ArrowLeft, AlertTriangle, Zap, Flame, Target, Download } from 'lucide-react'
|
||||
import { useToast } from '@/store/toast'
|
||||
import { useConfirm } from '@/store/confirm'
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PlayPage() {
|
||||
const [activeEvent, setActiveEvent] = useState<ActiveEvent | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const [proofFile, setProofFile] = useState<File | null>(null)
|
||||
const [proofFiles, setProofFiles] = useState<File[]>([])
|
||||
const [proofUrl, setProofUrl] = useState('')
|
||||
const [comment, setComment] = useState('')
|
||||
const [isCompleting, setIsCompleting] = useState(false)
|
||||
@@ -57,7 +57,7 @@ export function PlayPage() {
|
||||
|
||||
// Bonus challenge completion
|
||||
const [expandedBonusId, setExpandedBonusId] = useState<number | null>(null)
|
||||
const [bonusProofFile, setBonusProofFile] = useState<File | null>(null)
|
||||
const [bonusProofFiles, setBonusProofFiles] = useState<File[]>([])
|
||||
const [bonusProofUrl, setBonusProofUrl] = useState('')
|
||||
const [bonusComment, setBonusComment] = useState('')
|
||||
const [isCompletingBonus, setIsCompletingBonus] = useState(false)
|
||||
@@ -232,12 +232,12 @@ export function PlayPage() {
|
||||
// For playthrough: allow file, URL, or comment
|
||||
// For challenges: require file or URL
|
||||
if (currentAssignment.is_playthrough) {
|
||||
if (!proofFile && !proofUrl && !comment) {
|
||||
if (proofFiles.length === 0 && !proofUrl && !comment) {
|
||||
toast.warning('Пожалуйста, предоставьте доказательство (файл, ссылку или комментарий)')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!proofFile && !proofUrl) {
|
||||
if (proofFiles.length === 0 && !proofUrl) {
|
||||
toast.warning('Пожалуйста, предоставьте доказательство (файл или ссылку)')
|
||||
return
|
||||
}
|
||||
@@ -246,12 +246,12 @@ export function PlayPage() {
|
||||
setIsCompleting(true)
|
||||
try {
|
||||
const result = await wheelApi.complete(currentAssignment.id, {
|
||||
proof_file: proofFile || undefined,
|
||||
proof_files: proofFiles.length > 0 ? proofFiles : undefined,
|
||||
proof_url: proofUrl || undefined,
|
||||
comment: comment || undefined,
|
||||
})
|
||||
toast.success(`Выполнено! +${result.points_earned} очков (бонус серии: +${result.streak_bonus})`)
|
||||
setProofFile(null)
|
||||
setProofFiles([])
|
||||
setProofUrl('')
|
||||
setComment('')
|
||||
await loadData()
|
||||
@@ -291,7 +291,7 @@ export function PlayPage() {
|
||||
|
||||
const handleBonusComplete = async (bonusId: number) => {
|
||||
if (!currentAssignment) return
|
||||
if (!bonusProofFile && !bonusProofUrl && !bonusComment) {
|
||||
if (bonusProofFiles.length === 0 && !bonusProofUrl && !bonusComment) {
|
||||
toast.warning('Прикрепите файл, ссылку или комментарий')
|
||||
return
|
||||
}
|
||||
@@ -302,13 +302,13 @@ export function PlayPage() {
|
||||
currentAssignment.id,
|
||||
bonusId,
|
||||
{
|
||||
proof_file: bonusProofFile || undefined,
|
||||
proof_files: bonusProofFiles.length > 0 ? bonusProofFiles : undefined,
|
||||
proof_url: bonusProofUrl || undefined,
|
||||
comment: bonusComment || undefined,
|
||||
}
|
||||
)
|
||||
toast.success(`Бонус отмечен! +${result.points_earned} очков начислится при завершении игры`)
|
||||
setBonusProofFile(null)
|
||||
setBonusProofFiles([])
|
||||
setBonusProofUrl('')
|
||||
setBonusComment('')
|
||||
setExpandedBonusId(null)
|
||||
@@ -965,11 +965,28 @@ export function PlayPage() {
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-sm mb-1">Игра</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{currentAssignment.is_playthrough
|
||||
? currentAssignment.game?.title
|
||||
: currentAssignment.challenge?.game.title}
|
||||
</p>
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
<p className="text-xl font-bold text-white">
|
||||
{currentAssignment.is_playthrough
|
||||
? currentAssignment.game?.title
|
||||
: currentAssignment.challenge?.game.title}
|
||||
</p>
|
||||
{(currentAssignment.is_playthrough
|
||||
? currentAssignment.game?.download_url
|
||||
: currentAssignment.challenge?.game.download_url) && (
|
||||
<a
|
||||
href={currentAssignment.is_playthrough
|
||||
? currentAssignment.game?.download_url
|
||||
: currentAssignment.challenge?.game.download_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-3 py-1.5 bg-neon-500/20 text-neon-400 rounded-lg text-sm font-medium border border-neon-500/30 flex items-center gap-1.5 hover:bg-neon-500/30 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Скачать игру
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentAssignment.is_playthrough ? (
|
||||
@@ -1023,7 +1040,7 @@ export function PlayPage() {
|
||||
onClick={() => {
|
||||
if (bonus.status === 'pending') {
|
||||
setExpandedBonusId(expandedBonusId === bonus.id ? null : bonus.id)
|
||||
setBonusProofFile(null)
|
||||
setBonusProofFiles([])
|
||||
setBonusProofUrl('')
|
||||
setBonusComment('')
|
||||
if (bonusFileInputRef.current) bonusFileInputRef.current.value = ''
|
||||
@@ -1062,24 +1079,40 @@ export function PlayPage() {
|
||||
ref={bonusFileInputRef}
|
||||
type="file"
|
||||
accept="image/*,video/*"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
e.stopPropagation()
|
||||
validateAndSetFile(e.target.files?.[0] || null, setBonusProofFile, bonusFileInputRef)
|
||||
const files = Array.from(e.target.files || [])
|
||||
setBonusProofFiles(prev => [...prev, ...files])
|
||||
e.target.value = ''
|
||||
}}
|
||||
/>
|
||||
{bonusProofFile ? (
|
||||
<div className="flex items-center gap-2 p-2 bg-dark-700/50 rounded-lg border border-dark-600">
|
||||
<span className="text-white text-sm flex-1 truncate">{bonusProofFile.name}</span>
|
||||
{bonusProofFiles.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{bonusProofFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center gap-2 p-2 bg-dark-700/50 rounded-lg border border-dark-600">
|
||||
<span className="text-white text-sm flex-1 truncate">{file.name}</span>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setBonusProofFiles(prev => prev.filter((_, i) => i !== index))
|
||||
}}
|
||||
className="p-1 rounded text-gray-400 hover:text-white hover:bg-dark-600 transition-colors"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setBonusProofFile(null)
|
||||
if (bonusFileInputRef.current) bonusFileInputRef.current.value = ''
|
||||
bonusFileInputRef.current?.click()
|
||||
}}
|
||||
className="p-1 rounded text-gray-400 hover:text-white hover:bg-dark-600 transition-colors"
|
||||
className="w-full p-2 border border-dashed border-neon-500/30 rounded-lg text-neon-400 hover:border-neon-500/50 hover:bg-neon-500/5 transition-all text-sm flex items-center justify-center gap-2"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
<Upload className="w-4 h-4" />
|
||||
Добавить еще файл
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -1121,7 +1154,7 @@ export function PlayPage() {
|
||||
handleBonusComplete(bonus.id)
|
||||
}}
|
||||
isLoading={isCompletingBonus}
|
||||
disabled={!bonusProofFile && !bonusProofUrl && !bonusComment}
|
||||
disabled={bonusProofFiles.length === 0 && !bonusProofUrl && !bonusComment}
|
||||
icon={<Check className="w-3 h-3" />}
|
||||
>
|
||||
Выполнено
|
||||
@@ -1131,7 +1164,7 @@ export function PlayPage() {
|
||||
variant="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setBonusProofFile(null)
|
||||
setBonusProofFiles([])
|
||||
setBonusProofUrl('')
|
||||
setBonusComment('')
|
||||
setExpandedBonusId(null)
|
||||
@@ -1202,19 +1235,37 @@ export function PlayPage() {
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="image/*,video/*"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={(e) => validateAndSetFile(e.target.files?.[0] || null, setProofFile, fileInputRef)}
|
||||
onChange={(e) => {
|
||||
const files = Array.from(e.target.files || [])
|
||||
setProofFiles(prev => [...prev, ...files])
|
||||
// Reset input to allow selecting same files again
|
||||
e.target.value = ''
|
||||
}}
|
||||
/>
|
||||
|
||||
{proofFile ? (
|
||||
<div className="flex items-center gap-2 p-3 bg-dark-700/50 rounded-xl border border-dark-600">
|
||||
<span className="text-white flex-1 truncate">{proofFile.name}</span>
|
||||
<button
|
||||
onClick={() => setProofFile(null)}
|
||||
className="p-1.5 rounded-lg text-gray-400 hover:text-white hover:bg-dark-600 transition-colors"
|
||||
{proofFiles.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{proofFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center gap-2 p-3 bg-dark-700/50 rounded-xl border border-dark-600">
|
||||
<span className="text-white flex-1 truncate">{file.name}</span>
|
||||
<button
|
||||
onClick={() => setProofFiles(proofFiles.filter((_, i) => i !== index))}
|
||||
className="p-1.5 rounded-lg text-gray-400 hover:text-white hover:bg-dark-600 transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<NeonButton
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
icon={<Upload className="w-4 h-4" />}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
Добавить ещё файлы
|
||||
</NeonButton>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
@@ -1224,10 +1275,10 @@ export function PlayPage() {
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
icon={<Upload className="w-4 h-4" />}
|
||||
>
|
||||
Выбрать файл
|
||||
Выбрать файлы
|
||||
</NeonButton>
|
||||
<p className="text-xs text-gray-500 mt-2 text-center">
|
||||
Макс. 15 МБ для изображений, 30 МБ для видео
|
||||
Можно выбрать несколько файлов. Макс. 15 МБ для изображений, 30 МБ для видео
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -1257,8 +1308,8 @@ export function PlayPage() {
|
||||
onClick={handleComplete}
|
||||
isLoading={isCompleting}
|
||||
disabled={currentAssignment.is_playthrough
|
||||
? (!proofFile && !proofUrl && !comment)
|
||||
: (!proofFile && !proofUrl)
|
||||
? (proofFiles.length === 0 && !proofUrl && !comment)
|
||||
: (proofFiles.length === 0 && !proofUrl)
|
||||
}
|
||||
icon={<Check className="w-4 h-4" />}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user