diff --git a/app/(customer)/dashboard/(organization)/settings/page.tsx b/app/(customer)/dashboard/(organization)/settings/page.tsx index bb36710c..dc1ff52a 100644 --- a/app/(customer)/dashboard/(organization)/settings/page.tsx +++ b/app/(customer)/dashboard/(organization)/settings/page.tsx @@ -10,6 +10,7 @@ import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/edit- import {Metadata} from "next"; import {OrganizationTabs} from "@/components/wrappers/dashboard/organization/tabs/organization-tabs"; import {getOrganizationChannels} from "@/db/services/notification-channel"; +import {computeOrganizationPermissions} from "@/lib/acl/organization-acl"; export const metadata: Metadata = { title: "Settings", @@ -20,32 +21,34 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) { const user = await currentUser(); const activeMember = await getActiveMember() - if (!organization) { + if (!organization || !activeMember) { notFound(); } const notificationChannels = await getOrganizationChannels(organization.id) - const isMember = activeMember?.role === "member"; - const isOwner = activeMember?.role === "owner"; + const permissions = computeOrganizationPermissions(activeMember); + return ( Organization settings - {!isMember && organization.slug !== "default" && ( + {permissions.canManageSettings && organization.slug !== "default" && ( )} + - {isOwner && organization.slug !== "default" && ( + {permissions.canManageDangerZone && organization.slug !== "default" && ( )} diff --git a/src/components/wrappers/dashboard/organization/tabs/organization-tabs.tsx b/src/components/wrappers/dashboard/organization/tabs/organization-tabs.tsx index 93cb53bb..48907b5c 100644 --- a/src/components/wrappers/dashboard/organization/tabs/organization-tabs.tsx +++ b/src/components/wrappers/dashboard/organization/tabs/organization-tabs.tsx @@ -3,7 +3,7 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {useEffect, useState} from "react"; import {useRouter, useSearchParams} from "next/navigation"; -import {OrganizationWithMembers} from "@/db/schema/03_organization"; +import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/03_organization"; import { SettingsOrganizationMembersTable } from "@/components/wrappers/dashboard/settings/settings-organization-members-table"; @@ -11,18 +11,26 @@ import { OrganizationNotifiersTab } from "@/components/wrappers/dashboard/organization/tabs/organization-notifiers-tab/organization-notifiers-tab"; import {NotificationChannel} from "@/db/schema/09_notification-channel"; +import {useOrganizationPermissions} from "@/hooks/use-organization-permissions"; export type OrganizationTabsProps = { organization: OrganizationWithMembers; notificationChannels: NotificationChannel[]; + activeMember: MemberWithUser }; -export const OrganizationTabs = ({organization, notificationChannels}: OrganizationTabsProps) => { +export const OrganizationTabs = ({activeMember, organization, notificationChannels}: OrganizationTabsProps) => { const router = useRouter(); const searchParams = useSearchParams(); const [tab, setTab] = useState(() => searchParams.get("tab") ?? "users"); + const { + canManageUsers, + canManageNotifications, + } = useOrganizationPermissions(activeMember); + + useEffect(() => { const newTab = searchParams.get("tab") ?? "users"; setTab(newTab); @@ -32,25 +40,43 @@ export const OrganizationTabs = ({organization, notificationChannels}: Organizat router.push(`?tab=${value}`); }; + return ( - - - - Users - - - Notifiers - - - +
+ {canManageNotifications ? + + + + Users + + + + Notifiers + + + + + + + + + + : - - - - - + } + + +
+ + ); }; diff --git a/src/hooks/use-organization-permissions.ts b/src/hooks/use-organization-permissions.ts new file mode 100644 index 00000000..116bd8c5 --- /dev/null +++ b/src/hooks/use-organization-permissions.ts @@ -0,0 +1,8 @@ +"use client"; + +import {MemberWithUser} from "@/db/schema/03_organization"; +import {computeOrganizationPermissions} from "@/lib/acl/organization-acl"; + +export const useOrganizationPermissions = (activeMember: MemberWithUser | null) => { + return computeOrganizationPermissions(activeMember); +}; diff --git a/src/lib/acl/organization-acl.ts b/src/lib/acl/organization-acl.ts new file mode 100644 index 00000000..e7ac3225 --- /dev/null +++ b/src/lib/acl/organization-acl.ts @@ -0,0 +1,37 @@ +import {MemberWithUser} from "@/db/schema/03_organization"; + + +export type OrganizationPermissions = { + role: string | null; + isOwner: boolean; + isAdmin: boolean; + isMember: boolean; + + canManageSettings: boolean; + canManageUsers: boolean; + canManageNotifications: boolean; + canManageDangerZone: boolean; +}; + + +export const computeOrganizationPermissions = ( + activeMember: MemberWithUser | null +): OrganizationPermissions => { + const role = activeMember?.role ?? null; + + const isOwner = role === "owner"; + const isAdmin = role === "admin"; + const isMember = role === "member"; + + return { + role, + isOwner, + isAdmin, + isMember, + + canManageSettings: isOwner || isAdmin, + canManageUsers: isOwner || isAdmin, + canManageNotifications: isOwner || isAdmin, + canManageDangerZone: isOwner, + }; +};