import { useState, useRef, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { motion } from 'framer-motion';
import { ticketsApi } from '../api/tickets';
import { infoApi } from '../api/info';
import { useAuthStore } from '../store/auth';
import { logger } from '../utils/logger';
import { checkRateLimit, getRateLimitResetTime, RATE_LIMIT_KEYS } from '../utils/rateLimit';
import type { TicketDetail, TicketMessage } from '../types';
import { Card } from '@/components/data-display/Card';
import { Button } from '@/components/primitives/Button';
import { staggerContainer, staggerItem } from '@/components/motion/transitions';
import { usePlatform } from '@/platform';
const log = logger.createLogger('Support');
const PlusIcon = () => (
);
const SendIcon = () => (
);
const ImageIcon = () => (
);
const CloseIcon = () => (
);
// Media attachment state
interface MediaAttachment {
file: File;
preview: string;
uploading: boolean;
fileId?: string;
error?: string;
}
// Message media display component
function MessageMedia({ message, t }: { message: TicketMessage; t: (key: string) => string }) {
const [imageLoaded, setImageLoaded] = useState(false);
const [imageError, setImageError] = useState(false);
const [showFullImage, setShowFullImage] = useState(false);
if (!message.has_media || !message.media_file_id) {
return null;
}
const mediaUrl = ticketsApi.getMediaUrl(message.media_file_id);
if (message.media_type === 'photo') {
return (
<>
{!imageLoaded && !imageError && (
)}
{imageError ? (
{t('support.imageLoadFailed')}
) : (

setImageLoaded(true)}
onError={() => setImageError(true)}
onClick={() => setShowFullImage(true)}
/>
)}
{message.media_caption && (
{message.media_caption}
)}
{/* Full image modal */}
{showFullImage && (
setShowFullImage(false)}
>
)}
>
);
}
// For documents/videos - show download link
return (
);
}
export default function Support() {
log.debug('Component loaded');
const { t } = useTranslation();
const isAdmin = useAuthStore((state) => state.isAdmin);
const queryClient = useQueryClient();
const { openTelegramLink, openLink } = usePlatform();
const [selectedTicket, setSelectedTicket] = useState(null);
const [showCreateForm, setShowCreateForm] = useState(false);
const [newTitle, setNewTitle] = useState('');
const [newMessage, setNewMessage] = useState('');
const [replyMessage, setReplyMessage] = useState('');
const [rateLimitError, setRateLimitError] = useState(null);
// Media attachment states
const [createAttachment, setCreateAttachment] = useState(null);
const [replyAttachment, setReplyAttachment] = useState(null);
const createFileInputRef = useRef(null);
const replyFileInputRef = useRef(null);
const createPreviewRef = useRef(null);
const replyPreviewRef = useRef(null);
// Revoke blob URLs on unmount to prevent memory leaks
useEffect(() => {
const createRef = createPreviewRef;
const replyRef = replyPreviewRef;
return () => {
if (createRef.current) URL.revokeObjectURL(createRef.current);
if (replyRef.current) URL.revokeObjectURL(replyRef.current);
};
}, []);
const clearCreateAttachment = () => {
if (createPreviewRef.current) {
URL.revokeObjectURL(createPreviewRef.current);
createPreviewRef.current = null;
}
clearCreateAttachment();
};
const clearReplyAttachment = () => {
if (replyPreviewRef.current) {
URL.revokeObjectURL(replyPreviewRef.current);
replyPreviewRef.current = null;
}
clearReplyAttachment();
};
// Get support configuration
const { data: supportConfig, isLoading: configLoading } = useQuery({
queryKey: ['support-config'],
queryFn: infoApi.getSupportConfig,
});
const { data: tickets, isLoading } = useQuery({
queryKey: ['tickets'],
queryFn: () => ticketsApi.getTickets({ per_page: 20 }),
enabled: supportConfig?.tickets_enabled === true,
});
const { data: ticketDetail, isLoading: detailLoading } = useQuery({
queryKey: ['ticket', selectedTicket?.id],
queryFn: () => ticketsApi.getTicket(selectedTicket!.id),
enabled: !!selectedTicket,
});
// Handle file selection
const handleFileSelect = async (
file: File,
setAttachment: (a: MediaAttachment | null) => void,
) => {
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
setAttachment({
file,
preview: '',
uploading: false,
error: t('support.invalidFileType'),
});
return;
}
// Validate file size (10MB)
if (file.size > 10 * 1024 * 1024) {
setAttachment({
file,
preview: '',
uploading: false,
error: t('support.fileTooLarge'),
});
return;
}
// Revoke old blob URL before creating new one
const previewRef = setAttachment === setCreateAttachment ? createPreviewRef : replyPreviewRef;
if (previewRef.current) URL.revokeObjectURL(previewRef.current);
const preview = URL.createObjectURL(file);
previewRef.current = preview;
setAttachment({ file, preview, uploading: true });
try {
const result = await ticketsApi.uploadMedia(file, 'photo');
setAttachment({
file,
preview,
uploading: false,
fileId: result.file_id,
});
} catch {
setAttachment({
file,
preview,
uploading: false,
error: t('support.uploadFailed'),
});
}
};
const createMutation = useMutation({
mutationFn: async () => {
const media = createAttachment?.fileId
? {
media_type: 'photo',
media_file_id: createAttachment.fileId,
}
: undefined;
return ticketsApi.createTicket(newTitle, newMessage, media);
},
onSuccess: (ticket) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] });
setShowCreateForm(false);
setNewTitle('');
setNewMessage('');
clearCreateAttachment();
setSelectedTicket(ticket);
},
});
const replyMutation = useMutation({
mutationFn: async () => {
const media = replyAttachment?.fileId
? {
media_type: 'photo',
media_file_id: replyAttachment.fileId,
}
: undefined;
return ticketsApi.addMessage(selectedTicket!.id, replyMessage, media);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['ticket', selectedTicket?.id] });
setReplyMessage('');
clearReplyAttachment();
},
});
const getStatusBadge = (status: string) => {
switch (status) {
case 'open':
return 'badge-info';
case 'answered':
return 'badge-success';
case 'pending':
return 'badge-warning';
case 'closed':
return 'badge-neutral';
default:
return 'badge-neutral';
}
};
const getStatusLabel = (status: string) => {
return t(`support.status.${status}`) || status;
};
// Show loading while checking configuration
if (configLoading) {
return (
);
}
// If tickets are disabled, show redirect message
if (supportConfig && !supportConfig.tickets_enabled) {
log.debug('Tickets disabled, config:', supportConfig);
const getSupportMessage = () => {
log.debug('Getting support message for type:', supportConfig.support_type);
if (supportConfig.support_type === 'profile') {
const supportUsername = supportConfig.support_username || '@support';
log.debug('Opening profile:', supportUsername);
return {
title: isAdmin ? t('support.ticketsDisabled') : t('support.title'),
message: t('support.contactSupport', { username: supportUsername }),
buttonText: t('support.contactUs'),
buttonAction: () => {
log.debug('Button clicked, opening:', supportUsername);
// Extract username without @
const username = supportUsername.startsWith('@')
? supportUsername.slice(1)
: supportUsername;
const webUrl = `https://t.me/${username}`;
log.debug('Web URL:', webUrl);
// Use platform's openTelegramLink
openTelegramLink(webUrl);
},
};
}
if (supportConfig.support_type === 'url' && supportConfig.support_url) {
return {
title: isAdmin ? t('support.ticketsDisabled') : t('support.title'),
message: t('support.useExternalLink'),
buttonText: t('support.openSupport'),
buttonAction: () => {
openLink(supportConfig.support_url!, { tryInstantView: false });
},
};
}
// Fallback: contact support (should not normally happen if config is correct)
const supportUsername = supportConfig.support_username || '@support';
log.debug('Fallback: Opening profile:', supportUsername);
return {
title: isAdmin ? t('support.ticketsDisabled') : t('support.title'),
message: t('support.contactSupport', { username: supportUsername }),
buttonText: t('support.contactUs'),
buttonAction: () => {
log.debug('Fallback button clicked, opening:', supportUsername);
// Extract username without @
const username = supportUsername.startsWith('@')
? supportUsername.slice(1)
: supportUsername;
const webUrl = `https://t.me/${username}`;
log.debug('Fallback opening URL:', webUrl);
// Use platform's openTelegramLink
openTelegramLink(webUrl);
},
};
};
const supportMessage = getSupportMessage();
return (
{supportMessage.title}
{supportMessage.message}
);
}
// Attachment preview component
const AttachmentPreview = ({
attachment,
onRemove,
}: {
attachment: MediaAttachment;
onRemove: () => void;
}) => (
{attachment.preview && (

)}
{attachment.uploading && (
)}
{attachment.error &&
{attachment.error}
}
);
return (
{t('support.title')}
{/* Tickets List */}
{t('support.yourTickets')}
{isLoading ? (
) : tickets?.items && tickets.items.length > 0 ? (
{tickets.items.map((ticket) => (
))}
) : (
)}
{/* Ticket Detail / Create Form */}
{showCreateForm ? (
{t('support.createTicket')}
) : selectedTicket ? (
{ticketDetail?.title || selectedTicket.title}
{getStatusLabel(ticketDetail?.status || selectedTicket.status)}
{t('support.created')}{' '}
{new Date(selectedTicket.created_at).toLocaleDateString()}
{/* Messages */}
{detailLoading ? (
) : ticketDetail?.messages ? (
{ticketDetail.messages.map((msg) => (
{msg.is_from_admin ? t('support.supportTeam') : t('support.you')}
{new Date(msg.created_at).toLocaleString()}
{msg.message_text}
{/* Display media if present */}
))}
) : null}
{/* Reply Form */}
{ticketDetail?.status !== 'closed' && !ticketDetail?.is_reply_blocked && (
)}
{ticketDetail?.is_reply_blocked && (
{t('support.repliesDisabled')}
)}
) : (
{t('support.selectTicket')}
)}
);
}