From 7e507538661d3747609f025e6d24156330e400a5 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 30 Apr 2026 09:50:38 -0400 Subject: [PATCH 1/2] feat(download): fetch links from GitHub releases, fall back to SourceForge On mount, fetch the latest release from the GitHub API and update each platform card with the direct asset URL, version, and file size. Falls back to the SourceForge latest-download URL if the request fails. --- components/download-section.tsx | 73 +++++++++++++++++++++++++-------- lib/github.ts | 51 +++++++++++++++++++++++ 2 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 lib/github.ts diff --git a/components/download-section.tsx b/components/download-section.tsx index f9ef716..5f9127c 100644 --- a/components/download-section.tsx +++ b/components/download-section.tsx @@ -3,38 +3,53 @@ import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Download, Package } from "lucide-react" +import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" +import { findAsset, formatBytes, getLatestRelease } from "@/lib/github" -const downloads = [ +const SOURCEFORGE_URL = "https://sourceforge.net/projects/dashai/files/latest/download" + +type DownloadEntry = { + id: "mac_intel" | "mac_arm" | "windows" + icon: typeof Package + key: string + defaultPlatform: string + version: string + size: string + defaultFormat: string + link: string +} + +const FALLBACK_DOWNLOADS: DownloadEntry[] = [ { id: "mac_intel", icon: Package, key: "macIntel", defaultPlatform: "macOS Intel processors", - version: "v0.1.15", - size: "487 MB", - defaultFormat: "binary", - link: "https://dashai.nyc3.cdn.digitaloceanspaces.com/executables/DashAI-launcher-cpu-x86_64", + version: "", + size: "", + defaultFormat: ".dmg", + link: SOURCEFORGE_URL, }, { id: "mac_arm", icon: Package, key: "macArm", defaultPlatform: "macOS ARM processors", - version: "v0.1.15", - size: "381 MB", - defaultFormat: "binary", - link: "https://dashai.nyc3.cdn.digitaloceanspaces.com/executables/DashAI-launcher-cpu-arm64", + version: "", + size: "", + defaultFormat: ".dmg", + link: SOURCEFORGE_URL, }, { id: "windows", icon: Package, key: "windows", defaultPlatform: "Windows", - version: "v0.1.15", - size: "434 MB", + version: "", + size: "", defaultFormat: ".exe", - link: "https://dashai.nyc3.cdn.digitaloceanspaces.com/executables/DashAI-launcher-cpu.exe", + link: SOURCEFORGE_URL, }, ] @@ -53,6 +68,26 @@ async function trackClick(buttonId: string) { export function DownloadSection() { const { t } = useTranslation() + const [downloads, setDownloads] = useState(FALLBACK_DOWNLOADS) + + useEffect(() => { + getLatestRelease().then((release) => { + if (!release) return + setDownloads( + FALLBACK_DOWNLOADS.map((entry) => { + const asset = findAsset(release.assets, entry.id) + if (!asset) return entry + return { + ...entry, + version: release.tag_name, + size: formatBytes(asset.size), + link: asset.browser_download_url, + } + }) + ) + }) + }, []) + return (
@@ -66,7 +101,7 @@ export function DownloadSection() {

{t("download:description", { defaultValue: - "This early version lets you explore DashAI’s main features. We appreciate your feedback to help us improve before the official release.", + "This early version lets you explore DashAI's main features. We appreciate your feedback to help us improve before the official release.", })}

@@ -92,9 +127,11 @@ export function DownloadSection() {

{platform}

-

- {download.version} • {download.size} -

+ {download.version && ( +

+ {download.version} • {download.size} +

+ )}

{format}

) -} \ No newline at end of file +} diff --git a/lib/github.ts b/lib/github.ts new file mode 100644 index 0000000..46829b9 --- /dev/null +++ b/lib/github.ts @@ -0,0 +1,51 @@ +export interface ReleaseAsset { + name: string + browser_download_url: string + size: number +} + +export interface LatestRelease { + tag_name: string + assets: ReleaseAsset[] +} + +export async function getLatestRelease(): Promise { + try { + const res = await fetch( + "https://api.github.com/repos/DashAISoftware/DashAI/releases/latest", + { headers: { Accept: "application/vnd.github+json" } } + ) + if (!res.ok) return null + return res.json() + } catch { + return null + } +} + +export function formatBytes(bytes: number): string { + const mb = bytes / (1024 * 1024) + return `${Math.round(mb)} MB` +} + +export function findAsset( + assets: ReleaseAsset[], + platform: "windows" | "mac_intel" | "mac_arm" +): ReleaseAsset | undefined { + return assets.find(({ name }) => { + const n = name.toLowerCase() + switch (platform) { + case "windows": + return n.includes("windows") + case "mac_intel": + return ( + (n.includes("x64") || n.includes("intel")) && + (n.includes("osx") || n.includes("mac") || n.includes("darwin")) + ) + case "mac_arm": + return ( + (n.includes("arm") || n.includes("aarch")) && + (n.includes("osx") || n.includes("mac") || n.includes("darwin")) + ) + } + }) +} From cdf3e7095d267f634959ed71767e1f21de5e4fd0 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 30 Apr 2026 09:50:38 -0400 Subject: [PATCH 2/2] fix(i18n): prevent hydration mismatch from browser language detection LanguageDetector ran synchronously at init, but Node.js (SSG build) has no navigator, so it fell back to English. The browser then detected the user's actual language and rendered different text, causing React hydration errors. Fix: remove LanguageDetector from init, lock initial language to 'en', and detect + apply browser language after mount. --- app/i18n.ts | 3 +-- app/page.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/i18n.ts b/app/i18n.ts index b29c6e2..44fa179 100644 --- a/app/i18n.ts +++ b/app/i18n.ts @@ -1,5 +1,4 @@ import i18n from "i18next"; -import LanguageDetector from "i18next-browser-languagedetector"; import { initReactI18next } from "react-i18next"; import supportedByEN from "../public/locales/en/supportedBy.json"; import supportedByES from "../public/locales/es/supportedBy.json"; @@ -21,8 +20,8 @@ import footerES from "../public/locales/es/footer.json"; i18n .use(initReactI18next) - .use(LanguageDetector) .init({ + lng: "en", resources: { en: { navbar: navbarEN, diff --git a/app/page.tsx b/app/page.tsx index a276797..27d43db 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,7 @@ "use client" +import { useEffect } from "react" +import i18n from "i18next" import { Navbar } from "@/components/navbar" import { HeroSection } from "@/components/hero-section" import { FeaturesSection } from "@/components/features-section" @@ -11,7 +13,15 @@ import { ContactSection } from "@/components/contact-section" import { Footer } from "@/components/footer" import "./i18n" +const SUPPORTED_LANGS = ["en", "es"] + export default function Home() { + useEffect(() => { + const detected = navigator.language ?? "en" + const match = SUPPORTED_LANGS.find((l) => detected.startsWith(l)) ?? "en" + if (match !== i18n.language) i18n.changeLanguage(match) + }, []) + return (