mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: adding system settings page. And did some refactoring.
This commit is contained in:
+99
@@ -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",
|
||||
},
|
||||
];
|
||||
+20
@@ -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)
|
||||
]);
|
||||
+22
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user