mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 17:27:40 +00:00
misc: added pagination and moved to admin route
This commit is contained in:
@@ -83,6 +83,82 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/user-management/users",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
searchTerm: z.string().default(""),
|
||||||
|
offset: z.coerce.number().default(0),
|
||||||
|
limit: z.coerce.number().default(20)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
id: true
|
||||||
|
}).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const users = await server.services.superAdmin.getUsers({
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/user-management/users/:userId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
userId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const users = await server.services.superAdmin.deleteUser(req.params.userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
users
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/signup",
|
url: "/signup",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -92,75 +91,6 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/list",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
users: UsersSchema.pick({
|
|
||||||
username: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
email: true,
|
|
||||||
id: true
|
|
||||||
}).array()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: (req, res, done) => {
|
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
|
||||||
verifySuperAdmin(req, res, done);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handler: async () => {
|
|
||||||
const users = await server.services.user.listUsers();
|
|
||||||
|
|
||||||
return {
|
|
||||||
users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "DELETE",
|
|
||||||
url: "/:userId",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
userId: z.string()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
users: UsersSchema.pick({
|
|
||||||
username: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
email: true,
|
|
||||||
id: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: (req, res, done) => {
|
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
|
||||||
verifySuperAdmin(req, res, done);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handler: async (req) => {
|
|
||||||
const users = await server.services.user.deleteUser(req.params.userId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/me/project-favorites",
|
url: "/me/project-favorites",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { AuthMethod } from "../auth/auth-type";
|
|||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import { LoginMethod, TAdminSignUpDTO } from "./super-admin-types";
|
import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
|
||||||
|
|
||||||
type TSuperAdminServiceFactoryDep = {
|
type TSuperAdminServiceFactoryDep = {
|
||||||
serverCfgDAL: TSuperAdminDALFactory;
|
serverCfgDAL: TSuperAdminDALFactory;
|
||||||
@@ -209,9 +209,24 @@ export const superAdminServiceFactory = ({
|
|||||||
return { token, user: userInfo, organization };
|
return { token, user: userInfo, organization };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUsers = ({ offset, limit, searchTerm }: TAdminGetUsersDTO) => {
|
||||||
|
return userDAL.getUsersByFilter({
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
searchTerm
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUser = async (userId: string) => {
|
||||||
|
const user = await userDAL.deleteById(userId);
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initServerCfg,
|
initServerCfg,
|
||||||
updateServerCfg,
|
updateServerCfg,
|
||||||
adminSignUp
|
adminSignUp,
|
||||||
|
getUsers,
|
||||||
|
deleteUser
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export type TAdminSignUpDTO = {
|
|||||||
userAgent: string;
|
userAgent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAdminGetUsersDTO = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
searchTerm: string;
|
||||||
|
};
|
||||||
|
|
||||||
export enum LoginMethod {
|
export enum LoginMethod {
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
TUserEncryptionKeysUpdate
|
TUserEncryptionKeysUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TUserDALFactory = ReturnType<typeof userDALFactory>;
|
export type TUserDALFactory = ReturnType<typeof userDALFactory>;
|
||||||
|
|
||||||
@@ -18,6 +18,32 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
const userOrm = ormify(db, TableName.Users);
|
const userOrm = ormify(db, TableName.Users);
|
||||||
const findUserByUsername = async (username: string, tx?: Knex) => userOrm.findOne({ username }, tx);
|
const findUserByUsername = async (username: string, tx?: Knex) => userOrm.findOne({ username }, tx);
|
||||||
|
|
||||||
|
const getUsersByFilter = async ({
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
searchTerm
|
||||||
|
}: {
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
searchTerm: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
let query = db.replicaNode()(TableName.Users).where("isGhost", "=", false);
|
||||||
|
if (searchTerm) {
|
||||||
|
query = query.where((qb) => {
|
||||||
|
void qb
|
||||||
|
.whereILike("email", `%${searchTerm}%`)
|
||||||
|
.orWhereILike("firstName", `%${searchTerm}%`)
|
||||||
|
.orWhereILike("lastName", `%${searchTerm}%`)
|
||||||
|
.orWhereLike("username", `%${searchTerm}%`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await query.limit(limit).offset(offset).select(selectAllTableCols(TableName.Users));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Get users by filter" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// USER ENCRYPTION FUNCTIONS
|
// USER ENCRYPTION FUNCTIONS
|
||||||
// -------------------------
|
// -------------------------
|
||||||
const findUserEncKeyByUsername = async ({ username }: { username: string }) => {
|
const findUserEncKeyByUsername = async ({ username }: { username: string }) => {
|
||||||
@@ -159,6 +185,7 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
upsertUserEncryptionKey,
|
upsertUserEncryptionKey,
|
||||||
createUserEncryption,
|
createUserEncryption,
|
||||||
findOneUserAction,
|
findOneUserAction,
|
||||||
createUserAction
|
createUserAction,
|
||||||
|
getUsersByFilter
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -295,10 +295,6 @@ export const userServiceFactory = ({
|
|||||||
return updatedOrgMembership.projectFavorites;
|
return updatedOrgMembership.projectFavorites;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listUsers = async () => {
|
|
||||||
return userDAL.find({});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sendEmailVerificationCode,
|
sendEmailVerificationCode,
|
||||||
verifyEmailVerificationCode,
|
verifyEmailVerificationCode,
|
||||||
@@ -312,7 +308,6 @@ export const userServiceFactory = ({
|
|||||||
unlockUser,
|
unlockUser,
|
||||||
getUserPrivateKey,
|
getUserPrivateKey,
|
||||||
getUserProjectFavorites,
|
getUserProjectFavorites,
|
||||||
updateUserProjectFavorites,
|
updateUserProjectFavorites
|
||||||
listUsers
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { useCreateAdminUser, useUpdateServerConfig } from "./mutation";
|
export { useCreateAdminUser, useDeleteUser, useUpdateServerConfig } from "./mutation";
|
||||||
export { useGetServerConfig } from "./queries";
|
export { useAdminGetUsers, useGetServerConfig } from "./queries";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import { organizationKeys } from "../organization/queries";
|
import { organizationKeys } from "../organization/queries";
|
||||||
import { User } from "../users/types";
|
import { User } from "../users/types";
|
||||||
import { adminQueryKeys } from "./queries";
|
import { adminQueryKeys, adminStandaloneKeys } from "./queries";
|
||||||
import { TCreateAdminUserDTO, TServerConfig } from "./types";
|
import { TCreateAdminUserDTO, TServerConfig } from "./types";
|
||||||
|
|
||||||
export const useCreateAdminUser = () => {
|
export const useCreateAdminUser = () => {
|
||||||
@@ -43,3 +43,19 @@ export const useUpdateServerConfig = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDeleteUser = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (userId: string) => {
|
||||||
|
await apiRequest.delete(`/api/v1/admin/user-management/users/${userId}`);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [adminStandaloneKeys.getUsers]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { TServerConfig } from "./types";
|
import { User } from "../types";
|
||||||
|
import { AdminGetUsersFilters, TServerConfig } from "./types";
|
||||||
|
|
||||||
|
export const adminStandaloneKeys = {
|
||||||
|
getUsers: "get-users"
|
||||||
|
};
|
||||||
|
|
||||||
export const adminQueryKeys = {
|
export const adminQueryKeys = {
|
||||||
serverConfig: () => ["server-config"] as const
|
serverConfig: () => ["server-config"] as const,
|
||||||
|
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchServerConfig = async () => {
|
const fetchServerConfig = async () => {
|
||||||
@@ -32,3 +38,24 @@ export const useGetServerConfig = ({
|
|||||||
...options,
|
...options,
|
||||||
enabled: options?.enabled ?? true
|
enabled: options?.enabled ?? true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useAdminGetUsers = (filters: AdminGetUsersFilters) => {
|
||||||
|
return useInfiniteQuery({
|
||||||
|
queryKey: adminQueryKeys.getUsers(filters),
|
||||||
|
queryFn: async ({ pageParam }) => {
|
||||||
|
const { data } = await apiRequest.get<{ users: User[] }>(
|
||||||
|
"/api/v1/admin/user-management/users",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
...filters,
|
||||||
|
offset: pageParam
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.users;
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage, pages) =>
|
||||||
|
lastPage.length !== 0 ? pages.length * filters.limit : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -37,3 +37,8 @@ export type TCreateAdminUserDTO = {
|
|||||||
verifier: string;
|
verifier: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AdminGetUsersFilters = {
|
||||||
|
limit: number;
|
||||||
|
searchTerm: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
useAddUserToWsE2EE,
|
useAddUserToWsE2EE,
|
||||||
useAddUserToWsNonE2EE,
|
useAddUserToWsNonE2EE,
|
||||||
useDeleteUser,
|
|
||||||
useSendEmailVerificationCode,
|
useSendEmailVerificationCode,
|
||||||
useVerifyEmailVerificationCode
|
useVerifyEmailVerificationCode
|
||||||
} from "./mutation";
|
} from "./mutation";
|
||||||
@@ -20,7 +19,6 @@ export {
|
|||||||
useGetOrgUsers,
|
useGetOrgUsers,
|
||||||
useGetUser,
|
useGetUser,
|
||||||
useGetUserAction,
|
useGetUserAction,
|
||||||
useListUsers,
|
|
||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
useRegisterUserAction,
|
useRegisterUserAction,
|
||||||
useRevokeMySessions,
|
useRevokeMySessions,
|
||||||
|
|||||||
@@ -112,17 +112,3 @@ export const useUpdateUserProjectFavorites = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDeleteUser = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (userId: string) => {
|
|
||||||
await apiRequest.delete(`/api/v1/user/${userId}`);
|
|
||||||
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(userKeys.listUsers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -41,14 +41,6 @@ export const fetchUserDetails = async () => {
|
|||||||
|
|
||||||
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
||||||
|
|
||||||
export const fetchUsersList = async () => {
|
|
||||||
const { data } = await apiRequest.get<{ users: User[] }>("/api/v1/user/list");
|
|
||||||
|
|
||||||
return data.users;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useListUsers = () => useQuery(userKeys.listUsers, fetchUsersList);
|
|
||||||
|
|
||||||
export const useDeleteMe = () => {
|
export const useDeleteMe = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useState } from "react";
|
||||||
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -18,31 +19,103 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useUser } from "@app/context";
|
import { useUser } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { useDebounce, usePopUp } from "@app/hooks";
|
||||||
import { useDeleteUser, useListUsers } from "@app/hooks/api";
|
import { useAdminGetUsers, useDeleteUser } from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
const UserPanelTable = ({
|
||||||
|
searchUserFilter,
|
||||||
|
handlePopUpOpen
|
||||||
|
}: {
|
||||||
|
searchUserFilter: string;
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["removeUser"]>,
|
||||||
|
data: {
|
||||||
|
username: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { user } = useUser();
|
||||||
|
const userId = user?.id || "";
|
||||||
|
const debounedSearchTerm = useDebounce(searchUserFilter, 500);
|
||||||
|
|
||||||
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useAdminGetUsers({
|
||||||
|
limit: 20,
|
||||||
|
searchTerm: debounedSearchTerm
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEmpty = !isLoading && !data?.pages?.[0].length;
|
||||||
|
return (
|
||||||
|
<div className="mt-4">
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="w-5/12">Name</Th>
|
||||||
|
<Th className="w-5/12">Username</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="users" />}
|
||||||
|
{!isLoading &&
|
||||||
|
data?.pages?.map((users) =>
|
||||||
|
users.map(({ username, email, firstName, lastName, id }) => {
|
||||||
|
const name = firstName || lastName ? `${firstName} ${lastName}` : "-";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={`user-${id}`} className="w-full">
|
||||||
|
<Td className="w-5/12">{name}</Td>
|
||||||
|
<Td className="w-5/12">{email}</Td>
|
||||||
|
<Td>
|
||||||
|
{userId !== id && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<IconButton
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
isDisabled={userId === id}
|
||||||
|
onClick={() => handlePopUpOpen("removeUser", { username, id })}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isLoading && isEmpty && <EmptyState title="No users found" icon={faUsers} />}
|
||||||
|
</TableContainer>
|
||||||
|
{!isEmpty && (
|
||||||
|
<Button
|
||||||
|
className="mt-4 py-3 text-sm"
|
||||||
|
isFullWidth
|
||||||
|
variant="star"
|
||||||
|
isLoading={isFetchingNextPage}
|
||||||
|
isDisabled={isFetchingNextPage || !hasNextPage}
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
>
|
||||||
|
{hasNextPage ? "Load More" : "End of list"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const UserPanel = () => {
|
export const UserPanel = () => {
|
||||||
const [searchUserFilter, setSearchUserFilter] = useState("");
|
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"removeUser"
|
"removeUser"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { user } = useUser();
|
const [searchUserFilter, setSearchUserFilter] = useState("");
|
||||||
const userId = user?.id || "";
|
|
||||||
const { data: users, isLoading } = useListUsers();
|
|
||||||
const { mutateAsync: deleteUser } = useDeleteUser();
|
|
||||||
|
|
||||||
const filterdUsers = useMemo(
|
const { mutateAsync: deleteUser } = useDeleteUser();
|
||||||
() =>
|
|
||||||
users?.filter(
|
|
||||||
({ firstName, lastName, username, email }) =>
|
|
||||||
firstName?.toLowerCase().includes(searchUserFilter.toLowerCase()) ||
|
|
||||||
lastName?.toLowerCase().includes(searchUserFilter.toLowerCase()) ||
|
|
||||||
username?.toLowerCase().includes(searchUserFilter.toLowerCase()) ||
|
|
||||||
email?.toLowerCase().includes(searchUserFilter.toLowerCase())
|
|
||||||
),
|
|
||||||
[users, searchUserFilter]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
@@ -74,54 +147,7 @@ export const UserPanel = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
placeholder="Search users..."
|
placeholder="Search users..."
|
||||||
/>
|
/>
|
||||||
|
<UserPanelTable searchUserFilter={searchUserFilter} handlePopUpOpen={handlePopUpOpen} />
|
||||||
<div className="mt-4">
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<THead>
|
|
||||||
<Tr>
|
|
||||||
<Th>Name</Th>
|
|
||||||
<Th>Username</Th>
|
|
||||||
<Th className="w-5" />
|
|
||||||
</Tr>
|
|
||||||
</THead>
|
|
||||||
<TBody>
|
|
||||||
{isLoading && <TableSkeleton columns={4} innerKey="users" />}
|
|
||||||
{!isLoading &&
|
|
||||||
filterdUsers?.map(({ username, email, firstName, lastName, id }) => {
|
|
||||||
const name = firstName || lastName ? `${firstName} ${lastName}` : "-";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tr key={`user-${id}`} className="w-full">
|
|
||||||
<Td>{name}</Td>
|
|
||||||
<Td>{email}</Td>
|
|
||||||
<Td>
|
|
||||||
{userId !== id && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<IconButton
|
|
||||||
size="lg"
|
|
||||||
colorSchema="danger"
|
|
||||||
variant="plain"
|
|
||||||
ariaLabel="update"
|
|
||||||
className="ml-4"
|
|
||||||
isDisabled={userId === id}
|
|
||||||
onClick={() => handlePopUpOpen("removeUser", { username, id })}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TBody>
|
|
||||||
</Table>
|
|
||||||
{!isLoading && filterdUsers?.length === 0 && (
|
|
||||||
<EmptyState title="No users found" icon={faUsers} />
|
|
||||||
)}
|
|
||||||
</TableContainer>
|
|
||||||
</div>
|
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.removeUser.isOpen}
|
isOpen={popUp.removeUser.isOpen}
|
||||||
deleteKey="remove"
|
deleteKey="remove"
|
||||||
|
|||||||
Reference in New Issue
Block a user