@omnibase/shadcn 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -35,9 +35,11 @@ __export(index_exports, {
35
35
  PricingTable: () => PricingTable,
36
36
  RecoveryForm: () => RecoveryForm,
37
37
  RegistrationForm: () => RegistrationForm,
38
+ RoleCreator: () => RoleCreator,
38
39
  SettingsForm: () => SettingsForm,
39
40
  SwitchActiveTenant: () => SwitchActiveTenant,
40
41
  TenantCreator: () => TenantCreator,
42
+ UserInvite: () => UserInvite,
41
43
  VerificationForm: () => VerificationForm,
42
44
  filterInputNodes: () => filterInputNodes,
43
45
  findAnchorNode: () => findAnchorNode,
@@ -1567,15 +1569,466 @@ function TenantCreator({
1567
1569
  ] })
1568
1570
  ] });
1569
1571
  }
1572
+
1573
+ // src/user-invite/index.tsx
1574
+ var import_react2 = require("react");
1575
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1576
+ function UserInvite({ roles, onInvite }) {
1577
+ const [email, setEmail] = (0, import_react2.useState)("");
1578
+ const [selectedRole, setSelectedRole] = (0, import_react2.useState)("");
1579
+ const [isSubmitting, setIsSubmitting] = (0, import_react2.useState)(false);
1580
+ const [error, setError] = (0, import_react2.useState)(null);
1581
+ const isValidEmail = (email2) => {
1582
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1583
+ return emailRegex.test(email2);
1584
+ };
1585
+ const isFormValid = () => {
1586
+ return email.trim() !== "" && isValidEmail(email) && selectedRole !== "";
1587
+ };
1588
+ const handleSubmit = async (e) => {
1589
+ e.preventDefault();
1590
+ setError(null);
1591
+ if (!isFormValid()) {
1592
+ setError("Please fill in all fields with valid data");
1593
+ return;
1594
+ }
1595
+ setIsSubmitting(true);
1596
+ try {
1597
+ const inviteData = {
1598
+ email: email.trim(),
1599
+ role: selectedRole
1600
+ };
1601
+ await onInvite?.(inviteData);
1602
+ setEmail("");
1603
+ setSelectedRole("");
1604
+ } catch (err) {
1605
+ setError(
1606
+ err instanceof Error ? err.message : "Failed to send invitation"
1607
+ );
1608
+ } finally {
1609
+ setIsSubmitting(false);
1610
+ }
1611
+ };
1612
+ const handleReset = () => {
1613
+ setEmail("");
1614
+ setSelectedRole("");
1615
+ setError(null);
1616
+ };
1617
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Card, { className: "w-full max-w-2xl", children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CardHeader, { children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CardTitle, { children: "Invite User" }),
1620
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CardDescription, { children: "Send an invitation to a new user to join your organization" })
1621
+ ] }),
1622
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CardContent, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-6", children: [
1623
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "space-y-2", children: [
1624
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: "email", children: "Email Address" }),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1626
+ Input,
1627
+ {
1628
+ id: "email",
1629
+ type: "email",
1630
+ placeholder: "colleague@company.com",
1631
+ value: email,
1632
+ onChange: (e) => setEmail(e.target.value),
1633
+ "aria-invalid": email !== "" && !isValidEmail(email)
1634
+ }
1635
+ ),
1636
+ email !== "" && !isValidEmail(email) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-xs text-destructive", children: "Please enter a valid email address" })
1637
+ ] }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "space-y-2", children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: "role", children: "Role" }),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Select, { value: selectedRole, onValueChange: setSelectedRole, children: [
1641
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectTrigger, { id: "role", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectValue, { placeholder: "Select a role" }) }),
1642
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectContent, { children: roles.length > 0 ? roles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectItem, { value: role, children: role }, role)) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectItem, { value: "no-roles", disabled: true, children: "No roles available" }) })
1643
+ ] }),
1644
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-xs text-muted-foreground", children: "The role determines what permissions the user will have" })
1645
+ ] }),
1646
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "p-3 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-sm text-destructive", children: error }) }),
1647
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex justify-end space-x-3", children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1649
+ Button,
1650
+ {
1651
+ type: "button",
1652
+ variant: "outline",
1653
+ onClick: handleReset,
1654
+ disabled: isSubmitting,
1655
+ children: "Reset"
1656
+ }
1657
+ ),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { type: "submit", disabled: !isFormValid() || isSubmitting, children: isSubmitting ? "Sending..." : "Send Invitation" })
1659
+ ] })
1660
+ ] }) })
1661
+ ] });
1662
+ }
1663
+
1664
+ // src/role-creator/index.tsx
1665
+ var import_react3 = require("react");
1666
+
1667
+ // src/components/ui/checkbox.tsx
1668
+ var React14 = require("react");
1669
+ var CheckboxPrimitive = __toESM(require("@radix-ui/react-checkbox"), 1);
1670
+ var import_lucide_react3 = require("lucide-react");
1671
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1672
+ function Checkbox({
1673
+ className,
1674
+ ...props
1675
+ }) {
1676
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1677
+ CheckboxPrimitive.Root,
1678
+ {
1679
+ "data-slot": "checkbox",
1680
+ className: cn(
1681
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
1682
+ className
1683
+ ),
1684
+ ...props,
1685
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1686
+ CheckboxPrimitive.Indicator,
1687
+ {
1688
+ "data-slot": "checkbox-indicator",
1689
+ className: "grid place-content-center text-current transition-none",
1690
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react3.CheckIcon, { className: "size-3.5" })
1691
+ }
1692
+ )
1693
+ }
1694
+ );
1695
+ }
1696
+
1697
+ // src/components/ui/separator.tsx
1698
+ var React15 = require("react");
1699
+ var SeparatorPrimitive = __toESM(require("@radix-ui/react-separator"), 1);
1700
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1701
+ function Separator2({
1702
+ className,
1703
+ orientation = "horizontal",
1704
+ decorative = true,
1705
+ ...props
1706
+ }) {
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1708
+ SeparatorPrimitive.Root,
1709
+ {
1710
+ "data-slot": "separator",
1711
+ decorative,
1712
+ orientation,
1713
+ className: cn(
1714
+ "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
1715
+ className
1716
+ ),
1717
+ ...props
1718
+ }
1719
+ );
1720
+ }
1721
+
1722
+ // src/role-creator/index.tsx
1723
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1724
+ function RoleCreator({
1725
+ definitions,
1726
+ roles,
1727
+ namespaceMap = {},
1728
+ onRoleCreate,
1729
+ onRoleUpdate
1730
+ }) {
1731
+ const [roleName, setRoleName] = (0, import_react3.useState)("");
1732
+ const [selectedPermissions, setSelectedPermissions] = (0, import_react3.useState)(
1733
+ /* @__PURE__ */ new Set()
1734
+ );
1735
+ const [showSuggestions, setShowSuggestions] = (0, import_react3.useState)(false);
1736
+ const [isEditMode, setIsEditMode] = (0, import_react3.useState)(false);
1737
+ const [editingRoleId, setEditingRoleId] = (0, import_react3.useState)(null);
1738
+ const { tenantNamespace, fineGrainedNamespaces } = (0, import_react3.useMemo)(() => {
1739
+ const tenant = definitions.find(
1740
+ (def) => def.namespace.toLowerCase() === "tenant"
1741
+ );
1742
+ const fineGrained = definitions.filter(
1743
+ (def) => def.namespace.toLowerCase() !== "tenant"
1744
+ );
1745
+ return {
1746
+ tenantNamespace: tenant,
1747
+ fineGrainedNamespaces: fineGrained
1748
+ };
1749
+ }, [definitions]);
1750
+ const roleSuggestions = (0, import_react3.useMemo)(() => {
1751
+ return roles.map((role) => role.role_name);
1752
+ }, [roles]);
1753
+ const handleRoleNameChange = (value) => {
1754
+ setRoleName(value);
1755
+ const existingRole = roles.find(
1756
+ (role) => role.role_name.toLowerCase() === value.toLowerCase()
1757
+ );
1758
+ if (existingRole) {
1759
+ setIsEditMode(true);
1760
+ setEditingRoleId(existingRole.id);
1761
+ const permissions = /* @__PURE__ */ new Set();
1762
+ existingRole.permissions.forEach((perm) => {
1763
+ permissions.add(perm);
1764
+ });
1765
+ setSelectedPermissions(permissions);
1766
+ } else {
1767
+ setIsEditMode(false);
1768
+ setEditingRoleId(null);
1769
+ }
1770
+ };
1771
+ const buildPermissionString = (namespace, relation, resourceId) => {
1772
+ if (resourceId) {
1773
+ return `${namespace.toLowerCase()}:${resourceId}#${relation}`;
1774
+ }
1775
+ return `${namespace.toLowerCase()}#${relation}`;
1776
+ };
1777
+ const togglePermission = (permissionString) => {
1778
+ const newPermissions = new Set(selectedPermissions);
1779
+ if (newPermissions.has(permissionString)) {
1780
+ newPermissions.delete(permissionString);
1781
+ } else {
1782
+ newPermissions.add(permissionString);
1783
+ }
1784
+ setSelectedPermissions(newPermissions);
1785
+ };
1786
+ const handleSubmit = () => {
1787
+ if (!roleName.trim()) return;
1788
+ const permissionsArray = Array.from(selectedPermissions);
1789
+ if (isEditMode && editingRoleId) {
1790
+ onRoleUpdate?.({
1791
+ role_id: editingRoleId,
1792
+ role_name: roleName,
1793
+ permissions: permissionsArray
1794
+ });
1795
+ } else {
1796
+ onRoleCreate?.({
1797
+ role_name: roleName,
1798
+ permissions: permissionsArray
1799
+ });
1800
+ }
1801
+ };
1802
+ const handleReset = () => {
1803
+ setRoleName("");
1804
+ setSelectedPermissions(/* @__PURE__ */ new Set());
1805
+ setIsEditMode(false);
1806
+ setEditingRoleId(null);
1807
+ };
1808
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Card, { className: "w-full max-w-4xl", children: [
1809
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardHeader, { children: [
1810
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CardTitle, { children: isEditMode ? `Edit Role: ${roleName}` : "Create New Role" }),
1811
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CardDescription, { children: isEditMode ? "Update permissions for this existing role" : "Define a new role with specific permissions" })
1812
+ ] }),
1813
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(CardContent, { className: "space-y-6", children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "space-y-2 relative", children: [
1815
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Label, { htmlFor: "role-name", children: "Role Name" }),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1817
+ Input,
1818
+ {
1819
+ id: "role-name",
1820
+ placeholder: "Enter role name (e.g., admin, developer, viewer)",
1821
+ value: roleName,
1822
+ onChange: (e) => handleRoleNameChange(e.target.value),
1823
+ onFocus: () => setShowSuggestions(true),
1824
+ onBlur: () => setTimeout(() => setShowSuggestions(false), 200)
1825
+ }
1826
+ ),
1827
+ showSuggestions && roleSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "absolute z-10 w-full mt-1 bg-background border rounded-md shadow-lg max-h-48 overflow-y-auto", children: roleSuggestions.filter(
1828
+ (suggestion) => suggestion.toLowerCase().includes(roleName.toLowerCase())
1829
+ ).map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1830
+ "button",
1831
+ {
1832
+ className: "w-full px-4 py-2 text-left hover:bg-accent hover:text-accent-foreground text-sm",
1833
+ onMouseDown: () => {
1834
+ handleRoleNameChange(suggestion);
1835
+ setShowSuggestions(false);
1836
+ },
1837
+ children: suggestion
1838
+ },
1839
+ suggestion
1840
+ )) }),
1841
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-xs text-amber-600 dark:text-amber-400", children: "\u26A0 Editing existing role - changes will update all users with this role" })
1842
+ ] }),
1843
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Separator2, {}),
1844
+ tenantNamespace && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "space-y-4", children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "text-lg font-semibold", children: "Organization Permissions" }),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-muted-foreground", children: "Tenant-wide permissions that apply across the entire organization" })
1848
+ ] }),
1849
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pl-4", children: tenantNamespace.relations.map((relation) => {
1850
+ const permissionString = buildPermissionString(
1851
+ tenantNamespace.namespace,
1852
+ relation
1853
+ );
1854
+ const isChecked = selectedPermissions.has(permissionString);
1855
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1856
+ "div",
1857
+ {
1858
+ className: "flex items-center space-x-2",
1859
+ children: [
1860
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1861
+ Checkbox,
1862
+ {
1863
+ id: permissionString,
1864
+ checked: isChecked,
1865
+ onCheckedChange: () => togglePermission(permissionString)
1866
+ }
1867
+ ),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1869
+ Label,
1870
+ {
1871
+ htmlFor: permissionString,
1872
+ className: "text-sm font-normal cursor-pointer",
1873
+ children: relation.replace(/_/g, " ")
1874
+ }
1875
+ )
1876
+ ]
1877
+ },
1878
+ permissionString
1879
+ );
1880
+ }) })
1881
+ ] }),
1882
+ fineGrainedNamespaces.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1883
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Separator2, {}),
1884
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "space-y-6", children: [
1885
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [
1886
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "text-lg font-semibold", children: "Fine-Grained Permissions" }),
1887
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-muted-foreground", children: "Resource-specific permissions that require an object ID" })
1888
+ ] }),
1889
+ fineGrainedNamespaces.map((namespace) => {
1890
+ const namespaceLower = namespace.namespace.toLowerCase();
1891
+ const resourceMap = namespaceMap[namespaceLower] || [];
1892
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "pl-4", children: [
1893
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h4", { className: "text-md font-medium capitalize", children: namespace.namespace }),
1894
+ resourceMap.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-4 space-y-6", children: resourceMap.map((resource) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1895
+ "div",
1896
+ {
1897
+ className: "border rounded-lg p-4 space-y-3 bg-muted/30",
1898
+ children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center justify-between", children: [
1900
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "font-medium text-sm", children: resource.label }),
1901
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-xs text-muted-foreground font-mono", children: resource.id })
1902
+ ] }),
1903
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: namespace.relations.map((relation) => {
1904
+ const permissionString = buildPermissionString(
1905
+ namespace.namespace,
1906
+ relation,
1907
+ resource.id
1908
+ );
1909
+ const isChecked = selectedPermissions.has(permissionString);
1910
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1911
+ "div",
1912
+ {
1913
+ className: "flex items-center space-x-2",
1914
+ children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1916
+ Checkbox,
1917
+ {
1918
+ id: permissionString,
1919
+ checked: isChecked,
1920
+ onCheckedChange: () => togglePermission(permissionString)
1921
+ }
1922
+ ),
1923
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1924
+ Label,
1925
+ {
1926
+ htmlFor: permissionString,
1927
+ className: "text-sm font-normal cursor-pointer",
1928
+ children: relation.replace(/_/g, " ")
1929
+ }
1930
+ )
1931
+ ]
1932
+ },
1933
+ permissionString
1934
+ );
1935
+ }) })
1936
+ ]
1937
+ },
1938
+ resource.id
1939
+ )) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-3 p-4 border-2 border-dashed rounded-lg text-center", children: [
1940
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1941
+ "No ",
1942
+ namespace.namespace.toLowerCase(),
1943
+ " resources available"
1944
+ ] }),
1945
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("p", { className: "text-xs text-muted-foreground mt-1", children: [
1946
+ "Use wildcard permissions (e.g., ",
1947
+ namespaceLower,
1948
+ ":*#permission) for all resources"
1949
+ ] })
1950
+ ] }),
1951
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-4 space-y-2", children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Label, { className: "text-sm font-medium", children: [
1953
+ "Wildcard Permissions (All ",
1954
+ namespace.namespace,
1955
+ "s)"
1956
+ ] }),
1957
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: namespace.relations.map((relation) => {
1958
+ const permissionString = buildPermissionString(
1959
+ namespace.namespace,
1960
+ relation,
1961
+ "*"
1962
+ );
1963
+ const isChecked = selectedPermissions.has(permissionString);
1964
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1965
+ "div",
1966
+ {
1967
+ className: "flex items-center space-x-2",
1968
+ children: [
1969
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1970
+ Checkbox,
1971
+ {
1972
+ id: permissionString,
1973
+ checked: isChecked,
1974
+ onCheckedChange: () => togglePermission(permissionString)
1975
+ }
1976
+ ),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1978
+ Label,
1979
+ {
1980
+ htmlFor: permissionString,
1981
+ className: "text-sm font-normal cursor-pointer",
1982
+ children: [
1983
+ relation.replace(/_/g, " "),
1984
+ " (all)"
1985
+ ]
1986
+ }
1987
+ )
1988
+ ]
1989
+ },
1990
+ permissionString
1991
+ );
1992
+ }) })
1993
+ ] })
1994
+ ] }) }, namespace.id);
1995
+ })
1996
+ ] })
1997
+ ] }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Separator2, {}),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "space-y-2", children: [
2000
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Label, { className: "text-sm font-medium", children: [
2001
+ "Selected Permissions (",
2002
+ selectedPermissions.size,
2003
+ ")"
2004
+ ] }),
2005
+ selectedPermissions.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "p-4 bg-muted rounded-md max-h-40 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "space-y-1 text-xs font-mono", children: Array.from(selectedPermissions).map((perm) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { className: "text-muted-foreground", children: perm }, perm)) }) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-muted-foreground italic", children: "No permissions selected" })
2006
+ ] }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex justify-end space-x-3", children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { variant: "outline", onClick: handleReset, children: "Reset" }),
2009
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2010
+ Button,
2011
+ {
2012
+ onClick: handleSubmit,
2013
+ disabled: !roleName.trim() || selectedPermissions.size === 0,
2014
+ children: isEditMode ? "Update Role" : "Create Role"
2015
+ }
2016
+ )
2017
+ ] })
2018
+ ] })
2019
+ ] });
2020
+ }
1570
2021
  // Annotate the CommonJS export names for ESM import in node:
