Refactoring in roles.

This commit is contained in:
charlesgauthereau
2025-11-01 14:50:08 +01:00
parent 027928c29f
commit ae766dab2f
36 changed files with 1800 additions and 383 deletions
@@ -1,20 +1,21 @@
"use client";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import {Card, CardContent, CardHeader} from "@/components/ui/card";
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Form} from "@/components/ui/form";
import {Button} from "@/components/ui/button";
import {toast} from "sonner";
import {useMutation} from "@tanstack/react-query";
import {TooltipProvider} from "@/components/ui/tooltip";
import Link from "next/link";
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
import { LoginSchema, LoginType } from "@/components/wrappers/auth/login/login-form/login-form.schema";
import { SocialAuthButton, SocialProviderType } from "@/components/wrappers/auth/login/button-auth/social-auth-button";
import { signIn } from "@/lib/auth/auth-client";
import { useRouter } from "next/navigation";
import { Icon } from "@iconify/react";
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
import {signIn} from "@/lib/auth/auth-client";
import {useRouter} from "next/navigation";
import {Icon} from "@iconify/react";
import {env} from "@/env.mjs";
export type loginFormProps = {
defaultValues?: LoginType;
@@ -29,7 +30,7 @@ export const LoginForm = (props: loginFormProps) => {
const mutation = useMutation({
mutationFn: async (values: LoginType) => {
const { error } = await signIn.email(values, {
const {error} = await signIn.email(values, {
onSuccess: () => {
toast.success("Login success");
router.push("/dashboard/profile");
@@ -41,13 +42,18 @@ export const LoginForm = (props: loginFormProps) => {
},
});
const availableProviders: SocialProviderType[] = [
{
id: "google",
name: "Google",
icon: <Icon icon={"logos:google-icon"} width="25" height="25" />,
},
];
const availableProviders: SocialProviderType[] = [];
if (env.NEXT_PUBLIC_GOOGLE_AUTH) {
availableProviders.push(
{
id: "google",
name: "Google",
icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
},
)
}
return (
<TooltipProvider>
@@ -70,13 +76,14 @@ export const LoginForm = (props: loginFormProps) => {
control={form.control}
name="email"
defaultValue=""
render={({ field }) => (
render={({field}) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input autoComplete="email webauthn" placeholder="exemple@portabase.io" {...field} />
<Input autoComplete="email webauthn"
placeholder="exemple@portabase.io" {...field} />
</FormControl>
<FormMessage />
<FormMessage/>
</FormItem>
)}
/>
@@ -84,7 +91,7 @@ export const LoginForm = (props: loginFormProps) => {
control={form.control}
name="password"
defaultValue=""
render={({ field }) => (
render={({field}) => (
<FormItem>
<div className="flex items-center">
<FormLabel>Password</FormLabel>
@@ -93,9 +100,10 @@ export const LoginForm = (props: loginFormProps) => {
</Link>*/}
</div>
<FormControl>
<PasswordInput autoComplete="current-password webauthn" placeholder="Your password" {...field} />
<PasswordInput autoComplete="current-password webauthn"
placeholder="Your password" {...field} />
</FormControl>
<FormMessage />
<FormMessage/>
</FormItem>
)}
/>
@@ -107,7 +115,7 @@ export const LoginForm = (props: loginFormProps) => {
</Link>
</div>
</Form>
<SocialAuthButton providers={availableProviders} />
<SocialAuthButton providers={availableProviders}/>
</CardContent>
</Card>
</TooltipProvider>
@@ -3,22 +3,29 @@ import {cn} from "@/lib/utils";
import {Plus} from "lucide-react";
type EmptyStatePlaceholderProps = {
url: string;
url?: string;
text: string;
}
export const EmptyStatePlaceholder = ({url, text}: EmptyStatePlaceholderProps) => {
return (
<Link
href={url}
className={cn(
"flex flex-col items-center justify-center w-full rounded-2xl border border-dashed border-muted p-6 lg:p-10",
"hover:bg-muted/50 transition-colors text-muted-foreground hover:text-primary text-center space-y-2"
)}
>
<Plus className="w-5 h-5 lg:w-6 lg:h-6"/>
<span className="text-sm lg:text-base font-medium">{text}</span>
</Link>
<>{url ?
<Link
href={url}
className={cn(
"flex flex-col items-center justify-center w-full rounded-2xl border border-dashed border-muted p-6 lg:p-10",
"hover:bg-muted/50 transition-colors text-muted-foreground hover:text-primary text-center space-y-2"
)}
>
<Plus className="w-5 h-5 lg:w-6 lg:h-6"/>
<span className="text-sm lg:text-base font-medium">{text}</span>
</Link>
:
<div className="flex flex-col items-center justify-center py-12 text-center">
<p className="text-lg text-muted-foreground">{text}</p>
</div>
}
</>
)
}
@@ -8,7 +8,6 @@ import {Setting} from "@/db/schema/01_setting";
import {useEffect, useState} from "react";
import {useRouter, useSearchParams} from "next/navigation";
import {AdminUsersTable} from "@/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table";
import {AdminSettingsTab} from "@/components/wrappers/dashboard/admin/admin-settings-tab/admin-settings-tab";
export type AdminTabsProps = {
users: UserWithAccounts[];
@@ -42,11 +41,7 @@ export const AdminTabs = ({users, settings}: AdminTabsProps) => {
<TabsTrigger className="w-full" value="storage">
Storage
</TabsTrigger>
<TabsTrigger className="w-full" value="settings">
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="users">
<AdminUsersTable users={users}/>
</TabsContent>
@@ -56,9 +51,6 @@ export const AdminTabs = ({users, settings}: AdminTabsProps) => {
<TabsContent value="storage">
<SettingsStorageTab settings={settings}/>
</TabsContent>
<TabsContent value="settings">
<AdminSettingsTab/>
</TabsContent>
</Tabs>
);
};
@@ -1,4 +1,4 @@
import {User, UserWithAccounts} from "@/db/schema/02_user";
import {UserWithAccounts} from "@/db/schema/02_user";
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card";
import {DataTable} from "@/components/wrappers/common/table/data-table";
import {usersColumnsAdmin} from "@/components/wrappers/dashboard/admin/admin-user-tab/columns-users";
@@ -18,7 +18,10 @@ export const AdminUsersTable = (props: AdminUsersTableProps) => {
<CardDescription>Manage your users</CardDescription>
</CardHeader>
<CardContent>
<DataTable columns={usersColumnsAdmin} data={users}/>
<DataTable
enableSelect={false}
columns={usersColumnsAdmin}
data={users}/>
</CardContent>
</Card>
</div>
@@ -72,7 +72,7 @@ export const usersColumnsAdmin: ColumnDef<UserWithAccounts>[] = [
accessorKey: "accounts",
header: "Provider ID",
cell: ({row}) => {
return(
return (
<div>
{row.original.accounts.map((item) => (
<div key={item.id}>
@@ -107,18 +107,20 @@ export const usersColumnsAdmin: ColumnDef<UserWithAccounts>[] = [
});
return (
<div className="flex items-center gap-2">
<ButtonWithLoading
disabled={!isSuperAdmin || !session || session?.user.email === row.original.email}
variant="outline"
text=""
icon={<Trash2 color="red" size={15}/>}
onClick={async () => {
await mutation.mutateAsync();
}}
size="sm"
/>
</div>
<>
<div className="flex items-center gap-2">
<ButtonWithLoading
disabled={!isSuperAdmin || !session || session?.user.email === row.original.email}
variant="outline"
text=""
icon={<Trash2 color="red" size={15}/>}
onClick={async () => {
await mutation.mutateAsync();
}}
size="sm"
/>
</div>
</>
);
},
},
@@ -14,7 +14,9 @@ export const AgentCard = (props: agentCardProps) => {
const { data: agent } = props;
return (
<Link href={`/dashboard/agents/${agent.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
<Link href={`/dashboard/agents/${agent.id}`}
className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md rounded-xl"
>
<Card className="flex flex-row justify-between">
<div className="flex-1 text-left">
<CardHeader className="text-2xl font-bold">{agent.name}</CardHeader>
@@ -18,7 +18,7 @@ export const LoggedInDropdown = (props: LoggedInDropdownProps) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{props.children}</DropdownMenuTrigger>
<DropdownMenuContent side="top" className="w-[--radix-popper-anchor-width]">
<DropdownMenuContent side="top" className="min-w-[var(--radix-popper-anchor-width)]">
<DropdownMenuItem
onClick={() => {
redirect("/dashboard/profile");
@@ -29,11 +29,12 @@ export const SidebarMenuCustomMain = () => {
const groupContent: SidebarGroupItem["group_content"] = [
{ title: "Projects", url: "/projects", icon: Layers, details:true },
{ title: "Statistics", url: "/statistics", icon: ChartArea },
{ title: "Settings", url: "/settings", icon: Settings, details:true }
];
if (activeOrganization && (member?.data?.role === "admin" || member?.data?.role === "owner")) {
groupContent.push({ title: "Settings", url: "/settings", icon: Settings, details:true });
}
// if (activeOrganization && (member?.data?.role === "admin" || member?.data?.role === "owner")) {
// groupContent.push({ title: "Settings", url: "/settings", icon: Settings, details:true });
// }
const items: SidebarGroupItem[] = [
{
@@ -41,7 +41,6 @@ export function CreateOrganizationModal({open, onOpenChange, onSuccess}: createO
await authClient.organization.setActive({organizationSlug: result.data.value.slug});
onSuccess?.();
toast.success(result.data.actionSuccess?.message || "Organization Created.");
// router.push("/");
router.replace(`/dashboard/home`);
} else {
// @ts-ignore
@@ -82,26 +81,6 @@ export function CreateOrganizationModal({open, onOpenChange, onSuccess}: createO
</FormItem>
)}
/>
{/*<FormField*/}
{/* control={form.control}*/}
{/* name="slug"*/}
{/* defaultValue=""*/}
{/* render={({field}) => (*/}
{/* <FormItem>*/}
{/* <FormLabel>Slug</FormLabel>*/}
{/* <FormControl>*/}
{/* <Input*/}
{/* {...field}*/}
{/* onChange={(e) => {*/}
{/* const value = e.target.value.replaceAll(" ", "-").toLowerCase();*/}
{/* field.onChange(value);*/}
{/* }}*/}
{/* />*/}
{/* </FormControl>*/}
{/* <FormMessage/>*/}
{/* </FormItem>*/}
{/* )}*/}
{/*/>*/}
<DialogFooter>
<div className="flex items-center justify-between w-full">
<Button type="submit">Create</Button>
@@ -39,5 +39,6 @@ export function OrganizationCombobox() {
return <>{state === "expanded" &&
<ComboBox sideBar values={values} defaultValue={activeOrganization?.slug} onValueChange={onValueChange}
reload={handleReset}/>}</>;
reload={handleReset}/>}
</>;
}
@@ -23,6 +23,7 @@ export const deleteProjectAction = userAction.schema(z.string()).action(async ({
.set({
isArchived: true,
slug: uuid,
name: uuid,
})
.where(eq(drizzleDb.schemas.project.id, parsedInput))
.returning();
@@ -12,6 +12,7 @@ import {useMutation} from "@tanstack/react-query";
import {deleteBackupAction} from "@/features/dashboard/restore/restore.action";
import {toast} from "sonner";
import {useRouter} from "next/navigation";
import {MemberWithUser} from "@/db/schema/03_organization";
type DatabaseBackupListProps = {
@@ -19,6 +20,7 @@ type DatabaseBackupListProps = {
settings: Setting;
database: DatabaseWith;
backups: Backup[];
activeMember: MemberWithUser
}
@@ -69,7 +71,7 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
backupId: backup.id,
databaseId: backup.databaseId,
status: backup.status,
file: backup.file!,
file: backup.file ?? "",
projectSlug: props.database?.project?.slug!
});
return {
@@ -97,50 +99,58 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
},
});
const isMember = props.activeMember.role === "member";
return (
<DataTable
columns={backupColumns(props.isAlreadyRestore, props.settings, props.database)}
enableSelect={!isMember}
columns={backupColumns(props.isAlreadyRestore, props.settings, props.database, props.activeMember)}
data={filteredBackups}
enablePagination
selectedActions={(rows) => (
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
<div className="flex gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonWithLoading
variant="outline"
text="Actions"
onClick={() => {
<>
}}
disabled={rows.length === 0 || mutationDeleteBackups.isPending}
icon={<MoreHorizontal/>}
isPending={mutationDeleteBackups.isPending}
size="sm"
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
<div className="flex gap-2">
{!isMember && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonWithLoading
variant="outline"
text="Actions"
onClick={() => {
}}
disabled={rows.length === 0 || mutationDeleteBackups.isPending}
icon={<MoreHorizontal/>}
isPending={mutationDeleteBackups.isPending}
size="sm"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={async () => {
console.log("Deleting rows:", rows)
await mutationDeleteBackups.mutateAsync(rows)
}}
className="text-red-600 focus:text-red-700"
>
<Trash2 className="w-4 h-4 mr-2"/>
Delete Selected
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
<FiltersDropdown
items={items}
selectedItems={selectedFilters}
onSelect={handleSelectFilter}
clearFilters={clearFilters}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={async () => {
console.log("Deleting rows:", rows)
await mutationDeleteBackups.mutateAsync(rows)
}}
className="text-red-600 focus:text-red-700"
>
<Trash2 className="w-4 h-4 mr-2"/>
Delete Selected
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<FiltersDropdown
items={items}
selectedItems={selectedFilters}
onSelect={handleSelectFilter}
clearFilters={clearFilters}
/>
</div>
</div>
</div>
</div>
</>
)}
/>
)
@@ -9,11 +9,13 @@ import {useMutation} from "@tanstack/react-query";
import {deleteRestoreAction} from "@/features/dashboard/restore/restore.action";
import {toast} from "sonner";
import {useRouter} from "next/navigation";
import {MemberWithUser} from "@/db/schema/03_organization";
type DatabaseRestoreListProps = {
isAlreadyRestore: boolean;
restorations: Restoration[];
activeMember: MemberWithUser
}
export const DatabaseRestoreList = (props: DatabaseRestoreListProps) => {
@@ -47,40 +49,46 @@ export const DatabaseRestoreList = (props: DatabaseRestoreListProps) => {
router.refresh();
},
});
const isMember = props.activeMember.role === "member";
return (
<DataTable
columns={restoreColumns(props.isAlreadyRestore)}
enableSelect={!isMember}
columns={restoreColumns(props.isAlreadyRestore, props.activeMember)}
data={props.restorations}
enablePagination
selectedActions={(rows) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonWithLoading
variant="outline"
text="Actions"
onClick={() => {
}}
disabled={rows.length === 0 || mutationDeleteRestorations.isPending}
icon={<MoreHorizontal/>}
isPending={mutationDeleteRestorations.isPending}
size="sm"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={async () => {
await mutationDeleteRestorations.mutateAsync(rows)
}}
disabled={props.isAlreadyRestore}
className="text-red-600 focus:text-red-700"
>
<Trash2 className="w-4 h-4 mr-2"/>
Delete Selected
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<>
{!isMember && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonWithLoading
variant="outline"
text="Actions"
onClick={() => {
}}
disabled={rows.length === 0 || mutationDeleteRestorations.isPending}
icon={<MoreHorizontal/>}
isPending={mutationDeleteRestorations.isPending}
size="sm"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={async () => {
await mutationDeleteRestorations.mutateAsync(rows)
}}
disabled={props.isAlreadyRestore}
className="text-red-600 focus:text-red-700"
>
<Trash2 className="w-4 h-4 mr-2"/>
Delete Selected
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</>
)}
/>
)
@@ -8,13 +8,15 @@ import {Backup, Database, DatabaseWith, Restoration} from "@/db/schema/07_databa
import {Setting} from "@/db/schema/01_setting";
import {DatabaseBackupList} from "@/components/wrappers/dashboard/projects/database/database-backup-list";
import {DatabaseRestoreList} from "@/components/wrappers/dashboard/projects/database/database-restore-list";
import {MemberWithUser} from "@/db/schema/03_organization";
export type DatabaseTabsProps = {
settings: Setting
backups: Backup[];
restorations: Restoration[];
isAlreadyRestore: boolean;
database: DatabaseWith;
settings: Setting,
backups: Backup[],
restorations: Restoration[],
isAlreadyRestore: boolean,
database: DatabaseWith,
activeMember: MemberWithUser
};
export const DatabaseTabs = (props: DatabaseTabsProps) => {
@@ -58,12 +60,14 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
settings={props.settings}
database={props.database}
backups={props.backups}
activeMember={props.activeMember}
/>
</TabsContent>
<TabsContent className="h-full justify-between" value="restore">
<DatabaseRestoreList
isAlreadyRestore={props.isAlreadyRestore}
restorations={props.restorations}
activeMember={props.activeMember}
/>
</TabsContent>
</Tabs>
@@ -15,7 +15,7 @@ export const ProjectCard = (props: projectCardProps) => {
return (
<Link
href={`/dashboard/projects/${project.id}`}
className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md"
className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md rounded-xl"
>
<Card className="flex flex-row justify-between">
<div className="flex-1 text-left">
@@ -18,7 +18,7 @@ export const ProjectDatabaseCard = (props: projectDatabaseCardProps) => {
const { organizationSlug, data: database, extendedProps: extendedProps } = props;
return (
<Link className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md" href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
<Link className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md rounded-xl" href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
<DatabaseCard data={database} />
</Link>
);
@@ -5,7 +5,7 @@ import { ProjectSchema } from "@/components/wrappers/dashboard/projects/project-
import { z } from "zod";
import { ServerActionResult } from "@/types/action-type";
import { db } from "@/db";
import { eq, inArray } from "drizzle-orm";
import {and, eq, inArray} from "drizzle-orm";
import {Project} from "@/db/schema/06_project";
import * as drizzleDb from "@/db";
import {Database} from "@/db/schema/07_database";
@@ -21,6 +21,22 @@ export const createProjectAction = userAction
.action(async ({ parsedInput }): Promise<ServerActionResult<Project>> => {
try {
const slug = slugify(parsedInput.data.name);
const existingProject = await db.query.project.findFirst({
where: and(eq(drizzleDb.schemas.project.name, parsedInput.data.name) ),
})
if (existingProject) {
return {
success: false,
actionError: {
message: "A project with this name already exists.",
status: 400,
messageParams: { projectName: parsedInput.data.name },
},
};
}
const [createdProject] = await db
.insert(drizzleDb.schemas.project)
.values({
@@ -31,7 +47,10 @@ export const createProjectAction = userAction
.returning();
if (parsedInput.data.databases.length > 0) {
await db.update(drizzleDb.schemas.database).set({ projectId: createdProject.id }).where(inArray(drizzleDb.schemas.database.id, parsedInput.data.databases));
await db
.update(drizzleDb.schemas.database)
.set({ projectId: createdProject.id })
.where(inArray(drizzleDb.schemas.database.id, parsedInput.data.databases));
}
return {
@@ -43,6 +62,7 @@ export const createProjectAction = userAction
},
};
} catch (error) {
console.log(error);
return {
success: false,
actionError: {
@@ -55,6 +75,9 @@ export const createProjectAction = userAction
}
});
export const updateProjectAction = userAction
.schema(
z.object({
@@ -1,15 +1,20 @@
"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 {updateMemberRoleAction} from "@/components/wrappers/dashboard/settings/update-member.action";
import {RoleSchemaMember} from "@/components/wrappers/dashboard/settings/member.schema";
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/settings/update-member.action";
import { RoleSchemaMember } from "@/components/wrappers/dashboard/settings/member.schema";
export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
{
@@ -18,7 +23,7 @@ export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
cell: ({ row }) => {
const [role, setRole] = useState<string>(row.getValue("role"));
const { data: session } = useSession();
const activeOrgaMember = authClient.useActiveMember();
const updateMutation = useMutation({
mutationFn: () =>
@@ -28,36 +33,59 @@ export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
role: RoleSchemaMember.parse(role),
}),
onSuccess: () => {
toast.success(`User updated successfully.`);
toast.success("User updated successfully.");
},
onError: () => {
toast.error(`An error occurred while updating user information.`);
toast.error("An error occurred while updating user information.");
},
});
// Only allow cycling between admin <-> member
const handleUpdateRole = async () => {
const nextRole =
role === "owner" ? "admin" : role === "admin" ? "member" : "owner";
const nextRole = role === "admin" ? "member" : "admin";
setRole(nextRole);
await updateMutation.mutateAsync();
};
const isCurrentUser = session?.user.email === row.original.user.email;
const isMember = session?.user.role === "member";
const isMember = activeOrgaMember.data?.role === "member";
const isRowRoleOwner = role === "owner";
const isDisabled = isMember || isCurrentUser || isRowRoleOwner;
const isDisabled = isMember || isCurrentUser;
// Dynamic tooltip reason
const disabledReason = isCurrentUser
? "You cannot change your own role"
: isRowRoleOwner
? "Owner role cannot be modified"
: "Members cannot edit roles";
return (
const badge = (
<Badge
className={isDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"}
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
);
},
},
{
@@ -67,6 +95,5 @@ export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
{
accessorKey: "user.email",
header: "Email",
}
];
},
];
@@ -1,7 +1,5 @@
import {DataTable} from "@/components/wrappers/common/table/data-table";
import {usersColumnsAdmin} from "@/components/wrappers/dashboard/admin/columns-users";
import {MemberWithUser, Organization, OrganizationWithMembers} from "@/db/schema/03_organization";
import {OrganizationInvitation} from "@/db/schema/05_invitation";
import {organizationMemberColumns} from "@/components/wrappers/dashboard/settings/columns-organization-members";
@@ -11,12 +9,12 @@ interface SettingsOrganizationMembersTableProps {
export const SettingsOrganizationMembersTable = ({organization}: SettingsOrganizationMembersTableProps) => {
return (
<div className="flex flex-col h-full py-4">
<div className="flex gap-4 h-fit justify-between">
<h1>List of Organization members</h1>
</div>
<div className="mt-5 h-full">
<DataTable columns={organizationMemberColumns} data={organization.members as MemberWithUser[]}/>
<div className="flex flex-col h-full ">
<div className=" h-full">
<DataTable
columns={organizationMemberColumns}
enableSelect={false}
data={organization.members as MemberWithUser[]}/>
</div>
</div>
)