diff --git a/lumina/components/UploadComponent.tsx b/lumina/components/UploadComponent.tsx index 4acbba3..3ad96eb 100644 --- a/lumina/components/UploadComponent.tsx +++ b/lumina/components/UploadComponent.tsx @@ -1,7 +1,7 @@ -import { useNostr } from "nostr-react" +import { useNostr, useNostrEvents } from "nostr-react" import { finalizeEvent, nip19, type NostrEvent } from "nostr-tools" import type React from "react" -import { type ChangeEvent, type FormEvent, useState } from "react" +import { type ChangeEvent, type FormEvent, useState, useEffect, useCallback } from "react" import { Button } from "./ui/button" import { Textarea } from "./ui/textarea" import { bytesToHex, hexToBytes } from "@noble/hashes/utils" @@ -10,6 +10,15 @@ import { Label } from "./ui/label" import { Input } from "./ui/input" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" import { encode } from "blurhash" +import { + Drawer, + DrawerContent, + DrawerHeader, + DrawerTitle, + DrawerDescription, + DrawerFooter, +} from "@/components/ui/drawer" +import { Spinner } from "@/components/spinner" async function signEvent(loginType: string | null, authEvent: string) { let authEventSigned = {} @@ -27,7 +36,7 @@ async function signEvent(loginType: string | null, authEvent: string) { } } } - return authEventSigned; + return authEventSigned } async function calculateBlurhash(file: File): Promise { @@ -59,6 +68,51 @@ const UploadComponent: React.FC = () => { const [previewUrl, setPreviewUrl] = useState("") const [isLoading, setIsLoading] = useState(false) + const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const [uploadedNoteId, setUploadedNoteId] = useState("") + const [retryCount, setRetryCount] = useState(0) + const [shouldFetch, setShouldFetch] = useState(false) + + const { events, isLoading: isNoteLoading } = useNostrEvents({ + filter: shouldFetch + ? { + ids: uploadedNoteId ? [uploadedNoteId] : [], + kinds: [20], + limit: 1, + } + : { ids: [], kinds: [20], limit: 1 }, + enabled: shouldFetch, + }) + + useEffect(() => { + if (uploadedNoteId) { + setShouldFetch(true) + } + }, [uploadedNoteId]) + + useEffect(() => { + let timeoutId: NodeJS.Timeout + + if (shouldFetch && events.length === 0 && !isNoteLoading) { + timeoutId = setTimeout(() => { + setRetryCount((prevCount) => prevCount + 1) + setShouldFetch(false) + setShouldFetch(true) + }, 5000) // Retry every 5 seconds + } + + return () => { + if (timeoutId) { + clearTimeout(timeoutId) + } + } + }, [shouldFetch, events, isNoteLoading]) + + const handleRetry = useCallback(() => { + setRetryCount((prevCount) => prevCount + 1) + setShouldFetch(false) + setShouldFetch(true) + }, []) const handleFileChange = (event: ChangeEvent) => { const file = event.target.files?.[0] @@ -97,7 +151,7 @@ const UploadComponent: React.FC = () => { hashtags = hashtags.map((hashtag) => hashtag.slice(1)) } - // If file is is preent, upload it to the media server + // If file is present, upload it to the media server if (file) { const readFileAsArrayBuffer = (file: File): Promise => { return new Promise((resolve, reject) => { @@ -134,24 +188,7 @@ const UploadComponent: React.FC = () => { console.log(authEvent) // Sign auth event - let authEventSigned = await signEvent(loginType, JSON.stringify(authEvent)) - // ---- - // let authEventSigned = {} - // if (loginType === "extension") { - // authEventSigned = await window.nostr.signEvent(authEvent) - // } else if (loginType === "amber") { - // // TODO: Sign event with amber - // alert("Signing with Amber is not implemented yet, sorry!") - // } else if (loginType === "raw_nsec") { - // if (typeof window !== "undefined") { - // let nsecStr = null - // nsecStr = window.localStorage.getItem("nsec") - // if (nsecStr != null) { - // authEventSigned = finalizeEvent(authEvent, hexToBytes(nsecStr)) - // } - // } - // } - // console.log(authEventSigned) + const authEventSigned = await signEvent(loginType, JSON.stringify(authEvent)) // Actually upload the file await fetch("https://void.cat/upload", { @@ -160,9 +197,7 @@ const UploadComponent: React.FC = () => { headers: { authorization: "Nostr " + btoa(JSON.stringify(authEventSigned)) }, }).then(async (res) => { if (res.ok) { - // alert('File uploaded successfully'); const responseText = await res.text() - // alert(responseText); const responseJson = JSON.parse(responseText) finalFileUrl = responseJson.url } else { @@ -186,21 +221,6 @@ const UploadComponent: React.FC = () => { } } - // If we have a file, add the file url to the note content - // and also to the note tags imeta - // "tags": [ - // [ - // "imeta", - // "url https://nostr.build/i/my-image.jpg", - // "m image/jpeg", - // "blurhash eVF$^OI:${M{o#*0-nNFxakD-?xVM}WEWB%iNKxvR-oetmo#R-aen$", - // "dim 3024x4032", - // "alt A scenic photo overlooking the coast of Costa Rica", - // "x ", - // "fallback https://nostrcheck.me/alt1.jpg", - // "fallback https://void.cat/alt1.jpg" - // ] - // ] if (finalFileUrl) { const image = new Image() image.src = URL.createObjectURL(file) @@ -233,33 +253,21 @@ const UploadComponent: React.FC = () => { let signedEvent: NostrEvent | null = null // Sign the actual note - signedEvent = await signEvent(loginType, JSON.stringify(noteEvent)) as NostrEvent - // if (loginType === "extension") { - // signedEvent = await window.nostr.signEvent(noteEvent) - // } else if (loginType === "amber") { - // // TODO: Sign event with amber - // alert("Signing with Amber is not implemented yet, sorry!") - // } else if (loginType === "raw_nsec") { - // if (typeof window !== "undefined") { - // let nsecStr = null - // nsecStr = window.localStorage.getItem("nsec") - // if (nsecStr != null) { - // signedEvent = finalizeEvent(noteEvent, hexToBytes(nsecStr)) - // } - // } - // } + signedEvent = (await signEvent(loginType, JSON.stringify(noteEvent))) as NostrEvent - // If the got a signed event, publish it to nostr + // If we got a signed event, publish it to nostr if (signedEvent) { console.log("final Event: ") console.log(signedEvent) publish(signedEvent) } - // Redirect to the note setIsLoading(false) if (signedEvent != null) { - window.location.href = "/note/" + nip19.noteEncode(signedEvent.id) + setUploadedNoteId(signedEvent.id) + setIsDrawerOpen(true) + setShouldFetch(true) + setRetryCount(0) } } @@ -274,17 +282,10 @@ const UploadComponent: React.FC = () => { id="description" className="w-full" > - - - Image Upload - -
- -
- {previewUrl && Preview} -
-
-
+
+ +
+ {previewUrl && Preview} {isLoading ? ( + )} + + + + + ) } diff --git a/lumina/components/spinner.tsx b/lumina/components/spinner.tsx new file mode 100644 index 0000000..942e152 --- /dev/null +++ b/lumina/components/spinner.tsx @@ -0,0 +1,20 @@ +import { cn } from "@/lib/utils" + +export function Spinner({ className }: { className?: string }) { + return ( + + + + ) +} \ No newline at end of file