diff --git a/packages/marketplace/src/actions/__tests__/developer.ts b/packages/marketplace/src/actions/__tests__/developer.ts index bee0a44634..871cbc6c3c 100644 --- a/packages/marketplace/src/actions/__tests__/developer.ts +++ b/packages/marketplace/src/actions/__tests__/developer.ts @@ -13,6 +13,7 @@ import { fetchMonthlyBillingFailure, developerWebhookPing, developerSetWebhookPingStatus, + developerApplyAppDetails, } from '../developer' import ActionTypes from '../../constants/action-types' import { appsDataStub } from '../../sagas/__stubs__/apps' @@ -112,4 +113,8 @@ describe('developer actions', () => { expect(developerSetWebhookPingStatus.type).toEqual(ActionTypes.DEVELOPER_SET_PING_WEBHOOK_STATUS) expect(developerSetWebhookPingStatus('SUCCESS').data).toEqual('SUCCESS') }) + it('should create a developerApplyAppDetails action', () => { + expect(developerApplyAppDetails.type).toEqual(ActionTypes.DEVELOPER_APPLY_APP_DETAIL) + expect(developerApplyAppDetails({}).data).toEqual({}) + }) }) diff --git a/packages/marketplace/src/actions/developer.ts b/packages/marketplace/src/actions/developer.ts index 3cbeb0b0c8..22550767f4 100644 --- a/packages/marketplace/src/actions/developer.ts +++ b/packages/marketplace/src/actions/developer.ts @@ -45,3 +45,4 @@ export const developerWebhookPing = actionCreator(Action export const developerSetWebhookPingStatus = actionCreator( ActionTypes.DEVELOPER_SET_PING_WEBHOOK_STATUS, ) +export const developerApplyAppDetails = actionCreator(ActionTypes.DEVELOPER_APPLY_APP_DETAIL) diff --git a/packages/marketplace/src/components/pages/client-app-detail/__test__/client-app-detail.test.tsx b/packages/marketplace/src/components/pages/client-app-detail/__test__/client-app-detail.test.tsx index 5aa9cbeb12..5a0f329fb0 100644 --- a/packages/marketplace/src/components/pages/client-app-detail/__test__/client-app-detail.test.tsx +++ b/packages/marketplace/src/components/pages/client-app-detail/__test__/client-app-detail.test.tsx @@ -8,10 +8,13 @@ import ClientAppDetail, { handleCloseInstallConfirmationModal, handleInstallAppButtonClick, renderAppHeaderButtonGroup, + handleApplyAppDetailsFromLocalStorage, } from '../client-app-detail' import { Button } from '@reapit/elements' import Routes from '@/constants/routes' import appState from '@/reducers/__stubs__/app-state' +import { developerApplyAppDetails } from '@/actions/developer' +import { AppDetailData } from '@/reducers/client/app-detail' describe('ClientAppDetail', () => { let store @@ -169,4 +172,18 @@ describe('ClientAppDetail', () => { expect(mockFunction).toBeCalledWith(true) }) }) + + describe('handleApplyAppDetailsFromLocalStorage', () => { + it('should run correctly', () => { + const dispatch = jest.fn() + const appId = 'appId' + const value = { id: 'appId' } as AppDetailData + const stringValue = JSON.stringify(value) + const spyLocalStorageGetItem = jest.spyOn(window.localStorage, 'getItem').mockImplementation(() => stringValue) + const fn = handleApplyAppDetailsFromLocalStorage(dispatch, 'DEVELOPER', appId) + fn() + expect(spyLocalStorageGetItem).toBeCalledWith('developer-preview-app') + expect(dispatch).toBeCalledWith(developerApplyAppDetails(value)) + }) + }) }) diff --git a/packages/marketplace/src/components/pages/client-app-detail/client-app-detail.tsx b/packages/marketplace/src/components/pages/client-app-detail/client-app-detail.tsx index 83ca5fc48b..d3dcf682b3 100644 --- a/packages/marketplace/src/components/pages/client-app-detail/client-app-detail.tsx +++ b/packages/marketplace/src/components/pages/client-app-detail/client-app-detail.tsx @@ -10,17 +10,22 @@ import ClientAppUninstallConfirmation from '@/components/ui/client-app-detail/cl import { DesktopIntegrationTypeModel } from '@/actions/app-integration-types' import { AppDetailDataNotNull } from '@/reducers/client/app-detail' import { selectIntegrationTypes } from '@/selector/integration-types' -import { useSelector } from 'react-redux' -import { selectAppDetailData, selectAppDetailLoading } from '@/selector/client-app-detail' +import { useSelector, useDispatch } from 'react-redux' +import { selectAppDetailData, selectAppDetailLoading, selectAppDetailError } from '@/selector/client-app-detail' import { selectLoginType, selectIsAdmin } from '@/selector/auth' import AppHeader from '@/components/ui/standalone-app-detail/app-header' import AppContent from './app-content' -import { Loader, Button, FormSection } from '@reapit/elements' +import { Loader, Button, Alert, FormSection } from '@reapit/elements' import clientAppDetailStyles from '@/styles/pages/client-app-detail.scss?mod' import ClientAppInstallConfirmation from '@/components/ui/client-app-detail/client-app-install-confirmation' import { Aside } from './aside' +import { clientFetchAppDetailFailed } from '@/actions/client' +import { developerApplyAppDetails } from '@/actions/developer' +import { useParams } from 'react-router' +import { Dispatch } from 'redux' import { getDesktopIntegrationTypes } from '@/utils/get-desktop-integration-types' import Routes from '@/constants/routes' +import { LoginType } from '@reapit/cognito-auth' export type ClientAppDetailProps = {} @@ -36,7 +41,34 @@ export const handleInstallAppButtonClick = (setIsVisibleInstallConfirmation: (is } } -export const onBackToAppsButtonClick = (history: History) => { +export const handleApplyAppDetailsFromLocalStorage = ( + dispatch: Dispatch, + loginType: LoginType, + appId?: string, +) => () => { + if (loginType !== 'DEVELOPER' || !appId) return + try { + const appDataString = localStorage.getItem('developer-preview-app') + if (!appDataString) { + throw 'No app preview' + } + + const appData = JSON.parse(appDataString) + if (appData.id !== appId) { + throw 'No app preview' + } + + dispatch(developerApplyAppDetails(appData)) + } catch (err) { + dispatch(clientFetchAppDetailFailed(err)) + } +} + +export const onBackToAppsButtonClick = (history: History, loginType: LoginType) => { + if (loginType === 'DEVELOPER') + return () => { + history.push(Routes.DEVELOPER_MY_APPS) + } return () => { history.push(Routes.CLIENT) } @@ -81,7 +113,10 @@ export const renderAppHeaderButtonGroup = ( } const ClientAppDetail: React.FC = () => { + const dispatch = useDispatch() const history = useHistory() + const { appId } = useParams() + const [isVisibleInstallConfirmation, setIsVisibleInstallConfirmation] = React.useState(false) const [isVisibleUninstallConfirmation, setIsVisibleUninstallConfirmation] = React.useState(false) const closeUninstallConfirmationModal = React.useCallback( @@ -108,11 +143,16 @@ const ClientAppDetail: React.FC = () => { const isLoadingAppDetail = useSelector(selectAppDetailLoading) const loginType = useSelector(selectLoginType) const isAdmin = useSelector(selectIsAdmin) + const error = useSelector(selectAppDetailError) + const isInstallBtnHidden = loginType === 'CLIENT' && !isAdmin // selector selectAppDetailData return {} if not data const unfetched = Object.keys(appDetailData).length === 0 const { id = '', installedOn = '' } = appDetailData + React.useEffect(handleApplyAppDetailsFromLocalStorage(dispatch, loginType, appId), [dispatch]) + + if (error) return if (isLoadingAppDetail || unfetched) { return } @@ -133,7 +173,7 @@ const ClientAppDetail: React.FC = () => { /> - diff --git a/packages/marketplace/src/components/pages/developer-submit-app/__test__/__snapshots__/developer-submit-app.test.tsx.snap b/packages/marketplace/src/components/pages/developer-submit-app/__test__/__snapshots__/developer-submit-app.test.tsx.snap index 9879093886..6d35f09ec7 100644 --- a/packages/marketplace/src/components/pages/developer-submit-app/__test__/__snapshots__/developer-submit-app.test.tsx.snap +++ b/packages/marketplace/src/components/pages/developer-submit-app/__test__/__snapshots__/developer-submit-app.test.tsx.snap @@ -1967,6 +1967,21 @@ exports[`DeveloperSubmitApp should match a snapshot 1`] = `
+ + + { }) }) }) + + describe('handleOpenAppPreview', () => { + it('should run correctly', () => { + const params = { appDetails: {}, values: {}, scopes: [], appId: 'appId' } + const spyLocalStorageSetItem = jest.spyOn(window.localStorage, 'setItem') + const spyOpenUrl = jest.spyOn(window, 'open') + const expected = JSON.stringify({ scopes: [] }) + + const fn = handleOpenAppPreview(params) + fn() + expect(spyLocalStorageSetItem).toBeCalledWith('developer-preview-app', expected) + expect(spyOpenUrl).toBeCalledWith('developer/apps/appId/preview', '_blank') + }) + }) }) diff --git a/packages/marketplace/src/components/pages/developer-submit-app/developer-submit-app.tsx b/packages/marketplace/src/components/pages/developer-submit-app/developer-submit-app.tsx index 759d93dc17..1f034eba65 100644 --- a/packages/marketplace/src/components/pages/developer-submit-app/developer-submit-app.tsx +++ b/packages/marketplace/src/components/pages/developer-submit-app/developer-submit-app.tsx @@ -17,6 +17,7 @@ import { FormikHelpers, H6, FlexContainerResponsive, + FormikValues, } from '@reapit/elements' import { FIELD_ERROR_DESCRIPTION } from '@/constants/form' @@ -44,6 +45,7 @@ import UploadImageSection from './upload-image-section' import MarketplaceStatusSection from './marketplace-status-section' import PermissionSection from './permission-section' import styles from '@/styles/pages/developer-submit-app.scss?mod' +import { ScopeModel } from '@/types/marketplace-api-schema' export type DeveloperSubmitAppProps = {} @@ -274,6 +276,25 @@ export const handleOnSubmitAnotherApp = (dispatch: Dispatch) => { } } +export type HandleOpenAppPreview = { + scopes: ScopeModel[] + values: FormikValues + appId?: string + appDetails?: AppDetailModel & { apiKey?: string } +} + +export const handleOpenAppPreview = ({ appDetails, values, scopes, appId }: HandleOpenAppPreview) => () => { + const appDetailState = { + ...appDetails, + ...values, + scopes: scopes.filter(scope => values.scopes.includes(scope.name)), + } + + const url = `developer/apps/${appId}/preview` + localStorage.setItem('developer-preview-app', JSON.stringify(appDetailState)) + window.open(url, '_blank') +} + export const DeveloperSubmitApp: React.FC = () => { let initialValues let formState @@ -377,6 +398,18 @@ export const DeveloperSubmitApp: React.FC = () => { + {!isSubmitApp && (