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') || 'Invalid file type. Use JPEG, PNG, GIF, or WebP.',
})
return
}
// Validate file size (10MB)
if (file.size > 10 * 1024 * 1024) {
setAttachment({
file,
preview: '',
uploading: false,
error: t('support.fileTooLarge') || 'File is too large. Maximum size is 10MB.',
})
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 (error) {
setAttachment({
file,
preview,
uploading: false,
error: t('support.uploadFailed') || 'Failed to upload image',
})
}
}
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')}
)}
)
}