From 032d62a3127db9d37b82d278e2b0da17312b5144 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 00:51:51 +0000 Subject: [PATCH] Add GitHub build status hook --- .../molecules/GitHubBuildStatus.tsx | 122 +++------------- src/hooks/use-github-build-status.ts | 131 ++++++++++++++++++ 2 files changed, 148 insertions(+), 105 deletions(-) create mode 100644 src/hooks/use-github-build-status.ts diff --git a/src/components/molecules/GitHubBuildStatus.tsx b/src/components/molecules/GitHubBuildStatus.tsx index c3fa04b..5d4219e 100644 --- a/src/components/molecules/GitHubBuildStatus.tsx +++ b/src/components/molecules/GitHubBuildStatus.tsx @@ -1,4 +1,3 @@ -import { useState, useEffect } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -14,31 +13,8 @@ import { CheckSquare, } from '@phosphor-icons/react' import { Skeleton } from '@/components/ui/skeleton' -import { toast } from 'sonner' import copy from '@/data/github-build-status.json' - -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 -} - -interface Workflow { - id: number - name: string - path: string - state: string - badge_url: string -} +import { useGithubBuildStatus, Workflow, WorkflowRun } from '@/hooks/use-github-build-status' interface GitHubBuildStatusProps { owner: string @@ -67,6 +43,7 @@ interface WorkflowRunItemProps { interface WorkflowBadgeListProps { workflows: Workflow[] copiedBadge: string | null + defaultBranch: string onCopyBadge: (workflowPath: string, workflowName: string, branch?: string) => void getBadgeUrl: (workflowPath: string, branch?: string) => string getBadgeMarkdown: (workflowPath: string, workflowName: string, branch?: string) => string @@ -80,9 +57,6 @@ interface BranchBadgeListProps { getBadgeUrl: (workflowPath: string, branch?: string) => string } -const formatWithCount = (template: string, count: number) => - template.replace('{count}', count.toString()) - const WorkflowRunStatus = ({ status, conclusion }: WorkflowRunStatusProps) => { const getStatusIcon = () => { if (status === 'completed') { @@ -169,6 +143,7 @@ const WorkflowRunItem = ({ workflow, renderStatus, renderBadge, formatTime }: Wo const WorkflowBadgeList = ({ workflows, copiedBadge, + defaultBranch, onCopyBadge, getBadgeUrl, getBadgeMarkdown, @@ -189,7 +164,7 @@ const WorkflowBadgeList = ({ onClick={() => onCopyBadge(workflow.path, workflow.name)} className="h-7 text-xs" > - {copiedBadge === `${workflow.path}-default` ? ( + {copiedBadge === `${workflow.path}-${defaultBranch}` ? ( ) : ( @@ -260,82 +235,18 @@ const BranchBadgeList = ({ ) export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHubBuildStatusProps) { - const [workflows, setWorkflows] = useState([]) - const [allWorkflows, setAllWorkflows] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [copiedBadge, setCopiedBadge] = useState(null) - - useEffect(() => { - fetchData() - }, [owner, repo]) - - const fetchData = 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) - } - } - - const getBadgeUrl = (workflowPath: string, branch?: string) => { - 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` - } - - const getBadgeMarkdown = (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})` - } - - const copyBadgeMarkdown = (workflowPath: string, workflowName: string, branch?: string) => { - const markdown = getBadgeMarkdown(workflowPath, workflowName, branch) - navigator.clipboard.writeText(markdown) - const key = `${workflowPath}-${branch || 'default'}` - setCopiedBadge(key) - toast.success(copy.toast.badgeCopied) - setTimeout(() => setCopiedBadge(null), 2000) - } - - const formatTime = (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 - } + const { + workflows, + allWorkflows, + loading, + error, + copiedBadge, + fetchData, + copyBadgeMarkdown, + getBadgeUrl, + getBadgeMarkdown, + formatTime, + } = useGithubBuildStatus({ owner, repo, defaultBranch }) if (loading) { return ( @@ -437,6 +348,7 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu + template.replace('{count}', count.toString()) + +export const useGithubBuildStatus = ({ + owner, + repo, + defaultBranch = 'main', +}: UseGithubBuildStatusArgs) => { + const [workflows, setWorkflows] = useState([]) + const [allWorkflows, setAllWorkflows] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [copiedBadge, setCopiedBadge] = useState(null) + + 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 = (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` + } + + const getBadgeMarkdown = (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})` + } + + const copyBadgeMarkdown = (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) + } + + const formatTime = (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 + } + + return { + workflows, + allWorkflows, + loading, + error, + copiedBadge, + fetchData, + copyBadgeMarkdown, + getBadgeUrl, + getBadgeMarkdown, + formatTime, + } +}