"use client" import { useState, type ChangeEvent, type FormEvent } from "react" import { ReloadIcon } from "@radix-ui/react-icons" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import type { NostrEvent } from "nostr-tools" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { signEvent } from "@/lib/utils" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Copy, Check, ExternalLink, FileImage, Clock, Database, ArrowLeft } from "lucide-react" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { Spinner } from "./spinner" import { createHash } from "crypto" interface UploadResponse { url: string size: number type: string sha256: string uploaded: number nip94: { tags: string[][] content: string } } function formatBytes(bytes: number, decimals = 2) { if (bytes === 0) return "0 Bytes" const k = 1024 const dm = decimals < 0 ? 0 : decimals const sizes = ["Bytes", "KB", "MB", "GB", "TB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i] } function formatDate(timestamp: number) { return new Date(timestamp * 1000).toLocaleString() } const CopyButton = ({ text }: { text: string }) => { const [copied, setCopied] = useState(false) const handleCopy = () => { navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) } return (

{copied ? "Copied!" : "Copy to clipboard"}

) } const UploadResponseView = ({ data, onReset }: { data: UploadResponse; onReset: () => void }) => { const dimensions = data.nip94.tags.find((tag) => tag[0] === "dim")?.[1] || "" const blurhash = data.nip94.tags.find((tag) => tag[0] === "blurhash")?.[1] || "" const thumbUrl = data.nip94.tags.find((tag) => tag[0] === "thumb")?.[1] || "" return (
Upload Successful {data.type}
File uploaded on {formatDate(data.uploaded)}
Uploaded image
File Size
{formatBytes(data.size)}
Upload Time
{formatDate(data.uploaded)}
{dimensions && (
Dimensions
{dimensions}
)}
URL Hash Metadata
{data.url}
{thumbUrl && (
{thumbUrl}
)}
{data.sha256}
{blurhash && (
{blurhash}
)}
{data.nip94.tags.map((tag, index) => (
{tag[0]} {tag[1]}
))}
) } const UploadComponent = () => { const loginType = typeof window !== "undefined" ? window.localStorage.getItem("loginType") : null const [previewUrl, setPreviewUrl] = useState("") const [isLoading, setIsLoading] = useState(false) const [serverChoice, setServerChoice] = useState("blossom.band") const [uploadResponse, setUploadResponse] = useState(null) const [showUploadForm, setShowUploadForm] = useState(true) const handleFileChange = (event: ChangeEvent) => { const file = event.target.files?.[0] if (file) { const url = URL.createObjectURL(file) setPreviewUrl(url) return () => URL.revokeObjectURL(url) } } const handleServerChange = (value: string) => { setServerChoice(value) } const resetUpload = () => { setUploadResponse(null) setShowUploadForm(true) setPreviewUrl("") } async function onSubmit(event: FormEvent) { event.preventDefault() setIsLoading(true) const formData = new FormData(event.currentTarget) const file = formData.get("file") as File let sha256 = "" let finalFileUrl = "" console.log("File:", file) if (!file.size) { alert("Please select a file first") setIsLoading(false) return } // If file is present, upload it to the media server if (file) { const readFileAsArrayBuffer = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as ArrayBuffer) reader.onerror = () => reject(reader.error) reader.readAsArrayBuffer(file) }) } try { const arrayBuffer = await readFileAsArrayBuffer(file) const hashBuffer = createHash("sha256").update(Buffer.from(arrayBuffer)).digest() sha256 = hashBuffer.toString("hex") const unixNow = () => Math.floor(Date.now() / 1000) const newExpirationValue = () => (unixNow() + 60 * 5).toString() const createdAt = Math.floor(Date.now() / 1000) // Create auth event for blossom auth via nostr const authEvent: NostrEvent = { kind: 24242, content: "File upload", created_at: createdAt, tags: [ ["t", "media"], ["x", sha256], ["expiration", newExpirationValue()], ], pubkey: "", // Add a placeholder for pubkey id: "", // Add a placeholder for id sig: "", // Add a placeholder for sig } console.log(authEvent) // Sign auth event const authEventSigned = (await signEvent(loginType, authEvent)) as NostrEvent // authEventSigned as base64 encoded string const authString = Buffer.from(JSON.stringify(authEventSigned)).toString("base64") const blossomServer = "https://" + serverChoice await fetch(blossomServer + "/media", { method: "PUT", body: file, headers: { authorization: "Nostr " + authString }, }).then(async (res) => { if (res.ok) { const responseText = await res.text() const responseJson = JSON.parse(responseText) finalFileUrl = responseJson.url sha256 = responseJson.sha256 // Set the upload response data setUploadResponse(responseJson) if (finalFileUrl) { const image = new Image() image.src = URL.createObjectURL(file) await new Promise((resolve) => { image.onload = resolve }) } setIsLoading(false) if (finalFileUrl != null) { setShowUploadForm(false) } } else { throw new Error("Failed to upload file: " + (await res.text())) } }) } catch (error) { alert(error) console.error("Error reading file:", error) setIsLoading(false) } } } return (
{showUploadForm ? ( Upload Image Select an image to upload to {serverChoice}
{previewUrl && (
Preview
)} {isLoading ? ( ) : ( )}
) : uploadResponse ? ( ) : (

Processing upload...

)}
) } export default UploadComponent