mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Some bug and visuals improvements.
This commit is contained in:
@@ -5,26 +5,40 @@ import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/AdminEma
|
||||
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/AdminStorageTab/SettingsStorageTab";
|
||||
import { AdminUsersTable } from "@/components/wrappers/dashboard/admin/admin-user-table";
|
||||
import { User } from "@/db/schema/01_user";
|
||||
import {Setting} from "@/db/schema/00_setting";
|
||||
import { Setting } from "@/db/schema/00_setting";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export type AdminTabsProps = {
|
||||
users: User[];
|
||||
settings: Setting;
|
||||
};
|
||||
|
||||
export const AdminTabs = (props: AdminTabsProps) => {
|
||||
const { users, settings } = props;
|
||||
export const AdminTabs = ({ users, settings }: AdminTabsProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [tab, setTab] = useState<string>(() => searchParams.get("tab") ?? "users");
|
||||
|
||||
useEffect(() => {
|
||||
const newTab = searchParams.get("tab") ?? "users";
|
||||
setTab(newTab);
|
||||
}, [searchParams]);
|
||||
|
||||
const handleChangeTab = (value: string) => {
|
||||
router.push(`?tab=${value}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs defaultValue="users">
|
||||
<Tabs className="h-full" value={tab} onValueChange={handleChangeTab}>
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger className="w-full " value="users">
|
||||
<TabsTrigger className="w-full" value="users">
|
||||
Users
|
||||
</TabsTrigger>
|
||||
<TabsTrigger className="w-full " value="email">
|
||||
<TabsTrigger className="w-full" value="email">
|
||||
Email
|
||||
</TabsTrigger>
|
||||
<TabsTrigger className="w-full " value="storage">
|
||||
<TabsTrigger className="w-full" value="storage">
|
||||
Storage
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const AdminUsersTable = (props: AdminUsersTableProps) => {
|
||||
<div className="flex gap-4 h-fit justify-between">
|
||||
<h1>List of Portabase's users</h1>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="mt-5 h-full">
|
||||
<DataTable columns={usersColumnsAdmin} data={users} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,8 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
|
||||
header: "Role",
|
||||
cell: ({row}) => {
|
||||
const [role, setRole] = useState<string>(row.getValue("role"));
|
||||
const { data: session } = useSession();
|
||||
const isCurrentUser = session?.user.email === row.original.email;
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}),
|
||||
@@ -37,7 +39,11 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className="cursor-pointer" onClick={() => handleUpdateRole()} variant="outline">
|
||||
<Badge
|
||||
className={isCurrentUser ? "cursor-not-allowed opacity-50" : "cursor-pointer"}
|
||||
onClick={isCurrentUser ? undefined : () => handleUpdateRole()}
|
||||
variant="outline"
|
||||
>
|
||||
{role}
|
||||
</Badge>
|
||||
);
|
||||
@@ -73,23 +79,18 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{!session || session?.user.email === row.original.email ? null :
|
||||
<ButtonWithLoading
|
||||
disabled={!session || session?.user.email === row.original.email}
|
||||
variant="outline"
|
||||
text=""
|
||||
icon={<Trash2 color="red" size={15}/>}
|
||||
onClick={async () => {
|
||||
await mutation.mutateAsync();
|
||||
}}
|
||||
size="icon"
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ export function AppSidebar() {
|
||||
<SidebarMenuCustomMain/>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarMenu>
|
||||
<SidebarMenu className="mb-2">
|
||||
<SidebarMenuItem>
|
||||
<LoggedInButton />
|
||||
</SidebarMenuItem>
|
||||
|
||||
@@ -10,7 +10,7 @@ export const SideBarFooterCredit = (props: SideBarFooterCreditProps) => {
|
||||
return (
|
||||
<>
|
||||
{state === "expanded" && (
|
||||
<div className="text-center">
|
||||
<div className="text-center mb-2">
|
||||
<h1 className="text-[10px]">Portabase Community Edition v{env.NEXT_PUBLIC_PROJECT_VERSION}</h1>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -13,10 +13,15 @@ export const ProjectCard = (props: projectCardProps) => {
|
||||
const { data: project, organizationSlug } = props;
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/projects/${project.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
|
||||
<Link
|
||||
href={`/dashboard/projects/${project.id}`}
|
||||
className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md"
|
||||
>
|
||||
<Card className="flex flex-row justify-between">
|
||||
<div className="">
|
||||
<CardHeader className="text-2xl font-bold">{project.name}</CardHeader>
|
||||
<div className="flex-1 text-left">
|
||||
<CardHeader className="text-2xl font-bold">
|
||||
{project.name}
|
||||
</CardHeader>
|
||||
<CardContent>{project.databases.length} databases</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const DatabaseCard = (props: databaseCardProps) => {
|
||||
|
||||
<Card className="flex flex-row justify-between">
|
||||
<div className="flex items-center space-x-4 px-4">
|
||||
<Image src="/PostgreSQL.png" alt="Database type Icon" width={60} height={60} className="object-cover ml-4" />
|
||||
<Image src="/images/postgresql.png" alt="Database type Icon" width={60} height={60} className="object-cover ml-4" />
|
||||
|
||||
<div className="justify-between">
|
||||
<div className="font-medium">Name: {database.name}</div>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {User, Settings, UserOrganization} from "@prisma/client";
|
||||
import {SettingsUsersTab} from "@/components/wrappers/dashboard/settings/SettingsUsersTab/SettingsUsersTab";
|
||||
|
||||
|
||||
export type SettingsTabsProps = {
|
||||
currentUser: User;
|
||||
users: UserOrganization[];
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const SettingsTabs = (props: SettingsTabsProps) => {
|
||||
|
||||
const {currentUser, users} = props;
|
||||
|
||||
return (
|
||||
<SettingsUsersTab currentUser={currentUser} users={users}/>
|
||||
)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import {usersColumns} from "@/components/wrappers/dashboard/settings/SettingsUsersTab/columns-users-settings";
|
||||
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
|
||||
import {flexRender, Row} from "@tanstack/react-table";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
|
||||
export type SettingsUsersTabProps = {
|
||||
// currentUser: User;
|
||||
// users: UserOrganization[]
|
||||
}
|
||||
|
||||
export const SettingsUsersTab = (props: SettingsUsersTabProps) => {
|
||||
|
||||
// const {currentUser, users} = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full py-4">
|
||||
<div className="flex gap-4 h-fit justify-between">
|
||||
<h1>List of organization's users</h1>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
{/*<DataTableWithPagination*/}
|
||||
{/* columns={usersColumns}*/}
|
||||
{/* data={users}*/}
|
||||
{/* DataTable={UsersDataTable}*/}
|
||||
{/* dataTableProps={{currentUser}}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type usersDataTableProps = {
|
||||
// currentUser: User;
|
||||
// table: any,
|
||||
}
|
||||
|
||||
export const UsersDataTable = (
|
||||
// {currentUser, table}: usersDataTableProps
|
||||
) => {
|
||||
|
||||
return (
|
||||
<div className="rounded-md border w-full ">
|
||||
{/*<Table className="w-full">*/}
|
||||
{/* <TableHeader>*/}
|
||||
{/* {table.getHeaderGroups().map((headerGroup) => (*/}
|
||||
{/* <TableRow key={headerGroup.id}>*/}
|
||||
{/* {headerGroup.headers.map((header) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <TableHead key={header.id}>*/}
|
||||
{/* {header.isPlaceholder*/}
|
||||
{/* ? null*/}
|
||||
{/* : flexRender(*/}
|
||||
{/* header.column.columnDef.header,*/}
|
||||
{/* header.getContext()*/}
|
||||
{/* )}*/}
|
||||
{/* </TableHead>*/}
|
||||
{/* )*/}
|
||||
{/* })}*/}
|
||||
{/* </TableRow>*/}
|
||||
{/* ))}*/}
|
||||
{/* </TableHeader>*/}
|
||||
{/* <TableBody>*/}
|
||||
{/* {table.getRowModel().rows?.length ? (*/}
|
||||
{/* table.getRowModel().rows.map((row: Row<UserOrganization>) => {*/}
|
||||
{/* return(*/}
|
||||
{/* <TableRow*/}
|
||||
{/* className={cn((row.original.userId) === currentUser.id ? "opacity-40 pointer-events-none" : "")}*/}
|
||||
{/* key={row.id}*/}
|
||||
{/* data-state={row.getIsSelected() && "selected"}*/}
|
||||
{/* >*/}
|
||||
{/* {row.getVisibleCells().map((cell) => (*/}
|
||||
{/* <TableCell key={cell.id}>*/}
|
||||
{/* {flexRender(*/}
|
||||
{/* cell.column.columnDef.cell,*/}
|
||||
{/* cell.getContext(),*/}
|
||||
{/* )}*/}
|
||||
{/* </TableCell>*/}
|
||||
{/* ))}*/}
|
||||
{/* </TableRow>*/}
|
||||
{/* )*/}
|
||||
{/* }*/}
|
||||
|
||||
{/* )) : (*/}
|
||||
{/* <TableRow>*/}
|
||||
{/* <TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">*/}
|
||||
{/* No results.*/}
|
||||
{/* </TableCell>*/}
|
||||
{/* </TableRow>*/}
|
||||
{/* )}*/}
|
||||
{/* </TableBody>*/}
|
||||
{/*</Table>*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { updateUserOrganizationAction } from "@/components/wrappers/dashboard/settings/SettingsUsersTab/settings-user-tab.action";
|
||||
import {OrganizationMember} from "@/db/schema/03_member";
|
||||
|
||||
export const usersColumns: ColumnDef<OrganizationMember>[] = [
|
||||
{
|
||||
accessorKey: "role",
|
||||
header: "Role",
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const [role, setRole] = useState<string>(row.getValue("role"));
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: () => updateUserOrganizationAction({ id: row.original.id, role: role }),
|
||||
onSuccess: () => {
|
||||
toast.success(`User updated successfully.`);
|
||||
// router.refresh()
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(`An error occurred while updating user information.`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleUpdateRole = async () => {
|
||||
const nextRole = role === "admin" ? "member" : role === "member" ? "admin" : "admin";
|
||||
setRole(nextRole);
|
||||
await updateMutation.mutateAsync();
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className="cursor-pointer" onClick={() => handleUpdateRole()} variant="outline">
|
||||
{role}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "user.name",
|
||||
header: "Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "user.email",
|
||||
header: "Email",
|
||||
},
|
||||
{
|
||||
accessorKey: "updatedAt",
|
||||
header: "Updated At",
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue("updatedAt")).toLocaleString("fr-FR");
|
||||
},
|
||||
},
|
||||
];
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
"use server"
|
||||
import {userAction} from "@/safe-actions";
|
||||
import {z} from "zod";
|
||||
import {prisma} from "@/prisma";
|
||||
|
||||
|
||||
export const updateUserOrganizationAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
role: z.string(),
|
||||
}
|
||||
)
|
||||
)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
const updatedUserOrganization = await prisma.userOrganization.update({
|
||||
where: {
|
||||
id: parsedInput.id,
|
||||
},
|
||||
data: {
|
||||
role: parsedInput.role,
|
||||
},
|
||||
})
|
||||
return {
|
||||
data: updatedUserOrganization,
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {MemberWithUser} from "@/db/schema/02_organization";
|
||||
import {useState} from "react";
|
||||
import {useSession} from "@/lib/auth/auth-client";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
|
||||
import {toast} from "sonner";
|
||||
import {Badge} from "@/components/ui/badge";
|
||||
|
||||
export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
|
||||
{
|
||||
accessorKey: "role",
|
||||
header: "Role",
|
||||
cell: ({row}) => {
|
||||
const [role, setRole] = useState<string>(row.getValue("role"));
|
||||
const { data: session } = useSession();
|
||||
const isCurrentUser = session?.user.email === row.original.user.email;
|
||||
|
||||
// const updateMutation = useMutation({
|
||||
// mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}),
|
||||
// onSuccess: () => {
|
||||
// toast.success(`User updated successfully.`);
|
||||
// },
|
||||
// onError: () => {
|
||||
// toast.error(`An error occurred while updating user information.`);
|
||||
// },
|
||||
// });
|
||||
|
||||
const handleUpdateRole = async () => {
|
||||
// const nextRole = role === "admin" ? "pending" : role === "pending" ? "user" : "admin";
|
||||
// setRole(nextRole);
|
||||
// await updateMutation.mutateAsync();
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge
|
||||
className={isCurrentUser ? "cursor-not-allowed opacity-50" : "cursor-pointer"}
|
||||
onClick={isCurrentUser ? undefined : () => handleUpdateRole()}
|
||||
variant="outline"
|
||||
>
|
||||
{role}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "user.name",
|
||||
header: "Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "user.email",
|
||||
header: "Email",
|
||||
}
|
||||
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
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/02_organization";
|
||||
import {OrganizationInvitation} from "@/db/schema/04_invitation";
|
||||
import {organizationMemberColumns} from "@/components/wrappers/dashboard/settings/columns-organization-members";
|
||||
|
||||
|
||||
interface SettingsOrganizationMembersTableProps {
|
||||
organization: OrganizationWithMembers
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user