diff --git a/src/components/molecules/GitHubBuildStatus.tsx b/src/components/molecules/GitHubBuildStatus.tsx
index 05d72e2..c3fa04b 100644
--- a/src/components/molecules/GitHubBuildStatus.tsx
+++ b/src/components/molecules/GitHubBuildStatus.tsx
@@ -3,18 +3,19 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import {
- GitBranch,
- CheckCircle,
- XCircle,
- Clock,
+import {
+ GitBranch,
+ CheckCircle,
+ XCircle,
+ Clock,
ArrowSquareOut,
Warning,
Copy,
- CheckSquare
+ 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
@@ -45,6 +46,219 @@ interface GitHubBuildStatusProps {
defaultBranch?: string
}
+interface WorkflowRunStatusProps {
+ status: string
+ conclusion: string | null
+}
+
+interface WorkflowRunDetailsProps {
+ branch: string
+ updatedAt: string
+ event: string
+}
+
+interface WorkflowRunItemProps {
+ workflow: WorkflowRun
+ renderStatus: (status: string, conclusion: string | null) => React.ReactNode
+ renderBadge: (status: string, conclusion: string | null) => React.ReactNode
+ formatTime: (dateString: string) => string
+}
+
+interface WorkflowBadgeListProps {
+ workflows: Workflow[]
+ copiedBadge: string | null
+ onCopyBadge: (workflowPath: string, workflowName: string, branch?: string) => void
+ getBadgeUrl: (workflowPath: string, branch?: string) => string
+ getBadgeMarkdown: (workflowPath: string, workflowName: string, branch?: string) => string
+}
+
+interface BranchBadgeListProps {
+ branches: string[]
+ workflows: Workflow[]
+ copiedBadge: string | null
+ onCopyBadge: (workflowPath: string, workflowName: string, branch: string) => void
+ 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') {
+ if (conclusion === 'success') {
+ return
+ }
+ if (conclusion === 'failure') {
+ return
+ }
+ if (conclusion === 'cancelled') {
+ return
+ }
+ }
+ return
+ }
+
+ return
{getStatusIcon()}
+}
+
+const WorkflowRunBadge = ({ status, conclusion }: WorkflowRunStatusProps) => {
+ if (status === 'completed') {
+ if (conclusion === 'success') {
+ return (
+
+ {copy.status.success}
+
+ )
+ }
+ if (conclusion === 'failure') {
+ return {copy.status.failed}
+ }
+ if (conclusion === 'cancelled') {
+ return {copy.status.cancelled}
+ }
+ }
+ return (
+
+ {copy.status.running}
+
+ )
+}
+
+const WorkflowRunDetails = ({ branch, updatedAt, event }: WorkflowRunDetailsProps) => (
+
+ {branch}
+ •
+ {updatedAt}
+ •
+ {event}
+
+)
+
+const WorkflowRunItem = ({ workflow, renderStatus, renderBadge, formatTime }: WorkflowRunItemProps) => (
+
+
+ {renderStatus(workflow.status, workflow.conclusion)}
+
+
+
{workflow.name}
+ {renderBadge(workflow.status, workflow.conclusion)}
+
+
+
+
+
+
+)
+
+const WorkflowBadgeList = ({
+ workflows,
+ copiedBadge,
+ onCopyBadge,
+ getBadgeUrl,
+ getBadgeMarkdown,
+}: WorkflowBadgeListProps) => (
+
+
{copy.sections.workflowBadges}
+
+ {workflows.map((workflow) => (
+
+
+
{workflow.name}
+
+
+
})
+
+ {getBadgeMarkdown(workflow.path, workflow.name)}
+
+
+ ))}
+
+
+)
+
+const BranchBadgeList = ({
+ branches,
+ workflows,
+ copiedBadge,
+ onCopyBadge,
+ getBadgeUrl,
+}: BranchBadgeListProps) => (
+
+
{copy.sections.branchBadges}
+
+ {branches.slice(0, 3).map((branch) => (
+
+
+ {workflows.slice(0, 2).map((workflow) => (
+
+
+
{workflow.name}
+
+
+

+
+ ))}
+
+ ))}
+
+
+)
+
export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHubBuildStatusProps) {
const [workflows, setWorkflows] = useState([])
const [allWorkflows, setAllWorkflows] = useState([])
@@ -60,14 +274,14 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
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' },
+ headers: { Accept: 'application/vnd.github.v3+json' },
}),
fetch(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, {
- headers: { 'Accept': 'application/vnd.github.v3+json' },
- })
+ headers: { Accept: 'application/vnd.github.v3+json' },
+ }),
])
if (!runsResponse.ok || !workflowsResponse.ok) {
@@ -76,7 +290,7 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
const runsData = await runsResponse.json()
const workflowsData = await workflowsResponse.json()
-
+
setWorkflows(runsData.workflow_runs || [])
setAllWorkflows(workflowsData.workflows || [])
} catch (err) {
@@ -105,40 +319,10 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
navigator.clipboard.writeText(markdown)
const key = `${workflowPath}-${branch || 'default'}`
setCopiedBadge(key)
- toast.success('Badge markdown copied to clipboard')
+ toast.success(copy.toast.badgeCopied)
setTimeout(() => setCopiedBadge(null), 2000)
}
- const getStatusIcon = (status: string, conclusion: string | null) => {
- if (status === 'completed') {
- if (conclusion === 'success') {
- return
- }
- if (conclusion === 'failure') {
- return
- }
- if (conclusion === 'cancelled') {
- return
- }
- }
- return
- }
-
- const getStatusBadge = (status: string, conclusion: string | null) => {
- if (status === 'completed') {
- if (conclusion === 'success') {
- return Success
- }
- if (conclusion === 'failure') {
- return Failed
- }
- if (conclusion === 'cancelled') {
- return Cancelled
- }
- }
- return Running
- }
-
const formatTime = (dateString: string) => {
const date = new Date(dateString)
const now = new Date()
@@ -147,10 +331,10 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
- if (days > 0) return `${days}d ago`
- if (hours > 0) return `${hours}h ago`
- if (minutes > 0) return `${minutes}m ago`
- return 'just now'
+ 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
}
if (loading) {
@@ -159,9 +343,9 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
- GitHub Actions
+ {copy.title}
- Recent workflow runs
+ {copy.loading.description}
{[...Array(3)].map((_, i) => (
@@ -187,22 +371,17 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
- GitHub Actions
+ {copy.title}
- Unable to fetch workflow status
+ {copy.error.description}
{error}
-
@@ -217,20 +396,18 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
- GitHub Actions
+ {copy.title}
- No workflow runs found
+ {copy.empty.description}
-
- No GitHub Actions workflows have been run yet.
-
+ {copy.empty.body}
)
}
- const uniqueBranches = Array.from(new Set(workflows.map(w => w.head_branch)))
+ const uniqueBranches = Array.from(new Set(workflows.map((workflow) => workflow.head_branch)))
return (
@@ -239,164 +416,66 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
- GitHub Actions
+ {copy.title}
- Build status badges and recent workflow runs
+ {copy.header.description}
-
- Refresh
+
+ {copy.header.refresh}
- Status Badges
- Recent Runs
+ {copy.tabs.badges}
+ {copy.tabs.runs}
-
+
-
-
Workflow Badges
-
- {allWorkflows.map((workflow) => (
-
-
-
{workflow.name}
-
copyBadgeMarkdown(workflow.path, workflow.name)}
- className="h-7 text-xs"
- >
- {copiedBadge === `${workflow.path}-default` ? (
-
- ) : (
-
- )}
-
-
-
})
-
- {getBadgeMarkdown(workflow.path, workflow.name)}
-
-
- ))}
-
-
+
{uniqueBranches.length > 0 && (
-
-
Branch-Specific Badges
-
- {uniqueBranches.slice(0, 3).map((branch) => (
-
-
- {allWorkflows.slice(0, 2).map((workflow) => (
-
-
-
{workflow.name}
-
copyBadgeMarkdown(workflow.path, workflow.name, branch)}
- className="h-7 text-xs"
- >
- {copiedBadge === `${workflow.path}-${branch}` ? (
-
- ) : (
-
- )}
-
-
-

-
- ))}
-
- ))}
-
-
+
)}
{workflows.map((workflow) => (
-
-
- {getStatusIcon(workflow.status, workflow.conclusion)}
-
-
-
- {workflow.name}
-
- {getStatusBadge(workflow.status, workflow.conclusion)}
-
-
- {workflow.head_branch}
- •
- {formatTime(workflow.updated_at)}
- •
- {workflow.event}
-
-
-
-
-
-
-
-
-
+ workflow={workflow}
+ renderStatus={(status, conclusion) => (
+
+ )}
+ renderBadge={(status, conclusion) => (
+
+ )}
+ formatTime={formatTime}
+ />
))}
-
+
- View All Workflows
+ {copy.actions.viewAllWorkflows}
diff --git a/src/data/github-build-status.json b/src/data/github-build-status.json
new file mode 100644
index 0000000..9386859
--- /dev/null
+++ b/src/data/github-build-status.json
@@ -0,0 +1,44 @@
+{
+ "title": "GitHub Actions",
+ "loading": {
+ "description": "Recent workflow runs"
+ },
+ "error": {
+ "description": "Unable to fetch workflow status",
+ "retry": "Try Again"
+ },
+ "empty": {
+ "description": "No workflow runs found",
+ "body": "No GitHub Actions workflows have been run yet."
+ },
+ "header": {
+ "description": "Build status badges and recent workflow runs",
+ "refresh": "Refresh"
+ },
+ "tabs": {
+ "badges": "Status Badges",
+ "runs": "Recent Runs"
+ },
+ "sections": {
+ "workflowBadges": "Workflow Badges",
+ "branchBadges": "Branch-Specific Badges"
+ },
+ "actions": {
+ "viewAllWorkflows": "View All Workflows"
+ },
+ "toast": {
+ "badgeCopied": "Badge markdown copied to clipboard"
+ },
+ "status": {
+ "success": "Success",
+ "failed": "Failed",
+ "cancelled": "Cancelled",
+ "running": "Running"
+ },
+ "time": {
+ "daysAgo": "{count}d ago",
+ "hoursAgo": "{count}h ago",
+ "minutesAgo": "{count}m ago",
+ "justNow": "just now"
+ }
+}