import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Check, Copy, Link2, Search, Shield, User, Users } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/dropdown-menu"; import { getUserList } from "@/main-axios"; import { createSessionShare, getActiveSessionShares, revokeSessionShare, type SessionShareProtocol, type SessionSharePermissionLevel, type SessionShareRecord, } from "@/api/session-sharing-api"; const EXPIRY_PRESETS = [ { key: "oneHour", hours: 1 }, { key: "oneDay", hours: 24 }, { key: "sevenDays", hours: 24 * 7 }, { key: "thirtyDays", hours: 24 * 30 }, { key: "custom", hours: undefined }, ] as const; type ExpiryPresetKey = (typeof EXPIRY_PRESETS)[number]["key"]; export function ShareSessionModal({ open, onClose, hostId, sessionId, protocol, tabInstanceId, }: { open: boolean; onClose: () => void; hostId: number; sessionId: string | null; protocol: SessionShareProtocol; tabInstanceId?: string; }) { const { t } = useTranslation(); const [mode, setMode] = useState<"link" | "user">("link"); const [permissionLevel, setPermissionLevel] = useState("read-only"); const [expiryPreset, setExpiryPreset] = useState("oneDay"); const [customHours, setCustomHours] = useState(""); const [search, setSearch] = useState(""); const [users, setUsers] = useState<{ id: string; username: string }[]>([]); const [selectedUserId, setSelectedUserId] = useState(null); const [submitting, setSubmitting] = useState(false); const [createdLink, setCreatedLink] = useState(null); const [shares, setShares] = useState([]); const [sharesLoaded, setSharesLoaded] = useState(false); useEffect(() => { if (!open) return; setMode("link"); setPermissionLevel("read-only"); setExpiryPreset("oneDay"); setCustomHours(""); setSearch(""); setSelectedUserId(null); setCreatedLink(null); setSharesLoaded(false); setShares([]); }, [open, sessionId]); useEffect(() => { if (!open || sharesLoaded) return; setSharesLoaded(true); Promise.all([ getUserList().catch(() => ({ users: [] })), getActiveSessionShares(hostId).catch(() => ({ shares: [] })), ]).then(([usersRes, sharesRes]) => { setUsers( (usersRes.users ?? []).map((u) => ({ id: String(u.userId), username: u.username, })), ); setShares(sharesRes.shares ?? []); }); }, [open, hostId, sharesLoaded]); const filteredUsers = useMemo(() => { const q = search.trim().toLowerCase(); return q ? users.filter((u) => u.username.toLowerCase().includes(q)) : users; }, [users, search]); const expiryHours = (() => { if (expiryPreset === "custom") { const hours = Number(customHours); return Number.isFinite(hours) && hours > 0 ? hours : undefined; } return EXPIRY_PRESETS.find((p) => p.key === expiryPreset)?.hours; })(); async function refreshShares() { try { const res = await getActiveSessionShares(hostId); setShares(res.shares ?? []); } catch { // silently ignore } } async function handleCreate() { if (!sessionId) return; if (mode === "user" && !selectedUserId) return; if (expiryPreset === "custom" && !expiryHours) return; setSubmitting(true); try { const result = await createSessionShare({ hostId, sessionId, tabInstanceId, protocol, shareType: mode, targetUserId: mode === "user" ? (selectedUserId ?? undefined) : undefined, permissionLevel, expiryHours, }); if (mode === "link" && result.linkToken) { const url = `${window.location.origin}${window.location.pathname}?view=shared&token=${result.linkToken}`; setCreatedLink(url); toast.success(t("sessionSharing.linkCreated")); } else { toast.success(t("sessionSharing.shareCreated")); setSelectedUserId(null); } await refreshShares(); } catch (error) { const status = (error as { status?: number })?.status; if (mode === "user" && status === 403) { toast.error(t("sessionSharing.userLacksHostAccess")); } else { toast.error(t("sessionSharing.shareFailed")); } } finally { setSubmitting(false); } } async function handleCopyLink() { if (!createdLink) return; try { await navigator.clipboard.writeText(createdLink); toast.success(t("sessionSharing.linkCopied")); } catch { // clipboard API unavailable, ignore } } async function handleRevoke(shareId: string) { try { await revokeSessionShare(shareId); setShares((prev) => prev.filter((s) => s.id !== shareId)); toast.success(t("sessionSharing.revoked")); } catch { toast.error(t("sessionSharing.revokeFailed")); } } return ( !next && onClose()}> {t("sessionSharing.modalTitle")} {mode === "link" ? t("sessionSharing.linkModeDescription") : t("sessionSharing.userModeDescription")}
{(["link", "user"] as const).map((m) => ( ))}
{mode === "user" && ( <>
setSearch(e.target.value)} className="pl-8" />
{filteredUsers.length === 0 ? (
{t("sessionSharing.noUsersFound")}
) : ( filteredUsers.map((user) => { const isSelected = selectedUserId === user.id; return ( ); }) )}
)}
{t("sessionSharing.permissionLevel.label")}
{EXPIRY_PRESETS.map((preset) => ( setExpiryPreset(preset.key)} > {expiryPreset === preset.key ? ( ) : ( )} {t(`hosts.sharing.expiry.${preset.key}`)} ))}
{expiryPreset === "custom" && ( setCustomHours(e.target.value)} className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> )}

{permissionLevel === "read-only" ? t("sessionSharing.permissionLevel.readOnlyDescription") : t("sessionSharing.permissionLevel.readWriteDescription")}

{createdLink && (
)}
{t("sessionSharing.activeShares")} {shares.length > 0 && ( ({shares.length}) )}
{shares.length === 0 && (
{t("sessionSharing.noActiveShares")}
)} {shares.map((share) => { const targetUser = users.find( (u) => u.id === share.targetUserId, ); return (
{share.shareType === "link" ? ( ) : ( )}
{share.shareType === "link" ? t("sessionSharing.linkShareBadge") : t("sessionSharing.userShareBadge", { username: targetUser?.username ?? share.targetUserId ?? "?", })} {share.permissionLevel === "read-write" ? t("sessionSharing.permissionLevel.readWrite") : t("sessionSharing.permissionLevel.readOnly")} {" ยท "} {t("sessionSharing.expiresAt", { date: new Date(share.expiresAt).toLocaleString(), })}
); })}
); }