alphasim-competition-page / app /src /components /upload-command-panel.tsx
fei-1995's picture
feat: file upload
f451c8b
import { useMemo, useState } from "react"
import { Check, Copy, Terminal } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { buildUploadCommand } from "@/lib/submission-utils"
type UploadCommandPanelProps = {
presignedUrl: string
}
export function UploadCommandPanel({
presignedUrl,
}: UploadCommandPanelProps) {
const [copied, setCopied] = useState<"url" | "command" | null>(null)
const uploadCommand = useMemo(
() => buildUploadCommand(presignedUrl),
[presignedUrl]
)
async function handleCopy(value: string, kind: "url" | "command") {
await navigator.clipboard.writeText(value)
setCopied(kind)
window.setTimeout(() => {
setCopied((current) => (current === kind ? null : current))
}, 1400)
}
return (
<Card
size="sm"
className="min-w-0 max-w-full border-border bg-card text-card-foreground shadow-none"
>
<CardHeader className="gap-2">
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
<Terminal className="size-4" />
Upload Docker image tar
</CardTitle>
<p className="text-sm text-muted-foreground">
This pre-signed URL expires in 3 hours. Upload your image tar with
the generated command below.
</p>
</CardHeader>
<CardContent className="min-w-0 space-y-4">
<div className="space-y-2">
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Pre-signed URL
</p>
<Button
size="sm"
type="button"
variant="outline"
onClick={() => void handleCopy(presignedUrl, "url")}
>
{copied === "url" ? <Check className="size-4" /> : <Copy className="size-4" />}
{copied === "url" ? "Copied" : "Copy"}
</Button>
</div>
<pre className="max-w-full overflow-x-auto rounded-xl border border-border bg-muted/40 px-4 py-3 text-xs text-muted-foreground">
<code>{presignedUrl}</code>
</pre>
</div>
<div className="space-y-2">
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Shell command
</p>
<Button
size="sm"
type="button"
variant="outline"
onClick={() => void handleCopy(uploadCommand, "command")}
>
{copied === "command" ? (
<Check className="size-4" />
) : (
<Copy className="size-4" />
)}
{copied === "command" ? "Copied" : "Copy"}
</Button>
</div>
<pre className="max-w-full overflow-x-auto rounded-xl border border-border bg-zinc-950 px-4 py-3 text-xs text-zinc-50">
<code>{uploadCommand}</code>
</pre>
</div>
</CardContent>
</Card>
)
}