snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalFooterAnnotation,
|
||||
ModalFooterControllers,
|
||||
ModalHeader,
|
||||
ModalTitle,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type OutboundMessageCloseConfirmationModalProps = {
|
||||
onConfirm(): void;
|
||||
onCancel(): void;
|
||||
};
|
||||
|
||||
const OutboundMessageCloseConfirmationModal = ({ onConfirm, onCancel }: OutboundMessageCloseConfirmationModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const modalId = useId();
|
||||
|
||||
return (
|
||||
<Modal aria-labelledby={`${modalId}-title`} aria-describedby={`${modalId}-description`} open>
|
||||
<ModalHeader>
|
||||
<ModalTitle id={`${modalId}-title`}>{t('Discard_message')}</ModalTitle>
|
||||
<ModalClose aria-label={t('Close')} onClick={onCancel} />
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<p aria-live='assertive' id={`${modalId}-description`}>
|
||||
{t('Are_you_sure_you_want_to_discard_this_outbound_message')}
|
||||
</p>
|
||||
</ModalContent>
|
||||
<ModalFooter justifyContent='space-between'>
|
||||
<ModalFooterAnnotation>{t('This_action_cannot_be_undone')}</ModalFooterAnnotation>
|
||||
<ModalFooterControllers>
|
||||
<Button
|
||||
ref={(el) => {
|
||||
el?.focus();
|
||||
}}
|
||||
secondary
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('Keep_editing')}
|
||||
</Button>
|
||||
<Button danger onClick={onConfirm}>
|
||||
{t('Discard')}
|
||||
</Button>
|
||||
</ModalFooterControllers>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutboundMessageCloseConfirmationModal;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import OutboundMessageModal from './OutboundMessageModal';
|
||||
|
||||
jest.mock('../../components/OutboundMessageWizard', () => ({
|
||||
__esModule: true,
|
||||
default: () => <div>Outbound message Wizard</div>,
|
||||
}));
|
||||
|
||||
const appRoot = mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Close: 'Close',
|
||||
Discard: 'Discard',
|
||||
Discard_message: 'Discard message',
|
||||
Outbound_message: 'Outbound message',
|
||||
This_action_cannot_be_undone: 'This action cannot be undone',
|
||||
Keep_editing: 'Keep editing',
|
||||
Are_you_sure_you_want_to_discard_this_outbound_message: 'Are you sure you want to discard this outbound message?',
|
||||
})
|
||||
.build();
|
||||
|
||||
it('should display confirmation before closing the modal', async () => {
|
||||
const onClose = jest.fn();
|
||||
render(<OutboundMessageModal onClose={onClose} />, { wrapper: appRoot });
|
||||
|
||||
expect(screen.getByRole('dialog', { name: 'Outbound message' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog', { name: 'Discard message' })).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: 'Discard message' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog', { name: 'Discard message' })).toHaveAccessibleDescription(
|
||||
'Are you sure you want to discard this outbound message?',
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Discard' }));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close confirmation and leave modal open when cancel is clicked', async () => {
|
||||
const onClose = jest.fn();
|
||||
render(<OutboundMessageModal onClose={onClose} />, { wrapper: appRoot });
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: 'Discard message' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog', { name: 'Discard message' })).toHaveAccessibleDescription(
|
||||
'Are you sure you want to discard this outbound message?',
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Keep editing' }));
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: 'Discard message' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog', { name: 'Outbound message' })).toBeInTheDocument();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Modal, ModalBackdrop, ModalClose, ModalContent, ModalHeader, ModalTitle } from '@rocket.chat/fuselage';
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import { useRouter } from '@rocket.chat/ui-contexts';
|
||||
import { useEffect, useId, useState } from 'react';
|
||||
import type { KeyboardEvent, ComponentProps } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import OutboundMessageCloseConfirmationModal from './OutboundMessageCloseConfirmationModal';
|
||||
import OutboundMessageWizard from '../../components/OutboundMessageWizard';
|
||||
|
||||
export type OutboundMessageModalProps = {
|
||||
defaultValues?: ComponentProps<typeof OutboundMessageWizard>['defaultValues'];
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const OutboundMessageModal = ({ defaultValues, onClose }: OutboundMessageModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [initialRoute] = useState(router.getLocationPathname());
|
||||
const [isClosing, setClosingConfirmation] = useState(false);
|
||||
const modalId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
// NOTE: close the modal when the route changes.
|
||||
// This is necessary to ensure that the modal closes when navigating the user to the edit contact page or other relevant routes.
|
||||
return router.subscribeToRouteChange(() => {
|
||||
if (initialRoute === router.getLocationPathname()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClose();
|
||||
});
|
||||
}, [initialRoute, onClose, router]);
|
||||
|
||||
const handleKeyDown = useStableCallback((e: KeyboardEvent<HTMLDivElement>): void => {
|
||||
if (e.key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
// Toggle confirmation visibility on Esc.
|
||||
setClosingConfirmation(!isClosing);
|
||||
});
|
||||
|
||||
return (
|
||||
<ModalBackdrop
|
||||
backgroundColor='transparent'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Modal aria-labelledby={modalId} display={isClosing ? 'none' : undefined}>
|
||||
<ModalHeader>
|
||||
<ModalTitle id={modalId}>{t('Outbound_message')}</ModalTitle>
|
||||
<ModalClose aria-label={t('Close')} onClick={() => setClosingConfirmation(true)} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent paddingBlockEnd={16} height='100%'>
|
||||
<OutboundMessageWizard defaultValues={defaultValues} onSuccess={onClose} onError={onClose} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
{isClosing ? <OutboundMessageCloseConfirmationModal onConfirm={onClose} onCancel={() => setClosingConfirmation(false)} /> : null}
|
||||
</ModalBackdrop>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutboundMessageModal;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default } from './OutboundMessageModal';
|
||||
export * from './OutboundMessageModal';
|
||||
export * from './useOutboundMessageModal';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import { useSetModal } from '@rocket.chat/ui-contexts';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import OutboundMessageModal from './OutboundMessageModal';
|
||||
|
||||
export const useOutboundMessageModal = () => {
|
||||
const setModal = useSetModal();
|
||||
|
||||
const close = useStableCallback((): void => setModal(null));
|
||||
|
||||
const open = useStableCallback((defaultValues?: ComponentProps<typeof OutboundMessageModal>['defaultValues']) => {
|
||||
setModal(<OutboundMessageModal defaultValues={defaultValues} onClose={close} />);
|
||||
});
|
||||
|
||||
return useMemo(() => ({ open, close }), [open, close]);
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import OutboundMessageUpsellModal from './OutboundMessageUpsellModal';
|
||||
import { CONTACT_SALES_LINK, OUTBOUND_DOCS_LINK } from '../../constants';
|
||||
|
||||
const openExternalLink = jest.fn();
|
||||
jest.mock('../../../../../../hooks/useExternalLink', () => ({
|
||||
useExternalLink: jest.fn(() => openExternalLink),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../../../client/lib/getURL', () => ({
|
||||
getURL: (url: string) => url,
|
||||
}));
|
||||
|
||||
const appRoot = mockAppRoot().withJohnDoe().withTranslations('en', 'core', {
|
||||
Learn_more: 'Learn more',
|
||||
Contact_sales: 'Contact sales',
|
||||
Outbound_message_upsell_annotation: 'Outbound_message_upsell_annotation',
|
||||
No_phone_number_available_for_selected_channel: 'No phone number available for the selected channel',
|
||||
});
|
||||
|
||||
describe('OutboundMessageUpsellModal', () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('hasModule is false', () => {
|
||||
describe('user is not admin', () => {
|
||||
it('should render only "Learn more" button', () => {
|
||||
render(<OutboundMessageUpsellModal onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByRole('button', { name: 'Learn more' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the annotation', () => {
|
||||
render(<OutboundMessageUpsellModal onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByText('Outbound_message_upsell_annotation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call openExternalLink with docs link when "Learn more" is clicked', async () => {
|
||||
render(<OutboundMessageUpsellModal onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Learn more' }));
|
||||
expect(openExternalLink).toHaveBeenCalledWith(OUTBOUND_DOCS_LINK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user is admin', () => {
|
||||
it('should render "Learn more" and "Contact sales" buttons', () => {
|
||||
render(<OutboundMessageUpsellModal isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByRole('button', { name: 'Learn more' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Contact sales' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render "Upgrade" button when isCommunity is true', () => {
|
||||
render(<OutboundMessageUpsellModal isAdmin isCommunity onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByRole('button', { name: 'Upgrade' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call openExternalLink with docs link when "Learn more" is clicked', async () => {
|
||||
render(<OutboundMessageUpsellModal isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Learn more' }));
|
||||
expect(openExternalLink).toHaveBeenCalledWith(OUTBOUND_DOCS_LINK);
|
||||
});
|
||||
|
||||
it('should call openExternalLink with sales link when "Contact sales" is clicked', async () => {
|
||||
render(<OutboundMessageUpsellModal isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Contact sales' }));
|
||||
expect(openExternalLink).toHaveBeenCalledWith(CONTACT_SALES_LINK);
|
||||
});
|
||||
|
||||
it('should not render the annotation', () => {
|
||||
render(<OutboundMessageUpsellModal isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.queryByText('Outbound_message_upsell_annotation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasModule is true', () => {
|
||||
describe('user is not admin', () => {
|
||||
it('should render only "Learn more" button', () => {
|
||||
render(<OutboundMessageUpsellModal hasModule onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByRole('button', { name: 'Learn more' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the annotation', () => {
|
||||
render(<OutboundMessageUpsellModal hasModule onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByText('Outbound_message_upsell_annotation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call openExternalLink with docs link when "Learn more" is clicked', async () => {
|
||||
render(<OutboundMessageUpsellModal hasModule onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Learn more' }));
|
||||
expect(openExternalLink).toHaveBeenCalledWith(OUTBOUND_DOCS_LINK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user is admin', () => {
|
||||
it('should render only "Learn more" button', () => {
|
||||
render(<OutboundMessageUpsellModal hasModule isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.getByRole('button', { name: 'Learn more' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the annotation', () => {
|
||||
render(<OutboundMessageUpsellModal hasModule isAdmin onClose={onClose} />, { wrapper: appRoot.build() });
|
||||
expect(screen.queryByText('Outbound_message_upsell_annotation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { action } from 'storybook/actions';
|
||||
|
||||
import OutboundMessageUpsellModal from './OutboundMessageUpsellModal';
|
||||
|
||||
const g = globalThis as typeof globalThis & {
|
||||
__meteor_runtime_config__?: { ROOT_URL_PATH_PREFIX?: string };
|
||||
};
|
||||
|
||||
g.__meteor_runtime_config__ = {
|
||||
...(g.__meteor_runtime_config__ ?? {}),
|
||||
ROOT_URL_PATH_PREFIX: '',
|
||||
};
|
||||
|
||||
export default {
|
||||
component: OutboundMessageUpsellModal,
|
||||
args: {
|
||||
onClose: action('onClose'),
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof OutboundMessageUpsellModal>;
|
||||
|
||||
type Story = StoryObj<typeof OutboundMessageUpsellModal>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
hasModule: false,
|
||||
isAdmin: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithModuleForAdmin: Story = {
|
||||
args: {
|
||||
hasModule: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithModuleForNonAdmin: Story = {
|
||||
args: {
|
||||
hasModule: true,
|
||||
isAdmin: false,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import GenericUpsellModal from '../../../../../../components/GenericUpsellModal';
|
||||
import { useExternalLink } from '../../../../../../hooks/useExternalLink';
|
||||
import { getURL } from '../../../../../../lib/getURL';
|
||||
import { CONTACT_SALES_LINK, OUTBOUND_DOCS_LINK } from '../../constants';
|
||||
|
||||
export type OutboundMessageUpsellModalProps = {
|
||||
hasModule?: boolean;
|
||||
isAdmin?: boolean;
|
||||
isCommunity?: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const OutboundMessageUpsellModal = ({ isCommunity, hasModule, isAdmin, onClose }: OutboundMessageUpsellModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const openExternalLink = useExternalLink();
|
||||
|
||||
const props = useMemo(() => {
|
||||
if (isAdmin && !hasModule) {
|
||||
return {
|
||||
cancelText: t('Learn_more'),
|
||||
onCancel: () => openExternalLink(OUTBOUND_DOCS_LINK),
|
||||
confirmText: isCommunity ? t('Upgrade') : t('Contact_sales'),
|
||||
onConfirm: () => openExternalLink(CONTACT_SALES_LINK),
|
||||
onClose,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
cancelText: t('Learn_more'),
|
||||
annotation: !isAdmin ? t('Outbound_message_upsell_annotation') : undefined,
|
||||
onCancel: () => openExternalLink(OUTBOUND_DOCS_LINK),
|
||||
onClose,
|
||||
};
|
||||
}, [hasModule, isAdmin, isCommunity, onClose, openExternalLink, t]);
|
||||
|
||||
return (
|
||||
<GenericUpsellModal
|
||||
{...props}
|
||||
title={t('Outbound_message_upsell_title')}
|
||||
description={t('Outbound_message_upsell_description')}
|
||||
img={getURL('images/outbound-message-upsell.svg')}
|
||||
imgAlt={t('Outbound_message_upsell_image_alt')}
|
||||
imgHeight={255}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutboundMessageUpsellModal;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from './OutboundMessageUpsellModal';
|
||||
export * from './useOutboundMessageUpsellModal';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import { useLicense } from '@rocket.chat/ui-client';
|
||||
import { useRole, useSetModal } from '@rocket.chat/ui-contexts';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import OutboundMessageUpsellModal from './OutboundMessageUpsellModal';
|
||||
import { useHasLicenseModule } from '../../../../../../hooks/useHasLicenseModule';
|
||||
|
||||
export const useOutboundMessageUpsellModal = () => {
|
||||
const setModal = useSetModal();
|
||||
const isAdmin = useRole('admin');
|
||||
const license = useLicense();
|
||||
const { data: hasModule = false } = useHasLicenseModule('outbound-messaging');
|
||||
|
||||
const close = useStableCallback(() => setModal(null));
|
||||
const open = useStableCallback(() =>
|
||||
setModal(<OutboundMessageUpsellModal isCommunity={!license.data?.license} isAdmin={isAdmin} hasModule={hasModule} onClose={close} />),
|
||||
);
|
||||
|
||||
return useMemo(() => ({ open, close }), [open, close]);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './OutboundMessageUpsellModal';
|
||||
export * from './OutboundMessageModal';
|
||||
Reference in New Issue
Block a user