Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 2m9s
338 lines
8.3 KiB
TypeScript
338 lines
8.3 KiB
TypeScript
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
import { createColumnHelper } from "@tanstack/react-table";
|
|
import { format } from "date-fns-tz";
|
|
import { KeyRound } from "lucide-react";
|
|
import { Suspense } from "react";
|
|
import { toast } from "sonner";
|
|
import { Button } from "../../components/ui/button";
|
|
import { Input } from "../../components/ui/input";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "../../components/ui/select";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "../../components/ui/tooltip";
|
|
import { authClient } from "../../lib/auth-client";
|
|
import { selectableRoles } from "../../lib/auth-permissions";
|
|
import { getUsers } from "../../lib/queries/getUsers";
|
|
import { permissionQuery } from "../../lib/queries/permsCheck";
|
|
import LstTable from "../../lib/tableStuff/LstTable";
|
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
|
import { trackLstEvent } from "../../lib/umami.utils";
|
|
import NewUser from "./-components/Newuser";
|
|
|
|
export const Route = createFileRoute("/admin/users")({
|
|
beforeLoad: async ({ location }) => {
|
|
const { data: session } = await authClient.getSession();
|
|
// const allowedRole = ["systemAdmin", "admin"];
|
|
const canAccess = await authClient.admin.hasPermission({
|
|
permissions: {
|
|
user: ["create"],
|
|
},
|
|
});
|
|
|
|
if (!session?.user) {
|
|
throw redirect({
|
|
to: "/",
|
|
search: {
|
|
redirect: location.href,
|
|
},
|
|
});
|
|
}
|
|
|
|
//if (!allowedRole.includes(session.user.role as string)) {
|
|
if (!canAccess) {
|
|
throw redirect({
|
|
to: "/",
|
|
});
|
|
}
|
|
|
|
return { user: session.user };
|
|
},
|
|
component: RouteComponent,
|
|
});
|
|
|
|
const UserTable = () => {
|
|
const { data, refetch } = useSuspenseQuery(getUsers());
|
|
//const { data: session } = useSession();
|
|
const { data: canImpersonate = false } = useQuery(
|
|
permissionQuery({
|
|
user: ["impersonate"],
|
|
}),
|
|
);
|
|
const { data: canUpdate = false } = useQuery(
|
|
permissionQuery({
|
|
user: ["update"],
|
|
}),
|
|
);
|
|
|
|
const updatePassword = useMutation({
|
|
mutationFn: async ({ user, password }: { user: any; password: string }) => {
|
|
return authClient.admin.setUserPassword({
|
|
userId: user.id,
|
|
newPassword: password,
|
|
});
|
|
},
|
|
onSuccess: () => {
|
|
toast.success("Password updated");
|
|
},
|
|
onError: (error) => {
|
|
toast.error(error.message);
|
|
},
|
|
});
|
|
|
|
const handleRoleChange = async (row: any, newRole: string) => {
|
|
//console.log("update this user", row, newRole);
|
|
|
|
const { data, error } = await authClient.admin.updateUser({
|
|
userId: row.id,
|
|
data: { role: newRole },
|
|
});
|
|
|
|
if (error) {
|
|
console.error(error);
|
|
toast.error(error.message);
|
|
return;
|
|
}
|
|
|
|
toast.success(`${data.name}, role was just changed to: ${newRole}`);
|
|
refetch();
|
|
};
|
|
|
|
const columnHelper = createColumnHelper<any>();
|
|
|
|
const columns = [
|
|
columnHelper.accessor("name", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader column={column} title="Name" searchable={true} />
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => i.getValue(),
|
|
}),
|
|
columnHelper.accessor("username", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader column={column} title="Username" searchable={true} />
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => i.getValue(),
|
|
}),
|
|
columnHelper.accessor("email", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader column={column} title="Email" searchable={true} />
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => i.getValue(),
|
|
}),
|
|
|
|
// columnHelper.accessor("role", {
|
|
// header: ({ column }) => (
|
|
// <SearchableHeader column={column} title="Role" searchable={false} />
|
|
// ),
|
|
// filterFn: "includesString",
|
|
// cell: (i) => i.getValue(),
|
|
// }),
|
|
columnHelper.accessor("role", {
|
|
header: ({ column }) => <SearchableHeader column={column} title="Role" />,
|
|
filterFn: "includesString",
|
|
cell: ({ row, getValue }) => {
|
|
const currentRole = getValue();
|
|
|
|
return (
|
|
<Select
|
|
value={currentRole}
|
|
onValueChange={(newRole) => {
|
|
handleRoleChange(row.original, newRole);
|
|
}}
|
|
>
|
|
<SelectTrigger className="w-[180px]">
|
|
<SelectValue placeholder="Select role" />
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
{selectableRoles.map((role) => (
|
|
<SelectItem key={role.value} value={role.value}>
|
|
{role.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
},
|
|
}),
|
|
];
|
|
|
|
if (canUpdate) {
|
|
columns.push(
|
|
columnHelper.accessor("changePassword", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader column={column} title="Change Password" />
|
|
),
|
|
filterFn: "includesString",
|
|
cell: ({ row }) => {
|
|
return (
|
|
<div className="flex flex-row items-center gap-2">
|
|
<Input
|
|
type="password"
|
|
placeholder="New password"
|
|
className="w-[200px]"
|
|
onKeyDown={(e) => {
|
|
if (e.key !== "Enter") return;
|
|
|
|
const password = e.currentTarget.value.trim();
|
|
|
|
if (!password) return;
|
|
|
|
updatePassword.mutate({
|
|
user: row.original,
|
|
password,
|
|
});
|
|
|
|
e.currentTarget.value = "";
|
|
}}
|
|
/>
|
|
<Tooltip>
|
|
<TooltipTrigger>
|
|
<Button
|
|
size="icon"
|
|
variant="outline"
|
|
onClick={(e) => {
|
|
const input =
|
|
e.currentTarget.parentElement?.querySelector("input");
|
|
|
|
const password = input?.value.trim();
|
|
|
|
if (!password) return;
|
|
|
|
updatePassword.mutate({
|
|
user: row.original,
|
|
password,
|
|
});
|
|
|
|
if (input) {
|
|
input.value = "";
|
|
}
|
|
}}
|
|
>
|
|
<KeyRound className="h-4 w-4" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>
|
|
Update Password, fill out and press enter or update here
|
|
</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
);
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (canImpersonate) {
|
|
columns.push(
|
|
columnHelper.accessor("banned", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader column={column} title="Banned" searchable={false} />
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => <span>{i.getValue() ? "True" : "False"}</span>,
|
|
}),
|
|
columnHelper.accessor("impersonate", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader
|
|
column={column}
|
|
title="Impersonate User"
|
|
searchable={false}
|
|
/>
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => {
|
|
const beSomeone = async () => {
|
|
trackLstEvent("impersonateUser_click", {
|
|
module: "users",
|
|
action: "click",
|
|
label: "impersonating user",
|
|
page: window.location.pathname,
|
|
});
|
|
|
|
const { data, error } = await authClient.admin.impersonateUser({
|
|
userId: i.row.original.id, // required
|
|
});
|
|
|
|
if (data) {
|
|
await authClient.getSession();
|
|
window.location.replace("/lst/app");
|
|
}
|
|
|
|
if (error) {
|
|
console.log(error);
|
|
}
|
|
};
|
|
|
|
const cantImpersonate = ["admin", "systemAdmin"];
|
|
if (cantImpersonate.includes(i.row.original.role)) return;
|
|
return <Button onClick={beSomeone}>Become user</Button>;
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
columns.push(
|
|
columnHelper.accessor("updatedAt", {
|
|
header: ({ column }) => (
|
|
<SearchableHeader
|
|
column={column}
|
|
title="Updated at"
|
|
searchable={false}
|
|
/>
|
|
),
|
|
filterFn: "includesString",
|
|
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
|
}),
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex justify-end m-2">
|
|
<NewUser refetch={refetch} />
|
|
</div>
|
|
<LstTable data={data} columns={columns} pageSize={50} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
function RouteComponent() {
|
|
// const createUser = async () => {
|
|
// const { data: newUser, error } = await authClient.admin.createUser({
|
|
// email: "cowch@gmail.com", // required
|
|
// password: "crazypassword", // required
|
|
// name: "James Smith", // required
|
|
// role: "manager",
|
|
// });
|
|
// };
|
|
|
|
// const besomeone = async () => {
|
|
// const { data, error } = await authClient.admin.impersonateUser({
|
|
// userId: "iswCNVzQ9cWulbmsaMbeX6e7fV6Eme6t", // required
|
|
// });
|
|
|
|
// await authClient.getSession();
|
|
// window.location.replace("/lst/app");
|
|
// };
|
|
|
|
return (
|
|
<Suspense fallback={<SkellyTable />}>
|
|
<UserTable />
|
|
</Suspense>
|
|
);
|
|
}
|