import { useState, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useBackButton } from '../platform/hooks/useBackButton'; import { usePlatform } from '../platform/hooks/usePlatform'; import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent, type DragStartEvent, } from '@dnd-kit/core'; import { useTelegramDnd } from '../hooks/useTelegramDnd'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { adminPaymentMethodsApi } from '../api/adminPaymentMethods'; import type { PaymentMethodConfig } from '../types'; // ============ Icons ============ const BackIcon = () => ( ); const GripIcon = () => ( ); const ChevronRightIcon = () => ( ); const CheckIcon = () => ( ); const SaveIcon = () => ( ); // ============ Method icon by type ============ const METHOD_ICONS: Record = { telegram_stars: '\u2B50', tribute: '\uD83C\uDF81', cryptobot: '\uD83E\uDE99', heleket: '\u26A1', yookassa: '\uD83C\uDFE6', mulenpay: '\uD83D\uDCB3', pal24: '\uD83D\uDCB8', platega: '\uD83D\uDCB0', wata: '\uD83D\uDCA7', freekassa: '\uD83D\uDCB5', cloudpayments: '\u2601\uFE0F', kassa_ai: '\uD83C\uDFE6', }; // ============ Sortable Card ============ interface SortableCardProps { config: PaymentMethodConfig; onClick: () => void; } function SortablePaymentCard({ config, onClick }: SortableCardProps) { const { t } = useTranslation(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: config.method_id, }); const style: React.CSSProperties = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? 50 : undefined, position: isDragging ? 'relative' : undefined, }; const displayName = config.display_name || config.default_display_name; const icon = METHOD_ICONS[config.method_id] || '\uD83D\uDCB3'; // Build condition summary chips const chips: string[] = []; if (config.user_type_filter === 'telegram') chips.push(t('admin.paymentMethods.userTypeTelegram')); if (config.user_type_filter === 'email') chips.push(t('admin.paymentMethods.userTypeEmail')); if (config.first_topup_filter === 'yes') chips.push(t('admin.paymentMethods.firstTopupYes')); if (config.first_topup_filter === 'no') chips.push(t('admin.paymentMethods.firstTopupNo')); if (config.promo_group_filter_mode === 'selected' && config.allowed_promo_group_ids.length > 0) { chips.push( `${config.allowed_promo_group_ids.length} ${t('admin.paymentMethods.promoGroupsShort')}`, ); } // Count enabled sub-options let subOptionsInfo = ''; if (config.available_sub_options && config.sub_options) { const enabledCount = config.available_sub_options.filter( (o) => config.sub_options?.[o.id] !== false, ).length; const totalCount = config.available_sub_options.length; if (enabledCount < totalCount) { subOptionsInfo = `${enabledCount}/${totalCount}`; } } return (
{/* Drag handle */} {/* Drag handle - larger touch target for mobile */} {/* Method icon */}
{icon}
{/* Content */}
{displayName} {config.is_enabled ? ( {t('admin.paymentMethods.enabled')} ) : ( {t('admin.paymentMethods.disabled')} )} {!config.is_provider_configured && ( {t('admin.paymentMethods.notConfigured')} )} {subOptionsInfo && ( {subOptionsInfo} )}
{/* Condition chips */} {chips.length > 0 && (
{chips.map((chip, i) => ( {chip} ))}
)}
{/* Chevron */}
); } // ============ Toast ============ function Toast({ message, onClose }: { message: string; onClose: () => void }) { useEffect(() => { const timer = setTimeout(onClose, 3000); return () => clearTimeout(timer); }, [onClose]); return (
{message}
); } // ============ Main Page ============ export default function AdminPaymentMethods() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { capabilities } = usePlatform(); // Use native Telegram back button in Mini App useBackButton(() => navigate('/admin')); const [methods, setMethods] = useState([]); const [orderChanged, setOrderChanged] = useState(false); const [toastMessage, setToastMessage] = useState(null); // Fetch payment methods const { data: fetchedMethods, isLoading } = useQuery({ queryKey: ['admin-payment-methods'], queryFn: adminPaymentMethodsApi.getAll, }); // Sync fetched data to local state useEffect(() => { if (fetchedMethods && !orderChanged) { setMethods(fetchedMethods); } }, [fetchedMethods, orderChanged]); // Save order mutation const saveOrderMutation = useMutation({ mutationFn: (methodIds: string[]) => adminPaymentMethodsApi.updateOrder(methodIds), onSuccess: () => { setOrderChanged(false); queryClient.invalidateQueries({ queryKey: ['admin-payment-methods'] }); setToastMessage(t('admin.paymentMethods.orderSaved')); }, onError: () => { setToastMessage(t('common.error')); }, }); // DnD sensors - PointerSensor handles both mouse and touch const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), ); // Telegram swipe behavior for drag-and-drop const { onDragStart: onTelegramDragStart, onDragEnd: onTelegramDragEnd, onDragCancel: onTelegramDragCancel, } = useTelegramDnd(); const handleDragStart = useCallback( (_event: DragStartEvent) => { onTelegramDragStart(); }, [onTelegramDragStart], ); const handleDragEnd = useCallback( (event: DragEndEvent) => { onTelegramDragEnd(); const { active, over } = event; if (over && active.id !== over.id) { setMethods((prev) => { const oldIndex = prev.findIndex((m) => m.method_id === active.id); const newIndex = prev.findIndex((m) => m.method_id === over.id); if (oldIndex === -1 || newIndex === -1) return prev; return arrayMove(prev, oldIndex, newIndex); }); setOrderChanged(true); } }, [onTelegramDragEnd], ); const handleDragCancel = useCallback(() => { onTelegramDragCancel(); }, [onTelegramDragCancel]); const handleSaveOrder = () => { saveOrderMutation.mutate(methods.map((m) => m.method_id)); }; const handleCloseToast = useCallback(() => setToastMessage(null), []); return (
{/* Header */}
{/* Show back button only on web, not in Telegram Mini App */} {!capabilities.hasBackButton && ( )}

{t('admin.paymentMethods.title')}

{t('admin.paymentMethods.description')}

{orderChanged && ( )}
{/* Drag hint */}
{t('admin.paymentMethods.dragHint')}
{/* Methods list */}
{isLoading ? (
) : methods.length > 0 ? ( m.method_id)} strategy={verticalListSortingStrategy} >
{methods.map((config) => ( navigate(`/admin/payment-methods/${config.method_id}/edit`)} /> ))}
) : (
{'\uD83D\uDCB3'}
{t('admin.paymentMethods.noMethods')}
)}
{/* Toast */} {toastMessage && }
); }