1571
2022
  0 && (module.exports = {
1572
2023
  LoginForm,
1573
2024
  PricingTable,
1574
2025
  RecoveryForm,
1575
2026
  RegistrationForm,
2027
+ RoleCreator,
1576
2028
  SettingsForm,
1577
2029
  SwitchActiveTenant,
1578
2030
  TenantCreator,
2031
+ UserInvite,
1579
2032
  VerificationForm,
1580
2033
  filterInputNodes,
1581
2034
  findAnchorNode,
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ import { LoginFlow, RegistrationFlow, VerificationFlow, RecoveryFlow, SettingsFl
3
3
  import * as React$1 from 'react';
4
4
  import { Tenant } from '@omnibase/core-js/tenants';
5
5
  import { Product } from '@omnibase/core-js/payments';
6
+ import { NamespaceDefinition, Role } from '@omnibase/core-js';
6
7
 
7
8
  type LoginFormProps = {
8
9
  flow: LoginFlow;
@@ -126,4 +127,34 @@ interface TenantCreatorProps {
126
127
  }
127
128
  declare function TenantCreator({ config, formActions, className, }: TenantCreatorProps): react_jsx_runtime.JSX.Element;
128
129
 
129
- export { type CustomFormProps, type FlowType, LoginForm, type LoginFormProps, type NodesByGroup, PricingTable, type PricingTableProps, RecoveryForm, type RecoveryFormProps, RegistrationForm, type RegistrationFormProps, SettingsForm, type SettingsFormProps, SwitchActiveTenant, type SwitchActiveTenantProps, TenantCreator, type TenantCreatorConfig, type TenantCreatorFormActions, type TenantCreatorProps, VerificationForm, type VerificationFormProps, filterInputNodes, findAnchorNode, findCsrfToken, findSubmitButton, groupNodesByGroup, isUiNodeInputAttributes, sortNodes };
130
+ interface UserInviteData {
131
+ email: string;
132
+ role: string;
133
+ }
134
+ interface UserInviteProps {
135
+ roles: string[];
136
+ onInvite?: (inviteData: UserInviteData) => void | Promise<void>;
137
+ }
138
+ declare function UserInvite({ roles, onInvite }: UserInviteProps): react_jsx_runtime.JSX.Element;
139
+
140
+ interface NamespaceMapEntry {
141
+ id: string;
142
+ label: string;
143
+ }
144
+ interface RoleCreatorProps {
145
+ definitions: NamespaceDefinition[];
146
+ roles: Role[];
147
+ namespaceMap?: Record<string, NamespaceMapEntry[]>;
148
+ onRoleCreate?: (roleData: {
149
+ role_name: string;
150
+ permissions: string[];
151
+ }) => void;
152
+ onRoleUpdate?: (roleData: {
153
+ role_id: string;
154
+ role_name: string;
155
+ permissions: string[];
156
+ }) => void;
157
+ }
158
+ declare function RoleCreator({ definitions, roles, namespaceMap, onRoleCreate, onRoleUpdate, }: RoleCreatorProps): react_jsx_runtime.JSX.Element;
159
+
160
+ export { type CustomFormProps, type FlowType, LoginForm, type LoginFormProps, type NodesByGroup, PricingTable, type PricingTableProps, RecoveryForm, type RecoveryFormProps, RegistrationForm, type RegistrationFormProps, RoleCreator, SettingsForm, type SettingsFormProps, SwitchActiveTenant, type SwitchActiveTenantProps, TenantCreator, type TenantCreatorConfig, type TenantCreatorFormActions, type TenantCreatorProps, UserInvite, VerificationForm, type VerificationFormProps, filterInputNodes, findAnchorNode, findCsrfToken, findSubmitButton, groupNodesByGroup, isUiNodeInputAttributes, sortNodes };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { LoginFlow, RegistrationFlow, VerificationFlow, RecoveryFlow, SettingsFl
3
3
  import * as React$1 from 'react';
4
4
  import { Tenant } from '@omnibase/core-js/tenants';
5
5
  import { Product } from '@omnibase/core-js/payments';
6
+ import { NamespaceDefinition, Role } from '@omnibase/core-js';
6
7
 
7
8
  type LoginFormProps = {
8
9
  flow: LoginFlow;
@@ -126,4 +127,34 @@ interface TenantCreatorProps {
126
127
  }
127
128
  declare function TenantCreator({ config, formActions, className, }: TenantCreatorProps): react_jsx_runtime.JSX.Element;
128
129
 
129
- export { type CustomFormProps, type FlowType, LoginForm, type LoginFormProps, type NodesByGroup, PricingTable, type PricingTableProps, RecoveryForm, type RecoveryFormProps, RegistrationForm, type RegistrationFormProps, SettingsForm, type SettingsFormProps, SwitchActiveTenant, type SwitchActiveTenantProps, TenantCreator, type TenantCreatorConfig, type TenantCreatorFormActions, type TenantCreatorProps, VerificationForm, type VerificationFormProps, filterInputNodes, findAnchorNode, findCsrfToken, findSubmitButton, groupNodesByGroup, isUiNodeInputAttributes, sortNodes };
130
+ interface UserInviteData {
131
+ email: string;
132
+ role: string;
133
+ }
134
+ interface UserInviteProps {
135
+ roles: string[];
136
+ onInvite?: (inviteData: UserInviteData) => void | Promise<void>;
137
+ }
138
+ declare function UserInvite({ roles, onInvite }: UserInviteProps): react_jsx_runtime.JSX.Element;
139
+
140
+ interface NamespaceMapEntry {
141
+ id: string;
142
+ label: string;
143
+ }
144
+ interface RoleCreatorProps {
145
+ definitions: NamespaceDefinition[];
146
+ roles: Role[];
147
+ namespaceMap?: Record<string, NamespaceMapEntry[]>;
148
+ onRoleCreate?: (roleData: {
149
+ role_name: string;
150
+ permissions: string[];
151
+ }) => void;
152
+ onRoleUpdate?: (roleData: {
153
+ role_id: string;
154
+ role_name: string;
155
+ permissions: string[];
156
+ }) => void;
157
+ }
158
+ declare function RoleCreator({ definitions, roles, namespaceMap, onRoleCreate, onRoleUpdate, }: RoleCreatorProps): react_jsx_runtime.JSX.Element;
159
+
160
+ export { type CustomFormProps, type FlowType, LoginForm, type LoginFormProps, type NodesByGroup, PricingTable, type PricingTableProps, RecoveryForm, type RecoveryFormProps, RegistrationForm, type RegistrationFormProps, RoleCreator, SettingsForm, type SettingsFormProps, SwitchActiveTenant, type SwitchActiveTenantProps, TenantCreator, type TenantCreatorConfig, type TenantCreatorFormActions, type TenantCreatorProps, UserInvite, VerificationForm, type VerificationFormProps, filterInputNodes, findAnchorNode, findCsrfToken, findSubmitButton, groupNodesByGroup, isUiNodeInputAttributes, sortNodes };
package/dist/index.js CHANGED
@@ -1518,14 +1518,465 @@ function TenantCreator({
1518
1518
  ] })
1519
1519
  ] });
1520
1520
  }
1521
+
1522
+ // src/user-invite/index.tsx
1523
+ import { useState as useState5 } from "react";
1524
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1525
+ function UserInvite({ roles, onInvite }) {
1526
+ const [email, setEmail] = useState5("");
1527
+ const [selectedRole, setSelectedRole] = useState5("");
1528
+ const [isSubmitting, setIsSubmitting] = useState5(false);
1529
+ const [error, setError] = useState5(null);
1530
+ const isValidEmail = (email2) => {
1531
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1532
+ return emailRegex.test(email2);
1533
+ };
1534
+ const isFormValid = () => {
1535
+ return email.trim() !== "" && isValidEmail(email) && selectedRole !== "";
1536
+ };
1537
+ const handleSubmit = async (e) => {
1538
+ e.preventDefault();
1539
+ setError(null);
1540
+ if (!isFormValid()) {
1541
+ setError("Please fill in all fields with valid data");
1542
+ return;
1543
+ }
1544
+ setIsSubmitting(true);
1545
+ try {
1546
+ const inviteData = {
1547
+ email: email.trim(),
1548
+ role: selectedRole
1549
+ };
1550
+ await onInvite?.(inviteData);
1551
+ setEmail("");
1552
+ setSelectedRole("");
1553
+ } catch (err) {
1554
+ setError(
1555
+ err instanceof Error ? err.message : "Failed to send invitation"
1556
+ );
1557
+ } finally {
1558
+ setIsSubmitting(false);
1559
+ }
1560
+ };
1561
+ const handleReset = () => {
1562
+ setEmail("");
1563
+ setSelectedRole("");
1564
+ setError(null);
1565
+ };
1566
+ return /* @__PURE__ */ jsxs14(Card, { className: "w-full max-w-2xl", children: [
1567
+ /* @__PURE__ */ jsxs14(CardHeader, { children: [
1568
+ /* @__PURE__ */ jsx23(CardTitle, { children: "Invite User" }),
1569
+ /* @__PURE__ */ jsx23(CardDescription, { children: "Send an invitation to a new user to join your organization" })
1570
+ ] }),
1571
+ /* @__PURE__ */ jsx23(CardContent, { children: /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-6", children: [
1572
+ /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
1573
+ /* @__PURE__ */ jsx23(Label, { htmlFor: "email", children: "Email Address" }),
1574
+ /* @__PURE__ */ jsx23(
1575
+ Input,
1576
+ {
1577
+ id: "email",
1578
+ type: "email",
1579
+ placeholder: "colleague@company.com",
1580
+ value: email,
1581
+ onChange: (e) => setEmail(e.target.value),
1582
+ "aria-invalid": email !== "" && !isValidEmail(email)
1583
+ }
1584
+ ),
1585
+ email !== "" && !isValidEmail(email) && /* @__PURE__ */ jsx23("p", { className: "text-xs text-destructive", children: "Please enter a valid email address" })
1586
+ ] }),
1587
+ /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
1588
+ /* @__PURE__ */ jsx23(Label, { htmlFor: "role", children: "Role" }),
1589
+ /* @__PURE__ */ jsxs14(Select, { value: selectedRole, onValueChange: setSelectedRole, children: [
1590
+ /* @__PURE__ */ jsx23(SelectTrigger, { id: "role", children: /* @__PURE__ */ jsx23(SelectValue, { placeholder: "Select a role" }) }),
1591
+ /* @__PURE__ */ jsx23(SelectContent, { children: roles.length > 0 ? roles.map((role) => /* @__PURE__ */ jsx23(SelectItem, { value: role, children: role }, role)) : /* @__PURE__ */ jsx23(SelectItem, { value: "no-roles", disabled: true, children: "No roles available" }) })
1592
+ ] }),
1593
+ /* @__PURE__ */ jsx23("p", { className: "text-xs text-muted-foreground", children: "The role determines what permissions the user will have" })
1594
+ ] }),
1595
+ error && /* @__PURE__ */ jsx23("div", { className: "p-3 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ jsx23("p", { className: "text-sm text-destructive", children: error }) }),
1596
+ /* @__PURE__ */ jsxs14("div", { className: "flex justify-end space-x-3", children: [
1597
+ /* @__PURE__ */ jsx23(
1598
+ Button,
1599
+ {
1600
+ type: "button",
1601
+ variant: "outline",
1602
+ onClick: handleReset,
1603
+ disabled: isSubmitting,
1604
+ children: "Reset"
1605
+ }
1606
+ ),
1607
+ /* @__PURE__ */ jsx23(Button, { type: "submit", disabled: !isFormValid() || isSubmitting, children: isSubmitting ? "Sending..." : "Send Invitation" })
1608
+ ] })
1609
+ ] }) })
1610
+ ] });
1611
+ }
1612
+
1613
+ // src/role-creator/index.tsx
1614
+ import { useState as useState6, useMemo as useMemo2 } from "react";
1615
+
1616
+ // src/components/ui/checkbox.tsx
1617
+ import "react";
1618
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
1619
+ import { CheckIcon } from "lucide-react";
1620
+ import { jsx as jsx24 } from "react/jsx-runtime";
1621
+ function Checkbox({
1622
+ className,
1623
+ ...props
1624
+ }) {
1625
+ return /* @__PURE__ */ jsx24(
1626
+ CheckboxPrimitive.Root,
1627
+ {
1628
+ "data-slot": "checkbox",
1629
+ className: cn(
1630
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
1631
+ className
1632
+ ),
1633
+ ...props,
1634
+ children: /* @__PURE__ */ jsx24(
1635
+ CheckboxPrimitive.Indicator,
1636
+ {
1637
+ "data-slot": "checkbox-indicator",
1638
+ className: "grid place-content-center text-current transition-none",
1639
+ children: /* @__PURE__ */ jsx24(CheckIcon, { className: "size-3.5" })
1640
+ }
1641
+ )
1642
+ }
1643
+ );
1644
+ }
1645
+
1646
+ // src/components/ui/separator.tsx
1647
+ import "react";
1648
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
1649
+ import { jsx as jsx25 } from "react/jsx-runtime";
1650
+ function Separator2({
1651
+ className,
1652
+ orientation = "horizontal",
1653
+ decorative = true,
1654
+ ...props
1655
+ }) {
1656
+ return /* @__PURE__ */ jsx25(
1657
+ SeparatorPrimitive.Root,
1658
+ {
1659
+ "data-slot": "separator",
1660
+ decorative,
1661
+ orientation,
1662
+ className: cn(
1663
+ "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
1664
+ className
1665
+ ),
1666
+ ...props
1667
+ }
1668
+ );
1669
+ }
1670
+
1671
+ // src/role-creator/index.tsx
1672
+ import { Fragment as Fragment2, jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
1673
+ function RoleCreator({
1674
+ definitions,
1675
+ roles,
1676
+ namespaceMap = {},
1677
+ onRoleCreate,
1678
+ onRoleUpdate
1679
+ }) {
1680
+ const [roleName, setRoleName] = useState6("");
1681
+ const [selectedPermissions, setSelectedPermissions] = useState6(
1682
+ /* @__PURE__ */ new Set()
1683
+ );
1684
+ const [showSuggestions, setShowSuggestions] = useState6(false);
1685
+ const [isEditMode, setIsEditMode] = useState6(false);
1686
+ const [editingRoleId, setEditingRoleId] = useState6(null);
1687
+ const { tenantNamespace, fineGrainedNamespaces } = useMemo2(() => {
1688
+ const tenant = definitions.find(
1689
+ (def) => def.namespace.toLowerCase() === "tenant"
1690
+ );
1691
+ const fineGrained = definitions.filter(
1692
+ (def) => def.namespace.toLowerCase() !== "tenant"
1693
+ );
1694
+ return {
1695
+ tenantNamespace: tenant,
1696
+ fineGrainedNamespaces: fineGrained
1697
+ };
1698
+ }, [definitions]);
1699
+ const roleSuggestions = useMemo2(() => {
1700
+ return roles.map((role) => role.role_name);
1701
+ }, [roles]);
1702
+ const handleRoleNameChange = (value) => {
1703
+ setRoleName(value);
1704
+ const existingRole = roles.find(
1705
+ (role) => role.role_name.toLowerCase() === value.toLowerCase()
1706
+ );
1707
+ if (existingRole) {
1708
+ setIsEditMode(true);
1709
+ setEditingRoleId(existingRole.id);
1710
+ const permissions = /* @__PURE__ */ new Set();
1711
+ existingRole.permissions.forEach((perm) => {
1712
+ permissions.add(perm);
1713
+ });
1714
+ setSelectedPermissions(permissions);
1715
+ } else {
1716
+ setIsEditMode(false);
1717
+ setEditingRoleId(null);
1718
+ }
1719
+ };
1720
+ const buildPermissionString = (namespace, relation, resourceId) => {
1721
+ if (resourceId) {
1722
+ return `${namespace.toLowerCase()}:${resourceId}#${relation}`;
1723
+ }
1724
+ return `${namespace.toLowerCase()}#${relation}`;
1725
+ };
1726
+ const togglePermission = (permissionString) => {
1727
+ const newPermissions = new Set(selectedPermissions);
1728
+ if (newPermissions.has(permissionString)) {
1729
+ newPermissions.delete(permissionString);
1730
+ } else {
1731
+ newPermissions.add(permissionString);
1732
+ }
1733
+ setSelectedPermissions(newPermissions);
1734
+ };
1735
+ const handleSubmit = () => {
1736
+ if (!roleName.trim()) return;
1737
+ const permissionsArray = Array.from(selectedPermissions);
1738
+ if (isEditMode && editingRoleId) {
1739
+ onRoleUpdate?.({
1740
+ role_id: editingRoleId,
1741
+ role_name: roleName,
1742
+ permissions: permissionsArray
1743
+ });
1744
+ } else {
1745
+ onRoleCreate?.({
1746
+ role_name: roleName,
1747
+ permissions: permissionsArray
1748
+ });
1749
+ }
1750
+ };
1751
+ const handleReset = () => {
1752
+ setRoleName("");
1753
+ setSelectedPermissions(/* @__PURE__ */ new Set());
1754
+ setIsEditMode(false);
1755
+ setEditingRoleId(null);
1756
+ };
1757
+ return /* @__PURE__ */ jsxs15(Card, { className: "w-full max-w-4xl", children: [
1758
+ /* @__PURE__ */ jsxs15(CardHeader, { children: [
1759
+ /* @__PURE__ */ jsx26(CardTitle, { children: isEditMode ? `Edit Role: ${roleName}` : "Create New Role" }),
1760
+ /* @__PURE__ */ jsx26(CardDescription, { children: isEditMode ? "Update permissions for this existing role" : "Define a new role with specific permissions" })
1761
+ ] }),
1762
+ /* @__PURE__ */ jsxs15(CardContent, { className: "space-y-6", children: [
1763
+ /* @__PURE__ */ jsxs15("div", { className: "space-y-2 relative", children: [
1764
+ /* @__PURE__ */ jsx26(Label, { htmlFor: "role-name", children: "Role Name" }),
1765
+ /* @__PURE__ */ jsx26(
1766
+ Input,
1767
+ {
1768
+ id: "role-name",
1769
+ placeholder: "Enter role name (e.g., admin, developer, viewer)",
1770
+ value: roleName,
1771
+ onChange: (e) => handleRoleNameChange(e.target.value),
1772
+ onFocus: () => setShowSuggestions(true),
1773
+ onBlur: () => setTimeout(() => setShowSuggestions(false), 200)
1774
+ }
1775
+ ),
1776
+ showSuggestions && roleSuggestions.length > 0 && /* @__PURE__ */ jsx26("div", { className: "absolute z-10 w-full mt-1 bg-background border rounded-md shadow-lg max-h-48 overflow-y-auto", children: roleSuggestions.filter(
1777
+ (suggestion) => suggestion.toLowerCase().includes(roleName.toLowerCase())
1778
+ ).map((suggestion) => /* @__PURE__ */ jsx26(
1779
+ "button",
1780
+ {
1781
+ className: "w-full px-4 py-2 text-left hover:bg-accent hover:text-accent-foreground text-sm",
1782
+ onMouseDown: () => {
1783
+ handleRoleNameChange(suggestion);
1784
+ setShowSuggestions(false);
1785
+ },
1786
+ children: suggestion
1787
+ },
1788
+ suggestion
1789
+ )) }),
1790
+ isEditMode && /* @__PURE__ */ jsx26("p", { className: "text-xs text-amber-600 dark:text-amber-400", children: "\u26A0 Editing existing role - changes will update all users with this role" })
1791
+ ] }),
1792
+ /* @__PURE__ */ jsx26(Separator2, {}),
1793
+ tenantNamespace && /* @__PURE__ */ jsxs15("div", { className: "space-y-4", children: [
1794
+ /* @__PURE__ */ jsxs15("div", { children: [
1795
+ /* @__PURE__ */ jsx26("h3", { className: "text-lg font-semibold", children: "Organization Permissions" }),
1796
+ /* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground", children: "Tenant-wide permissions that apply across the entire organization" })
1797
+ ] }),
1798
+ /* @__PURE__ */ jsx26("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pl-4", children: tenantNamespace.relations.map((relation) => {
1799
+ const permissionString = buildPermissionString(
1800
+ tenantNamespace.namespace,
1801
+ relation
1802
+ );
1803
+ const isChecked = selectedPermissions.has(permissionString);
1804
+ return /* @__PURE__ */ jsxs15(
1805
+ "div",
1806
+ {
1807
+ className: "flex items-center space-x-2",
1808
+ children: [
1809
+ /* @__PURE__ */ jsx26(
1810
+ Checkbox,
1811
+ {
1812
+ id: permissionString,
1813
+ checked: isChecked,
1814
+ onCheckedChange: () => togglePermission(permissionString)
1815
+ }
1816
+ ),
1817
+ /* @__PURE__ */ jsx26(
1818
+ Label,
1819
+ {
1820
+ htmlFor: permissionString,
1821
+ className: "text-sm font-normal cursor-pointer",
1822
+ children: relation.replace(/_/g, " ")
1823
+ }
1824
+ )
1825
+ ]
1826
+ },
1827
+ permissionString
1828
+ );
1829
+ }) })
1830
+ ] }),
1831
+ fineGrainedNamespaces.length > 0 && /* @__PURE__ */ jsxs15(Fragment2, { children: [
1832
+ /* @__PURE__ */ jsx26(Separator2, {}),
1833
+ /* @__PURE__ */ jsxs15("div", { className: "space-y-6", children: [
1834
+ /* @__PURE__ */ jsxs15("div", { children: [
1835
+ /* @__PURE__ */ jsx26("h3", { className: "text-lg font-semibold", children: "Fine-Grained Permissions" }),
1836
+ /* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground", children: "Resource-specific permissions that require an object ID" })
1837
+ ] }),
1838
+ fineGrainedNamespaces.map((namespace) => {
1839
+ const namespaceLower = namespace.namespace.toLowerCase();
1840
+ const resourceMap = namespaceMap[namespaceLower] || [];
1841
+ return /* @__PURE__ */ jsx26("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs15("div", { className: "pl-4", children: [
1842
+ /* @__PURE__ */ jsx26("h4", { className: "text-md font-medium capitalize", children: namespace.namespace }),
1843
+ resourceMap.length > 0 ? /* @__PURE__ */ jsx26("div", { className: "mt-4 space-y-6", children: resourceMap.map((resource) => /* @__PURE__ */ jsxs15(
1844
+ "div",
1845
+ {
1846
+ className: "border rounded-lg p-4 space-y-3 bg-muted/30",
1847
+ children: [
1848
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between", children: [
1849
+ /* @__PURE__ */ jsx26("span", { className: "font-medium text-sm", children: resource.label }),
1850
+ /* @__PURE__ */ jsx26("span", { className: "text-xs text-muted-foreground font-mono", children: resource.id })
1851
+ ] }),
1852
+ /* @__PURE__ */ jsx26("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: namespace.relations.map((relation) => {
1853
+ const permissionString = buildPermissionString(
1854
+ namespace.namespace,
1855
+ relation,
1856
+ resource.id
1857
+ );
1858
+ const isChecked = selectedPermissions.has(permissionString);
1859
+ return /* @__PURE__ */ jsxs15(
1860
+ "div",
1861
+ {
1862
+ className: "flex items-center space-x-2",
1863
+ children: [
1864
+ /* @__PURE__ */ jsx26(
1865
+ Checkbox,
1866
+ {
1867
+ id: permissionString,
1868
+ checked: isChecked,
1869
+ onCheckedChange: () => togglePermission(permissionString)
1870
+ }
1871
+ ),
1872
+ /* @__PURE__ */ jsx26(
1873
+ Label,
1874
+ {
1875
+ htmlFor: permissionString,
1876
+ className: "text-sm font-normal cursor-pointer",
1877
+ children: relation.replace(/_/g, " ")
1878
+ }
1879
+ )
1880
+ ]
1881
+ },
1882
+ permissionString
1883
+ );
1884
+ }) })
1885
+ ]
1886
+ },
1887
+ resource.id
1888
+ )) }) : /* @__PURE__ */ jsxs15("div", { className: "mt-3 p-4 border-2 border-dashed rounded-lg text-center", children: [
1889
+ /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground", children: [
1890
+ "No ",
1891
+ namespace.namespace.toLowerCase(),
1892
+ " resources available"
1893
+ ] }),
1894
+ /* @__PURE__ */ jsxs15("p", { className: "text-xs text-muted-foreground mt-1", children: [
1895
+ "Use wildcard permissions (e.g., ",
1896
+ namespaceLower,
1897
+ ":*#permission) for all resources"
1898
+ ] })
1899
+ ] }),
1900
+ /* @__PURE__ */ jsxs15("div", { className: "mt-4 space-y-2", children: [
1901
+ /* @__PURE__ */ jsxs15(Label, { className: "text-sm font-medium", children: [
1902
+ "Wildcard Permissions (All ",
1903
+ namespace.namespace,
1904
+ "s)"
1905
+ ] }),
1906
+ /* @__PURE__ */ jsx26("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: namespace.relations.map((relation) => {
1907
+ const permissionString = buildPermissionString(
1908
+ namespace.namespace,
1909
+ relation,
1910
+ "*"
1911
+ );
1912
+ const isChecked = selectedPermissions.has(permissionString);
1913
+ return /* @__PURE__ */ jsxs15(
1914
+ "div",
1915
+ {
1916
+ className: "flex items-center space-x-2",
1917
+ children: [
1918
+ /* @__PURE__ */ jsx26(
1919
+ Checkbox,
1920
+ {
1921
+ id: permissionString,
1922
+ checked: isChecked,
1923
+ onCheckedChange: () => togglePermission(permissionString)
1924
+ }
1925
+ ),
1926
+ /* @__PURE__ */ jsxs15(
1927
+ Label,
1928
+ {
1929
+ htmlFor: permissionString,
1930
+ className: "text-sm font-normal cursor-pointer",
1931
+ children: [
1932
+ relation.replace(/_/g, " "),
1933
+ " (all)"
1934
+ ]
1935
+ }
1936
+ )
1937
+ ]
1938
+ },
1939
+ permissionString
1940
+ );
1941
+ }) })
1942
+ ] })
1943
+ ] }) }, namespace.id);
1944
+ })
1945
+ ] })
1946
+ ] }),
1947
+ /* @__PURE__ */ jsx26(Separator2, {}),
1948
+ /* @__PURE__ */ jsxs15("div", { className: "space-y-2", children: [
1949
+ /* @__PURE__ */ jsxs15(Label, { className: "text-sm font-medium", children: [
1950
+ "Selected Permissions (",
1951
+ selectedPermissions.size,
1952
+ ")"
1953
+ ] }),
1954
+ selectedPermissions.size > 0 ? /* @__PURE__ */ jsx26("div", { className: "p-4 bg-muted rounded-md max-h-40 overflow-y-auto", children: /* @__PURE__ */ jsx26("ul", { className: "space-y-1 text-xs font-mono", children: Array.from(selectedPermissions).map((perm) => /* @__PURE__ */ jsx26("li", { className: "text-muted-foreground", children: perm }, perm)) }) }) : /* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground italic", children: "No permissions selected" })
1955
+ ] }),
1956
+ /* @__PURE__ */ jsxs15("div", { className: "flex justify-end space-x-3", children: [
1957
+ /* @__PURE__ */ jsx26(Button, { variant: "outline", onClick: handleReset, children: "Reset" }),
1958
+ /* @__PURE__ */ jsx26(
1959
+ Button,
1960
+ {
1961
+ onClick: handleSubmit,
1962
+ disabled: !roleName.trim() || selectedPermissions.size === 0,
1963
+ children: isEditMode ? "Update Role" : "Create Role"
1964
+ }
1965
+ )
1966
+ ] })
1967
+ ] })
1968
+ ] });
1969
+ }
1521
1970
  export {
1522
1971
  LoginForm,
1523
1972
  PricingTable,
1524
1973
  RecoveryForm,
1525
1974
  RegistrationForm,
1975
+ RoleCreator,
1526
1976
  SettingsForm,
1527
1977
  SwitchActiveTenant,
1528
1978
  TenantCreator,
1979
+ UserInvite,
1529
1980
  VerificationForm,
1530
1981
  filterInputNodes,
1531
1982
  findAnchorNode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnibase/shadcn",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "OmniBase ShadCN UI Package",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,37 +36,40 @@
36
36
  "react-dom": "^19.1.0"
37
37
  },
