From 329beb62ff9ecc36e8fc8bb113dec93b42f6c716 Mon Sep 17 00:00:00 2001 From: Rares Mardare <40542072+teodosii@users.noreply.github.com> Date: Tue, 29 Nov 2022 18:59:34 +0200 Subject: [PATCH] Tweaks for mobile app verification (#916) * slightly style mobile app screen * minor tweaks * more changes * wrap calls in waitFor to stop jest complains, PR review fix * fixed polling * use timeout instead of interval * suggestion from Joe --- grafana-plugin/jest.config.js | 2 +- grafana-plugin/src/assets/img/qr-code.png | Bin 0 -> 27047 bytes .../MobileAppVerification.module.scss | 35 + .../MobileAppVerification.test.tsx | 106 +- .../MobileAppVerification.tsx | 88 +- .../MobileAppVerification.test.tsx.snap | 5709 ++--------------- .../DisconnectButton.module.scss | 6 + .../DisconnectButton.test.tsx | 2 +- .../{index.tsx => DisconnectButton.tsx} | 14 +- .../DisconnectButton.test.tsx.snap | 3 +- .../__snapshots__/DownloadIcons.test.tsx.snap | 2 +- .../parts/DownloadIcons/index.tsx | 4 +- grafana-plugin/src/models/user/user.ts | 4 +- grafana-plugin/src/style/utils.css | 31 +- 14 files changed, 880 insertions(+), 5126 deletions(-) create mode 100644 grafana-plugin/src/assets/img/qr-code.png create mode 100644 grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.module.scss create mode 100644 grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.module.scss rename grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/{index.tsx => DisconnectButton.tsx} (59%) diff --git a/grafana-plugin/jest.config.js b/grafana-plugin/jest.config.js index 305169f5..f70e7d25 100644 --- a/grafana-plugin/jest.config.js +++ b/grafana-plugin/jest.config.js @@ -14,9 +14,9 @@ module.exports = { 'jest/outgoingWebhooksStub': '/src/jest/outgoingWebhooksStub.ts', '^jest$': '/src/jest', '^.+\\.(css|scss)$': '/src/jest/styleMock.ts', - // '^.+\\.(ts|tsx)$': 'ts-jest', '^lodash-es$': 'lodash', '^.+\\.svg$': '/src/jest/svgTransform.ts', + '^.+\\.png$': '/src/jest/grafanaMock.ts', }, setupFilesAfterEnv: ['/jest.setup.ts'], diff --git a/grafana-plugin/src/assets/img/qr-code.png b/grafana-plugin/src/assets/img/qr-code.png new file mode 100644 index 0000000000000000000000000000000000000000..2211b2ad74fc3b44cf7a8359c6ce926c74fd034c GIT binary patch literal 27047 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 RCjUywW}Cfrp4EX<{|y1xQpNxP literal 0 HcmV?d00001 diff --git a/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.module.scss b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.module.scss new file mode 100644 index 00000000..a97a1e43 --- /dev/null +++ b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.module.scss @@ -0,0 +1,35 @@ +.container { + display: flex; + flex-direction: row; + + &__box { + flex-basis: 50%; + } + + &__box:first-child { + margin-right: 8px; + } + &__box:last-child { + margin-left: 8px; + } +} + +.icon { + margin-top: -6px; + margin-left: 4px; + fill: var(--green-6); +} + +.disconnect__container { + position: relative; + display: flex; + justify-content: center; + width: 100%; +} + +.disconnect__qrCode { + width: 240px; + height: auto; + filter: blur(6px); + opacity: 0.6; +} diff --git a/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.test.tsx b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.test.tsx index 2ab917da..72926dd4 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.test.tsx +++ b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserStore } from 'models/user/user'; @@ -13,10 +13,12 @@ import MobileAppVerification from './MobileAppVerification'; jest.mock('state/useStore'); const useStore = useStoreOriginal as jest.Mock>; +const loadUserMock = jest.fn().mockReturnValue(undefined); const mockUseStore = (rest?: any, connected = false) => { const store = { userStore: { + loadUser: loadUserMock, currentUser: { messaging_backends: { MOBILE_APP: { connected }, @@ -35,6 +37,10 @@ const USER_PK = '8585'; const BACKEND = 'MOBILE_APP'; describe('MobileAppVerification', () => { + beforeEach(() => { + loadUserMock.mockClear(); + }); + test('it shows a loading message if it is currently fetching the QR code', async () => { const { userStore } = mockUseStore({ sendBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'), @@ -43,8 +49,10 @@ describe('MobileAppVerification', () => { const component = render(); expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + }); }); test('it shows a message when the mobile app is already connected', async () => { @@ -58,7 +66,9 @@ describe('MobileAppVerification', () => { const component = render(); expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(0); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(0); + }); }); test('it shows an error message if there was an error fetching the QR code', async () => { @@ -69,10 +79,12 @@ describe('MobileAppVerification', () => { const component = render(); await screen.findByText(/.*error fetching your QR code.*/); - expect(component.container).toMatchSnapshot(); + waitFor(() => { + expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + }); }); test("it shows a QR code if the app isn't already connected", async () => { @@ -81,12 +93,12 @@ describe('MobileAppVerification', () => { }); const component = render(); - await screen.findByText(/.*the QR code is only valid for one minute.*/); - expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + }); }); test('if we disconnect the app, it disconnects and fetches a new QR code', async () => { @@ -108,15 +120,15 @@ describe('MobileAppVerification', () => { // click the confirm button within the modal, which actually triggers the callback await user.click(screen.getByText('Remove')); - await screen.findByText(/.*the QR code is only valid for one minute.*/); - expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); - expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); - expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); + expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + }); }); test('it shows a loading message if it is currently disconnecting', async () => { @@ -144,11 +156,13 @@ describe('MobileAppVerification', () => { expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(1); + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledWith(USER_PK, BACKEND); - expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); - expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); + expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + }); }); test('it shows an error message if there was an error disconnecting the mobile app', async () => { @@ -163,7 +177,7 @@ describe('MobileAppVerification', () => { const component = render(); const user = userEvent.setup(); - const button = await screen.findByRole('button'); + const button = await screen.findByTestId('test__disconnect'); // click the disconnect button, which opens the modal await user.click(button); @@ -174,9 +188,51 @@ describe('MobileAppVerification', () => { expect(component.container).toMatchSnapshot(); - expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(0); + waitFor(() => { + expect(userStore.sendBackendConfirmationCode).toHaveBeenCalledTimes(0); - expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); - expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + expect(userStore.unlinkBackend).toHaveBeenCalledTimes(1); + expect(userStore.unlinkBackend).toHaveBeenCalledWith(USER_PK, BACKEND); + }); + }); + + test('it polls loadUser on first render if not connected', async () => { + mockUseStore( + { + sendBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'), + unlinkBackend: jest.fn().mockRejectedValueOnce('asdfadsfafds'), + }, + false + ); + + render(); + + await waitFor(() => { + expect(loadUserMock).toHaveBeenCalledTimes(1); + }); + }); + + test('it polls loadUser after disconnect', async () => { + mockUseStore( + { + sendBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dff'), + unlinkBackend: jest.fn().mockRejectedValueOnce('asdff'), + }, + true + ); + + render(); + + const user = userEvent.setup(); + const button = await screen.findByRole('button'); + + loadUserMock.mockClear(); + + await user.click(button); // click the disconnect button, which opens the modal + await user.click(screen.getByText('Remove')); // click the confirm button within the modal, which actually triggers the callback + + await waitFor(() => { + expect(loadUserMock).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.tsx b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.tsx index cdaf5af2..7b8d2afb 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.tsx +++ b/grafana-plugin/src/containers/MobileAppVerification/MobileAppVerification.tsx @@ -1,29 +1,33 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { HorizontalGroup, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; +import { Icon, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; +import cn from 'classnames/bind'; import { observer } from 'mobx-react'; +import qrCodeImage from 'assets/img/qr-code.png'; import Block from 'components/GBlock/Block'; import Text from 'components/Text/Text'; import { User } from 'models/user/user.types'; import { useStore } from 'state/useStore'; -import DisconnectButton from './parts/DisconnectButton'; +import styles from './MobileAppVerification.module.scss'; +import DisconnectButton from './parts/DisconnectButton/DisconnectButton'; import DownloadIcons from './parts/DownloadIcons'; import QRCode from './parts/QRCode'; +const cx = cn.bind(styles); + type Props = { userPk: User['pk']; }; +const INTERVAL_MS = 5000; const BACKEND = 'MOBILE_APP'; const MobileAppVerification = observer(({ userPk }: Props) => { const { userStore } = useStore(); - const [mobileAppIsCurrentlyConnected, setMobileAppIsCurrentlyConnected] = useState( - userStore.currentUser.messaging_backends[BACKEND]?.connected === true - ); + const [mobileAppIsCurrentlyConnected, setMobileAppIsCurrentlyConnected] = useState(isUserConnected()); const [fetchingQRCode, setFetchingQRCode] = useState(!mobileAppIsCurrentlyConnected); const [QRCodeValue, setQRCodeValue] = useState(null); @@ -31,6 +35,7 @@ const MobileAppVerification = observer(({ userPk }: Props) => { const [disconnectingMobileApp, setDisconnectingMobileApp] = useState(false); const [errorDisconnectingMobileApp, setErrorDisconnectingMobileApp] = useState(null); + const [userTimeoutId, setUserTimeoutId] = useState(undefined); const fetchQRCode = useCallback(async () => { setFetchingQRCode(true); @@ -59,9 +64,24 @@ const MobileAppVerification = observer(({ userPk }: Props) => { } catch (e) { setErrorDisconnectingMobileApp('There was an error disconnecting your mobile app. Please try again.'); } + setDisconnectingMobileApp(false); + pollUserProfile(); }, [userPk, resetState]); + useEffect(() => { + if (!isUserConnected()) { + pollUserProfile(); + } + + // clear on unmount + return () => { + if (userTimeoutId) { + clearTimeout(userTimeoutId); + } + }; + }, []); + useEffect(() => { if (!mobileAppIsCurrentlyConnected) { fetchQRCode(); @@ -76,33 +96,63 @@ const MobileAppVerification = observer(({ userPk }: Props) => { content = {errorFetchingQRCode || errorDisconnectingMobileApp}; } else if (mobileAppIsCurrentlyConnected) { content = ( - - Your mobile app is currently connected. Click below to disconnect. - + + + App connected + + + You can sync one application to your account. To setup new device please disconnect app first. + +
+ + +
); } else if (QRCodeValue) { content = ( - - - - Note: the QR code is only valid for one minute. If you have issues connecting your mobile app, try refreshing - this page to generate a new code. + + + Sign In + + Open Grafana IRM mobile application and scan this code to sync it with your account. +
+ +
+ + Note: the QR code is only valid for one minute. If you have issues connecting your mobile + app, try refreshing this page to generate a new code.
); } return ( - - - {content} - - +
+ - + + {content} + +
); + + function isUserConnected(user?: User): boolean { + return !!(user || userStore.currentUser).messaging_backends[BACKEND]?.connected; + } + + async function pollUserProfile(): Promise { + clearTimeout(userTimeoutId); + setUserTimeoutId(undefined); + + const user = await userStore.loadUser(userPk); + if (!isUserConnected(user)) { + setUserTimeoutId(setTimeout(() => pollUserProfile(), INTERVAL_MS)); + } else { + setMobileAppIsCurrentlyConnected(true); + } + } }); export default MobileAppVerification; diff --git a/grafana-plugin/src/containers/MobileAppVerification/__snapshots__/MobileAppVerification.test.tsx.snap b/grafana-plugin/src/containers/MobileAppVerification/__snapshots__/MobileAppVerification.test.tsx.snap index bb4670cf..6bcaa911 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/__snapshots__/MobileAppVerification.test.tsx.snap +++ b/grafana-plugin/src/containers/MobileAppVerification/__snapshots__/MobileAppVerification.test.tsx.snap @@ -3,4903 +3,133 @@ exports[`MobileAppVerification if we disconnect the app, it disconnects and fetches a new QR code 1`] = `
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
+
+ Apple + + iOS + +
+
+
+
+ Play Store + + Android + +
+
+
+
+
+
+
+
+
+ + App connected +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - Note: the QR code is only valid for one minute. If you have issues connecting your mobile app, try refreshing this page to generate a new code. - -
+
-
-
-
-
+ + You can sync one application to your account. To setup new device please disconnect app first. + +
+
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
-
-
- Apple - - iOS - -
-
-
-
- Play Store - - Android - -
-
-
-
-
-
-
-
- -`; - -exports[`MobileAppVerification it shows a QR code if the app isn't already connected 1`] = ` -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - Note: the QR code is only valid for one minute. If you have issues connecting your mobile app, try refreshing this page to generate a new code. - -
-
-
-
-
-
-
-
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
-
-
- Apple - - iOS - -
-
-
-
- Play Store - - Android - -
-
-
-
-
-
-
-
-
-`; - -exports[`MobileAppVerification it shows a loading message if it is currently disconnecting 1`] = ` -
-
-
-
-
- Loading... - -
- -
-
-
-
-
-
-
-
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
-
-
- Apple - - iOS - -
-
-
-
- Play Store - - Android - -
-
-
-
-
-
-
-
-
-`; - -exports[`MobileAppVerification it shows a loading message if it is currently fetching the QR code 1`] = ` -
-
-
-
-
- Loading... - -
- -
-
-
-
-
-
-
-
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
-
-
- Apple - - iOS - -
-
-
-
- Play Store - - Android - -
-
-
-
-
-
-
-
-
-`; - -exports[`MobileAppVerification it shows a message when the mobile app is already connected 1`] = ` -
-
-
-
-
-
- - Your mobile app is currently connected. Click below to disconnect. - -
-
+
+
+`; + +exports[`MobileAppVerification it shows a QR code if the app isn't already connected 1`] = ` +
+
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
+ - Apple - - iOS - -
-
-
-
- Play Store - - Android - -
+ iOS +
+
+
+ Play Store + + Android + +
+
+
+
+
+
+
+
+ Loading... + +
+ +
+
+
+
+
+`; + +exports[`MobileAppVerification it shows a loading message if it is currently disconnecting 1`] = ` +
+
+
+
+
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
+
+
+
+ Apple + + iOS + +
+
+
+
+ Play Store + + Android + +
+
+
+
+
+
+
+
+ Loading... + +
+ +
+
+
+
+
+`; + +exports[`MobileAppVerification it shows a loading message if it is currently fetching the QR code 1`] = ` +
+
+
+
+
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
+
+
+
+ Apple + + iOS + +
+
+
+
+ Play Store + + Android + +
+
+
+
+
+
+
+
+ Loading... + +
+ +
+
+
+
+
+`; + +exports[`MobileAppVerification it shows a message when the mobile app is already connected 1`] = ` +
+
+
+
+
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
+
+
+
+ Apple + + iOS + +
+
+
+
+ Play Store + + Android + +
+
+
+
+
+
+
+
+
+ + App connected +
+ + + +
+
+
+
+ + You can sync one application to your account. To setup new device please disconnect app first. + +
+
+
+ +
@@ -4995,98 +592,89 @@ exports[`MobileAppVerification it shows a message when the mobile app is already exports[`MobileAppVerification it shows an error message if there was an error disconnecting the mobile app 1`] = `
- - There was an error disconnecting your mobile app. Please try again. - -
-
-
-
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
+ - Apple - - iOS - -
+ iOS +
+
+
-
+ - Play Store - - Android - -
+ Android +
+
+ + There was an error disconnecting your mobile app. Please try again. + +
`; @@ -5094,98 +682,89 @@ exports[`MobileAppVerification it shows an error message if there was an error d exports[`MobileAppVerification it shows an error message if there was an error fetching the QR code 1`] = `
- - There was an error fetching your QR code. Please try again. - -
-
-
-
+ + Download + +
+
+ + The Grafana IRM app is available on both the App Store and Google Play Store. + +
+
- - Download - -
-
- - The Grafana IRM app is available on both the App Store and Google Play Store. - -
-
-
+ - Apple - - iOS - -
+ iOS +
+
+
-
+ - Play Store - - Android - -
+ Android +
+
+ + There was an error fetching your QR code. Please try again. + +
`; diff --git a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.module.scss b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.module.scss new file mode 100644 index 00000000..8e4b047d --- /dev/null +++ b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.module.scss @@ -0,0 +1,6 @@ +.disconnect-button { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.test.tsx b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.test.tsx index b660d5af..f3551b49 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.test.tsx +++ b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import DisconnectButton from '.'; +import DisconnectButton from './DisconnectButton'; describe('DisconnectButton', () => { test('it renders properly', () => { diff --git a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/index.tsx b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.tsx similarity index 59% rename from grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/index.tsx rename to grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.tsx index ec108f47..435dabb4 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/index.tsx +++ b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/DisconnectButton.tsx @@ -1,17 +1,27 @@ import React, { FC } from 'react'; import { Button } from '@grafana/ui'; +import cn from 'classnames/bind'; import WithConfirm from 'components/WithConfirm/WithConfirm'; +import styles from './DisconnectButton.module.scss'; + +const cx = cn.bind(styles); + type Props = { onClick: () => void; }; -// TODO: right now this shows a confirmation pop-up modal on top of the user settings modal, do we want to maybe change this? const DisconnectButton: FC = ({ onClick }) => ( - diff --git a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/__snapshots__/DisconnectButton.test.tsx.snap b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/__snapshots__/DisconnectButton.test.tsx.snap index 88ffb190..22996a57 100644 --- a/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/__snapshots__/DisconnectButton.test.tsx.snap +++ b/grafana-plugin/src/containers/MobileAppVerification/parts/DisconnectButton/__snapshots__/DisconnectButton.test.tsx.snap @@ -3,7 +3,8 @@ exports[`DisconnectButton it renders properly 1`] = `