import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { openExternal } from "@/lib/utils"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Star, GitFork, Clock, Download, Blocks, Heart, Copy, CircleCheck, Info } from "lucide-react"; import AudioTTSProIcon from "@/assets/audiotts-pro.webp"; import ChatGPTTTSIcon from "@/assets/chatgpt-tts.webp"; import XProIcon from "@/assets/x-pro.webp"; import SpotubeDLIcon from "@/assets/icons/spotubedl.svg"; import SpotiDownloaderIcon from "@/assets/icons/spotidownloader.svg"; import XBatchDLIcon from "@/assets/icons/xbatchdl.svg"; import SpotiFLACNextIcon from "@/assets/icons/next.svg"; import KofiLogo from "@/assets/ko-fi.gif"; import KofiSvg from "@/assets/kofi_symbol.svg"; import UsdtBarcode from "@/assets/usdt.jpg"; import { langColors } from "@/assets/github-lang-colors"; export function AboutPage() { const [activeTab, setActiveTab] = useState<"projects" | "support">("projects"); const [repoStats, setRepoStats] = useState>({}); const [copiedUsdt, setCopiedUsdt] = useState(false); useEffect(() => { const fetchRepoStats = async () => { const CACHE_KEY = "github_repo_stats_v3"; const CACHE_DURATION = 1000 * 60 * 60; const cached = localStorage.getItem(CACHE_KEY); if (cached) { try { const { data, timestamp } = JSON.parse(cached); if (Date.now() - timestamp < CACHE_DURATION) { setRepoStats(data); return; } } catch (err) { console.error("Failed to parse cache:", err); } } const repos = [ { name: "SpotiDownloader", owner: "afkarxyz" }, { name: "SpotiFLAC-Next", owner: "spotiverse" }, { name: "Twitter-X-Media-Batch-Downloader", owner: "afkarxyz" }, ]; const stats: Record = {}; for (const repo of repos) { try { const [repoRes, releasesRes, langsRes] = await Promise.all([ fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`), fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/releases`), fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/languages`), ]); if (repoRes.status === 403) { if (cached) { const { data } = JSON.parse(cached); setRepoStats(data); } return; } if (repoRes.ok) { const repoData = await repoRes.json(); const releases = releasesRes.ok ? await releasesRes.json() : []; const languages = langsRes.ok ? await langsRes.json() : {}; let totalDownloads = 0; let latestDownloads = 0; let latestVersion = ""; if (releases.length > 0) { latestVersion = releases[0].tag_name || ""; latestDownloads = releases[0].assets?.reduce((sum: number, asset: any) => sum + (asset.download_count || 0), 0) || 0; totalDownloads = releases.reduce((sum: number, release: any) => { return (sum + (release.assets?.reduce((s: number, a: any) => s + (a.download_count || 0), 0) || 0)); }, 0); } const topLangs = Object.entries(languages) .sort(([, a]: any, [, b]: any) => b - a) .slice(0, 4) .map(([lang]) => lang); stats[repo.name] = { stars: repoData.stargazers_count, forks: repoData.forks_count, createdAt: repoData.created_at, description: repoData.description, totalDownloads, latestDownloads, latestVersion, languages: topLangs, }; } } catch (err) { console.error(`Failed to fetch stats for ${repo.name}:`, err); if (cached) { const { data } = JSON.parse(cached); setRepoStats(data); return; } } } setRepoStats(stats); localStorage.setItem(CACHE_KEY, JSON.stringify({ data: stats, timestamp: Date.now() })); }; fetchRepoStats(); }, []); const formatTimeAgo = (dateString: string): string => { const now = new Date(); const updated = new Date(dateString); const diffMs = now.getTime() - updated.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffMonths = Math.floor(diffDays / 30); if (diffDays === 0) return "today"; if (diffDays === 1) return "1d"; if (diffDays < 30) return `${diffDays}d`; if (diffMonths === 1) return "1mo"; if (diffMonths < 12) return `${diffMonths}mo`; const diffYears = Math.floor(diffMonths / 12); return `${diffYears}y`; }; const formatNumber = (num: number): string => { if (num >= 1000) { return num.toLocaleString(); } return num.toString(); }; const getLangColor = (lang: string): string => { return langColors[lang] || "#858585"; }; const getRepoDescription = (repoName: string): string => { return repoStats[repoName]?.description || ""; }; return (

About

{activeTab === "projects" && (
openExternal("https://exyezed.qzz.io/")}> Browser Extensions & Scripts AudioTTS Pro ChatGPT TTS X Pro openExternal("https://spotubedl.com/")}> SpotubeDL{" "} SpotubeDL Download Spotify Tracks, Albums, Playlists as MP3/OGG/Opus with High Quality.
openExternal("https://github.com/afkarxyz/SpotiDownloader")}>
SpotiDownloader {repoStats["SpotiDownloader"]?.latestVersion && ( {repoStats["SpotiDownloader"].latestVersion} )}
SpotiDownloader {getRepoDescription("SpotiDownloader")}
{repoStats["SpotiDownloader"] && (
{repoStats["SpotiDownloader"].languages?.map((lang: string) => ( {lang} ))}
{" "} {formatNumber(repoStats["SpotiDownloader"].stars)} {" "} {repoStats["SpotiDownloader"].forks} {" "} {formatTimeAgo(repoStats["SpotiDownloader"].createdAt)}
TOTAL:{" "} {formatNumber(repoStats["SpotiDownloader"].totalDownloads)} LATEST:{" "} {formatNumber(repoStats["SpotiDownloader"].latestDownloads)}
)}
openExternal("https://github.com/spotiverse/SpotiFLAC-Next")}>
SpotiFLAC Next {repoStats["SpotiFLAC-Next"]?.latestVersion && ( {repoStats["SpotiFLAC-Next"].latestVersion} )}
SpotiFLAC Next {getRepoDescription("SpotiFLAC-Next")}
{repoStats["SpotiFLAC-Next"] && ( {repoStats["SpotiFLAC-Next"].languages?.length > 0 && (
{repoStats["SpotiFLAC-Next"].languages.map((lang: string) => ( {lang} ))}
)}
{" "} {formatNumber(repoStats["SpotiFLAC-Next"].stars)} {" "} {repoStats["SpotiFLAC-Next"].forks} {" "} {formatTimeAgo(repoStats["SpotiFLAC-Next"].createdAt)}
Note

SpotiFLAC Next is a separate project created as a thank-you to everyone who has supported SpotiFLAC on Ko-fi.

)}
openExternal("https://github.com/afkarxyz/Twitter-X-Media-Batch-Downloader")}>
Twitter/X Media Batch Downloader {repoStats["Twitter-X-Media-Batch-Downloader"]?.latestVersion && ( {repoStats["Twitter-X-Media-Batch-Downloader"].latestVersion} )}
Twitter/X Media Batch Downloader {getRepoDescription("Twitter-X-Media-Batch-Downloader")}
{repoStats["Twitter-X-Media-Batch-Downloader"] && (
{repoStats["Twitter-X-Media-Batch-Downloader"].languages?.map((lang: string) => ( {lang} ))}
{" "} {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"].stars)} {" "} {repoStats["Twitter-X-Media-Batch-Downloader"].forks} {" "} {formatTimeAgo(repoStats["Twitter-X-Media-Batch-Downloader"] .createdAt)}
TOTAL:{" "} {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"] .totalDownloads)} LATEST:{" "} {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"] .latestDownloads)}
)}
)} {activeTab === "support" && (
Ko-fi

Support via Ko-fi

Enjoying the project? You can support ongoing development by buying me a coffee.

USDT Barcode

USDT (TRC20)

Crypto donations are also accepted. Scan the QR code or copy the address.

THnzAAwZgp2Sq5CAXLP2njQDhTvgZG9EWs
)}
); }