refactor(users): lots of auth stuff added to make it more easy to manage users
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 2m9s
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 2m9s
This commit is contained in:
@@ -29,30 +29,43 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
email: loginEmail,
|
||||
login: loginEmail,
|
||||
password: "",
|
||||
rememberMe: rememberMe,
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
// set remember me incase we want it later
|
||||
const loginValue = value.login.trim();
|
||||
const isEmailLogin = loginValue.includes("@");
|
||||
|
||||
if (value.rememberMe) {
|
||||
localStorage.setItem("rememberMe", value.rememberMe.toString());
|
||||
localStorage.setItem("loginEmail", value.email.toLocaleLowerCase());
|
||||
localStorage.setItem("loginEmail", loginValue.toLocaleLowerCase());
|
||||
} else {
|
||||
localStorage.removeItem("rememberMe");
|
||||
localStorage.removeItem("loginEmail");
|
||||
}
|
||||
|
||||
try {
|
||||
const login = await authClient.signIn.email({
|
||||
email: value.email,
|
||||
password: value.password,
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
navigate({ to: redirectPath ?? "/" });
|
||||
},
|
||||
},
|
||||
});
|
||||
const login = isEmailLogin
|
||||
? await authClient.signIn.email({
|
||||
email: loginValue.toLowerCase(),
|
||||
password: value.password,
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
navigate({ to: redirectPath ?? "/" });
|
||||
},
|
||||
},
|
||||
})
|
||||
: await authClient.signIn.username({
|
||||
username: loginValue,
|
||||
password: value.password,
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
navigate({ to: redirectPath ?? "/" });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (login.error) {
|
||||
toast.error(`${login.error?.message}`);
|
||||
@@ -95,11 +108,11 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
||||
form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<form.AppField name="email">
|
||||
<form.AppField name="login">
|
||||
{(field) => (
|
||||
<field.InputField
|
||||
label="Email"
|
||||
inputType="email"
|
||||
label="Username or Email Address"
|
||||
inputType="text"
|
||||
required={rememberMe}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useAppForm } from "@/lib/formSutff";
|
||||
import { Separator } from "../../components/ui/separator";
|
||||
|
||||
export const Route = createFileRoute("/(auth)/user/signup")({
|
||||
component: RouteComponent,
|
||||
@@ -22,6 +23,7 @@ function RouteComponent() {
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
username: "",
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
if (value.password !== value.confirmPassword) {
|
||||
@@ -33,6 +35,7 @@ function RouteComponent() {
|
||||
name: value.name,
|
||||
email: value.email,
|
||||
password: value.password,
|
||||
username: value.username ?? value.name,
|
||||
callbackURL: `${window.location.origin}/lst/app`,
|
||||
});
|
||||
|
||||
@@ -71,6 +74,15 @@ function RouteComponent() {
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<div className="m-2">
|
||||
<p>Username is option if left blank it will be your name</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<form.AppField name="username">
|
||||
{(field) => (
|
||||
<field.InputField label="Username" inputType="text" />
|
||||
)}
|
||||
</form.AppField>
|
||||
|
||||
{/* Email */}
|
||||
<form.AppField name="email">
|
||||
|
||||
@@ -5,6 +5,7 @@ import Header from "@/components/Header";
|
||||
import { AppSidebar } from "@/components/Sidebar/sidebar";
|
||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { ThemeProvider } from "@/lib/theme-provider";
|
||||
import { TooltipProvider } from "../components/ui/tooltip";
|
||||
import { useSession } from "../lib/auth-client";
|
||||
|
||||
const RootLayout = () => {
|
||||
@@ -14,16 +15,17 @@ const RootLayout = () => {
|
||||
<ThemeProvider>
|
||||
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
||||
<Header />
|
||||
<TooltipProvider>
|
||||
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||
<AppSidebar />
|
||||
|
||||
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||
<AppSidebar />
|
||||
|
||||
<main className="w-full p-4">
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<main className="w-full p-4">
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
<Toaster expand richColors closeButton />
|
||||
</SidebarProvider>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { api } from "../../../lib/apiHelper";
|
||||
import { useAppForm } from "../../../lib/formSutff";
|
||||
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
||||
|
||||
@@ -31,7 +32,7 @@ export default function NewScanUser({ refetch }: { refetch: any }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
const { data } = await api.post(
|
||||
"/lst/api/mobile/auth/user",
|
||||
{
|
||||
name: value.name,
|
||||
|
||||
153
frontend/src/routes/admin/-components/Newuser.tsx
Normal file
153
frontend/src/routes/admin/-components/Newuser.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { authClient } from "../../../lib/auth-client";
|
||||
import { selectableRoles } from "../../../lib/auth-permissions";
|
||||
import { useAppForm } from "../../../lib/formSutff";
|
||||
|
||||
export default function NewUser({ refetch }: { refetch: any }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
role: "",
|
||||
username: "",
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
if (value.name === "" || value.email === "" || value.password === "") {
|
||||
toast.error("Missing Mandatory data please try again ");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await authClient.admin.createUser({
|
||||
email: value.email, // required
|
||||
password: value.password, // required
|
||||
name: value.name, // required
|
||||
role: (value.role ?? "user") as any,
|
||||
data: { username: value.username },
|
||||
});
|
||||
|
||||
if (data?.user) {
|
||||
toast.success(`${value.name}, was just created `);
|
||||
form.reset();
|
||||
setOpen(false);
|
||||
refetch();
|
||||
}
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const closeModel = (e: boolean) => {
|
||||
setOpen(e);
|
||||
|
||||
if (!e) {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
const openForm = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
|
||||
<Button onClick={openForm}>Create new user</Button>
|
||||
|
||||
<DialogContent showCloseButton={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Scan user.</DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<form.AppField name="name">
|
||||
{(field) => (
|
||||
<field.InputField
|
||||
label="Name"
|
||||
inputType="text"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Username can be your windows or anything, if you do not fill this
|
||||
out your name is used as your username
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<form.AppField name="username">
|
||||
{(field) => (
|
||||
<field.InputField label="Username" inputType="text" />
|
||||
)}
|
||||
</form.AppField>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<form.AppField name="email">
|
||||
{(field) => (
|
||||
<field.InputField
|
||||
label="Email"
|
||||
inputType="email"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<form.AppField name="password">
|
||||
{(field) => (
|
||||
<field.InputField
|
||||
label="Password"
|
||||
inputType="text"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
</div>
|
||||
|
||||
<div className="w-32">
|
||||
<form.AppField name="role">
|
||||
{(field) => (
|
||||
<field.SelectField
|
||||
label="Roles"
|
||||
placeholder="Select role"
|
||||
options={selectableRoles}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2 ">
|
||||
<form.AppForm>
|
||||
<form.SubmitButton>Submit</form.SubmitButton>
|
||||
</form.AppForm>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "../../components/ui/tooltip";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { authClient } from "../../lib/auth-client";
|
||||
import { notificationSubs } from "../../lib/queries/notificationSubs";
|
||||
import { notifications } from "../../lib/queries/notifications";
|
||||
@@ -36,7 +37,7 @@ const updateNotifications = async (
|
||||
//console.log(id, data);
|
||||
try {
|
||||
const res = await axios.patch(
|
||||
`/lst/api/notification/${id}`,
|
||||
`/notification/${id}`,
|
||||
{ interval: data.interval },
|
||||
{
|
||||
withCredentials: true,
|
||||
@@ -110,7 +111,7 @@ const NotificationTable = () => {
|
||||
|
||||
const removeNotification = async (ns: any) => {
|
||||
try {
|
||||
const res = await axios.delete(`/lst/api/notification/sub`, {
|
||||
const res = await api.delete(`/notification/sub`, {
|
||||
withCredentials: true,
|
||||
data: {
|
||||
userId: ns.userId,
|
||||
@@ -168,7 +169,7 @@ const NotificationTable = () => {
|
||||
setActiveToggle(e);
|
||||
|
||||
try {
|
||||
const res = await axios.patch(
|
||||
const res = await api.patch(
|
||||
`/lst/api/notification/${i.row.original.id}`,
|
||||
{
|
||||
active: !activeToggle,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Suspense, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Spinner } from "../../components/ui/spinner";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { authClient } from "../../lib/auth-client";
|
||||
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||
@@ -19,7 +20,13 @@ import NewScanUser from "./-components/NewScanUser";
|
||||
export const Route = createFileRoute("/admin/scanUsers")({
|
||||
beforeLoad: async ({ location }) => {
|
||||
const { data: session } = await authClient.getSession();
|
||||
const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||
//const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||
|
||||
const canAccess = await authClient.admin.hasPermission({
|
||||
permissions: {
|
||||
mobile: ["create"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
throw redirect({
|
||||
@@ -30,7 +37,9 @@ export const Route = createFileRoute("/admin/scanUsers")({
|
||||
});
|
||||
}
|
||||
|
||||
if (!allowedRole.includes(session.user.role as string)) {
|
||||
//if (!allowedRole.includes(session.user.role as string)) {
|
||||
|
||||
if (!canAccess) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
});
|
||||
@@ -47,7 +56,7 @@ const updateSettings = async (
|
||||
) => {
|
||||
//console.log(id, data);
|
||||
try {
|
||||
const res = await axios.patch(`/lst/api/mobile/auth/user/${id}`, data, {
|
||||
const res = await axios.patch(`/mobile/auth/user/${id}`, data, {
|
||||
withCredentials: true,
|
||||
timeout: 15000,
|
||||
validateStatus: () => true,
|
||||
@@ -123,7 +132,7 @@ const ScanUserTable = () => {
|
||||
<Button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
||||
const { data } = await api.get("/mobile/pin/new");
|
||||
updateSetting.mutate({
|
||||
id: row.original.id,
|
||||
field: "pinNumber",
|
||||
@@ -171,7 +180,7 @@ const ScanUserTable = () => {
|
||||
setActiveToggle(true);
|
||||
|
||||
try {
|
||||
const res = await axios.delete(
|
||||
const res = await api.delete(
|
||||
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
||||
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
|
||||
import { format } from "date-fns-tz";
|
||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "../../components/ui/tooltip";
|
||||
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { authClient } from "../../lib/auth-client";
|
||||
import { servers } from "../../lib/queries/servers";
|
||||
import LstTable from "../../lib/tableStuff/LstTable";
|
||||
@@ -116,8 +117,8 @@ const ServerTable = () => {
|
||||
`${i.row.original.name} just started the upgrade monitor logs for errors.`,
|
||||
);
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`/lst/api/admin/build/updateServer`,
|
||||
const res = await api.post(
|
||||
`/admin/build/updateServer`,
|
||||
{
|
||||
server: i.row.original.server,
|
||||
destination: i.row.original.serverLoc,
|
||||
@@ -218,8 +219,8 @@ function RouteComponent() {
|
||||
];
|
||||
const triggerBuild = async () => {
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`/lst/api/admin/build/release`,
|
||||
const res = await api.post(
|
||||
`/admin/build/release`,
|
||||
|
||||
{
|
||||
withCredentials: true,
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "../../components/ui/tooltip";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { authClient } from "../../lib/auth-client";
|
||||
import { getSettings } from "../../lib/queries/getSettings";
|
||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||
@@ -48,7 +49,7 @@ const updateSettings = async (
|
||||
) => {
|
||||
//console.log(id, data);
|
||||
try {
|
||||
const res = await axios.patch(`/lst/api/settings/${id}`, data, {
|
||||
const res = await api.patch(`/settings/${id}`, data, {
|
||||
withCredentials: true,
|
||||
});
|
||||
toast.success(`Setting just updated`);
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
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 { authClient, useSession } from "../../lib/auth-client";
|
||||
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 allowedRole = ["systemAdmin", "admin"];
|
||||
const canAccess = await authClient.admin.hasPermission({
|
||||
permissions: {
|
||||
user: ["create"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
throw redirect({
|
||||
@@ -25,7 +48,8 @@ export const Route = createFileRoute("/admin/users")({
|
||||
});
|
||||
}
|
||||
|
||||
if (!allowedRole.includes(session.user.role as string)) {
|
||||
//if (!allowedRole.includes(session.user.role as string)) {
|
||||
if (!canAccess) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
});
|
||||
@@ -37,8 +61,51 @@ export const Route = createFileRoute("/admin/users")({
|
||||
});
|
||||
|
||||
const UserTable = () => {
|
||||
const { data } = useSuspenseQuery(getUsers());
|
||||
const { data: session } = useSession();
|
||||
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>();
|
||||
|
||||
@@ -50,6 +117,13 @@ const UserTable = () => {
|
||||
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} />
|
||||
@@ -57,27 +131,113 @@ const UserTable = () => {
|
||||
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" searchable={false} />
|
||||
),
|
||||
header: ({ column }) => <SearchableHeader column={column} title="Role" />,
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
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"),
|
||||
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 (session && session.user.role === "systemAdmin") {
|
||||
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 }) => (
|
||||
@@ -126,7 +286,28 @@ const UserTable = () => {
|
||||
);
|
||||
}
|
||||
|
||||
return <LstTable data={data} columns={columns} pageSize={50} />;
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user