import { useState, useRef } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { ticketsApi } from '../api/tickets';
import { infoApi } from '../api/info';
import { logger } from '../utils/logger';
import { checkRateLimit, getRateLimitResetTime, RATE_LIMIT_KEYS } from '../utils/rateLimit';
import type { TicketDetail, TicketMessage } from '../types';
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 queryClient = useQueryClient();
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);
// 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;
}
// Create preview
const preview = URL.createObjectURL(file);
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('');
setCreateAttachment(null);
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('');
setReplyAttachment(null);
},
});
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: t('support.ticketsDisabled'),
message: t('support.contactSupport', { username: supportUsername }),
buttonText: t('support.contactUs'),
buttonAction: () => {
log.debug('Button clicked, opening:', supportUsername);
const webApp = window.Telegram?.WebApp;
// Extract username without @
const username = supportUsername.startsWith('@')
? supportUsername.slice(1)
: supportUsername;
const webUrl = `https://t.me/${username}`;
log.debug('Web URL:', webUrl, 'WebApp methods:', {
openTelegramLink: !!webApp?.openTelegramLink,
openLink: !!webApp?.openLink,
});
// Try openTelegramLink first (for tg:// links)
if (webApp?.openTelegramLink) {
log.debug('Using openTelegramLink with web URL');
try {
webApp.openTelegramLink(webUrl);
return;
} catch (e) {
log.error('openTelegramLink failed:', e);
}
}
// Fallback to openLink
if (webApp?.openLink) {
log.debug('Using openLink');
try {
webApp.openLink(webUrl, { try_browser: true });
return;
} catch (e) {
log.error('openLink failed:', e);
}
}
// Last resort - window.open
log.debug('Using window.open');
window.open(webUrl, '_blank');
},
};
}
if (supportConfig.support_type === 'url' && supportConfig.support_url) {
return {
title: t('support.ticketsDisabled'),
message: t('support.useExternalLink'),
buttonText: t('support.openSupport'),
buttonAction: () => {
const webApp = window.Telegram?.WebApp;
if (webApp?.openLink) {
webApp.openLink(supportConfig.support_url!, { try_browser: true });
} else {
window.open(supportConfig.support_url!, '_blank');
}
},
};
}
// 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: t('support.ticketsDisabled'),
message: t('support.contactSupport', { username: supportUsername }),
buttonText: t('support.contactUs'),
buttonAction: () => {
log.debug('Fallback button clicked, opening:', supportUsername);
const webApp = window.Telegram?.WebApp;
// Extract username without @
const username = supportUsername.startsWith('@')
? supportUsername.slice(1)
: supportUsername;
const webUrl = `https://t.me/${username}`;
log.debug('Fallback opening URL:', webUrl);
if (webApp?.openTelegramLink) {
log.debug('Fallback using openTelegramLink');
webApp.openTelegramLink(webUrl);
} else if (webApp?.openLink) {
log.debug('Fallback using openLink');
webApp.openLink(webUrl, { try_browser: true });
} else {
log.debug('Fallback using window.open');
window.open(webUrl, '_blank');
}
},
};
};
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')}
)}
);
}