refactor: rename triggerBuild to triggerFlash, enhance build handling logic, and improve profile target management in the application

This commit is contained in:
Ben Allfree
2025-11-23 21:28:18 -08:00
parent f1be099e93
commit 55a126c48c
6 changed files with 286 additions and 146 deletions
+8 -2
View File
@@ -23,7 +23,10 @@ function App() {
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/profiles/:id" element={<ProfileDetail />} />
<Route path="/profiles/:id/flash" element={<ProfileFlash />} />
<Route
path="/profiles/:id/flash/:profileTargetId"
element={<ProfileFlash />}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Unauthenticated>
@@ -35,7 +38,10 @@ function App() {
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/builds/:buildId" element={<BuildDetail />} />
<Route path="/profiles/:id" element={<ProfileDetail />} />
<Route path="/profiles/:id/flash" element={<ProfileFlash />} />
<Route
path="/profiles/:id/flash/:profileTargetId"
element={<ProfileFlash />}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Authenticated>
-17
View File
@@ -11,7 +11,6 @@ import type { Doc, Id } from '../../convex/_generated/dataModel'
export default function Dashboard() {
const profiles = useQuery(api.profiles.list)
const triggerBuild = useMutation(api.builds.triggerBuild)
const removeProfile = useMutation(api.profiles.remove)
const [isEditing, setIsEditing] = useState(false)
const [editingProfile, setEditingProfile] = useState<Doc<'profiles'> | null>(
@@ -28,19 +27,6 @@ export default function Dashboard() {
setIsEditing(true)
}
const handleBuild = async (profileId: Id<'profiles'>) => {
try {
await triggerBuild({ profileId })
toast.success('Build started', {
description: 'Check the build status below.',
})
} catch (error) {
toast.error('Build failed', {
description: String(error),
})
}
}
const handleDelete = async (
profileId: Id<'profiles'>,
profileName: string
@@ -107,9 +93,6 @@ export default function Dashboard() {
>
Edit
</Button>
<Button size="sm" onClick={() => handleBuild(profile._id)}>
Build
</Button>
<Button
size="sm"
variant="destructive"
+16 -4
View File
@@ -1,7 +1,8 @@
import { useQuery } from 'convex/react'
import { useMutation, useQuery } from 'convex/react'
import * as React from 'react'
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import { api } from '../../convex/_generated/api'
import type { Id } from '../../convex/_generated/dataModel'
@@ -11,6 +12,7 @@ import { TARGETS } from '../constants/targets'
export default function ProfileDetail() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const triggerFlash = useMutation(api.builds.triggerFlash)
const profile = useQuery(
api.profiles.get,
id ? { id: id as Id<'profiles'> } : 'skip'
@@ -75,9 +77,19 @@ export default function ProfileDetail() {
(module) => profile.config[module.id] === false
)
const handleFlash = () => {
if (selectedTarget) {
navigate(`/profiles/${id}/flash`)
const handleFlash = async () => {
if (!selectedTarget || !id) return
try {
const profileTargetId = await triggerFlash({
profileId: id as Id<'profiles'>,
target: selectedTarget,
})
navigate(`/profiles/${id}/flash/${profileTargetId}`)
} catch (error) {
toast.error('Failed to start flash', {
description: String(error),
})
}
}
+124 -5
View File
@@ -1,15 +1,134 @@
import { useParams } from 'react-router-dom'
import { useQuery } from 'convex/react'
import {
ArrowLeft,
CheckCircle,
ExternalLink,
Loader2,
XCircle,
} from 'lucide-react'
import { Link, useParams } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { humanizeStatus } from '@/lib/utils'
import { api } from '../../convex/_generated/api'
import type { Id } from '../../convex/_generated/dataModel'
export default function ProfileFlash() {
const { id } = useParams<{ id: string }>()
const { id, profileTargetId } = useParams<{
id: string
profileTargetId: string
}>()
const data = useQuery(
api.profiles.getProfileTarget,
profileTargetId
? { profileTargetId: profileTargetId as Id<'profileTargets'> }
: 'skip'
)
if (data === undefined) {
return (
<div className="min-h-screen bg-slate-950 text-white p-8 flex items-center justify-center">
<Loader2 className="w-8 h-8 animate-spin text-cyan-500" />
</div>
)
}
if (data === null || !data.build) {
return (
<div className="min-h-screen bg-slate-950 text-white p-8">
<div className="max-w-4xl mx-auto">
<Link
to={`/profiles/${id}`}
className="inline-flex items-center text-slate-400 hover:text-white mb-4"
>
<ArrowLeft className="w-4 h-4 mr-2" /> Back to Profile
</Link>
<div className="bg-slate-900/50 rounded-lg border border-slate-800 p-6">
<p className="text-slate-400">Build not found</p>
</div>
</div>
</div>
)
}
const build = data.build
const profileTarget = data.profileTarget
const getStatusColor = (status: string) => {
if (status === 'success') return 'text-green-400'
if (status === 'failure') return 'text-red-400'
return 'text-blue-400'
}
const getStatusIcon = (status: string) => {
if (status === 'success') {
return <CheckCircle className="w-6 h-6 text-green-500" />
}
if (status === 'failure') {
return <XCircle className="w-6 h-6 text-red-500" />
}
return <Loader2 className="w-6 h-6 text-blue-500 animate-spin" />
}
const githubActionUrl =
build.githubRunId > 0
? `https://github.com/MeshEnvy/configurable-web-flasher/actions/runs/${build.githubRunId}`
: null
return (
<div className="min-h-screen bg-slate-950 text-white p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold mb-8">Flash Firmware</h1>
<Link
to={`/profiles/${id}`}
className="inline-flex items-center text-slate-400 hover:text-white mb-4"
>
<ArrowLeft className="w-4 h-4 mr-2" /> Back to Profile
</Link>
<div className="bg-slate-900/50 rounded-lg border border-slate-800 p-6">
<p className="text-slate-400">Flash functionality coming soon...</p>
<p className="text-slate-500 text-sm mt-2">Profile ID: {id}</p>
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
{getStatusIcon(build.status)}
<div>
<h1 className="text-3xl font-bold">{profileTarget.target}</h1>
<div className="flex items-center gap-2 text-slate-400 mt-1">
<span className={getStatusColor(build.status)}>
{humanizeStatus(build.status)}
</span>
<span></span>
<span>{new Date(build.startedAt).toLocaleString()}</span>
</div>
</div>
</div>
</div>
{githubActionUrl && (
<div className="mt-4">
<a
href={githubActionUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-cyan-400 hover:text-cyan-300"
>
View on GitHub Actions
<ExternalLink className="w-4 h-4 ml-2" />
</a>
</div>
)}
{build.status === 'success' && build.artifactUrl && (
<div className="mt-4">
<a
href={build.artifactUrl}
target="_blank"
rel="noopener noreferrer"
>
<Button className="bg-cyan-600 hover:bg-cyan-700">
Download Firmware
</Button>
</a>
</div>
)}
</div>
</div>
</div>