'use client' import { useEffect, useState } from 'react' import Link from 'next/link' import { Button } from '@/components/ui/button' import { Download, Clock, Calendar, ArrowDownToLine, FileDown, Tag, Github, PackageOpen, Info, ChevronDown, Check, ExternalLink, } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion' import { Skeleton } from '@/components/ui/skeleton' interface Release { id: number name: string tag_name: string published_at: string body: string html_url: string assets: Array<{ id: number name: string browser_download_url: string size: number download_count: number }> prerelease: boolean } export default function DownloadPage() { const [releases, setReleases] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { async function fetchReleases() { try { const response = await fetch( 'https://api.github.com/repos/vernu/textbee/releases' ) if (!response.ok) { throw new Error('Failed to fetch releases') } const data = await response.json() setReleases(data) } catch (err) { setError('Failed to load releases. Please try again later.') console.error(err) } finally { setLoading(false) } } fetchReleases() }, []) // Get the latest stable release (not prerelease) const latestRelease = releases.find((release) => !release.prerelease) // Format date const formatDate = (dateString: string) => { const date = new Date(dateString) return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }) } // Format file size const formatFileSize = (bytes: number) => { if (bytes < 1024) return bytes + ' B' else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB' else return (bytes / 1048576).toFixed(1) + ' MB' } // Parse markdown lists from release notes const parseReleaseNotes = (body: string) => { if (!body) return { description: '', changelog: [] } const lines = body.split('\n').map((line) => line.trim()) // Find where the changelog starts (usually after ## Changelog or similar) const changelogIndex = lines.findIndex( (line) => line.startsWith('##') && (line.toLowerCase().includes('changelog') || line.toLowerCase().includes('changes') || line.toLowerCase().includes("what's new")) ) let description = '' let changelog: string[] = [] if (changelogIndex > 0) { description = lines.slice(0, changelogIndex).join('\n') changelog = lines .slice(changelogIndex + 1) .filter((line) => line.startsWith('-') || line.startsWith('*')) .map((line) => line.substring(1).trim()) } else { // If no explicit changelog section, treat list items as changelog const listItems = lines.filter( (line) => line.startsWith('-') || line.startsWith('*') ) if (listItems.length > 0) { changelog = listItems.map((line) => line.substring(1).trim()) description = lines .filter((line) => !line.startsWith('-') && !line.startsWith('*')) .join('\n') } else { description = body } } return { description, changelog } } return (
Download TextBee

Download TextBee App

Transform your Android device into a powerful SMS gateway with our easy-to-use application.

{/* Latest release section */}
Latest Version {latestRelease?.prerelease && ( Beta )}
{loading ? ( ) : error ? (

TextBee App

) : (

{latestRelease?.name || 'TextBee App'}

)}
{loading ? ( ) : error ? ( ) : latestRelease?.assets?.length ? ( ) : ( )}
{loading ? (
) : error ? (
{error}
) : latestRelease ? ( <>
Version: {latestRelease.tag_name}
Released: {formatDate(latestRelease.published_at)}
{latestRelease.assets?.[0] && ( <>
Size: {formatFileSize(latestRelease.assets[0].size)}
Downloads:{' '} {latestRelease.assets[0].download_count.toLocaleString()}
)}
{/* Release details */}
{(() => { const { description, changelog } = parseReleaseNotes( latestRelease.body || '' ) return ( <> {description && (
{description.split('\n').map((line, i) => (

{line}

))}
)} {changelog.length > 0 && (

What's New:

    {changelog.map((item, i) => (
  • {item}
  • ))}
)} ) })()}
Compatible with Android 7.0+ devices.
) : (
No releases available at this time.
)}
{/* All releases section */}

All Releases

{loading ? (
{[1, 2, 3].map((i) => (
))}
) : error ? (
{error}
) : releases.length === 0 ? (

No Releases Found

There are no releases available at this time.

) : ( {releases.map((release) => (

{release.name || release.tag_name}

{release.id === latestRelease?.id && ( Latest )} {release.prerelease && ( Beta )}
Released on {formatDate(release.published_at)}
{/* Release notes */} {(() => { const { description, changelog } = parseReleaseNotes( release.body || '' ) return ( <> {description && (
{description.split('\n').map((line, i) => (

{line}

))}
)} {changelog.length > 0 && (

Changes:

    {changelog.map((item, i) => (
  • {item}
  • ))}
)} ) })()} {/* Download assets */} {release.assets.length > 0 && (

Downloads:

{release.assets.map((asset) => (
{asset.name}
{formatFileSize(asset.size)} {asset.download_count.toLocaleString()}
))}
)}
))}
)}
{/* Requirements section */}

System Requirements

  • Android 7.0 (Nougat) or higher
  • SMS capability on the Android device
  • Internet connection for API communication
  • Battery optimization disabled for background operation (recommended)
) }