diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index a638205..2261c1f 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -19,6 +19,7 @@ import { useAdminSystem, useAdminUploads, } from "~/shared/services/admin"; +import type { AdminUploadsContent, AdminUploadsContentFlags } from "~/shared/services/admin"; import { clearAdminAuth, getAdminAuthSnapshot } from "~/shared/libs/admin-auth"; const numberFormatter = new Intl.NumberFormat("ru-RU"); @@ -151,6 +152,20 @@ type SyncLimitsFormValues = { disk_low_watermark_pct: number; }; +type UploadCategory = "issues" | "processing" | "ready" | "unindexed"; +type UploadFilter = "all" | UploadCategory; + +type UploadDecoration = { + item: AdminUploadsContent; + categories: Set; + searchText: string; + hasIssue: boolean; + isReady: boolean; + isProcessing: boolean; + isUnindexed: boolean; + flags: AdminUploadsContentFlags | null; +}; + const initialAuthState = getAdminAuthSnapshot().token ? "checking" : "unauthorized"; export const AdminPage = () => { @@ -159,6 +174,8 @@ export const AdminPage = () => { const [activeSection, setActiveSection] = useState<(typeof ADMIN_SECTIONS)[number]["id"]>("overview"); const [token, setToken] = useState(""); const [flash, setFlash] = useState(null); + const [uploadsFilter, setUploadsFilter] = useState("all"); + const [uploadsSearch, setUploadsSearch] = useState(""); useEffect(() => { if (!flash) { @@ -194,15 +211,28 @@ export const AdminPage = () => { }); const isDataEnabled = authState === "authorized"; + const normalizedUploadsSearch = uploadsSearch.trim(); + const isUploadsFilterActive = uploadsFilter !== "all"; + const hasUploadsSearch = normalizedUploadsSearch.length > 0; + const uploadsScanLimit = isUploadsFilterActive || hasUploadsSearch ? 200 : 100; const storageQuery = useAdminStorage({ enabled: isDataEnabled, refetchInterval: 60_000, }); - const uploadsQuery = useAdminUploads({ - enabled: isDataEnabled, - refetchInterval: 30_000, - }); + const uploadsQuery = useAdminUploads( + { + filter: isUploadsFilterActive ? uploadsFilter : undefined, + search: hasUploadsSearch ? normalizedUploadsSearch : undefined, + limit: 40, + scan: uploadsScanLimit, + }, + { + enabled: isDataEnabled, + refetchInterval: 30_000, + keepPreviousData: true, + }, + ); const systemQuery = useAdminSystem({ enabled: isDataEnabled, refetchInterval: 60_000, @@ -509,8 +539,8 @@ export const AdminPage = () => {

IPFS

- {(ipfs.identity as Record)?.ID ?? "—"} - {(ipfs.identity as Record)?.AgentVersion ?? "—"} + {formatUnknown((ipfs.identity as Record)?.ID)} + {formatUnknown((ipfs.identity as Record)?.AgentVersion)} {numberFormatter.format(Number((ipfs.bitswap as Record)?.Peers ?? 0))} {formatBytes(Number((ipfs.repo as Record)?.RepoSize ?? 0))} {formatBytes(Number((ipfs.repo as Record)?.StorageMax ?? 0))} @@ -615,17 +645,437 @@ export const AdminPage = () => { }; const renderUploads = () => { - if (!uploadsQuery.data) { - return uploadsQuery.isLoading ? ( -
Загрузка…
- ) : null; + if (uploadsQuery.isLoading && !uploadsQuery.data) { + return
Загрузка…
; } - const { states, total, recent } = uploadsQuery.data; + if (!uploadsQuery.data) { + return null; + } + + const { states, total, recent, contents = [], matching_total: matchingTotalRaw, category_totals: categoryTotalsRaw } = uploadsQuery.data; + + const categoryTotals = categoryTotalsRaw ?? {}; + const categoryCounts: Record = { + issues: categoryTotals.issues ?? 0, + processing: categoryTotals.processing ?? 0, + ready: categoryTotals.ready ?? 0, + unindexed: categoryTotals.unindexed ?? 0, + }; + + const computeFlags = (item: AdminUploadsContent): AdminUploadsContentFlags => { + const normalize = (value: string | null | undefined) => (value ?? '').toLowerCase(); + const uploadState = normalize(item.status.upload_state); + const conversionState = normalize(item.status.conversion_state); + const ipfsState = normalize(item.status.ipfs_state); + const pinState = normalize(item.ipfs?.pin_state); + const derivativeStates = item.derivatives.map((derivative) => normalize(derivative.status)); + const statusValues = [uploadState, conversionState, ipfsState, pinState, ...derivativeStates]; + + const hasIssue = + statusValues.some((value) => value.includes('fail') || value.includes('error') || value.includes('timeout')) || + item.upload_history.some((event) => Boolean(event.error)) || + item.derivatives.some((derivative) => Boolean(derivative.error)) || + Boolean(item.ipfs?.pin_error); + + const isOnchainIndexed = item.status.onchain?.indexed ?? false; + const isUnindexed = !isOnchainIndexed; + + const conversionDone = + conversionState.includes('converted') || + conversionState.includes('ready') || + derivativeStates.some((state) => state.includes('ready') || state.includes('converted') || state.includes('complete')); + + const ipfsDone = + ipfsState.includes('pinned') || + ipfsState.includes('ready') || + pinState.includes('pinned') || + pinState.includes('ready'); + + const isReady = !hasIssue && conversionDone && ipfsDone && isOnchainIndexed; + + const hasProcessingKeywords = + uploadState.includes('pending') || + uploadState.includes('process') || + uploadState.includes('queue') || + uploadState.includes('upload') || + conversionState.includes('pending') || + conversionState.includes('process') || + conversionState.includes('queue') || + conversionState.includes('convert') || + ipfsState.includes('pin') || + ipfsState.includes('sync') || + pinState.includes('pin') || + pinState.includes('sync') || + derivativeStates.some((state) => state.includes('pending') || state.includes('process') || state.includes('queue')); + + const isProcessingCandidate = !isReady && !hasIssue && hasProcessingKeywords; + const isProcessing = isProcessingCandidate || (!isReady && !hasIssue && !isProcessingCandidate); + + return { + issues: hasIssue, + processing: isProcessing, + ready: isReady, + unindexed: isUnindexed, + }; + }; + + const classifyUpload = (item: AdminUploadsContent): UploadDecoration => { + const flags = item.flags ?? computeFlags(item); + const categories = new Set(); + if (flags.issues) { + categories.add('issues'); + } + if (flags.processing) { + categories.add('processing'); + } + if (flags.ready) { + categories.add('ready'); + } + if (flags.unindexed) { + categories.add('unindexed'); + } + if (categories.size === 0) { + if (!(item.status.onchain?.indexed ?? false)) { + categories.add('unindexed'); + } + categories.add('processing'); + } + + const searchParts: Array = [ + item.title, + item.description, + item.encrypted_cid, + item.metadata_cid, + item.content_hash, + item.status.onchain?.item_address, + item.stored?.owner_address, + ]; + if (item.stored?.user) { + const user = item.stored.user; + searchParts.push(user.id, user.telegram_id, user.username, user.first_name, user.last_name); + } + const searchText = searchParts + .filter((value) => value !== null && value !== undefined && `${value}`.length > 0) + .map((value) => `${value}`.toLowerCase()) + .join(' '); + + return { + item, + categories, + searchText, + hasIssue: Boolean(flags.issues), + isReady: Boolean(flags.ready), + isProcessing: Boolean(flags.processing), + isUnindexed: Boolean(flags.unindexed), + flags: item.flags ?? flags, + }; + }; + + const decoratedContents = contents.map((item) => classifyUpload(item)); + + const searchNeedle = normalizedUploadsSearch.toLowerCase(); + const filteredEntries = decoratedContents.filter((entry) => { + if (uploadsFilter !== 'all' && !entry.categories.has(uploadsFilter)) { + return false; + } + if (searchNeedle && !entry.searchText.includes(searchNeedle)) { + return false; + } + return true; + }); + + const filteredContents = filteredEntries.map((entry) => entry.item); + const totalMatches = typeof matchingTotalRaw === 'number' ? matchingTotalRaw : filteredEntries.length; + const filtersActive = isUploadsFilterActive || hasUploadsSearch; + const noMatches = totalMatches === 0; + const issueEntries = filteredEntries.filter((entry) => entry.categories.has('issues')); + const filterOptions: Array<{ id: UploadFilter; label: string; count: number }> = [ + { id: 'all', label: 'Все', count: totalMatches }, + { id: 'issues', label: 'Ошибки', count: categoryCounts.issues }, + { id: 'processing', label: 'В обработке', count: categoryCounts.processing }, + { id: 'ready', label: 'Готово', count: categoryCounts.ready }, + { id: 'unindexed', label: 'Без on-chain', count: categoryCounts.unindexed }, + ]; + const handleResetFilters = () => { + setUploadsFilter('all'); + setUploadsSearch(''); + }; + const getProblemMessages = (item: AdminUploadsContent): string[] => { + const messages: string[] = []; + for (let index = item.upload_history.length - 1; index >= 0; index -= 1) { + const event = item.upload_history[index]; + if (event.error) { + messages.push(`Загрузка: ${event.error}`); + break; + } + } + const derivativeError = item.derivatives.find((derivative) => Boolean(derivative.error)); + if (derivativeError?.error) { + messages.push(`Дериватив ${derivativeError.kind}: ${derivativeError.error}`); + } + if (item.ipfs?.pin_error) { + messages.push(`IPFS: ${item.ipfs.pin_error}`); + } + const conversionState = item.status.conversion_state; + if (!messages.length && conversionState && conversionState.toLowerCase().includes('fail')) { + messages.push(`Конверсия: ${conversionState}`); + } + const uploadState = item.status.upload_state; + if (!messages.length && uploadState && uploadState.toLowerCase().includes('fail')) { + messages.push(`Загрузка: ${uploadState}`); + } + if (!messages.length) { + messages.push('Причина не указана. Проверьте логи.'); + } + return messages; + }; + + const toneByState = (state: string | null | undefined): BadgeTone => { + const normalized = (state ?? '').toLowerCase(); + if (!normalized) { + return 'neutral'; + } + if (normalized.includes('fail') || normalized.includes('error') || normalized.includes('timeout')) { + return 'danger'; + } + if ( + normalized.includes('process') || + normalized.includes('pending') || + normalized.includes('pin') || + normalized.includes('upload') || + normalized.includes('queue') + ) { + return 'warn'; + } + if ( + normalized.includes('ready') || + normalized.includes('pinned') || + normalized.includes('converted') || + normalized.includes('indexed') + ) { + return 'success'; + } + return 'neutral'; + }; + + const renderContentCard = (item: AdminUploadsContent) => { + const statusBadges = [ + { label: 'Загрузка', value: item.status.upload_state }, + { label: 'Конверсия', value: item.status.conversion_state }, + { label: 'IPFS', value: item.status.ipfs_state }, + { + label: 'On-chain', + value: item.status.onchain?.indexed ? `Индекс ${item.status.onchain.onchain_index ?? '≥8'}` : 'Локально', + toneOverride: item.status.onchain?.indexed ? ('success' as BadgeTone) : ('neutral' as BadgeTone), + }, + ]; + + const actionButtons: Array<{ label: string; href: string; download?: boolean }> = []; + if (item.links.web_view) { + actionButtons.push({ label: 'Открыть', href: item.links.web_view }); + } + if (item.links.api_view) { + actionButtons.push({ label: 'API', href: item.links.api_view }); + } + if (item.links.download_primary) { + actionButtons.push({ label: 'Скачать', href: item.links.download_primary, download: true }); + } + + const derivativeDownloads = item.links.download_derivatives.filter( + (entry, index, arr) => entry.url && arr.findIndex((comp) => comp.url === entry.url) === index, + ); + + return ( +
+
+
+
+ {item.title || item.encrypted_cid.slice(0, 12)} +
+
+ {item.content_type} · {formatBytes(item.size.encrypted ?? 0)} enc · {formatBytes(item.size.plain ?? 0)} plain +
+
+ {statusBadges.map((badge) => ( + + {badge.label}: {badge.value ?? '—'} + + ))} +
+
+ Обновлено {formatDate(item.updated_at)} · Создано {formatDate(item.created_at)} +
+
+ {actionButtons.length > 0 ? ( +
+ {actionButtons.map((button) => ( + + {button.label} + + ))} +
+ ) : null} +
+ +
+
+

Идентификаторы

+
+ {item.encrypted_cid} + {item.metadata_cid ?? '—'} + {item.content_hash ?? '—'} + + {item.status.onchain?.indexed + ? `#${item.status.onchain.onchain_index ?? '≥8'} · лицензий ${item.status.onchain.license_count ?? 0}` + : 'Не опубликован'} + + + {item.status.onchain?.item_address ?? '—'} + +
+
+ +
+

История загрузок

+ {item.upload_history.length === 0 ? ( +

Нет событий.

+ ) : ( +
    + {item.upload_history.map((event, index) => ( +
  • + + {event.state} + +
    +
    {formatDate(event.at)}
    + {event.filename ?
    {event.filename}
    : null} + {event.error ?
    {event.error}
    : null} +
    +
  • + ))} +
+ )} +
+
+ +
+
+

Деривативы

+ {item.derivatives.length === 0 ? ( +

Деривативы отсутствуют.

+ ) : ( +
+ {item.derivatives.map((derivative) => ( +
+
+
{derivative.kind}
+ {derivative.status} +
+
+ Размер: {derivative.size_bytes ? formatBytes(derivative.size_bytes) : '—'} + Обновлено: {formatDate(derivative.updated_at)} +
+ {derivative.error ?
{derivative.error}
: null} + {derivative.download_url ? ( + + Скачать + + ) : null} +
+ ))} +
+ )} +
+ +
+
+

IPFS

+ {item.ipfs ? ( +
+
+ {item.ipfs.pin_state ?? '—'} +
+ {item.ipfs.pin_error ?
{item.ipfs.pin_error}
: null} +
+ {formatBytes(item.ipfs.bytes_fetched ?? 0)} / {formatBytes(item.ipfs.bytes_total ?? 0)} +
+
{formatDate(item.ipfs.updated_at)}
+
+ ) : ( +

Нет информации.

+ )} +
+ +
+

Хранение

+ {item.stored ? ( +
+
ID: {item.stored.stored_id ?? '—'}
+
Владелец: {item.stored.owner_address ?? '—'}
+
Тип: {item.stored.type ?? '—'}
+ {item.stored.user ? ( +
+ Пользователь #{item.stored.user.id} · TG {item.stored.user.telegram_id} +
+ ) : null} + {item.stored.download_url ? ( + + Скачать оригинал + + ) : null} +
+ ) : ( +

В хранилище не найден.

+ )} +
+ + {derivativeDownloads.length > 0 ? ( +
+

Прямые ссылки

+ +
+ ) : null} +
+
+
+ ); + }; + return (
{ } > +
+
+ {filterOptions.map((option) => { + const isActive = option.id === uploadsFilter; + return ( + + ); + })} + {filtersActive ? ( + + ) : null} +
+
+ setUploadsSearch(event.target.value)} + placeholder="Поиск по названию, CID или адресу" + className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-slate-100 shadow-inner shadow-black/40 focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-500/40" + /> + {uploadsSearch ? ( + + ) : null} +
+
+ +
+ Найдено {numberFormatter.format(filteredContents.length)} из {numberFormatter.format(totalMatches)} +
+
{Object.entries(states).map(([state, count]) => ( ))}
-
+ +
+ + + + +
+ + {issueEntries.length > 0 ? ( +
+
+
+ Проблемные загрузки · {numberFormatter.format(issueEntries.length)} +
+ +
+
+ {issueEntries.slice(0, 5).map(({ item }) => { + const problems = getProblemMessages(item); + return ( +
+
+ {item.title || `${item.encrypted_cid.slice(0, 8)}…`} + {formatDate(item.updated_at)} +
+
CID: {item.encrypted_cid}
+
    + {problems.map((problem, index) => ( +
  • {problem}
  • + ))} +
+
+ ); + })} + {issueEntries.length > 5 ? ( + + ) : null} +
+
+ ) : null} + +
+ {noMatches ? ( +

+ {filtersActive ? 'Нет контента по выбранным фильтрам.' : 'Контент ещё не загружался.'} +

+ ) : ( + filteredContents.map(renderContentCard) + )} +
+ +
@@ -657,9 +1227,9 @@ export const AdminPage = () => { {recent.map((item) => ( - - - + + + ))} @@ -669,7 +1239,6 @@ export const AdminPage = () => { ); }; - const renderSystem = () => { if (!systemQuery.data) { return systemQuery.isLoading ? ( @@ -875,7 +1444,7 @@ export const AdminPage = () => {
Загрузка…
) : null; } - const { ipfs, pin_counts, derivatives, convert_backlog, limits } = statusQuery.data; + const { ipfs, pin_counts, derivatives, convert_backlog } = statusQuery.data; return (
diff --git a/src/pages/view-content/index.tsx b/src/pages/view-content/index.tsx index e1a42ec..9936d3d 100644 --- a/src/pages/view-content/index.tsx +++ b/src/pages/view-content/index.tsx @@ -10,6 +10,7 @@ import { AudioPlayer } from '~/shared/ui/audio-player'; import { useAuth } from '~/shared/services/auth'; import { CongratsModal } from './components/congrats-modal'; import { ErrorModal } from './components/error-modal'; +import { resolveStartPayload } from '~/shared/utils/start-payload'; type InvoiceStatus = 'paid' | 'failed' | 'cancelled' | 'pending'; @@ -21,9 +22,10 @@ interface InvoiceEvent { export const ViewContentPage = () => { const WebApp = useWebApp(); + const { contentId } = resolveStartPayload(); const { data: content, refetch: refetchContent } = useViewContent( - WebApp.initDataUnsafe?.start_param + contentId ); const { mutateAsync: purchaseContent } = usePurchaseContent(); @@ -34,7 +36,32 @@ export const ViewContentPage = () => { const [isCongratsModal, setIsCongratsModal] = useState(false); const [isErrorModal, setIsErrorModal] = useState(false); + const statusState = content?.data?.status?.state ?? "uploaded"; + const conversionState = content?.data?.conversion?.state; + const uploadState = content?.data?.upload?.state; + const statusLabel = useMemo(() => { + switch (statusState) { + case "ready": + return "Готово"; + case "processing": + return "Обрабатывается"; + case "failed": + return "Ошибка"; + default: + return "Загружено"; + } + }, [statusState]); + + const mediaUrl = content?.data?.display_options?.content_url ?? null; + const isAudio = Boolean(content?.data?.content_type?.startsWith('audio')); + const isReady = Boolean(mediaUrl); + const metadataName = content?.data?.display_options?.metadata?.name; + const handleBuyContentTON = useCallback(async () => { + if (!contentId) { + console.error('No content identifier available for purchase'); + return; + } try { // Если не подключен, начинаем процесс подключения через auth if (!tonConnectUI.connected) { @@ -79,7 +106,7 @@ export const ViewContentPage = () => { // Теперь продолжаем с покупкой console.log('DEBUG: Proceeding with purchase'); const contentResponse = await purchaseContent({ - content_address: WebApp.initDataUnsafe?.start_param, + content_address: contentId, license_type: 'resale', }); @@ -106,7 +133,7 @@ export const ViewContentPage = () => { setIsErrorModal(true); console.error('Error handling Ton Connect subscription:', error); } - }, [auth, purchaseContent, refetchContent, tonConnectUI, WebApp.initDataUnsafe?.start_param]); + }, [auth, contentId, purchaseContent, refetchContent, tonConnectUI]); const handleBuyContentStars = useCallback(async () => { try { @@ -157,12 +184,16 @@ export const ViewContentPage = () => { }, [content]); useEffect(() => { + if (!contentId) { + return () => undefined; + } + const interval = setInterval(() => { void refetchContent(); }, 5000); return () => clearInterval(interval); - }, []); + }, [contentId, refetchContent]); const handleConfirmCongrats = () => { setIsCongratsModal(!isCongratsModal); @@ -190,7 +221,7 @@ export const ViewContentPage = () => {
{isCongratsModal && } {isErrorModal && } - {content?.data?.content_type.startsWith('audio') && + {isAudio && content?.data?.display_options?.metadata?.image && (
{
)} - {content?.data?.content_type.startsWith('audio') ? ( - - ) : ( - + ) : ( + + }} + url={mediaUrl} + /> + ) + ) : ( +
+

{statusLabel}. Конвертация может занять несколько минут.

+ {conversionState && ( +

+ Статус конвертера: {conversionState} +

+ )} + {uploadState && ( +

+ Загрузка: {uploadState} +

+ )} +
)}
-

- {content?.data?.display_options?.metadata?.name} -

+

{metadataName}

+ {statusLabel} {/*

Russian

*/} {/*

2022

*/}

diff --git a/src/shared/services/admin/index.ts b/src/shared/services/admin/index.ts index 40080e5..f1d8afd 100644 --- a/src/shared/services/admin/index.ts +++ b/src/shared/services/admin/index.ts @@ -78,6 +78,106 @@ export type AdminStorageResponse = { }; }; +export type AdminUploadsContentDerivative = { + kind: string; + status: string; + size_bytes: number | null; + error: string | null; + created_at: string | null; + updated_at: string | null; + attempts: number; + download_url: string | null; +}; + +export type AdminUploadsContentUploadHistoryItem = { + state: string; + at: string | null; + error: string | null; + filename: string | null; +}; + +export type AdminUploadsContentIpfs = { + pin_state: string | null; + pin_error: string | null; + bytes_total: number | null; + bytes_fetched: number | null; + pinned_at: string | null; + updated_at: string | null; +} | null; + +export type AdminUploadsContentStored = { + stored_id: number | null; + type: string | null; + owner_address: string | null; + user_id: number | null; + status: string | null; + content_url: string | null; + download_url: string | null; + created: string | null; + updated: string | null; + user?: { + id: number; + telegram_id: number; + username: string | null; + first_name: string | null; + last_name: string | null; + }; +} | null; + +export type AdminUploadsContentLinks = { + web_view: string | null; + start_app: string | null; + api_view: string | null; + download_primary: string | null; + download_derivatives: Array<{ + kind: string; + url: string; + size_bytes: number | null; + }>; +}; + +export type AdminUploadsContentFlags = { + issues: boolean; + processing: boolean; + ready: boolean; + unindexed: boolean; +}; + +export type AdminUploadsContentStatus = { + upload_state: string | null; + conversion_state: string | null; + ipfs_state: string | null; + onchain: { + indexed: boolean; + onchain_index: number | null; + item_address: string | null; + license_count?: number; + } | null; +}; + +export type AdminUploadsContent = { + encrypted_cid: string; + metadata_cid: string | null; + content_hash: string | null; + title: string; + description: string | null; + content_type: string; + size: { + encrypted: number | null; + plain: number | null; + }; + created_at: string | null; + updated_at: string | null; + status: AdminUploadsContentStatus; + upload_history: AdminUploadsContentUploadHistoryItem[]; + derivative_summary: Record; + derivatives: AdminUploadsContentDerivative[]; + ipfs: AdminUploadsContentIpfs; + stored: AdminUploadsContentStored; + links: AdminUploadsContentLinks; + flags?: AdminUploadsContentFlags; +}; + export type AdminUploadsResponse = { total: number; states: Record; @@ -91,8 +191,25 @@ export type AdminUploadsResponse = { updated_at: string; created_at: string; }>; + contents: AdminUploadsContent[]; + matching_total?: number; + filter?: string[]; + search?: string | null; + limit?: number; + scan?: number; + scanned?: number; + category_totals?: Record; }; +export type AdminUploadsQueryParams = { + filter?: string | string[]; + search?: string; + limit?: number; + scan?: number; +}; + +type AdminUploadsQueryKey = ['admin', 'uploads', string | null, string | null, number | null, number | null]; + export type AdminSystemResponse = { env: Record; service_config: Array<{ @@ -232,12 +349,34 @@ export const useAdminStorage = ( }; export const useAdminUploads = ( - options?: QueryOptions, + params?: AdminUploadsQueryParams, + options?: QueryOptions, ) => { - return useQuery( - ['admin', 'uploads'], + const filterParts = Array.isArray(params?.filter) + ? params.filter.filter(Boolean) + : params?.filter + ? [params.filter] + : []; + const filterValue = filterParts.length > 0 ? filterParts.join(',') : null; + const searchValue = params?.search?.trim() || null; + const limitValue = params?.limit ?? null; + const scanValue = params?.scan ?? null; + + const queryKey: AdminUploadsQueryKey = ['admin', 'uploads', filterValue, searchValue, limitValue, scanValue]; + + const queryParams: Record = { + filter: filterValue ?? undefined, + search: searchValue ?? undefined, + limit: limitValue ?? undefined, + scan: scanValue ?? undefined, + }; + + return useQuery( + queryKey, async () => { - const { data } = await request.get('/admin.uploads'); + const { data } = await request.get('/admin.uploads', { + params: queryParams, + }); return data; }, { diff --git a/src/shared/services/auth/index.ts b/src/shared/services/auth/index.ts index 6efbc07..907cf61 100644 --- a/src/shared/services/auth/index.ts +++ b/src/shared/services/auth/index.ts @@ -3,6 +3,7 @@ import { useTonConnectUI } from '@tonconnect/ui-react'; import { useMutation } from 'react-query'; import { request } from '~/shared/libs'; import { useWebApp } from '@vkruglikov/react-telegram-web-app'; +import { appendReferral } from '~/shared/utils/start-payload'; const sessionStorageKey = 'auth_v1_token'; const tonProofStorageKey = 'stored_ton_proof'; @@ -47,7 +48,7 @@ export const useAuth = () => { ton_balance: string; }; auth_v1_token: string; - }>('/auth.twa', params); + }>('/auth.twa', appendReferral(params)); if (res?.data?.auth_v1_token) { localStorage.setItem(sessionStorageKey, res.data.auth_v1_token); @@ -95,9 +96,9 @@ export const useAuth = () => { try { // Get the payload/token from backend - const value = await request.post<{ auth_v1_token: string }>('/auth.twa', { + const value = await request.post<{ auth_v1_token: string }>('/auth.twa', appendReferral({ twa_data: WebApp.initData, - }); + })); if (value?.data?.auth_v1_token) { console.log('DEBUG: Got token for connect params'); diff --git a/src/shared/services/authTwa/index.ts b/src/shared/services/authTwa/index.ts index 8ee1379..bf022cf 100644 --- a/src/shared/services/authTwa/index.ts +++ b/src/shared/services/authTwa/index.ts @@ -1,6 +1,7 @@ import { useMutation } from "react-query"; import { request } from "~/shared/libs"; import { useWebApp } from "@vkruglikov/react-telegram-web-app"; +import { appendReferral } from "~/shared/utils/start-payload"; const sessionStorageKey = "auth_v1_token"; @@ -10,9 +11,9 @@ export const useAuthTwa = () => { const makeAuthRequest = async () => { const res = await request.post<{ auth_v1_token: string; - }>("/auth.twa", { + }>("/auth.twa", appendReferral({ twa_data: WebApp.initData, - }); + })); if (res?.data?.auth_v1_token) { localStorage.setItem(sessionStorageKey, res.data.auth_v1_token); @@ -23,4 +24,4 @@ export const useAuthTwa = () => { }; return useMutation(["auth"], makeAuthRequest); -}; \ No newline at end of file +}; diff --git a/src/shared/services/content/index.ts b/src/shared/services/content/index.ts index 44e5417..183c01a 100644 --- a/src/shared/services/content/index.ts +++ b/src/shared/services/content/index.ts @@ -45,14 +45,20 @@ export const useCreateNewContent = () => { // ); // }; -export const useViewContent = (contentId: string) => { - return useQuery(["view", "content", contentId], () => { - return request.get(`/content.view/${contentId}`, { - headers: { - Authorization: localStorage.getItem('auth_v1_token') ?? "" - } - }); - }); +export const useViewContent = (contentId: string | null | undefined) => { + return useQuery( + ["view", "content", contentId], + () => { + return request.get(`/content.view/${contentId}`, { + headers: { + Authorization: localStorage.getItem('auth_v1_token') ?? "" + } + }); + }, + { + enabled: Boolean(contentId), + } + ); }; export const usePurchaseContent = () => {

{item.id}{item.filename || "—"}{item.size_bytes ? formatBytes(item.size_bytes) : "—"}{item.state}{item.filename || '—'}{item.size_bytes ? formatBytes(item.size_bytes) : '—'}{item.state} {formatDate(item.updated_at)}