import { useMachine } from "@xstate/react" import { User } from "api/typesGenerated" import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog" import { getPaginationContext, nonInitialPage, } from "components/PaginationWidget/utils" import { useMe } from "hooks/useMe" import { usePermissions } from "hooks/usePermissions" import { FC, ReactNode, useEffect } from "react" import { Helmet } from "react-helmet-async" import { useSearchParams, useNavigate } from "react-router-dom" import { siteRolesMachine } from "xServices/roles/siteRolesXService" import { usersMachine } from "xServices/users/usersXService" import { ConfirmDialog } from "../../components/Dialogs/ConfirmDialog/ConfirmDialog" import { ResetPasswordDialog } from "../../components/Dialogs/ResetPasswordDialog/ResetPasswordDialog" import { pageTitle } from "../../utils/page" import { UsersPageView } from "./UsersPageView" import { useStatusFilterMenu } from "./UsersFilter" import { useFilter } from "components/Filter/filter" import { useDashboard } from "components/Dashboard/DashboardProvider" import { deploymentConfigMachine } from "xServices/deploymentConfig/deploymentConfigMachine" export const Language = { suspendDialogTitle: "Suspend user", suspendDialogAction: "Suspend", suspendDialogMessagePrefix: "Do you want to suspend the user", activateDialogTitle: "Activate user", activateDialogAction: "Activate", activateDialogMessagePrefix: "Do you want to activate the user", } const getSelectedUser = (id: string, users?: User[]) => users?.find((u) => u.id === id) export const UsersPage: FC<{ children?: ReactNode }> = () => { const navigate = useNavigate() const searchParamsResult = useSearchParams() const { entitlements } = useDashboard() const [searchParams, setSearchParams] = searchParamsResult const filter = searchParams.get("filter") ?? "" const [usersState, usersSend] = useMachine(usersMachine, { context: { filter, paginationContext: getPaginationContext(searchParams), }, actions: { updateURL: (context, event) => setSearchParams({ page: event.page, filter: context.filter }), }, }) const { users, getUsersError, usernameToDelete, usernameToSuspend, usernameToActivate, userIdToResetPassword, newUserPassword, paginationRef, count, } = usersState.context const { updateUsers: canEditUsers, viewDeploymentValues } = usePermissions() const [rolesState] = useMachine(siteRolesMachine, { context: { hasPermission: canEditUsers, }, }) const { roles } = rolesState.context // Ideally this only runs if 'canViewDeployment' is true. // TODO: Prevent api call if the user does not have the perms. const [state] = useMachine(deploymentConfigMachine) const { deploymentValues } = state.context // Indicates if oidc roles are synced from the oidc idp. // Assign 'false' if unknown. const oidcRoleSyncEnabled = viewDeploymentValues && deploymentValues?.config.oidc?.user_role_field !== "" // Is loading if // - users are loading or // - the user can edit the users but the roles are loading const isLoading = usersState.matches("gettingUsers") || (canEditUsers && rolesState.matches("gettingRoles")) const me = useMe() const useFilterResult = useFilter({ searchParamsResult, onUpdate: () => { usersSend({ type: "UPDATE_PAGE", page: "1" }) }, }) useEffect(() => { usersSend({ type: "UPDATE_FILTER", query: useFilterResult.query }) }, [useFilterResult.query, usersSend]) const statusMenu = useStatusFilterMenu({ value: useFilterResult.values.status, onChange: (option) => useFilterResult.update({ ...useFilterResult.values, status: option?.value, }), }) return ( <> {pageTitle("Users")} { navigate( "/workspaces?filter=" + encodeURIComponent(`owner:${user.username}`), ) }} onViewActivity={(user) => { navigate( "/audit?filter=" + encodeURIComponent(`username:${user.username}`), ) }} onDeleteUser={(user) => { usersSend({ type: "DELETE_USER", userId: user.id, username: user.username, }) }} onSuspendUser={(user) => { usersSend({ type: "SUSPEND_USER", userId: user.id, username: user.username, }) }} onActivateUser={(user) => { usersSend({ type: "ACTIVATE_USER", userId: user.id, username: user.username, }) }} onResetUserPassword={(user) => { usersSend({ type: "RESET_USER_PASSWORD", userId: user.id }) }} onUpdateUserRoles={(user, roles) => { usersSend({ type: "UPDATE_USER_ROLES", userId: user.id, roles, }) }} isUpdatingUserRoles={usersState.matches("updatingUserRoles")} isLoading={isLoading} canEditUsers={canEditUsers} canViewActivity={entitlements.features.audit_log.enabled} paginationRef={paginationRef} isNonInitialPage={nonInitialPage(searchParams)} actorID={me.id} filterProps={{ filter: useFilterResult, error: getUsersError, menus: { status: statusMenu, }, }} /> { usersSend("CONFIRM_USER_DELETE") }} onCancel={() => { usersSend("CANCEL_USER_DELETE") }} /> { usersSend("CONFIRM_USER_SUSPENSION") }} onClose={() => { usersSend("CANCEL_USER_SUSPENSION") }} description={ <> {Language.suspendDialogMessagePrefix} {usernameToSuspend && " "} {usernameToSuspend ?? ""}? } /> { usersSend("CONFIRM_USER_ACTIVATION") }} onClose={() => { usersSend("CANCEL_USER_ACTIVATION") }} description={ <> {Language.activateDialogMessagePrefix} {usernameToActivate && " "} {usernameToActivate ?? ""}? } /> {userIdToResetPassword && ( { usersSend("CANCEL_USER_PASSWORD_RESET") }} onConfirm={() => { usersSend("CONFIRM_USER_PASSWORD_RESET") }} /> )} ) } export default UsersPage