From e6eaaa660a702d712085c3eef7e0e878afc2712f Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:50:29 +0530 Subject: [PATCH] feat: added invite team member from onboarding flow (#5410) * feat: added invite team member from onboarding flow * feat: removed commented code and added text to strings-translations * feat: added en-gb strings * feat: added more text to strings * feat: removed commented code and app.ts changes * feat: added test case for onboarding and invite flow * feat: added invite team member logEvents * feat: resovled comments * feat: cdoe refactor and test case changes --- frontend/jest.config.ts | 1 + frontend/public/locales/en-GB/onboarding.json | 8 + frontend/public/locales/en/onboarding.json | 8 + .../Onboarding.styles.scss | 119 +++++++++++- .../OnboardingContainer.tsx | 91 ++++++--- .../__test__/InviteUserFlow.test.tsx | 127 ++++++++++++ .../ModuleStepsContainer.styles.scss | 36 ++++ .../ModuleStepsContainer.tsx | 67 ++++--- .../InviteTeamMembers/index.tsx | 2 +- .../InviteUserModal/InviteUserModal.tsx | 182 ++++++++++++++++++ .../PendingInvitesContainer/index.tsx | 88 +-------- .../mocks-server/__mockdata__/invite_user.ts | 25 +++ frontend/src/mocks-server/handlers.ts | 8 + 13 files changed, 623 insertions(+), 139 deletions(-) create mode 100644 frontend/public/locales/en-GB/onboarding.json create mode 100644 frontend/public/locales/en/onboarding.json create mode 100644 frontend/src/container/OnboardingContainer/__test__/InviteUserFlow.test.tsx create mode 100644 frontend/src/container/OrganizationSettings/InviteUserModal/InviteUserModal.tsx create mode 100644 frontend/src/mocks-server/__mockdata__/invite_user.ts diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index d7776b0034..122b309dae 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -9,6 +9,7 @@ const config: Config.InitialOptions = { modulePathIgnorePatterns: ['dist'], moduleNameMapper: { '\\.(css|less|scss)$': '/__mocks__/cssMock.ts', + '\\.md$': '/__mocks__/cssMock.ts', }, globals: { extensionsToTreatAsEsm: ['.ts'], diff --git a/frontend/public/locales/en-GB/onboarding.json b/frontend/public/locales/en-GB/onboarding.json new file mode 100644 index 0000000000..573282687e --- /dev/null +++ b/frontend/public/locales/en-GB/onboarding.json @@ -0,0 +1,8 @@ +{ + "invite_user": "Invite your teammates", + "invite": "Invite", + "skip": "Skip", + "invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.", + "select_use_case": "Select a use-case to get started", + "get_started": "Get Started" +} diff --git a/frontend/public/locales/en/onboarding.json b/frontend/public/locales/en/onboarding.json new file mode 100644 index 0000000000..573282687e --- /dev/null +++ b/frontend/public/locales/en/onboarding.json @@ -0,0 +1,8 @@ +{ + "invite_user": "Invite your teammates", + "invite": "Invite", + "skip": "Skip", + "invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.", + "select_use_case": "Select a use-case to get started", + "get_started": "Get Started" +} diff --git a/frontend/src/container/OnboardingContainer/Onboarding.styles.scss b/frontend/src/container/OnboardingContainer/Onboarding.styles.scss index e81679d143..007c2d1f7f 100644 --- a/frontend/src/container/OnboardingContainer/Onboarding.styles.scss +++ b/frontend/src/container/OnboardingContainer/Onboarding.styles.scss @@ -1,16 +1,6 @@ .container { width: 100%; - // max-width: 1440px; margin: 0 auto; - - &.darkMode { - } - - &.lightMode { - .onboardingHeader { - color: #1d1d1d; - } - } } .moduleSelectContainer { @@ -61,6 +51,8 @@ width: 300px; transition: 0.3s; + background-color: #000; + .ant-card-body { padding: 0px; } @@ -80,6 +72,9 @@ overflow: hidden; text-overflow: ellipsis; text-align: center; + + border-bottom: 1px solid #303030; + background-color: var(--bg-ink-400); } .moduleStyles.selected { @@ -157,3 +152,107 @@ padding: 12px; margin: 24px 0; } + +.invite-member-wrapper { + display: flex; + justify-content: center; + align-items: center; + margin: 32px 0; + flex-direction: column; + gap: 12px; + + .invite-member { + display: flex; + width: 480px; + height: 64px; + padding: 16px; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + + .ant-typography { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; + } + + > button { + display: flex; + align-items: center; + border-radius: 2px; + } + } +} + +.onboarding-page { + display: flex; + flex-direction: column; + height: 100%; + align-items: center; + justify-content: space-between; +} + +.skip-to-console { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; + position: absolute; + top: 40px; + right: 40px; + cursor: pointer; + + &:hover { + color: var(--bg-vanilla-200); + } +} + +.lightMode { + .invite-member-wrapper { + .invite-member { + border: 1px solid var(--bg-vanilla-200); + background: var(--bg-vanilla-100); + + .ant-typography { + color: var(--bg-slate-200); + } + } + } + + .skip-to-console { + color: var(--bg-slate-200); + + &:hover { + color: var(--bg-slate-200); + } + } +} + +.lightMode { + .container { + .onboardingHeader { + color: var(--bg-slate-200); + } + } + + .moduleStyles { + background-color: var(--bg-vanilla-100); + } + + .moduleTitleStyle { + border-bottom: 1px solid var(--bg-vanilla-300); + background-color: var(--bg-vanilla-100); + } + + .moduleDesc { + background-color: var(--bg-vanilla-100); + } +} diff --git a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx index 5383f459f9..d1f89b0762 100644 --- a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx +++ b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx @@ -3,15 +3,19 @@ import './Onboarding.styles.scss'; import { ArrowRightOutlined } from '@ant-design/icons'; -import { Button, Card, Typography } from 'antd'; +import { Button, Card, Form, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import getIngestionData from 'api/settings/getIngestionData'; import cx from 'classnames'; import ROUTES from 'constants/routes'; import FullScreenHeader from 'container/FullScreenHeader/FullScreenHeader'; +import InviteUserModal from 'container/OrganizationSettings/InviteUserModal/InviteUserModal'; +import { InviteMemberFormValues } from 'container/OrganizationSettings/PendingInvitesContainer'; import useAnalytics from 'hooks/analytics/useAnalytics'; -import { useIsDarkMode } from 'hooks/useDarkMode'; import history from 'lib/history'; -import { useEffect, useState } from 'react'; +import { UserPlus } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; import { useEffectOnce } from 'react-use'; @@ -100,9 +104,9 @@ export default function Onboarding(): JSX.Element { const [selectedModuleSteps, setSelectedModuleSteps] = useState(APM_STEPS); const [activeStep, setActiveStep] = useState(1); const [current, setCurrent] = useState(0); - const isDarkMode = useIsDarkMode(); const { trackEvent } = useAnalytics(); const { location } = history; + const { t } = useTranslation(['onboarding']); const { selectedDataSource, @@ -279,13 +283,38 @@ export default function Onboarding(): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const [form] = Form.useForm(); + const [ + isInviteTeamMemberModalOpen, + setIsInviteTeamMemberModalOpen, + ] = useState(false); + + const toggleModal = useCallback( + (value: boolean): void => { + setIsInviteTeamMemberModalOpen(value); + if (!value) { + form.resetFields(); + } + }, + [form], + ); + return ( -
+
{activeStep === 1 && ( - <> +
+
{ + logEvent('Onboarding V2: Skip Button Clicked', {}); + history.push('/'); + }} + className="skip-to-console" + > + {t('skip')} +
-

Select a use-case to get started

+

{t('select_use_case')}

@@ -298,26 +327,13 @@ export default function Onboarding(): JSX.Element { 'moduleStyles', selectedModule.id === selectedUseCase.id ? 'selected' : '', )} - style={{ - backgroundColor: isDarkMode ? '#000' : '#FFF', - }} key={selectedUseCase.id} onClick={(): void => handleModuleSelect(selectedUseCase)} > - + {selectedUseCase.title} - + {selectedUseCase.desc} @@ -327,10 +343,31 @@ export default function Onboarding(): JSX.Element {
- +
+ + {t('invite_user_helper_text')} + +
+ {t('invite_user')} + +
+
+
)} {activeStep > 1 && ( @@ -345,9 +382,15 @@ export default function Onboarding(): JSX.Element { }} selectedModule={selectedModule} selectedModuleSteps={selectedModuleSteps} + setIsInviteTeamMemberModalOpen={setIsInviteTeamMemberModalOpen} />
)} +
); } diff --git a/frontend/src/container/OnboardingContainer/__test__/InviteUserFlow.test.tsx b/frontend/src/container/OnboardingContainer/__test__/InviteUserFlow.test.tsx new file mode 100644 index 0000000000..91d370e9ed --- /dev/null +++ b/frontend/src/container/OnboardingContainer/__test__/InviteUserFlow.test.tsx @@ -0,0 +1,127 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import { queryByAttribute, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen, within } from 'tests/test-utils'; + +import OnboardingContainer from '..'; +import { OnboardingContextProvider } from '../context/OnboardingContext'; + +jest.mock('react-markdown', () => jest.fn()); +jest.mock('rehype-raw', () => jest.fn()); + +const successNotification = jest.fn(); +jest.mock('hooks/useNotifications', () => ({ + __esModule: true, + useNotifications: jest.fn(() => ({ + notifications: { + success: successNotification, + error: jest.fn(), + }, + })), +})); + +window.analytics = { + track: jest.fn(), +}; + +describe('Onboarding invite team member flow', () => { + it('initial render and get started page', async () => { + const { findByText } = render( + + + , + ); + + await expect(findByText('SigNoz')).resolves.toBeInTheDocument(); + + // Check all the option present + const monitoringTexts = [ + { + title: 'Application Monitoring', + description: + 'Monitor application metrics like p99 latency, error rates, external API calls, and db calls.', + }, + { + title: 'Logs Management', + description: + 'Easily filter and query logs, build dashboards and alerts based on attributes in logs', + }, + { + title: 'Infrastructure Monitoring', + description: + 'Monitor Kubernetes infrastructure metrics, hostmetrics, or metrics of any third-party integration', + }, + { + title: 'AWS Monitoring', + description: + 'Monitor your traces, logs and metrics for AWS services like EC2, ECS, EKS etc.', + }, + { + title: 'Azure Monitoring', + description: + 'Monitor your traces, logs and metrics for Azure services like AKS, Container Apps, App Service etc.', + }, + ]; + + monitoringTexts.forEach(async ({ title, description }) => { + await expect(findByText(title)).resolves.toBeInTheDocument(); + await expect(findByText(description)).resolves.toBeInTheDocument(); + }); + + // Invite team member button + await expect(findByText('invite')).resolves.toBeInTheDocument(); + }); + + it('invite team member', async () => { + const { findByText } = render( + + + , + ); + + // Invite team member button + const inviteBtn = await findByText('invite'); + expect(inviteBtn).toBeInTheDocument(); + + fireEvent.click(inviteBtn); + const inviteModal = await screen.findByTestId('invite-team-members-modal'); + expect(inviteModal).toBeInTheDocument(); + + const inviteModalTitle = await within(inviteModal).findAllByText( + /invite_team_members/i, + ); + expect(inviteModalTitle[0]).toBeInTheDocument(); + + // Verify that the invite modal contains an input field for entering the email address + const emailInput = within(inviteModal).getByText('email_address'); + expect(emailInput).toBeInTheDocument(); + + // Verify that the invite modal contains a dropdown for selecting the role + const role = within(inviteModal).getByText('role'); + expect(role).toBeInTheDocument(); + + // Verify that the invite modal contains a button for sending the invitation + const sendButton = within(inviteModal).getByTestId( + 'invite-team-members-button', + ); + expect(sendButton).toBeInTheDocument(); + + // Verify that the invite modal sends the invitation + fireEvent.input(queryByAttribute('id', inviteModal, 'members_0_email')!, { + target: { value: 'test@example.com' }, + }); + expect( + queryByAttribute('value', inviteModal, 'test@example.com'), + ).toBeInTheDocument(); + + const roleDropdown = within(inviteModal).getByTestId('role-select'); + expect(roleDropdown).toBeInTheDocument(); + + fireEvent.click(sendButton); + + await waitFor(() => + expect(successNotification).toHaveBeenCalledWith({ + message: 'Invite sent successfully', + }), + ); + }); +}); diff --git a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.styles.scss b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.styles.scss index 02972209dd..dbbb6a9baf 100644 --- a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.styles.scss +++ b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.styles.scss @@ -39,6 +39,9 @@ .steps-container { width: 20%; height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; .steps-container-header { display: flex; @@ -69,6 +72,30 @@ } } } + + .invite-user-btn { + display: flex; + width: 170px; + height: 32px; + padding: 6px; + justify-content: center; + align-items: center; + border-radius: 2px; + margin-bottom: 31px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: none; + + .ant-typography { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 10px; + letter-spacing: 0.12px; + } + } } .selected-step-content { @@ -196,5 +223,14 @@ } } } + + .invite-user-btn { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + + .ant-typography { + color: var(--bg-slate-200); + } + } } } diff --git a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx index bc983c9ee8..28890e4d5a 100644 --- a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx +++ b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx @@ -18,8 +18,8 @@ import { hasFrameworks } from 'container/OnboardingContainer/utils/dataSourceUti import useAnalytics from 'hooks/analytics/useAnalytics'; import history from 'lib/history'; import { isEmpty, isNull } from 'lodash-es'; -import { HelpCircle } from 'lucide-react'; -import { useState } from 'react'; +import { HelpCircle, UserPlus } from 'lucide-react'; +import { SetStateAction, useState } from 'react'; import { useOnboardingContext } from '../../context/OnboardingContext'; import { @@ -33,6 +33,7 @@ interface ModuleStepsContainerProps { onReselectModule: any; selectedModule: ModuleProps; selectedModuleSteps: SelectedModuleStepProps[]; + setIsInviteTeamMemberModalOpen: (value: SetStateAction) => void; } interface MetaDataProps { @@ -63,6 +64,7 @@ export default function ModuleStepsContainer({ onReselectModule, selectedModule, selectedModuleSteps, + setIsInviteTeamMemberModalOpen, }: ModuleStepsContainerProps): JSX.Element { const { activeStep, @@ -409,32 +411,47 @@ Thanks return (
-
-
- SigNoz +
+
+
+ SigNoz -
SigNoz
+
SigNoz
+
+ + + + + +
- - - - - - +
diff --git a/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx index 778e792085..2ee7aca4c1 100644 --- a/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx +++ b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx @@ -50,7 +50,7 @@ function InviteTeamMembers({ form, onFinish }: Props): JSX.Element { - + ADMIN VIEWER EDITOR diff --git a/frontend/src/container/OrganizationSettings/InviteUserModal/InviteUserModal.tsx b/frontend/src/container/OrganizationSettings/InviteUserModal/InviteUserModal.tsx new file mode 100644 index 0000000000..ad158adaae --- /dev/null +++ b/frontend/src/container/OrganizationSettings/InviteUserModal/InviteUserModal.tsx @@ -0,0 +1,182 @@ +import { Button, Form, Modal } from 'antd'; +import { FormInstance } from 'antd/lib'; +import getPendingInvites from 'api/user/getPendingInvites'; +import sendInvite from 'api/user/sendInvite'; +import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useState, +} from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { PayloadProps } from 'types/api/user/getPendingInvites'; +import AppReducer from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +import InviteTeamMembers from '../InviteTeamMembers'; +import { InviteMemberFormValues } from '../PendingInvitesContainer'; + +export interface InviteUserModalProps { + isInviteTeamMemberModalOpen: boolean; + toggleModal: (value: boolean) => void; + form: FormInstance; + setDataSource?: Dispatch>; + shouldCallApi?: boolean; +} + +interface DataProps { + key: number; + name: string; + email: string; + accessLevel: ROLES; + inviteLink: string; +} + +function InviteUserModal(props: InviteUserModalProps): JSX.Element { + const { + isInviteTeamMemberModalOpen, + toggleModal, + form, + setDataSource, + shouldCallApi = false, + } = props; + const { notifications } = useNotifications(); + const { t } = useTranslation(['organizationsettings', 'common']); + const { user } = useSelector((state) => state.app); + const [isInvitingMembers, setIsInvitingMembers] = useState(false); + const [modalForm] = Form.useForm(form); + + const getPendingInvitesResponse = useQuery({ + queryFn: getPendingInvites, + queryKey: ['getPendingInvites', user?.accessJwt], + enabled: shouldCallApi, + }); + + const getParsedInviteData = useCallback( + (payload: PayloadProps = []) => + payload?.map((data) => ({ + key: data.createdAt, + name: data?.name, + email: data.email, + accessLevel: data.role, + inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`, + })), + [], + ); + + useEffect(() => { + if ( + getPendingInvitesResponse.status === 'success' && + getPendingInvitesResponse?.data?.payload + ) { + const data = getParsedInviteData( + getPendingInvitesResponse?.data?.payload || [], + ); + setDataSource?.(data); + } + }, [ + getParsedInviteData, + getPendingInvitesResponse?.data?.payload, + getPendingInvitesResponse.status, + setDataSource, + ]); + + const onInviteClickHandler = useCallback( + async (values: InviteMemberFormValues): Promise => { + try { + setIsInvitingMembers?.(true); + values?.members?.forEach( + async (member): Promise => { + const { error, statusCode } = await sendInvite({ + email: member.email, + name: member?.name, + role: member.role, + frontendBaseUrl: window.location.origin, + }); + + if (statusCode !== 200) { + notifications.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } else if (statusCode === 200) { + notifications.success({ + message: 'Invite sent successfully', + }); + } + }, + ); + + setTimeout(async () => { + const { data, status } = await getPendingInvitesResponse.refetch(); + if (status === 'success' && data.payload) { + setDataSource?.(getParsedInviteData(data?.payload || [])); + } + setIsInvitingMembers?.(false); + toggleModal(false); + }, 2000); + } catch (error) { + notifications.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }, + [ + getParsedInviteData, + getPendingInvitesResponse, + notifications, + setDataSource, + setIsInvitingMembers, + t, + toggleModal, + ], + ); + + return ( + toggleModal(false)} + centered + data-testid="invite-team-members-modal" + destroyOnClose + footer={[ + , + , + ]} + > + + + ); +} + +InviteUserModal.defaultProps = { + setDataSource: (): void => {}, + shouldCallApi: false, +}; + +export default InviteUserModal; diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx index 3e9276f596..7c4909ab8d 100644 --- a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx @@ -1,9 +1,8 @@ import { PlusOutlined } from '@ant-design/icons'; -import { Button, Form, Modal, Space, Typography } from 'antd'; +import { Button, Form, Space, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import deleteInvite from 'api/user/deleteInvite'; import getPendingInvites from 'api/user/getPendingInvites'; -import sendInvite from 'api/user/sendInvite'; import { ResizeTable } from 'components/ResizeTable'; import { INVITE_MEMBERS_HASH } from 'constants/app'; import ROUTES from 'constants/routes'; @@ -19,7 +18,7 @@ import { PayloadProps } from 'types/api/user/getPendingInvites'; import AppReducer from 'types/reducer/app'; import { ROLES } from 'types/roles'; -import InviteTeamMembers from '../InviteTeamMembers'; +import InviteUserModal from '../InviteUserModal/InviteUserModal'; import { TitleWrapper } from './styles'; function PendingInvitesContainer(): JSX.Element { @@ -28,7 +27,6 @@ function PendingInvitesContainer(): JSX.Element { setIsInviteTeamMemberModalOpen, ] = useState(false); const [form] = Form.useForm(); - const [isInvitingMembers, setIsInvitingMembers] = useState(false); const { t } = useTranslation(['organizationsettings', 'common']); const [state, setText] = useCopyToClipboard(); const { notifications } = useNotifications(); @@ -191,83 +189,15 @@ function PendingInvitesContainer(): JSX.Element { }, ]; - const onInviteClickHandler = useCallback( - async (values: InviteMemberFormValues): Promise => { - try { - setIsInvitingMembers(true); - values.members.forEach( - async (member): Promise => { - const { error, statusCode } = await sendInvite({ - email: member.email, - name: member.name, - role: member.role, - frontendBaseUrl: window.location.origin, - }); - - if (statusCode !== 200) { - notifications.error({ - message: - error || - t('something_went_wrong', { - ns: 'common', - }), - }); - } - }, - ); - - setTimeout(async () => { - const { data, status } = await getPendingInvitesResponse.refetch(); - if (status === 'success' && data.payload) { - setDataSource(getParsedInviteData(data?.payload || [])); - } - setIsInvitingMembers(false); - toggleModal(false); - }, 2000); - } catch (error) { - notifications.error({ - message: t('something_went_wrong', { - ns: 'common', - }), - }); - } - }, - [ - getParsedInviteData, - getPendingInvitesResponse, - notifications, - t, - toggleModal, - ], - ); - return (
- toggleModal(false)} - centered - destroyOnClose - footer={[ - , - , - ]} - > - - + diff --git a/frontend/src/mocks-server/__mockdata__/invite_user.ts b/frontend/src/mocks-server/__mockdata__/invite_user.ts new file mode 100644 index 0000000000..3b736df977 --- /dev/null +++ b/frontend/src/mocks-server/__mockdata__/invite_user.ts @@ -0,0 +1,25 @@ +export const inviteUser = { + status: 'success', + data: { + statusCode: 200, + error: null, + payload: [ + { + email: 'jane@doe.com', + name: 'Jane', + token: 'testtoken', + createdAt: 1715741587, + role: 'VIEWER', + organization: 'test', + }, + { + email: 'test+in@singoz.io', + name: '', + token: 'testtoken1', + createdAt: 1720095913, + role: 'VIEWER', + organization: 'test', + }, + ], + }, +}; diff --git a/frontend/src/mocks-server/handlers.ts b/frontend/src/mocks-server/handlers.ts index 4e48a4a908..1814d215f7 100644 --- a/frontend/src/mocks-server/handlers.ts +++ b/frontend/src/mocks-server/handlers.ts @@ -1,6 +1,7 @@ import { rest } from 'msw'; import { billingSuccessResponse } from './__mockdata__/billing'; +import { inviteUser } from './__mockdata__/invite_user'; import { licensesSuccessResponse } from './__mockdata__/licenses'; import { membersResponse } from './__mockdata__/members'; import { queryRangeSuccessResponse } from './__mockdata__/query_range'; @@ -89,4 +90,11 @@ export const handlers = [ rest.get('http://localhost/api/v1/billing', (req, res, ctx) => res(ctx.status(200), ctx.json(billingSuccessResponse)), ), + + rest.get('http://localhost/api/v1/invite', (_, res, ctx) => + res(ctx.status(200), ctx.json(inviteUser)), + ), + rest.post('http://localhost/api/v1/invite', (_, res, ctx) => + res(ctx.status(200), ctx.json(inviteUser)), + ), ];