import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { packsApi } from '@/api/packs' import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse } from '@/types/models' import type { AxiosError } from 'axios' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Checkbox } from '@/components/ui/checkbox' import { ImageUpload } from '@/components/ui/image-upload' import { PackCardsManager } from '@/components/PackCardsManager' import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight } from 'lucide-react' export default function PacksPage() { const queryClient = useQueryClient() const [page, setPage] = useState(1) const [search, setSearch] = useState('') const [showDisabled, setShowDisabled] = useState(false) const [selectedPack, setSelectedPack] = useState(null) const [isDialogOpen, setIsDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [packToDelete, setPackToDelete] = useState(null) // Form state const [formData, setFormData] = useState({ title: '', subtitle: '', description: '', color: '', enabled: true, googlePlayId: '', rustoreId: '', appStoreId: '', price: '', order: 0, version: '', size: 0, cover: undefined as string | undefined, }) // Card management state const [currentCardIds, setCurrentCardIds] = useState([]) const [cardsToAdd, setCardsToAdd] = useState([]) const [cardsToRemove, setCardsToRemove] = useState([]) const limit = 20 // Fetch packs const { data, isLoading, error } = useQuery>({ queryKey: ['packs', page, search, showDisabled], queryFn: () => packsApi.getPacks({ page, limit, search, showDisabled }), }) // Mutations const createMutation = useMutation({ mutationFn: (pack: EditCardPackDto) => packsApi.upsertPack(pack), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['packs'] }) toast.success('Pack created successfully') closeDialog() }, onError: (error: unknown) => { const axiosError = error as AxiosError<{ message?: string }> toast.error(axiosError.response?.data?.message || 'Failed to create pack') }, }) const updateMutation = useMutation({ mutationFn: (pack: EditCardPackDto) => packsApi.upsertPack(pack), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['packs'] }) toast.success('Pack updated successfully') closeDialog() }, onError: (error: unknown) => { const axiosError = error as AxiosError<{ message?: string }> toast.error(axiosError.response?.data?.message || 'Failed to update pack') }, }) const deleteMutation = useMutation({ mutationFn: (packId: string) => packsApi.deletePack(packId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['packs'] }) toast.success('Pack deleted successfully') setIsDeleteDialogOpen(false) setPackToDelete(null) }, onError: (error: unknown) => { const axiosError = error as AxiosError<{ message?: string }> toast.error(axiosError.response?.data?.message || 'Failed to delete pack') }, }) const openCreateDialog = () => { setSelectedPack(null) setFormData({ title: '', subtitle: '', description: '', color: '', enabled: true, googlePlayId: '', rustoreId: '', appStoreId: '', price: '', order: 0, version: '', size: 0, cover: undefined, }) setCurrentCardIds([]) setCardsToAdd([]) setCardsToRemove([]) setIsDialogOpen(true) } const openEditDialog = async (pack: CardPackPreviewDto) => { try { const fullPack = await packsApi.getPack(pack.id) setSelectedPack(fullPack) setFormData({ title: fullPack.title || '', subtitle: fullPack.subtitle || '', description: fullPack.description || '', color: fullPack.color || '', enabled: fullPack.enabled ?? true, googlePlayId: fullPack.googlePlayId || '', rustoreId: fullPack.rustoreId || '', appStoreId: fullPack.appStoreId || '', price: fullPack.price || '', order: fullPack.order || 0, version: fullPack.version || '', size: fullPack.size || 0, cover: fullPack.cover, }) // Initialize current card IDs from addCardIds (which contains all cards in pack) const cardIds = fullPack.addCardIds?.map((id) => id.toString()) || [] setCurrentCardIds(cardIds) setCardsToAdd([]) setCardsToRemove([]) setIsDialogOpen(true) } catch { toast.error('Failed to load pack details') } } const closeDialog = () => { setIsDialogOpen(false) setSelectedPack(null) setCurrentCardIds([]) setCardsToAdd([]) setCardsToRemove([]) } const handleCardsChange = (addIds: string[], removeIds: string[]) => { setCardsToAdd(addIds) setCardsToRemove(removeIds) } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!formData.title.trim()) { toast.error('Title is required') return } const packData: EditCardPackDto = { id: selectedPack?.id || '', title: formData.title.trim(), subtitle: formData.subtitle.trim() || undefined, description: formData.description.trim() || undefined, color: formData.color.trim() || undefined, enabled: formData.enabled, googlePlayId: formData.googlePlayId.trim() || undefined, rustoreId: formData.rustoreId.trim() || undefined, appStoreId: formData.appStoreId.trim() || undefined, price: formData.price.trim() || undefined, order: formData.order, version: formData.version.trim() || undefined, size: formData.size > 0 ? formData.size : undefined, cover: formData.cover || undefined, addCardIds: cardsToAdd.length > 0 ? cardsToAdd : undefined, removeCardIds: cardsToRemove.length > 0 ? cardsToRemove : undefined, } if (selectedPack) { updateMutation.mutate(packData) } else { createMutation.mutate(packData) } } const handleDelete = (pack: CardPackPreviewDto) => { setPackToDelete(pack) setIsDeleteDialogOpen(true) } const confirmDelete = () => { if (packToDelete) { deleteMutation.mutate(packToDelete.id) } } const handleSearch = (value: string) => { setSearch(value) setPage(1) // Reset to first page when searching } if (error) { return (

Packs Management

Error loading packs

Failed to load packs. Please try again later.

) } return (

Packs Management

View, create, edit and delete card packs

{/* Search and Filters */}
handleSearch(e.target.value)} className="max-w-sm" />
setShowDisabled(checked as boolean)} />
{/* Packs Table */} All Packs ({data?.total || 0}) Manage card packs and their contents {isLoading ? (
Loading packs...
) : ( <> ID Title Cards Enabled Order Actions {data?.items.map((pack) => ( {pack.id}
{pack.title}
{pack.subtitle && (
{pack.subtitle}
)}
{pack.cards} {pack.enabled ? 'Enabled' : 'Disabled'} {pack.order ?? 0}
))}
{/* Pagination */} {data && data.totalPages > 1 && (
Showing {((page - 1) * limit) + 1} to {Math.min(page * limit, data.total)} of {data.total} packs
Page {page} of {data.totalPages}
)} )}
{/* Create/Edit Dialog */} {selectedPack ? 'Edit Pack' : 'Create New Pack'} {selectedPack ? 'Update the pack information' : 'Add a new pack to the system'}
setFormData(prev => ({ ...prev, title: e.target.value }))} placeholder="Pack title" required />
setFormData(prev => ({ ...prev, subtitle: e.target.value }))} placeholder="Pack subtitle" />