import React, { useCallback, useEffect, useState } from "react"; import { Trash2, X } from "lucide-react"; import { rolesApi, type FormGrant, type Role } from "../../../services/api/roles"; import { usersApi } from "../../../services/api/users"; import type { User } from "../../../services/api/types"; import { usePermission } from "../../hooks/usePermission"; const GRANT_ACTIONS = ["read", "write", "delete"] as const; export function ShareFormPanel({ formId, onClose }: { formId: string; onClose: () => void }) { const { canModule } = usePermission(); const [visibility, setVisibility] = useState<"private" | "org">("org"); const [grants, setGrants] = useState([]); const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); const [subjectType, setSubjectType] = useState<"user" | "role">("user"); const [subjectId, setSubjectId] = useState(""); const [newActions, setNewActions] = useState(["read"]); const load = useCallback(async () => { setLoading(true); setError(""); const [gRes, uRes, rRes] = await Promise.all([ rolesApi.listFormGrants(formId), usersApi.list(), rolesApi.list(), ]); if (gRes.error) { setError(gRes.error.message || "Failed to load sharing settings"); setLoading(false); return; } if (gRes.data?.defaultVisibility) { setVisibility(gRes.data.defaultVisibility as "private" | "org"); } setGrants(gRes.data?.grants ?? []); if (uRes.data?.users) setUsers(uRes.data.users); if (rRes.data?.roles) setRoles(rRes.data.roles); setLoading(false); }, [formId]); useEffect(() => { void load(); }, [load]); const handleVisibilityChange = async (v: "private" | "org") => { setVisibility(v); setBusy(true); setError(""); const res = await rolesApi.updateFormVisibility(formId, v); if (res.error) { setError(res.error.message || "Failed to update visibility"); void load(); } setBusy(false); }; const toggleNewAction = (action: string) => { setNewActions((prev) => prev.includes(action) ? prev.filter((a) => a !== action) : [...prev, action], ); }; const handleAddGrant = async () => { if (!subjectId) { setError("Select a user or role"); return; } if (!newActions.length) { setError("Select at least one permission"); return; } setBusy(true); setError(""); const res = await rolesApi.createFormGrant(formId, { subjectType, subjectId, actions: newActions, }); if (res.error) { setError(res.error.message || "Failed to add grant"); setBusy(false); return; } setSubjectId(""); setNewActions(["read"]); await load(); setBusy(false); }; const handleRemoveGrant = async (grantId: string) => { setBusy(true); setError(""); const res = await rolesApi.removeFormGrant(formId, grantId); if (res.error) { setError(res.error.message || "Failed to remove grant"); } else { await load(); } setBusy(false); }; if (!canModule("forms", "write")) return null; const overlayStyle: React.CSSProperties = { position: "fixed", inset: 0, background: "rgba(0,0,0,.4)", zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center", padding: 16, }; const panelStyle: React.CSSProperties = { background: "var(--card)", padding: 20, borderRadius: 12, minWidth: 360, maxWidth: 480, width: "100%", maxHeight: "90vh", overflow: "auto", border: "1px solid var(--border)", }; const labelStyle: React.CSSProperties = { display: "block", fontSize: "var(--text-xs)", color: "var(--muted)", marginBottom: 4, }; const inputStyle: React.CSSProperties = { width: "100%", padding: 8, borderRadius: 8, border: "1px solid var(--border)", background: "var(--background)", color: "var(--foreground)", fontSize: "var(--text-sm)", }; return (
e.stopPropagation()}>

Share form

{error && (

{error}

)} {loading ? (

Loading…

) : ( <>
{grants.length === 0 ? (

No explicit grants. {visibility === "org" ? "Form is visible to your organisation." : "Only the creator can access this form."}

) : (
    {grants.map((g) => (
  • {g.subjectLabel || g.subjectId} ({g.subjectType}) — {(g.actions || []).join(", ")}
  • ))}
)}

Add access

{GRANT_ACTIONS.map((a) => ( ))}
)}
); }