'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Download, Loader2, Smartphone, Trash2, Upload } from 'lucide-react'; import { useAuth } from '@/components/id/auth-provider'; import { AdminShell } from '@/components/id/admin-shell'; import { useToast } from '@/components/id/toast-provider'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { AppRelease, deleteAdminAppRelease, fetchAdminAppReleases, getApiErrorMessage, updateAdminAppRelease, uploadAdminAppRelease } from '@/lib/api'; const ANDROID_PLATFORM = 'ANDROID' as const; const APK_ACCEPT = '.apk,application/vnd.android.package-archive'; function formatBytes(value: string) { const size = Number(value); if (!Number.isFinite(size) || size <= 0) return '—'; const units = ['Б', 'КБ', 'МБ', 'ГБ']; let amount = size; let unit = 0; while (amount >= 1024 && unit < units.length - 1) { amount /= 1024; unit += 1; } return `${amount.toFixed(amount >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; } function formatDate(value: string) { return new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(value)); } export default function AdminReleasesPage() { const { token, user } = useAuth(); const { showToast } = useToast(); const [releases, setReleases] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [version, setVersion] = useState(''); const [versionCode, setVersionCode] = useState(''); const [releaseNotes, setReleaseNotes] = useState(''); const [file, setFile] = useState(null); const canManage = Boolean(user?.canManageSettings); const loadReleases = useCallback(async () => { if (!token || !canManage) return; setLoading(true); try { const response = await fetchAdminAppReleases(token, ANDROID_PLATFORM); setReleases(response.releases ?? []); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось загрузить релизы') ?? 'Ошибка'); } finally { setLoading(false); } }, [canManage, showToast, token]); useEffect(() => { void loadReleases(); }, [loadReleases]); const androidReleases = useMemo( () => releases.filter((item) => item.platform === ANDROID_PLATFORM), [releases] ); async function handleUpload(event: React.FormEvent) { event.preventDefault(); if (!token || !file) { showToast('Выберите файл релиза'); return; } const parsedVersionCode = Number(versionCode); if (!version.trim() || !Number.isInteger(parsedVersionCode) || parsedVersionCode < 1) { showToast('Укажите версию и положительный код версии'); return; } setUploading(true); try { const created = await uploadAdminAppRelease(token, { platform: ANDROID_PLATFORM, version: version.trim(), versionCode: parsedVersionCode, releaseNotes: releaseNotes.trim() || undefined, file }); setReleases((current) => [created, ...current]); setVersion(''); setVersionCode(''); setReleaseNotes(''); setFile(null); showToast('Релиз загружен и опубликован'); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось загрузить релиз') ?? 'Ошибка'); } finally { setUploading(false); } } async function togglePublished(release: AppRelease) { if (!token) return; try { const updated = await updateAdminAppRelease(token, release.id, { isPublished: !release.isPublished }); setReleases((current) => current.map((item) => (item.id === release.id ? updated : item))); showToast(updated.isPublished ? 'Релиз опубликован' : 'Релиз скрыт'); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось обновить релиз') ?? 'Ошибка'); } } async function removeRelease(release: AppRelease) { if (!token) return; if (!window.confirm(`Удалить версию ${release.version} (Android)?`)) return; try { await deleteAdminAppRelease(token, release.id); setReleases((current) => current.filter((item) => item.id !== release.id)); showToast('Релиз удалён'); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось удалить релиз') ?? 'Ошибка'); } } function renderReleaseList(items: AppRelease[], emptyLabel: string) { if (loading) { return
Загружаем релизы...
; } if (!items.length) { return
{emptyLabel}
; } return (
{items.map((release, index) => { const isLatest = index === 0 && release.isPublished; return (

v{release.version}

build {release.versionCode} {isLatest ? ( Последняя ) : null} {!release.isPublished ? ( Скрыта ) : null}

{release.fileName} · {formatBytes(release.fileSize)} · {formatDate(release.createdAt)}

SHA-256: {release.sha256}

{release.releaseNotes ? (

{release.releaseNotes}

) : null}
); })}
); } if (!canManage) { return (
Недостаточно прав для управления релизами.
); } return (

Новая версия Android

Загрузите APK с подписью SHA-256

void handleUpload(event)}> setVersion(event.target.value)} required /> setVersionCode(event.target.value)} required />