38
38
  "dependencies": {
39
+ "@radix-ui/react-avatar": "^1.1.10",
40
+ "@radix-ui/react-checkbox": "^1.3.3",
39
41
  "@radix-ui/react-label": "^2.1.7",
40
42
  "@radix-ui/react-select": "^2.2.6",
43
+ "@radix-ui/react-separator": "^1.1.7",
41
44
  "@radix-ui/react-slot": "^1.2.3",
42
- "@types/node": "^24.5.2",
45
+ "@types/node": "^24.9.1",
43
46
  "class-variance-authority": "^0.7.1",
44
47
  "clsx": "^2.1.1",
45
48
  "lucide-react": "^0.544.0",
46
49
  "tailwind-merge": "^2.6.0"
47
50
  },
48
51
  "devDependencies": {
49
- "@omnibase/core-js": "workspace:*",
50
- "@storybook/addon-docs": "^9.1.8",
51
- "@storybook/react-vite": "^9.1.8",
52
- "@tailwindcss/vite": "^4.1.13",
53
- "@types/react": "^18.3.24",
54
- "@types/react-dom": "^18.3.7",
52
+ "@omnibase/core-js": "0.7.5",
53
+ "@storybook/addon-docs": "^9.1.15",
54
+ "@storybook/react-vite": "^9.1.15",
55
+ "@tailwindcss/vite": "^4.1.16",
56
+ "@types/react": "^19.2.2",
57
+ "@types/react-dom": "^19.2.2",
55
58
  "@typescript-eslint/eslint-plugin": "^6.21.0",
56
59
  "@typescript-eslint/parser": "^6.21.0",
57
- "@vitejs/plugin-react": "^5.0.3",
58
- "ajv": "^8.12.0",
60
+ "@vitejs/plugin-react": "^5.1.0",
61
+ "ajv": "^8.17.1",
59
62
  "autoprefixer": "^10.4.21",
60
63
  "eslint": "^8.57.1",
61
64
  "eslint-plugin-react-hooks": "^4.6.2",
62
- "eslint-plugin-storybook": "^9.1.8",
63
- "react": "^19.1.1",
64
- "react-dom": "^19.1.1",
65
- "storybook": "^9.1.8",
66
- "tailwindcss": "^4.1.13",
65
+ "eslint-plugin-storybook": "^9.1.15",
66
+ "react": "^19.2.0",
67
+ "react-dom": "^19.2.0",
68
+ "storybook": "^9.1.15",
69
+ "tailwindcss": "^4.1.16",
67
70
  "tsup": "^8.5.0",
68
- "typescript": "^5.9.2",
69
- "vite": "^7.1.7",
71
+ "typescript": "^5.9.3",
72
+ "vite": "^7.1.12",
70
73
  "vite-plugin-dts": "^4.5.4"
71
74
  }
72
75
  }