feat(tests, components): enhance Cypress commands and UI elements for improved testing and accessibility
- Added new Cypress commands for mocking admin-related API interactions, including list, create, update, delete, and change status functionalities. - Introduced data-cy attributes across various components (e.g., Breadcrumbs, Forms, Tables) to improve testability and accessibility. - Updated existing components to support new data-cy attributes for better integration with Cypress tests.
This commit is contained in:
@@ -7,26 +7,37 @@ interface BreadcrumbProps {
|
||||
|
||||
const Breadcrumb = ({ items }: BreadcrumbProps) => {
|
||||
const currentPage = items[items.length - 1];
|
||||
const toDataCySegment = (value: string) =>
|
||||
value.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
||||
|
||||
return (
|
||||
<div className="mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div
|
||||
className="mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
data-cy="breadcrumb"
|
||||
>
|
||||
<h2 className="text-[16px] font-bold leading-[30px] text-dark dark:text-white">
|
||||
{currentPage.name}
|
||||
</h2>
|
||||
|
||||
<nav>
|
||||
<nav data-cy="breadcrumb-nav">
|
||||
<ol className="flex items-center gap-2">
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
const dataCySegment = toDataCySegment(item.name);
|
||||
|
||||
return isLast ? (
|
||||
<li key={index} className="font-medium text-sm text-primary">
|
||||
<li
|
||||
key={index}
|
||||
className="font-medium text-sm text-primary"
|
||||
data-cy={`breadcrumb-current-${dataCySegment}`}
|
||||
>
|
||||
{item.name}
|
||||
</li>
|
||||
) : (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Link
|
||||
href={item.href}
|
||||
data-cy={`breadcrumb-link-${dataCySegment}`}
|
||||
className="font-medium text-sm hover:text-primary"
|
||||
>
|
||||
{item.name}
|
||||
|
||||
@@ -30,6 +30,7 @@ type InputGroupProps<T extends FieldValues> = {
|
||||
height?: "sm" | "default";
|
||||
autoComplete?: HTMLInputAutoCompleteAttribute;
|
||||
defaultValue?: string;
|
||||
dataCy?: string;
|
||||
register?: UseFormRegister<T>;
|
||||
errors?: FieldErrors<T>;
|
||||
} & Partial<UseFormRegisterReturn>;
|
||||
@@ -49,6 +50,7 @@ const InputGroup = <T extends FieldValues>({
|
||||
register,
|
||||
errors,
|
||||
autoComplete,
|
||||
dataCy,
|
||||
onChange,
|
||||
onBlur,
|
||||
ref,
|
||||
@@ -84,6 +86,7 @@ const InputGroup = <T extends FieldValues>({
|
||||
name={name}
|
||||
ref={ref}
|
||||
autoComplete={autoComplete}
|
||||
data-cy={dataCy}
|
||||
onChange={(e) => {
|
||||
onChange?.(e);
|
||||
handleChange?.(e);
|
||||
|
||||
@@ -25,6 +25,7 @@ type PropsType<T extends FieldValues> = {
|
||||
clearable?: boolean;
|
||||
onClear?: () => void;
|
||||
value?: string;
|
||||
dataCy?: string;
|
||||
} & (
|
||||
| { placeholder?: string; defaultValue?: string }
|
||||
| { placeholder: string; defaultValue?: string }
|
||||
@@ -48,6 +49,7 @@ export function Select<T extends FieldValues>({
|
||||
clearable = false,
|
||||
onClear,
|
||||
value: controlledValue,
|
||||
dataCy,
|
||||
...props
|
||||
}: PropsType<T>) {
|
||||
const id = useId();
|
||||
@@ -107,6 +109,7 @@ export function Select<T extends FieldValues>({
|
||||
onChange?.(e);
|
||||
}}
|
||||
onBlur={onBlur}
|
||||
data-cy={dataCy}
|
||||
className={cn(
|
||||
"w-full appearance-none rounded-xl border border-stroke/70 bg-white/90 px-5.5 py-3 shadow-[0_1px_2px_rgba(16,24,40,0.04),0_8px_20px_rgba(16,24,40,0.04)] outline-none backdrop-blur-[1px] transition-all duration-200 focus:border-primary/70 focus:bg-white focus:shadow-[0_0_0_2px_rgba(59,130,246,0.14),0_10px_24px_rgba(59,130,246,0.08)] active:border-primary/70 dark:border-dark-3/80 dark:bg-dark-2/85 dark:focus:border-primary/80 dark:focus:shadow-[0_0_0_2px_rgba(59,130,246,0.2),0_10px_24px_rgba(15,23,42,0.45)] [&>option]:text-dark-5 dark:[&>option]:text-dark-6",
|
||||
isOptionSelected && "text-dark dark:text-white",
|
||||
@@ -133,6 +136,7 @@ export function Select<T extends FieldValues>({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
data-cy={dataCy ? `${dataCy}-clear` : undefined}
|
||||
className="absolute right-10 top-1/2 -translate-y-1/2 text-dark-5 transition hover:scale-110 hover:text-dark dark:text-dark-6 dark:hover:text-white"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -22,9 +22,13 @@ function EmptyListWrapper<T>({
|
||||
|
||||
if (!list.length) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className="text-center" colSpan={columns.length * 100}>
|
||||
<h2>{emptyMessage}</h2>
|
||||
<TableRow data-cy="empty-list-row">
|
||||
<TableCell
|
||||
className="text-center"
|
||||
colSpan={columns.length * 100}
|
||||
data-cy="empty-list-cell"
|
||||
>
|
||||
<h2 data-cy="empty-list-message">{emptyMessage}</h2>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ const AdminListFilters = ({
|
||||
<form
|
||||
className="grid !min-w-full grid-cols-1 gap-4 xl:grid-cols-2"
|
||||
onSubmit={handleFilterSubmit(search)}
|
||||
data-cy="admins-filter-form"
|
||||
>
|
||||
<Select
|
||||
{...filterRegister("status")}
|
||||
@@ -43,6 +44,7 @@ const AdminListFilters = ({
|
||||
clearable
|
||||
value={watch("status")}
|
||||
onClear={() => setValue("status", undefined)}
|
||||
dataCy="admins-filter-status-select"
|
||||
/>
|
||||
<InputGroup
|
||||
{...filterRegister("search")}
|
||||
@@ -50,6 +52,7 @@ const AdminListFilters = ({
|
||||
label="search"
|
||||
placeholder="search"
|
||||
type="search"
|
||||
dataCy="admins-filter-search-input"
|
||||
/>
|
||||
{/* <InputGroup
|
||||
{...filterRegister("email")}
|
||||
@@ -86,6 +89,7 @@ const AdminListFilters = ({
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
data-cy="admins-filter-submit-button"
|
||||
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
|
||||
>
|
||||
{isMutating ? (
|
||||
@@ -96,6 +100,7 @@ const AdminListFilters = ({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-cy="admins-filter-close-button"
|
||||
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
|
||||
onClick={() => setIsFilterModalOpen(false)}
|
||||
>
|
||||
|
||||
@@ -35,6 +35,7 @@ const ChangeStatusModalContent: FC<IProps> = ({ status, setStatus }) => {
|
||||
name="status"
|
||||
defaultValue={status}
|
||||
placeholder="Please select new Status"
|
||||
dataCy="admins-change-status-select"
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -51,16 +51,21 @@ const AdminsTable = () => {
|
||||
} = useAdminsPresenter();
|
||||
|
||||
return (
|
||||
<ListWrapper>
|
||||
<ListWrapper data-cy="admins-page">
|
||||
<ListHeader
|
||||
onFilter={() => setIsFilterModalOpen(true)}
|
||||
createButtonText="Create Admin"
|
||||
dataCyPrefix="admins"
|
||||
/>
|
||||
<Table>
|
||||
<Table data-cy="admins-table">
|
||||
<TableHeader>
|
||||
<TableRow className="border-none uppercase [&>th]:text-start">
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column} colSpan={100}>
|
||||
{columns.map((column, index) => (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
data-cy={`admins-table-header-${index}`}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
))}
|
||||
@@ -77,31 +82,33 @@ const AdminsTable = () => {
|
||||
<TableRow
|
||||
key={item?.id || index}
|
||||
className="odd:bg-gray-2 dark:odd:bg-gray-7"
|
||||
data-cy={`admins-table-row-${item?.id ?? index}`}
|
||||
>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100} data-cy="admins-row-number">
|
||||
{getPaginatedRowNumber({ index })}
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100} data-cy="admins-row-full-name">
|
||||
{item?.full_name}
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100} data-cy="admins-row-email">
|
||||
{item?.email}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100} data-cy="admins-row-username">
|
||||
{item?.username}
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100} data-cy="admins-row-status">
|
||||
<Status status={item?.status ?? ""}>{item?.status}</Status>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-start xl:pr-7.5">
|
||||
<TableCell className="text-start xl:pr-7.5" data-cy="admins-row-actions">
|
||||
<div className="flex items-center justify-start gap-x-3.5">
|
||||
<button
|
||||
data-tooltip-id="delete"
|
||||
data-tooltip-content="Delete"
|
||||
data-tooltip-place="top"
|
||||
className="hover:text-red-500"
|
||||
data-cy={`admins-row-delete-${item?.id ?? index}`}
|
||||
onClick={() => {
|
||||
setCurrentUser(item);
|
||||
setIsModalOpen(true);
|
||||
@@ -116,6 +123,7 @@ const AdminsTable = () => {
|
||||
data-tooltip-content="Change status"
|
||||
data-tooltip-place="top"
|
||||
className="hover:text-yellow-dark"
|
||||
data-cy={`admins-row-manage-status-${item?.id ?? index}`}
|
||||
onClick={() => {
|
||||
setCurrentUser(item);
|
||||
setIsChangeStatusModalOpen(true);
|
||||
@@ -132,6 +140,7 @@ const AdminsTable = () => {
|
||||
data-tooltip-content="Edit"
|
||||
data-tooltip-place="top"
|
||||
className="hover:text-primary"
|
||||
data-cy={`admins-row-edit-${item?.id ?? index}`}
|
||||
onClick={() => router.push(`/admins/edit/${item.id}`)}
|
||||
>
|
||||
<Tooltip {..._TooltipDefaultParams({ id: "edit" })} />
|
||||
@@ -151,6 +160,8 @@ const AdminsTable = () => {
|
||||
onConfirm={() => {
|
||||
changeStatus({ id: currentUser?.id!, credentials: { status } });
|
||||
}}
|
||||
confirmText="Save"
|
||||
cancelText="Cancel"
|
||||
>
|
||||
<ChangeStatusModalContent
|
||||
status={currentUser?.status as AdminStatus}
|
||||
|
||||
@@ -38,6 +38,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
onSubmit={handleSubmit((data) => {
|
||||
onSubmit(data);
|
||||
})}
|
||||
data-cy={editMode ? "admin-edit-form" : "admin-create-form"}
|
||||
>
|
||||
<div className="flex flex-col gap-9">
|
||||
<ShowcaseSection
|
||||
@@ -62,6 +63,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
required
|
||||
type="text"
|
||||
name="full_name"
|
||||
dataCy="admin-form-full-name-input"
|
||||
/>
|
||||
{errors.full_name && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -96,6 +98,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="username"
|
||||
dataCy="admin-form-username-input"
|
||||
/>
|
||||
{errors.username && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -122,6 +125,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="email"
|
||||
dataCy="admin-form-email-input"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -152,6 +156,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="password"
|
||||
dataCy="admin-form-password-input"
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -180,6 +185,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="phone"
|
||||
dataCy="admin-form-phone-input"
|
||||
/>
|
||||
{errors.phone && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -203,6 +209,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="department"
|
||||
dataCy="admin-form-department-input"
|
||||
/>
|
||||
{errors.department && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
@@ -226,6 +233,7 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
|
||||
},
|
||||
})}
|
||||
name="position"
|
||||
dataCy="admin-form-position-input"
|
||||
/>
|
||||
{errors.position && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
|
||||
@@ -99,10 +99,12 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = (props) => {
|
||||
aria-labelledby="modal-title"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-cy="confirmation-modal"
|
||||
>
|
||||
<div
|
||||
className="fixed inset-0 bg-dark/70 bg-opacity-75 transition-opacity"
|
||||
onClick={onCancel}
|
||||
data-cy="confirmation-modal-overlay"
|
||||
/>
|
||||
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
@@ -139,6 +141,7 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = (props) => {
|
||||
className={`inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 sm:ml-3 sm:w-auto ${currentVariant.confirmBtn} dark:ring-offset-dark-2`}
|
||||
disabled={isMutating > 0}
|
||||
onClick={onConfirm}
|
||||
data-cy="confirmation-modal-confirm"
|
||||
>
|
||||
{isMutating ? (
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
||||
@@ -150,6 +153,7 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = (props) => {
|
||||
type="button"
|
||||
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-dark shadow-sm ring-1 ring-inset ring-stroke hover:bg-gray focus:outline-none focus:ring-2 focus:ring-dark-5 focus:ring-offset-2 dark:bg-dark-3 dark:text-white dark:ring-stroke-dark dark:hover:bg-dark-4 dark:focus:ring-offset-dark-2 sm:mt-0 sm:w-auto"
|
||||
onClick={onCancel}
|
||||
data-cy="confirmation-modal-cancel"
|
||||
>
|
||||
{cancelText}
|
||||
</button>
|
||||
|
||||
@@ -24,6 +24,7 @@ const FormFooter = ({ onCancel }: IProps) => {
|
||||
shape="rounded"
|
||||
size="small"
|
||||
type="submit"
|
||||
data-cy="admin-form-submit-button"
|
||||
disabled={isMutating > 0}
|
||||
className="mt-6 w-fit rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90"
|
||||
/>
|
||||
@@ -32,6 +33,7 @@ const FormFooter = ({ onCancel }: IProps) => {
|
||||
shape="rounded"
|
||||
size="small"
|
||||
type="button"
|
||||
data-cy="admin-form-cancel-button"
|
||||
variant="error"
|
||||
onClick={onCancel ?? router.back}
|
||||
className="mt-6 w-fit rounded-lg bg-error p-[13px] font-medium text-white hover:bg-opacity-90"
|
||||
|
||||
@@ -10,6 +10,7 @@ interface IProps {
|
||||
createButtonText?: string;
|
||||
filterButtonText?: string;
|
||||
onFilter?: () => void;
|
||||
dataCyPrefix?: string;
|
||||
}
|
||||
|
||||
const ListHeader = ({
|
||||
@@ -18,6 +19,7 @@ const ListHeader = ({
|
||||
createButtonText = "Create",
|
||||
filterButtonText = "Filter",
|
||||
onFilter,
|
||||
dataCyPrefix = "list-header",
|
||||
}: IProps) => {
|
||||
const pathName = usePathname();
|
||||
const router = useRouter();
|
||||
@@ -35,6 +37,7 @@ const ListHeader = ({
|
||||
shape="rounded"
|
||||
size="small"
|
||||
onClick={onFilter}
|
||||
data-cy={`${dataCyPrefix}-filter-button`}
|
||||
className="shadow-theme-xs mb-5 self-end rounded-xl border-stroke/70 bg-white/80 px-5 text-sm font-semibold text-dark transition-all duration-300 hover:border-primary/35 hover:text-primary dark:border-dark-3 dark:bg-dark-2 dark:text-white dark:hover:bg-white/[0.08]"
|
||||
/>
|
||||
</IsVisible>
|
||||
@@ -45,6 +48,7 @@ const ListHeader = ({
|
||||
shape="rounded"
|
||||
size="small"
|
||||
onClick={() => router.push(`${pathName}/create`)}
|
||||
data-cy={`${dataCyPrefix}-create-button`}
|
||||
className="shadow-theme-xs mb-5 self-end rounded-xl bg-gradient-to-r from-primary to-primary/85 text-sm font-semibold transition-all duration-300 hover:opacity-95"
|
||||
/>
|
||||
</IsVisible>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { ReactNode } from "react";
|
||||
import { HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
type ListWrapperProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const ListWrapper = ({ children }: ListWrapperProps) => {
|
||||
const ListWrapper = ({ children, ...props }: ListWrapperProps) => {
|
||||
return (
|
||||
<div className="flex flex-col rounded-xl border border-stroke/70 bg-white bg-[linear-gradient(225deg,rgba(87,80,241,0.18)_0%,rgba(87,80,241,0)_10%),linear-gradient(45deg,rgba(87,80,241,0.18)_0%,rgba(87,80,241,0)_10%)] px-7.5 pb-4 pt-7.5 dark:border-dark-3 dark:bg-gray-dark dark:bg-[linear-gradient(225deg,rgba(87,80,241,0.26)_0%,rgba(87,80,241,0)_10%),linear-gradient(45deg,rgba(87,80,241,0.26)_0%,rgba(87,80,241,0)_10%)]">
|
||||
<div
|
||||
{...props}
|
||||
className="flex flex-col rounded-xl border border-stroke/70 bg-white bg-[linear-gradient(225deg,rgba(87,80,241,0.18)_0%,rgba(87,80,241,0)_10%),linear-gradient(45deg,rgba(87,80,241,0.18)_0%,rgba(87,80,241,0)_10%)] px-7.5 pb-4 pt-7.5 dark:border-dark-3 dark:bg-gray-dark dark:bg-[linear-gradient(225deg,rgba(87,80,241,0.26)_0%,rgba(87,80,241,0)_10%),linear-gradient(45deg,rgba(87,80,241,0.26)_0%,rgba(87,80,241,0)_10%)]"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,11 +13,11 @@ const TableSkeleton = ({
|
||||
cellColSpan = 100,
|
||||
}: IProps) => {
|
||||
return (
|
||||
<TableBody>
|
||||
<TableBody data-cy="table-skeleton">
|
||||
{Array.from({ length }).map((_, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableRow key={i} data-cy={`table-skeleton-row-${i}`}>
|
||||
{Array.from({ length: column }).map((_, j) => (
|
||||
<TableCell key={j} colSpan={cellColSpan}>
|
||||
<TableCell key={j} colSpan={cellColSpan} data-cy="table-skeleton-cell">
|
||||
<Skeleton className="h-8 rounded-lg" />
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
@@ -50,14 +50,17 @@ const Modal: React.FC<ModalProps> = ({
|
||||
<div
|
||||
className="fixed inset-0 z-999999 flex items-center justify-center bg-dark/50 dark:bg-black/50"
|
||||
onClick={onClose}
|
||||
data-cy="modal-overlay"
|
||||
>
|
||||
<div
|
||||
className={`max-h-[90vh] max-w-[90vw] overflow-auto rounded-lg bg-white p-5 shadow-4 dark:bg-dark-2 ${classNames}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
data-cy="modal-content"
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="float-right mb-2.5 cursor-pointer border-none bg-transparent p-0 text-2xl text-dark-5 hover:text-primary dark:text-gray-5 dark:hover:text-primary"
|
||||
data-cy="modal-close-button"
|
||||
>
|
||||
<CrossIcon />
|
||||
</button>
|
||||
@@ -70,6 +73,7 @@ const Modal: React.FC<ModalProps> = ({
|
||||
shape="rounded"
|
||||
className="mt-6 w-fit capitalize"
|
||||
onClick={onConfirm}
|
||||
data-cy="modal-confirm-button"
|
||||
label={
|
||||
isMutating ? (
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
||||
@@ -85,6 +89,7 @@ const Modal: React.FC<ModalProps> = ({
|
||||
shape="rounded"
|
||||
className="mt-6 w-fit capitalize"
|
||||
onClick={onClose}
|
||||
data-cy="modal-cancel-button"
|
||||
label={cancelText}
|
||||
/>
|
||||
</IsVisible>
|
||||
|
||||
Reference in New Issue
Block a user