'use client'; import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { ArrowRight, Download, Monitor, ShieldCheck, Smartphone, Sparkles } from 'lucide-react'; import { BrandLogo } from '@/components/id/brand-logo'; import { usePublicSettings } from '@/components/id/public-settings-provider'; import { Button } from '@/components/ui/button'; import { AppRelease, AppReleasePlatform, buildAppReleaseDownloadUrl, fetchPublicAppReleases } from '@/lib/api'; 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: 'long', year: 'numeric' }).format(new Date(value)); } const platformCards: Array<{ platform: AppReleasePlatform; slug: 'android' | 'windows'; title: string; subtitle: string; icon: typeof Smartphone; gradient: string; }> = [ { platform: 'ANDROID', slug: 'android', title: 'Android', subtitle: 'Скачайте APK для телефона или планшета', icon: Smartphone, gradient: 'from-[#34c759] to-[#0f9d58]' }, { platform: 'WINDOWS', slug: 'windows', title: 'Windows', subtitle: 'Установщик для компьютера', icon: Monitor, gradient: 'from-[#3390ec] to-[#1d4ed8]' } ]; export default function DownloadsPage() { const { projectName } = usePublicSettings(); const [releases, setReleases] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { void (async () => { try { const response = await fetchPublicAppReleases(); setReleases(response.releases ?? []); } finally { setLoading(false); } })(); }, []); const latestByPlatform = useMemo(() => { const map = new Map(); for (const release of releases) { if (!map.has(release.platform)) { map.set(release.platform, release); } } return map; }, [releases]); const historyByPlatform = useMemo(() => ({ ANDROID: releases.filter((item) => item.platform === 'ANDROID'), WINDOWS: releases.filter((item) => item.platform === 'WINDOWS') }), [releases]); return (
Официальные сборки {projectName}

Скачайте приложение

Актуальные версии для Android и Windows. Каждый релиз подписан SHA-256 — мобильное приложение может автоматически проверять обновления по коду версии.

{platformCards.map((card) => { const latest = latestByPlatform.get(card.platform); const Icon = card.icon; return (

{card.title}

{card.subtitle}

{loading ? (

Проверяем доступные версии...

) : latest ? ( <>

Последняя версия

v{latest.version} build {latest.versionCode}

{formatBytes(latest.fileSize)} · {formatDate(latest.createdAt)}

{latest.releaseNotes ? (

{latest.releaseNotes}

) : null} ) : (

Сборка для этой платформы пока не опубликована.

)}
); })}
{platformCards.map((card) => { const history = historyByPlatform[card.platform]; return (

История версий · {card.title}

{loading ? (

Загружаем список версий...

) : history.length ? ( history.map((release, index) => (
v{release.version} build {release.versionCode} {index === 0 ? ( Актуальная ) : null}

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

)) ) : (

Версии пока не опубликованы.

)}
); })}

Проверка обновлений в приложении

Мобильный клиент может запрашивать{' '} GET /idp-api/releases/check?platform=ANDROID&versionCode=...{' '} и сравнивать SHA-256 перед установкой. Постоянные ссылки на последнюю версию: /downloads/android /downloads/windows

); }