mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
docs: dynamically fetch latest version from GitHub API
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const GITHUB_REPO = "tiann/hapi";
|
||||
const CACHE_KEY = "hapi-latest-version";
|
||||
const CACHE_TTL = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
interface CachedVersion {
|
||||
version: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export function useLatestVersion(fallback: string = "latest") {
|
||||
const [version, setVersion] = useState<string>(() => {
|
||||
// Try to get from cache first
|
||||
try {
|
||||
const cached = localStorage.getItem(CACHE_KEY);
|
||||
if (cached) {
|
||||
const { version, timestamp }: CachedVersion = JSON.parse(cached);
|
||||
if (Date.now() - timestamp < CACHE_TTL) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore cache errors
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVersion = async () => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
|
||||
{ headers: { Accept: "application/vnd.github.v3+json" } }
|
||||
);
|
||||
if (!res.ok) return;
|
||||
|
||||
const data = await res.json();
|
||||
const tag = data.tag_name as string;
|
||||
if (tag) {
|
||||
setVersion(tag);
|
||||
// Cache the result
|
||||
localStorage.setItem(
|
||||
CACHE_KEY,
|
||||
JSON.stringify({ version: tag, timestamp: Date.now() })
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Keep fallback on error
|
||||
}
|
||||
};
|
||||
|
||||
fetchVersion();
|
||||
}, []);
|
||||
|
||||
return version;
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"viewOnGithub": "View on GitHub"
|
||||
},
|
||||
"hero": {
|
||||
"version": "v0.3.1 is now available",
|
||||
"version": "{{version}} is now available",
|
||||
"title": "Vibe Coding <br/> <span class=\"text-primary\">Anytime, Anywhere.</span>",
|
||||
"subtitle": "Go for a hike, grab a coffee, or just relax. Your AI agents work in the background. We'll notify you via your favorite messaging app only when they need a nod.",
|
||||
"startBtn": "Start Your Freedom",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"viewOnGithub": "GitHub"
|
||||
},
|
||||
"hero": {
|
||||
"version": "v0.3.1 现已发布",
|
||||
"version": "{{version}} 现已发布",
|
||||
"title": "Vibe Coding <br/> <span class=\"text-primary\">随时随地,自由编程</span>",
|
||||
"subtitle": "去徒步,去喝咖啡,或者只是放松一下。你的 AI 代理在后台为你工作。只有在需要你确认时,我们才会通过你喜欢的即时通讯应用通知你。",
|
||||
"startBtn": "开启自由之旅",
|
||||
|
||||
@@ -9,11 +9,13 @@ import { Link } from "wouter";
|
||||
import { motion, useScroll, useTransform } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SEO } from "@/components/SEO";
|
||||
import { useLatestVersion } from "@/hooks/useLatestVersion";
|
||||
|
||||
export default function Home() {
|
||||
const [copied, setCopied] = useState("");
|
||||
const { scrollY } = useScroll();
|
||||
const { t } = useTranslation();
|
||||
const version = useLatestVersion();
|
||||
|
||||
const copyToClipboard = (text: string, key: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
@@ -34,9 +36,9 @@ export default function Home() {
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
|
||||
</span>
|
||||
{t('hero.version')}
|
||||
{t('hero.version', { version })}
|
||||
</div>
|
||||
|
||||
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-extrabold tracking-tight leading-[1.1] mb-6" dangerouslySetInnerHTML={{ __html: t('hero.title') }} />
|
||||
|
||||
<p className="text-xl text-muted-foreground max-w-lg leading-relaxed">
|
||||
|
||||
Reference in New Issue
Block a user