import React, { useState } from "react"; import { Share2, Calendar, Download, Copy, Check, Loader2, Clock, Shield, ExternalLink, } from "lucide-react"; import { cn } from "@/lib/utils"; import { Modal, CustomButton } from "./"; import { useAppTheme } from "@/hooks/useAppTheme"; import fileAttachmentService, { type FileAttachment } from "@/services/file-attachment-service"; import { DeleteConfirmationModal } from "./DeleteConfirmationModal"; interface FileShareModalProps { isOpen: boolean; onClose: () => void; file: FileAttachment; } export const FileShareModal: React.FC = ({ isOpen, onClose, file, }) => { const [expiresInHours, setExpiresInHours] = useState(24); const [maxDownloads, setMaxDownloads] = useState(""); const permissions = "download"; const { primaryColor } = useAppTheme(); const [isSharing, setIsSharing] = useState(false); const [shareData, setShareData] = useState<{ url: string; token: string; id: string } | null>(null); const [isRevoking, setIsRevoking] = useState(false); const [showRevokeConfirm, setShowRevokeConfirm] = useState(false); const [copied, setCopied] = useState(false); const handleCreateShare = async () => { setIsSharing(true); try { const baseUrl = import.meta.env.VITE_API_BASE_URL || "http://localhost:3000/api/v1"; const res = await fileAttachmentService.createShare(file.id, { share_type: "link", permissions, expires_in_hours: expiresInHours || undefined, max_downloads: maxDownloads === "" ? null : Number(maxDownloads), }); const fullUrl = `${baseUrl}/files/shared/${res.data.share_token}`; setShareData({ url: fullUrl, token: res.data.share_token, id: res.data.id }); } catch (error) { console.error("Failed to share:", error); } finally { setIsSharing(false); } }; const handleRevokeShare = async () => { if (!shareData) return; setIsRevoking(true); try { await fileAttachmentService.revokeShare(shareData.id); setShareData(null); setShowRevokeConfirm(false); } catch (error) { console.error("Failed to revoke share:", error); alert("Failed to revoke share link. Please try again."); } finally { setIsRevoking(false); } }; const copyToClipboard = async () => { if (!shareData) return; try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(shareData.url); setCopied(true); setTimeout(() => setCopied(false), 2000); } else { throw new Error("Clipboard API unavailable"); } } catch (err) { const textArea = document.createElement("textarea"); textArea.value = shareData.url; textArea.style.position = "fixed"; textArea.style.left = "-9999px"; textArea.style.top = "0"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand("copy"); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (copyErr) { console.error("Fallback copy failed:", copyErr); } document.body.removeChild(textArea); } }; return (
{!shareData ? ( <> {/* Expiry */}
{[1, 24, 72, 168].map((h) => ( ))}
{/* Max Downloads */}
setMaxDownloads(e.target.value === "" ? "" : Number(e.target.value))} placeholder="Unlimited" className="w-full h-10 border border-[rgba(0,0,0,0.12)] rounded-lg px-3 text-sm focus:outline-none focus:ring-1 transition-all" style={{ '--tw-ring-color': primaryColor, borderColor: 'rgba(0,0,0,0.12)' } as React.CSSProperties} />
{/* Permissions */}
Download
} className="rounded-xl shadow-lg" style={{ boxShadow: `${primaryColor}33 0px 8px 24px` }} > Generate Secure Link ) : (

Share Link Generated

Anyone with this link can {permissions === 'download' ? 'download' : 'view'} the file until {new Date(Date.now() + expiresInHours * 3600000).toLocaleString()}.

{shareData.url}
setShareData(null)} > Create Another } onClick={() => window.open(shareData.url, '_blank')} > Test Link
)}

Links are automatically revoked after expiration or reaching max downloads for security.

setShowRevokeConfirm(false)} onConfirm={handleRevokeShare} title="Revoke Share Link" message="Are you sure you want to revoke this share link? It will stop working immediately." isLoading={isRevoking} />
); };