mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
components/dev/Release becomes components/product/Release with a repo; /dev passes hanzoai/dev and draws exactly what it did.
40 lines
1.6 KiB
TypeScript
40 lines
1.6 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* A repository's latest release, read from GitHub when the page opens: /dev
|
|
* names hanzoai/dev's, /gen hanzoai/gen's.
|
|
*
|
|
* No page carries a copy of a version: it is asked for here, and until GitHub
|
|
* answers, or if it does not, nothing is drawn. A reply is held to the shape it
|
|
* must have — a version tag and a page of that repository's releases — before a
|
|
* word of it reaches the page.
|
|
*/
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { More } from '@hanzo/ui/marketing'
|
|
|
|
type Latest = { tag: string; url: string }
|
|
|
|
/** The release a reply names for `repo`, or null when it is not one. */
|
|
function latest(repo: string, body: unknown): Latest | null {
|
|
if (!body || typeof body !== 'object') return null
|
|
const { tag_name: tag, html_url: url } = body as Record<string, unknown>
|
|
if (typeof tag !== 'string' || !/^v?\d+\.\d+\.\d+$/.test(tag)) return null
|
|
if (typeof url !== 'string' || !url.startsWith(`https://github.com/${repo}/releases/`)) return null
|
|
return { tag, url }
|
|
}
|
|
|
|
export function Release({ repo }: { repo: `hanzoai/${string}` }) {
|
|
const [release, setRelease] = useState<Latest | null>(null)
|
|
useEffect(() => {
|
|
const stop = new AbortController()
|
|
fetch(`https://api.github.com/repos/${repo}/releases/latest`, { headers: { accept: 'application/vnd.github+json' }, signal: stop.signal })
|
|
.then((res) => (res.ok ? res.json() : null))
|
|
.then((body: unknown) => setRelease(latest(repo, body)))
|
|
.catch(() => {})
|
|
return () => stop.abort()
|
|
}, [repo])
|
|
if (!release) return null
|
|
return <More href={release.url}>{`Latest release ${release.tag} →`}</More>
|
|
}
|