diff --git a/src/components/BuildDownloadButton.tsx b/src/components/BuildDownloadButton.tsx new file mode 100644 index 0000000..ef3dd45 --- /dev/null +++ b/src/components/BuildDownloadButton.tsx @@ -0,0 +1,92 @@ +import { useMutation } from 'convex/react' +import { pick } from 'convex-helpers' +import { useState } from 'react' +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { api } from '../../convex/_generated/api' +import { type BuildFields, buildFields } from '../../convex/schema' + +interface BuildDownloadButtonProps { + build: BuildFields + type: 'firmware' | 'source' + variant?: 'default' | 'outline' + className?: string +} + +export function BuildDownloadButton({ + build, + type, + variant, + className, +}: BuildDownloadButtonProps) { + const generateDownloadUrl = useMutation( + api.builds.generateAnonymousDownloadUrl + ) + const generateSourceDownloadUrl = useMutation( + api.builds.generateAnonymousSourceDownloadUrl + ) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + // Default styling based on type + const defaultVariant = + variant ?? (type === 'firmware' ? 'default' : 'outline') + const defaultClassName = + className ?? + (type === 'firmware' + ? 'bg-cyan-600 hover:bg-cyan-700' + : 'bg-slate-700 hover:bg-slate-600') + + const handleDownload = async () => { + setError(null) + setIsLoading(true) + try { + const url = + type === 'firmware' + ? await generateDownloadUrl({ + build: pick( + build, + Object.keys(buildFields) as (keyof BuildFields)[] + ), + slug: 'download', + }) + : await generateSourceDownloadUrl({ + build: pick( + build, + Object.keys(buildFields) as (keyof BuildFields)[] + ), + slug: 'download', + }) + window.location.href = url + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + const errorMsg = + type === 'firmware' + ? 'Failed to generate download link.' + : 'Failed to generate source download link.' + setError(errorMsg) + toast.error(errorMsg, { + description: message, + }) + } finally { + setIsLoading(false) + } + } + + if (type === 'firmware' && !build.artifactPath) return null + if (type === 'source' && !build.sourceUrl && !build.buildHash) return null + + return ( +
+ + {error &&

{error}

} +
+ ) +} diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx index 5ac881d..2941675 100644 --- a/src/pages/Admin.tsx +++ b/src/pages/Admin.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery } from 'convex/react' import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { toast } from 'sonner' +import { BuildDownloadButton } from '@/components/BuildDownloadButton' import { Button } from '@/components/ui/button' import { api } from '../../convex/_generated/api' import type { Id } from '../../convex/_generated/dataModel' @@ -201,6 +202,18 @@ export default function Admin() { ))} )} + {(build.artifactPath || + build.sourceUrl || + build.buildHash) && ( +
+ {build.artifactPath && ( + + )} + {(build.sourceUrl || build.buildHash) && ( + + )} +
+ )}
- {downloadError && ( -

{downloadError}

- )} -
+ )} {build.sourceUrl && ( -
- - {sourceDownloadError && ( -

{sourceDownloadError}

- )} -
+ )} {status === 'failure' && (