import { useCallback, useEffect, useMemo, useState } from "react"; import { isAxiosError } from "axios"; import { useNavigate, useParams } from "react-router-dom"; import { Alert, Badge, Button, Group, Loader, Paper, Stack, Text, Title, } from "@mantine/core"; import { useTranslation } from "react-i18next"; import DownloadIcon from "@mui/icons-material/Download"; import LoginIcon from "@mui/icons-material/Login"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import { useFileActions } from "@app/contexts/FileContext"; import { useNavigationActions } from "@app/contexts/NavigationContext"; import { alert } from "@app/components/toast"; import { downloadShareLink, fetchShareLinkMetadata, importShareLinkToWorkbench, ShareLinkMetadata, } from "@app/services/shareLinkImport"; type ShareLinkStatus = | "loading" | "ready" | "login" | "forbidden" | "notfound" | "error"; export default function ShareLinkPage() { const { token } = useParams<{ token: string }>(); const { actions } = useFileActions(); const { actions: navActions } = useNavigationActions(); const navigate = useNavigate(); const { t } = useTranslation(); const [status, setStatus] = useState("loading"); const [metadata, setMetadata] = useState(null); const [isWorking, setIsWorking] = useState(false); const normalizedToken = useMemo(() => (token || "").trim(), [token]); const shareRole = (metadata?.accessRole ?? "viewer").toLowerCase(); const hasReadAccess = shareRole === "editor" || shareRole === "commenter" || shareRole === "viewer"; const canDownload = hasReadAccess; const canOpen = hasReadAccess; const loadMetadata = useCallback(async () => { if (!normalizedToken) { setStatus("notfound"); return; } setStatus("loading"); try { const data = await fetchShareLinkMetadata(normalizedToken); setMetadata(data); setStatus("ready"); } catch (error: unknown) { const statusCode = isAxiosError(error) ? error.response?.status : undefined; if (statusCode === 401) { setStatus("login"); } else if (statusCode === 403) { setStatus("forbidden"); } else if (statusCode === 404 || statusCode === 410) { setStatus("notfound"); } else { setStatus("error"); } } }, [normalizedToken]); useEffect(() => { void loadMetadata(); }, [loadMetadata]); const handleLogin = useCallback(() => { navigate("/login", { replace: true, state: { from: { pathname: `/share/${normalizedToken}` } }, }); }, [navigate, normalizedToken]); const handleDownload = useCallback(async () => { if (!normalizedToken || !canDownload) return; setIsWorking(true); try { const { blob, filename } = await downloadShareLink(normalizedToken); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename || "shared-file"; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); } catch (error: unknown) { const statusCode = isAxiosError(error) ? error.response?.status : undefined; if (statusCode === 401) { setStatus("login"); } else if (statusCode === 403) { setStatus("forbidden"); } else if (statusCode === 404 || statusCode === 410) { setStatus("notfound"); } else { alert({ alertType: "error", title: t( "storageShare.downloadFailed", "Unable to download this file.", ), expandable: false, durationMs: 3500, }); } } finally { setIsWorking(false); } }, [canDownload, normalizedToken, t]); const handleOpen = useCallback(async () => { if (!normalizedToken || !canOpen) return; setIsWorking(true); try { const selectedIds = await importShareLinkToWorkbench( normalizedToken, actions, metadata, ); if (selectedIds.length > 0) { actions.setSelectedFiles(selectedIds); } navActions.setWorkbench("viewer"); navigate("/", { replace: true }); } catch (error: unknown) { const statusCode = isAxiosError(error) ? error.response?.status : undefined; if (statusCode === 401) { setStatus("login"); } else if (statusCode === 403) { setStatus("forbidden"); } else if (statusCode === 404 || statusCode === 410) { setStatus("notfound"); } else { alert({ alertType: "error", title: t("storageShare.loadFailed", "Unable to open shared file."), expandable: false, durationMs: 3500, }); } } finally { setIsWorking(false); } }, [actions, canOpen, metadata, navActions, navigate, normalizedToken, t]); const title = metadata?.fileName || t("storageShare.titleDefault", "Shared file"); const ownerLabel = metadata?.owner || t("storageShare.ownerUnknown", "Unknown"); return (
{t("storageShare.shareHeading", "Shared file")} {metadata?.accessRole && ( {shareRole === "editor" ? t("storageShare.roleEditor", "Editor") : shareRole === "commenter" ? t("storageShare.roleCommenter", "Commenter") : t("storageShare.roleViewer", "Viewer")} )} {status === "loading" && ( {t("storageShare.loading", "Loading share link...")} )} {status === "ready" && ( <> {title} {t("storageShare.ownerLabel", "Owner")}: {ownerLabel} {metadata?.createdAt && ( {t("storageShare.createdAt", "Created")}{" "} {new Date(metadata.createdAt).toLocaleString()} )} {!canDownload && ( {shareRole === "commenter" ? t( "storageShare.accessLimitedCommenter", "Comment access is coming soon. Ask the owner for editor access if you need to download.", ) : t( "storageShare.accessLimitedViewer", "This link is view-only. Ask the owner for editor access if you need to download.", )} )} )} {status === "login" && ( {t( "storageShare.loginPrompt", "Sign in to access this shared file.", )} )} {status === "forbidden" && ( {t( "storageShare.accessDeniedBody", "You do not have access to this file. Ask the owner to share it with you.", )} )} {status === "notfound" && ( {t( "storageShare.expiredBody", "This share link is invalid or has expired.", )} )} {status === "error" && ( {t("storageShare.tryAgain", "Please try again later.")} )}
); }