feat: adding system settings page. And did some refactoring.

This commit is contained in:
charlesgauthereau
2026-01-17 22:26:44 +01:00
parent e63e62d3e0
commit 5b980fb382
24 changed files with 238 additions and 161 deletions
@@ -0,0 +1,99 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { MemberWithUser } from "@/db/schema/03_organization";
import { useState } from "react";
import { authClient, useSession } from "@/lib/auth/auth-client";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {updateMemberRoleAction} from "@/components/wrappers/dashboard/organization/settings/update-member.action";
import {RoleSchemaMember} from "@/components/wrappers/dashboard/organization/settings/member.schema";
export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
{
accessorKey: "role",
header: "Role",
cell: ({ row }) => {
const [role, setRole] = useState<string>(row.getValue("role"));
const { data: session } = useSession();
const activeOrgaMember = authClient.useActiveMember();
const updateMutation = useMutation({
mutationFn: () =>
updateMemberRoleAction({
memberId: row.original.id,
organizationId: row.original.organizationId,
role: RoleSchemaMember.parse(role),
}),
onSuccess: () => {
toast.success("User updated successfully.");
},
onError: () => {
toast.error("An error occurred while updating user information.");
},
});
// Only allow cycling between admin <-> member
const handleUpdateRole = async () => {
const nextRole = role === "admin" ? "member" : "admin";
setRole(nextRole);
await updateMutation.mutateAsync();
};
const isCurrentUser = session?.user.email === row.original.user.email;
const isMember = activeOrgaMember.data?.role === "member";
const isRowRoleOwner = role === "owner";
const isDisabled = isMember || isCurrentUser || isRowRoleOwner;
// Dynamic tooltip reason
const disabledReason = isCurrentUser
? "You cannot change your own role"
: isRowRoleOwner
? "Owner role cannot be modified"
: "Members cannot edit roles";
const badge = (
<Badge
className={
isDisabled
? "cursor-not-allowed opacity-50"
: "cursor-pointer hover:bg-accent"
}
onClick={isDisabled ? undefined : handleUpdateRole}
variant="outline"
>
{role}
</Badge>
);
return isDisabled ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{badge}</TooltipTrigger>
<TooltipContent>
<p>{disabledReason}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
badge
);
},
},
{
accessorKey: "user.name",
header: "Name",
},
{
accessorKey: "user.email",
header: "Email",
},
];
@@ -0,0 +1,20 @@
"use client"
import {buttonVariants} from "@/components/ui/button";
import {GearIcon} from "@radix-ui/react-icons";
import Link from "next/link";
import {usePathname} from "next/navigation";
export type EditButtonSettings = {}
export const EditButtonSettings= (props:EditButtonSettings) => {
const pathname = usePathname();
return(
<Link className={buttonVariants({variant: "outline"})}
href={`${pathname}/edit/`}
>
<GearIcon className="w-7 h-7"/>
</Link>
)
}
@@ -0,0 +1,8 @@
import {z} from "zod";
const RoleEnum = z.enum(["member", "owner", "admin"]);
export const RoleSchemaMember = z.union([
RoleEnum,
z.array(RoleEnum)
]);
@@ -0,0 +1,22 @@
import {DataTable} from "@/components/wrappers/common/table/data-table";
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/03_organization";
import {
organizationMemberColumns
} from "@/components/wrappers/dashboard/organization/settings/columns-organization-members";
interface SettingsOrganizationMembersTableProps {
organization: OrganizationWithMembers
}
export const SettingsOrganizationMembersTable = ({organization}: SettingsOrganizationMembersTableProps) => {
return (
<div className="flex flex-col h-full ">
<div className=" h-full">
<DataTable
columns={organizationMemberColumns}
enableSelect={false}
data={organization.members as MemberWithUser[]}/>
</div>
</div>
)
}
@@ -0,0 +1,47 @@
"use server";
import {userAction} from "@/lib/safe-actions/actions";
import {z} from "zod";
import {auth} from "@/lib/auth/auth";
import {ServerActionResult} from "@/types/action-type";
import {Member} from "better-auth/plugins";
import {headers} from "next/headers";
import {RoleSchemaMember} from "@/components/wrappers/dashboard/organization/settings/member.schema";
export const updateMemberRoleAction = userAction.schema(
z.object({
memberId: z.string(),
organizationId: z.string(),
role: RoleSchemaMember,
})
).action(async ({parsedInput}): Promise<ServerActionResult<Member>> => {
try {
const updatedMember = await auth.api.updateMemberRole({
body: {
role: parsedInput.role,
memberId: parsedInput.memberId,
organizationId: parsedInput.organizationId,
},
headers: await headers(),
});
return {
success: true,
value: updatedMember,
actionSuccess: {
message: "Member has been successfully updated.",
messageParams: {},
},
};
} catch (error) {
return {
success: false,
actionError: {
message: "Failed to update member role.",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: {},
},
};
}
});
@@ -4,18 +4,18 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {useEffect, useState} from "react";
import {useRouter, useSearchParams} from "next/navigation";
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/03_organization";
import {
SettingsOrganizationMembersTable
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
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";
import {StorageChannel} from "@/db/schema/12_storage-channel";
import {
SettingsOrganizationMembersTable
} from "@/components/wrappers/dashboard/organization/settings/settings-organization-members-table";
import {
OrganizationNotifiersTab
} from "@/components/wrappers/dashboard/organization/tabs/organization-channels-tab/organization-notifiers-tab";
import {
OrganizationStoragesTab
} from "@/components/wrappers/dashboard/organization/tabs/organization-notifiers-tab/organization-storages-tab";
import {StorageChannel} from "@/db/schema/12_storage-channel";
} from "@/components/wrappers/dashboard/organization/tabs/organization-channels-tab/organization-storages-tab";
export type OrganizationTabsProps = {
organization: OrganizationWithMembers;