From cb29331d58a8bb4951ee1f818f5233af623b6b7a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 7 Jan 2026 16:35:50 +0300 Subject: [PATCH] admoin --- .../src/components/TestPacksManager.test.tsx | 2 - admin/src/components/TestPacksManager.tsx | 5 +- .../forms/InputButtonsQuestionForm.tsx | 4 +- .../components/forms/SimpleQuestionForm.tsx | 4 +- admin/src/components/ui/image-upload.tsx | 77 ++++++ admin/src/pages/PacksPage.tsx | 240 +++++------------- admin/src/types/models.ts | 4 + 7 files changed, 149 insertions(+), 187 deletions(-) create mode 100644 admin/src/components/ui/image-upload.tsx diff --git a/admin/src/components/TestPacksManager.test.tsx b/admin/src/components/TestPacksManager.test.tsx index 6bebb32..690cfca 100644 --- a/admin/src/components/TestPacksManager.test.tsx +++ b/admin/src/components/TestPacksManager.test.tsx @@ -39,14 +39,12 @@ describe('TestPacksManager', () => { title: 'Pack One', cards: 10, enabled: true, - order: 0, }, { id: 'pack-2', title: 'Pack Two', cards: 5, enabled: true, - order: 1, }, ], total: 2, diff --git a/admin/src/components/TestPacksManager.tsx b/admin/src/components/TestPacksManager.tsx index 2db995d..a74d820 100644 --- a/admin/src/components/TestPacksManager.tsx +++ b/admin/src/components/TestPacksManager.tsx @@ -123,6 +123,7 @@ function TestPacksManagerInner({ const fallbackId = Array.from(nextSelected)[0] const fallback = fallbackId ? packsById.get(fallbackId) : undefined if (fallback?.color) onSelectedPackColorChange(fallback.color) + else onSelectedPackColorChange('') } return @@ -140,8 +141,8 @@ function TestPacksManagerInner({ }) } - if (onSelectedPackColorChange) { - onSelectedPackColorChange(pack.color ?? '') + if (onSelectedPackColorChange && pack.color) { + onSelectedPackColorChange(pack.color) } } diff --git a/admin/src/components/forms/InputButtonsQuestionForm.tsx b/admin/src/components/forms/InputButtonsQuestionForm.tsx index 1a0e370..8d4f976 100644 --- a/admin/src/components/forms/InputButtonsQuestionForm.tsx +++ b/admin/src/components/forms/InputButtonsQuestionForm.tsx @@ -111,7 +111,7 @@ export function InputButtonsQuestionForm({ setImage(value || '')} + onChange={(value: string | undefined) => setImage(value || '')} /> @@ -181,7 +181,7 @@ export function InputButtonsQuestionForm({ + onChange={(value: string | undefined) => updateButton(index, { image: value || undefined }) } /> diff --git a/admin/src/components/forms/SimpleQuestionForm.tsx b/admin/src/components/forms/SimpleQuestionForm.tsx index 47363d3..58015d8 100644 --- a/admin/src/components/forms/SimpleQuestionForm.tsx +++ b/admin/src/components/forms/SimpleQuestionForm.tsx @@ -102,7 +102,7 @@ export function SimpleQuestionForm({ setImage(value || '')} + onChange={(value: string | undefined) => setImage(value || '')} /> @@ -172,7 +172,7 @@ export function SimpleQuestionForm({ + onChange={(value: string | undefined) => updateButton(index, { image: value || undefined }) } /> diff --git a/admin/src/components/ui/image-upload.tsx b/admin/src/components/ui/image-upload.tsx new file mode 100644 index 0000000..0df2f75 --- /dev/null +++ b/admin/src/components/ui/image-upload.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react' +import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { ImageIcon, X } from 'lucide-react' + +interface ImageUploadProps { + label?: string + value?: string + onChange: (value: string | undefined) => void + disabled?: boolean + placeholder?: string +} + +export function ImageUpload({ + label, + value, + onChange, + disabled = false, + placeholder = 'Enter image URL', +}: ImageUploadProps) { + const [url, setUrl] = useState(value || '') + + const handleChange = (newValue: string) => { + setUrl(newValue) + onChange(newValue.trim() || undefined) + } + + const handleClear = () => { + setUrl('') + onChange(undefined) + } + + return ( +
+ {label && } +
+
+ + handleChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className="pl-9" + /> +
+ {url && ( + + )} +
+ {url && ( +
+ Preview { + // Hide broken images + e.currentTarget.style.display = 'none' + }} + /> +
+ )} +
+ ) +} + diff --git a/admin/src/pages/PacksPage.tsx b/admin/src/pages/PacksPage.tsx index 039355c..59720e3 100644 --- a/admin/src/pages/PacksPage.tsx +++ b/admin/src/pages/PacksPage.tsx @@ -3,7 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { packsApi, isPacksApiError } from '@/api/packs' import { formatApiError, getDetailedErrorMessage } from '@/lib/error-utils' -import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse } from '@/types/models' +import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse, Question } from '@/types/models' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' @@ -36,8 +36,6 @@ import { 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 { ColorPaletteInput } from '@/components/ui/color-palette-input' import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight } from 'lucide-react' export default function PacksPage() { @@ -52,19 +50,11 @@ export default function PacksPage() { // Form state const [formData, setFormData] = useState({ - title: '', - subtitle: '', + name: '', description: '', - color: '', - enabled: true, - googlePlayId: '', - rustoreId: '', - appStoreId: '', - price: '', - order: 0, - version: '', - size: 0, - cover: undefined as string | undefined, + category: '', + isPublic: true, + questions: [] as Question[], }) @@ -136,19 +126,11 @@ export default function PacksPage() { const openCreateDialog = () => { setSelectedPack(null) setFormData({ - title: '', - subtitle: '', + name: '', description: '', - color: '', - enabled: true, - googlePlayId: '', - rustoreId: '', - appStoreId: '', - price: '', - order: 0, - version: '', - size: 0, - cover: undefined, + category: '', + isPublic: true, + questions: [], }) setIsDialogOpen(true) } @@ -158,19 +140,11 @@ export default function PacksPage() { const fullPack = await packsApi.getPack(pack.id) setSelectedPack(fullPack) setFormData({ - title: fullPack.title || '', - subtitle: fullPack.subtitle || '', + name: fullPack.name || '', 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, + category: fullPack.category || '', + isPublic: fullPack.isPublic ?? true, + questions: fullPack.questions || [], }) setIsDialogOpen(true) @@ -191,27 +165,29 @@ export default function PacksPage() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() - if (!formData.title.trim()) { - toast.error('Title is required') + if (!formData.name.trim()) { + toast.error('Name is required') + return + } + + if (!formData.description.trim()) { + toast.error('Description is required') + return + } + + if (!formData.category.trim()) { + toast.error('Category is required') return } const packData: EditCardPackDto = { // Only include id for updates, not for new packs ...(selectedPack && { 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, + name: formData.name.trim(), + description: formData.description.trim(), + category: formData.category.trim(), + isPublic: formData.isPublic, + questions: formData.questions, } if (selectedPack) { @@ -336,12 +312,7 @@ export default function PacksPage() { {pack.id} -
-
{pack.title}
- {pack.subtitle && ( -
{pack.subtitle}
- )} -
+
{pack.title}
{pack.cards} @@ -426,146 +397,57 @@ export default function PacksPage() {
-
-
- - setFormData(prev => ({ ...prev, title: e.target.value }))} - placeholder="Pack title" - required - /> -
-
- - setFormData(prev => ({ ...prev, subtitle: e.target.value }))} - placeholder="Pack subtitle" - /> -
+
+ + setFormData(prev => ({ ...prev, name: e.target.value }))} + placeholder="Pack name" + required + />
- +