import { useRef, useState } from "react"; import { QRCodeSVG } from "qrcode.react"; import { sendCloud } from "../lib/sendCloud"; import { useProfileStore } from "../stores/useProfileStore"; type Stage = | { kind: "idle" } | { kind: "uploading"; loaded: number; total: number } | { kind: "done"; shareUrl: string; fileName: string; expiresAt: string; password: string | null } | { kind: "error"; message: string }; const EXPIRY_CHOICES: { label: string; days: number }[] = [ { label: "1 day", days: 1 }, { label: "7 days", days: 7 }, { label: "30 days", days: 30 }, ]; function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; } export default function CloudSharePanel() { const deviceId = useProfileStore((s) => s.deviceId); const [stage, setStage] = useState({ kind: "idle" }); const [file, setFile] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [expiryDays, setExpiryDays] = useState(7); const [copied, setCopied] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); const fileRef = useRef(null); const reset = () => { setStage({ kind: "idle" }); setFile(null); setEmail(""); setPassword(""); setExpiryDays(7); setShowAdvanced(false); }; const handleSend = async () => { if (!file) return; setStage({ kind: "uploading", loaded: 0, total: file.size }); try { const result = await sendCloud(file, { deviceId, recipientEmail: email.trim() || undefined, password: password.trim() || undefined, expiresInDays: expiryDays, onProgress: (loaded, total) => setStage({ kind: "uploading", loaded, total }), }); setStage({ kind: "done", shareUrl: result.shareUrl, fileName: file.name, expiresAt: result.expiresAt, password: password.trim() || null, }); } catch (err) { setStage({ kind: "error", message: err instanceof Error ? err.message : "unknown" }); } }; const copy = (value: string) => { navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); }; const canShare = typeof navigator !== "undefined" && typeof navigator.share === "function"; const share = async (url: string, fileName: string) => { try { await navigator.share({ title: `AnyDrop — ${fileName}`, text: "File shared via AnyDrop", url, }); } catch { // User canceled the share sheet, or the browser rejected — nothing to do. } }; return (
{stage.kind === "idle" && ( <> { const f = e.target.files?.[0]; if (f) setFile(f); }} /> {file ? (
File
{file.name}
{formatSize(file.size)}
) : ( )} {showAdvanced && (
setEmail(e.target.value)} placeholder="friend@example.com" className={inputCls} /> setPassword(e.target.value)} placeholder="Leave blank for none" minLength={4} className={inputCls} />
{EXPIRY_CHOICES.map((c) => ( ))}
)}

Sealed in your browser. Only the link holder {password && " + password"} can open it.

)} {stage.kind === "uploading" && ( <>
Uploading ciphertext

Sealing and uploading…

0 ? Math.round((stage.loaded / stage.total) * 100) : 0}%`, }} />

{formatSize(stage.loaded)} / {formatSize(stage.total)}

)} {stage.kind === "done" && ( <>
Ready to share
{stage.fileName}
Expires {new Date(stage.expiresAt).toLocaleDateString()} · one download
{canShare && ( )}

{stage.shareUrl}

{stage.password && (
Password (share separately)
{stage.password}
)} )} {stage.kind === "error" && ( <>
Failed

Could not complete the transfer

{stage.message}

)}
); } const inputCls = "w-full px-3 py-2 bg-paper border border-paper-edge rounded-sm text-ink text-sm " + "placeholder:text-ink-faint focus:outline-none focus:border-ink transition-colors " + "duration-fast ease-crisp"; function Field({ label, children }: { label: string; children: React.ReactNode }) { return ( ); }