Files
metabuilder/hooks/use-github-build-status.ts
johndoe6345789 78a54228df feat(hooks): Create centralized hooks npm package
- Added @metabuilder/hooks workspace package at root
- Consolidated 30 React hooks from across codebase into single module
- Implemented conditional exports for tree-shaking support
- Added comprehensive package.json with build/lint/typecheck scripts
- Created README.md documenting hook categories and usage patterns
- Updated root package.json workspaces array to include hooks
- Supports multi-version peer dependencies (React 18/19, Redux 8/9)

Usage:
  import { useDashboardLogic } from '@metabuilder/hooks'
  import { useLoginLogic } from '@metabuilder/hooks/useLoginLogic'

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-23 19:17:17 +00:00

152 lines
4.4 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import copy from '@/data/github-build-status.json'
export interface WorkflowRun {
id: number
name: string
status: string
conclusion: string | null
created_at: string
updated_at: string
html_url: string
head_branch: string
head_sha: string
event: string
workflow_id: number
path: string
}
export interface Workflow {
id: number
name: string
path: string
state: string
badge_url: string
}
interface UseGithubBuildStatusArgs {
owner: string
repo: string
defaultBranch?: string
}
export const useGithubBuildStatus = ({
owner,
repo,
defaultBranch = 'main',
}: UseGithubBuildStatusArgs) => {
const [workflows, setWorkflows] = useState<WorkflowRun[]>([])
const [allWorkflows, setAllWorkflows] = useState<Workflow[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [copiedBadge, setCopiedBadge] = useState<string | null>(null)
const formatWithCount = useCallback((template: string, count: number) => {
return template.replace('{count}', count.toString())
}, [])
const fetchData = useCallback(async () => {
try {
setLoading(true)
setError(null)
const [runsResponse, workflowsResponse] = await Promise.all([
fetch(`https://api.github.com/repos/${owner}/${repo}/actions/runs?per_page=5`, {
headers: { Accept: 'application/vnd.github.v3+json' },
}),
fetch(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, {
headers: { Accept: 'application/vnd.github.v3+json' },
}),
])
if (!runsResponse.ok || !workflowsResponse.ok) {
throw new Error(`GitHub API error: ${runsResponse.status}`)
}
const runsData = await runsResponse.json()
const workflowsData = await workflowsResponse.json()
setWorkflows(runsData.workflow_runs || [])
setAllWorkflows(workflowsData.workflows || [])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch workflows')
} finally {
setLoading(false)
}
}, [owner, repo])
useEffect(() => {
fetchData()
}, [fetchData])
const getBadgeUrl = useCallback(
(workflowPath: string, branch = defaultBranch) => {
const workflowFile = workflowPath.split('/').pop()
if (branch) {
return `https://github.com/${owner}/${repo}/actions/workflows/${workflowFile}/badge.svg?branch=${branch}`
}
return `https://github.com/${owner}/${repo}/actions/workflows/${workflowFile}/badge.svg`
},
[defaultBranch, owner, repo],
)
const getBadgeMarkdown = useCallback(
(workflowPath: string, workflowName: string, branch?: string) => {
const badgeUrl = getBadgeUrl(workflowPath, branch)
const actionUrl = `https://github.com/${owner}/${repo}/actions/workflows/${workflowPath.split('/').pop()}`
return `[![${workflowName}](${badgeUrl})](${actionUrl})`
},
[getBadgeUrl, owner, repo],
)
const copyBadgeMarkdown = useCallback(
(workflowPath: string, workflowName: string, branch?: string) => {
const markdown = getBadgeMarkdown(workflowPath, workflowName, branch)
navigator.clipboard.writeText(markdown)
const key = `${workflowPath}-${branch || defaultBranch}`
setCopiedBadge(key)
toast.success(copy.toast.badgeCopied)
setTimeout(() => setCopiedBadge(null), 2000)
},
[defaultBranch, getBadgeMarkdown],
)
const formatTime = useCallback(
(dateString: string) => {
const date = new Date(dateString)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
if (days > 0) return formatWithCount(copy.time.daysAgo, days)
if (hours > 0) return formatWithCount(copy.time.hoursAgo, hours)
if (minutes > 0) return formatWithCount(copy.time.minutesAgo, minutes)
return copy.time.justNow
},
[formatWithCount],
)
const actions = useMemo(
() => ({
refresh: fetchData,
copyBadgeMarkdown,
getBadgeUrl,
getBadgeMarkdown,
formatTime,
}),
[copyBadgeMarkdown, fetchData, formatTime, getBadgeMarkdown, getBadgeUrl],
)
return {
loading,
error,
workflows,
allWorkflows,
copiedBadge,
actions,
}
}