mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add … to actions that require confirmation (#9862)
This commit is contained in:
@@ -36,7 +36,7 @@ export const LicenseCard = ({
|
||||
return (
|
||||
<Paper key={license.id} elevation={2} className={styles.licenseCard}>
|
||||
<ConfirmDialog
|
||||
type="info"
|
||||
type="delete"
|
||||
hideCancel={false}
|
||||
open={licenseIDMarkedForRemoval !== undefined}
|
||||
onConfirm={() => {
|
||||
@@ -105,11 +105,11 @@ export const LicenseCard = ({
|
||||
<Stack spacing={2}>
|
||||
<Button
|
||||
className={styles.removeButton}
|
||||
variant="text"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={() => setLicenseIDMarkedForRemoval(license.id)}
|
||||
>
|
||||
Remove
|
||||
Remove…
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -148,10 +148,6 @@ const useStyles = makeStyles((theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
removeButton: {
|
||||
height: "17px",
|
||||
minHeight: "17px",
|
||||
padding: 0,
|
||||
border: "none",
|
||||
color: theme.palette.error.main,
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
|
||||
@@ -110,10 +110,10 @@ const LicensesSettingsPageView: FC<Props> = ({
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<Stack alignItems="center" spacing={0.5}>
|
||||
<span className={styles.title}>
|
||||
You don{"'"}t have any licenses!
|
||||
You don't have any licenses!
|
||||
</span>
|
||||
<span className={styles.description}>
|
||||
You{"'"}re missing out on high availability, RBAC, quotas, and
|
||||
You're missing out on high availability, RBAC, quotas, and
|
||||
much more. Contact{" "}
|
||||
<MuiLink href="mailto:sales@coder.com">sales</MuiLink> or{" "}
|
||||
<MuiLink href="https://coder.com/trial">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
@@ -60,6 +59,7 @@ export const GroupPage: React.FC = () => {
|
||||
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
|
||||
const isLoading = !groupData || !permissions;
|
||||
const canUpdateGroup = permissions ? permissions.canUpdateGroup : false;
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -80,17 +80,22 @@ export const GroupPage: React.FC = () => {
|
||||
<PageHeader
|
||||
actions={
|
||||
<Maybe condition={canUpdateGroup}>
|
||||
<Link to="settings" component={RouterLink}>
|
||||
<Button startIcon={<SettingsOutlined />}>Settings</Button>
|
||||
</Link>
|
||||
<Button
|
||||
startIcon={<SettingsOutlined />}
|
||||
to="settings"
|
||||
component={RouterLink}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
<Button
|
||||
disabled={groupData?.id === groupData?.organization_id}
|
||||
onClick={() => {
|
||||
setIsDeletingGroup(true);
|
||||
}}
|
||||
startIcon={<DeleteOutline />}
|
||||
className={styles.removeButton}
|
||||
>
|
||||
Delete
|
||||
Delete…
|
||||
</Button>
|
||||
</Maybe>
|
||||
}
|
||||
@@ -184,11 +189,11 @@ export const GroupPage: React.FC = () => {
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
|
||||
{group && (
|
||||
{groupQuery.data && (
|
||||
<DeleteDialog
|
||||
isOpen={isDeletingGroup}
|
||||
confirmLoading={deleteGroupMutation.isLoading}
|
||||
name={group.name}
|
||||
name={groupQuery.data.name}
|
||||
entity="group"
|
||||
onConfirm={async () => {
|
||||
try {
|
||||
@@ -303,10 +308,16 @@ const GroupMemberRow = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
autoComplete: {
|
||||
width: 300,
|
||||
},
|
||||
removeButton: {
|
||||
color: theme.palette.error.main,
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default GroupPage;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { fireEvent, screen, within } from "@testing-library/react";
|
||||
import * as API from "../../../api/api";
|
||||
import { renderWithAuth } from "../../../testHelpers/renderHelpers";
|
||||
import { Language as SSHKeysPageLanguage, SSHKeysPage } from "./SSHKeysPage";
|
||||
import { Language as SSHKeysPageViewLanguage } from "./SSHKeysPageView";
|
||||
import { MockGitSSHKey, mockApiError } from "testHelpers/entities";
|
||||
|
||||
describe("SSH keys Page", () => {
|
||||
@@ -20,9 +19,7 @@ describe("SSH keys Page", () => {
|
||||
await screen.findByText(MockGitSSHKey.public_key);
|
||||
|
||||
// Click on the "Regenerate" button to display the confirm dialog
|
||||
const regenerateButton = screen.getByRole("button", {
|
||||
name: SSHKeysPageViewLanguage.regenerateLabel,
|
||||
});
|
||||
const regenerateButton = screen.getByTestId("regenerate");
|
||||
fireEvent.click(regenerateButton);
|
||||
const confirmDialog = screen.getByRole("dialog");
|
||||
expect(confirmDialog).toHaveTextContent(
|
||||
@@ -67,9 +64,7 @@ describe("SSH keys Page", () => {
|
||||
);
|
||||
|
||||
// Click on the "Regenerate" button to display the confirm dialog
|
||||
const regenerateButton = screen.getByRole("button", {
|
||||
name: SSHKeysPageViewLanguage.regenerateLabel,
|
||||
});
|
||||
const regenerateButton = screen.getByTestId("regenerate");
|
||||
fireEvent.click(regenerateButton);
|
||||
const confirmDialog = screen.getByRole("dialog");
|
||||
expect(confirmDialog).toHaveTextContent(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PropsWithChildren, FC, useState } from "react";
|
||||
import { ConfirmDialog } from "../../../components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { Section } from "../../../components/SettingsLayout/Section";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Section } from "components/SettingsLayout/Section";
|
||||
import { SSHKeysPageView } from "./SSHKeysPageView";
|
||||
import { regenerateUserSSHKey, userSSHKey } from "api/queries/sshKeys";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
|
||||
|
||||
@@ -8,10 +8,6 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { FC } from "react";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
|
||||
export const Language = {
|
||||
regenerateLabel: "Regenerate",
|
||||
};
|
||||
|
||||
export interface SSHKeysPageViewProps {
|
||||
isLoading: boolean;
|
||||
getSSHKeyError?: unknown;
|
||||
@@ -57,8 +53,8 @@ export const SSHKeysPageView: FC<
|
||||
</p>
|
||||
<CodeExample code={sshKey.public_key.trim()} />
|
||||
<div>
|
||||
<Button onClick={onRegenerateClick}>
|
||||
{Language.regenerateLabel}
|
||||
<Button onClick={onRegenerateClick} data-testid="regenerate">
|
||||
Regenerate…
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
MockOwnerRole,
|
||||
} from "testHelpers/entities";
|
||||
import { Language as usersXServiceLanguage } from "xServices/users/usersXService";
|
||||
import * as API from "../../api/api";
|
||||
import { Role } from "../../api/typesGenerated";
|
||||
import * as API from "api/api";
|
||||
import { Role } from "api/typesGenerated";
|
||||
import { Language as ResetPasswordDialogLanguage } from "./ResetPasswordDialog";
|
||||
import { renderWithAuth } from "../../testHelpers/renderHelpers";
|
||||
import { server } from "../../testHelpers/server";
|
||||
import { renderWithAuth } from "testHelpers/renderHelpers";
|
||||
import { server } from "testHelpers/server";
|
||||
import { Language as UsersPageLanguage, UsersPage } from "./UsersPage";
|
||||
|
||||
const renderPage = () => {
|
||||
@@ -29,7 +29,7 @@ const suspendUser = async (setupActionSpies: () => void) => {
|
||||
await user.click(firstMoreButton);
|
||||
|
||||
const menu = await screen.findByRole("menu");
|
||||
const suspendButton = within(menu).getByText("Suspend");
|
||||
const suspendButton = within(menu).getByText(/Suspend/);
|
||||
|
||||
await user.click(suspendButton);
|
||||
|
||||
@@ -60,7 +60,7 @@ const deleteUser = async (setupActionSpies: () => void) => {
|
||||
await user.click(selectedMoreButton);
|
||||
|
||||
const menu = await screen.findByRole("menu");
|
||||
const deleteButton = within(menu).getByText("Delete");
|
||||
const deleteButton = within(menu).getByText(/Delete/);
|
||||
|
||||
await user.click(deleteButton);
|
||||
|
||||
@@ -89,7 +89,7 @@ const activateUser = async (setupActionSpies: () => void) => {
|
||||
fireEvent.click(suspendedMoreButton);
|
||||
|
||||
const menu = screen.getByRole("menu");
|
||||
const activateButton = within(menu).getByText("Activate");
|
||||
const activateButton = within(menu).getByText(/Activate/);
|
||||
fireEvent.click(activateButton);
|
||||
|
||||
// Check if the confirm message is displayed
|
||||
@@ -115,7 +115,7 @@ const resetUserPassword = async (setupActionSpies: () => void) => {
|
||||
fireEvent.click(firstMoreButton);
|
||||
|
||||
const menu = screen.getByRole("menu");
|
||||
const resetPasswordButton = within(menu).getByText("Reset password");
|
||||
const resetPasswordButton = within(menu).getByText(/Reset password/);
|
||||
|
||||
fireEvent.click(resetPasswordButton);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { type FC } from "react";
|
||||
import * as TypesGen from "../../../api/typesGenerated";
|
||||
import { combineClasses } from "../../../utils/combineClasses";
|
||||
import { AvatarData } from "../../../components/AvatarData/AvatarData";
|
||||
@@ -215,51 +215,45 @@ export const UsersTableBody: FC<
|
||||
<TableCell>
|
||||
<TableRowMenu
|
||||
data={user}
|
||||
menuItems={
|
||||
menuItems={[
|
||||
// Return either suspend or activate depending on status
|
||||
(user.status === "active" || user.status === "dormant"
|
||||
? [
|
||||
{
|
||||
label: "Suspend" as ReactNode,
|
||||
onClick: onSuspendUser,
|
||||
disabled: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: "Activate" as ReactNode,
|
||||
onClick: onActivateUser,
|
||||
disabled: false,
|
||||
},
|
||||
]
|
||||
).concat(
|
||||
{
|
||||
label: "Delete",
|
||||
onClick: onDeleteUser,
|
||||
disabled: user.id === actorID,
|
||||
},
|
||||
{
|
||||
label: "Reset password",
|
||||
onClick: onResetUserPassword,
|
||||
disabled: user.login_type !== "password",
|
||||
},
|
||||
{
|
||||
label: "View workspaces",
|
||||
onClick: onListWorkspaces,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
View activity
|
||||
{!canViewActivity && <EnterpriseBadge />}
|
||||
</>
|
||||
),
|
||||
onClick: onViewActivity,
|
||||
disabled: !canViewActivity,
|
||||
},
|
||||
)
|
||||
}
|
||||
user.status === "active" || user.status === "dormant"
|
||||
? {
|
||||
label: <>Suspend…</>,
|
||||
onClick: onSuspendUser,
|
||||
disabled: false,
|
||||
}
|
||||
: {
|
||||
label: <>Activate…</>,
|
||||
onClick: onActivateUser,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: <>Delete…</>,
|
||||
onClick: onDeleteUser,
|
||||
disabled: user.id === actorID,
|
||||
},
|
||||
{
|
||||
label: <>Reset password…</>,
|
||||
onClick: onResetUserPassword,
|
||||
disabled: user.login_type !== "password",
|
||||
},
|
||||
{
|
||||
label: "View workspaces",
|
||||
onClick: onListWorkspaces,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
View activity
|
||||
{!canViewActivity && <EnterpriseBadge />}
|
||||
</>
|
||||
),
|
||||
onClick: onViewActivity,
|
||||
disabled: !canViewActivity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user