Some bug and visuals improvements.

This commit is contained in:
charlesgauthereau
2025-07-26 22:23:00 +02:00
parent 847a567c52
commit 7be542f676
21 changed files with 410 additions and 322 deletions
@@ -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>
)
}
@@ -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");
},
},
];
@@ -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>
)
}