From dea31071dc5dfaa7621b2be22b3ef67bdbbe3e17 Mon Sep 17 00:00:00 2001 From: Dan Nguyen <45675930+danndz@users.noreply.github.com> Date: Mon, 16 Dec 2019 18:01:08 +0700 Subject: [PATCH] [CLD-573] Change button uninstall to installed on browse apps (#206) * [CLD-573] Change button uninstall to installed on browse apps * [CLD-573] fix review * [CLD-573] move inline style to css --- src/actions/__tests__/app-detail-modal.ts | 12 +-- src/actions/app-detail-modal.ts | 5 +- src/components/pages/__tests__/client.tsx | 4 + src/components/pages/client.tsx | 19 ++++- src/components/pages/my-apps.tsx | 5 ++ .../__snapshots__/app-detail.tsx.snap | 52 ++++++++++++ .../ui/__tests__/app-confirm-install.tsx | 12 +-- .../ui/__tests__/app-confirm-uninstall.tsx | 12 +-- src/components/ui/__tests__/app-detail.tsx | 24 ++---- src/components/ui/app-confirm-install.tsx | 14 ++-- src/components/ui/app-confirm-uninstall.tsx | 14 ++-- .../__snapshots__/app-detail-inner.tsx.snap | 64 +++++++++++++- .../__tests__/app-detail-inner.tsx | 17 ++-- .../__tests__/app-detail-modal.tsx | 14 ++-- .../ui/app-detail-modal/app-detail-inner.tsx | 84 ++++++++++++++----- .../ui/app-detail-modal/app-detail-modal.tsx | 14 ++-- src/components/ui/app-detail.tsx | 50 +++-------- src/constants/action-types.ts | 5 +- src/reducers/__tests__/app-detail-modal.ts | 10 +-- src/reducers/app-detail-modal.ts | 22 +++-- src/styles/blocks/app-detail.scss | 28 +++++++ 21 files changed, 334 insertions(+), 147 deletions(-) create mode 100644 src/components/ui/__tests__/__snapshots__/app-detail.tsx.snap diff --git a/src/actions/__tests__/app-detail-modal.ts b/src/actions/__tests__/app-detail-modal.ts index 93283842bb..ff93cb6c6a 100644 --- a/src/actions/__tests__/app-detail-modal.ts +++ b/src/actions/__tests__/app-detail-modal.ts @@ -1,18 +1,18 @@ import { - setAppDetailModalStateView, - setAppDetailModalStateViewConfirm, + setAppDetailModalStateBrowse, + setAppDetailModalStateInstall, setAppDetailModalStateSuccess } from '../app-detail-modal' import ActionTypes from '../../constants/action-types' describe('app permission actions', () => { - it('should create a setAppDetailModalStateView action', () => { - expect(setAppDetailModalStateView.type).toEqual(ActionTypes.SET_APP_DETAIL_MODAL_STATE_VIEW) + it('should create a setAppDetailModalStateBrowse action', () => { + expect(setAppDetailModalStateBrowse.type).toEqual(ActionTypes.SET_APP_DETAIL_MODAL_STATE_BROWSE) }) - it('should create a setAppDetailModalStateViewConfirm action', () => { - expect(setAppDetailModalStateViewConfirm.type).toEqual(ActionTypes.SET_APP_DETAIL_MODAL_STATE_CONFIRM) + it('should create a setAppDetailModalStateInstall action', () => { + expect(setAppDetailModalStateInstall.type).toEqual(ActionTypes.SET_APP_DETAIL_MODAL_STATE_INSTALL) }) it('should create a setAppDetailModalStateSuccess action', () => { diff --git a/src/actions/app-detail-modal.ts b/src/actions/app-detail-modal.ts index fc7d357b41..b60dd40f1a 100644 --- a/src/actions/app-detail-modal.ts +++ b/src/actions/app-detail-modal.ts @@ -1,7 +1,8 @@ import { actionCreator } from '../utils/actions' import ActionTypes from '../constants/action-types' -export const setAppDetailModalStateView = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_VIEW) -export const setAppDetailModalStateViewConfirm = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_CONFIRM) +export const setAppDetailModalStateBrowse = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_BROWSE) +export const setAppDetailModalStateInstall = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_INSTALL) export const setAppDetailModalStateUninstall = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_UNINSTALL) export const setAppDetailModalStateSuccess = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_SUCCESS) +export const setAppDetailModalStateManage = actionCreator(ActionTypes.SET_APP_DETAIL_MODAL_STATE_MANAGE) diff --git a/src/components/pages/__tests__/client.tsx b/src/components/pages/__tests__/client.tsx index 56d31e499c..814e2c44db 100644 --- a/src/components/pages/__tests__/client.tsx +++ b/src/components/pages/__tests__/client.tsx @@ -44,6 +44,7 @@ const props = (loading: boolean): ClientProps => ({ error: false }, clientId: '1', + setStateViewBrowse: jest.fn(), installationsFormState: 'PENDING', installationsSetFormState: jest.fn(), fetchAppDetail: jest.fn(), @@ -86,6 +87,7 @@ describe('Client', () => { }, clientId: '1', installationsFormState: 'PENDING', + setStateViewBrowse: jest.fn(), installationsSetFormState: jest.fn(), fetchAppDetail: jest.fn(), ...routerProps @@ -110,6 +112,7 @@ describe('Client', () => { }, clientId: '1', installationsFormState: 'PENDING', + setStateViewBrowse: jest.fn(), installationsSetFormState: jest.fn(), fetchAppDetail: jest.fn(), ...routerProps @@ -194,6 +197,7 @@ describe('Client', () => { appDetail: { appDetailData: appsDataStub.data }, + setStateViewBrowse: jest.fn(), fetchAppDetail: jest.fn(), clientId: 'ABC' } diff --git a/src/components/pages/client.tsx b/src/components/pages/client.tsx index 75c7d77fae..1a7ff137d8 100644 --- a/src/components/pages/client.tsx +++ b/src/components/pages/client.tsx @@ -16,8 +16,10 @@ import styles from '@/styles/pages/client.scss?mod' import { appInstallationsSetFormState } from '@/actions/app-installations' import { addQuery, getParamValueFromPath, hasFilterParams } from '@/utils/client-url-params' +import { setAppDetailModalStateBrowse } from '@/actions/app-detail-modal' export interface ClientMappedActions { + setStateViewBrowse: () => void fetchAppDetail: (id: string, clientId: string) => void installationsSetFormState: (formState: FormState) => void } @@ -33,8 +35,11 @@ export const handleAfterClose = ({ setVisible }) => () => setVisible(false) export const handleOnChange = history => (page: number) => { history.push(addQuery({ page })) } -export const handleOnCardClick = ({ setVisible, appDetail, fetchAppDetail, clientId }) => (app: AppSummaryModel) => { +export const handleOnCardClick = ({ setVisible, setStateViewBrowse, appDetail, fetchAppDetail, clientId }) => ( + app: AppSummaryModel +) => { setVisible(true) + setStateViewBrowse() if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) { fetchAppDetail(app.id, clientId) } @@ -62,6 +67,7 @@ export const Client: React.FunctionComponent = ({ fetchAppDetail, appDetail, clientId, + setStateViewBrowse, installationsFormState, installationsSetFormState }) => { @@ -98,7 +104,13 @@ export const Client: React.FunctionComponent = ({ list={featuredApps} title="Featured Apps" loading={loading} - onCardClick={handleOnCardClick({ setVisible, appDetail, fetchAppDetail, clientId })} + onCardClick={handleOnCardClick({ + setVisible, + setStateViewBrowse, + appDetail, + fetchAppDetail, + clientId + })} infoType="CLIENT_APPS_EMPTY" numOfColumn={3} /> @@ -108,7 +120,7 @@ export const Client: React.FunctionComponent = ({ 1 || hasParams ? '' : 'CLIENT_APPS_EMPTY'} pagination={{ totalCount, @@ -134,6 +146,7 @@ export const mapStateToProps = (state: ReduxState): ClientMappedProps => ({ }) export const mapDispatchToProps = (dispatch: any): ClientMappedActions => ({ + setStateViewBrowse: () => dispatch(setAppDetailModalStateBrowse()), fetchAppDetail: (id: string, clientId: string) => dispatch(appDetailRequestData({ id, clientId })), installationsSetFormState: (formState: FormState) => dispatch(appInstallationsSetFormState(formState)) }) diff --git a/src/components/pages/my-apps.tsx b/src/components/pages/my-apps.tsx index 2ac5f4f154..ab3f5dd568 100644 --- a/src/components/pages/my-apps.tsx +++ b/src/components/pages/my-apps.tsx @@ -15,8 +15,10 @@ import { selectClientId } from '@/selector/client' import { AppSummaryModel } from '@/types/marketplace-api-schema' import { handleLaunchApp } from '../../utils/launch-app' import { appInstallationsSetFormState } from '@/actions/app-installations' +import { setAppDetailModalStateManage } from '@/actions/app-detail-modal' export interface MyAppsMappedActions { + setStateViewManage: () => void fetchAppDetail: (id: string, clientId: string) => void fetchMyApp: (page: number) => void installationsSetFormState: (formState: FormState) => void @@ -47,6 +49,7 @@ export const MyApps: React.FunctionComponent = ({ fetchMyApp, fetchAppDetail, appDetail, + setStateViewManage, installationsFormState, installationsSetFormState, history @@ -74,6 +77,7 @@ export const MyApps: React.FunctionComponent = ({ onCardClick={(app: AppSummaryModel) => handleLaunchApp(app)} onSettingsClick={(app: AppSummaryModel) => { setVisible(true) + setStateViewManage() if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) { fetchAppDetail(app.id, clientId) } @@ -99,6 +103,7 @@ export const mapStateToProps = (state: ReduxState): MyAppsMappedProps => ({ }) export const mapDispatchToProps = (dispatch: any): MyAppsMappedActions => ({ + setStateViewManage: () => dispatch(setAppDetailModalStateManage()), fetchAppDetail: (id: string, clientId: string) => dispatch(appDetailRequestData({ id, clientId })), fetchMyApp: (page: number) => dispatch(myAppsRequestData(page)), installationsSetFormState: (formState: FormState) => dispatch(appInstallationsSetFormState(formState)) diff --git a/src/components/ui/__tests__/__snapshots__/app-detail.tsx.snap b/src/components/ui/__tests__/__snapshots__/app-detail.tsx.snap new file mode 100644 index 0000000000..fd2cea820f --- /dev/null +++ b/src/components/ui/__tests__/__snapshots__/app-detail.tsx.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AppDetailModalInner should match a snapshot 1`] = ` + + + + + } + subHeading="" + > +
+ + + Not listed + +
+
+
+

+ App ID: +

+
+

+
+

+ Permissions +

+ + + You have requested the following permissions for this App: + + +
    + + } + /> + + +`; diff --git a/src/components/ui/__tests__/app-confirm-install.tsx b/src/components/ui/__tests__/app-confirm-install.tsx index 649b5c07d9..a34a2aefe2 100644 --- a/src/components/ui/__tests__/app-confirm-install.tsx +++ b/src/components/ui/__tests__/app-confirm-install.tsx @@ -10,7 +10,7 @@ const mockProps = { installationsFormState: 'PENDING' as FormState, afterClose: jest.fn(), installApp: jest.fn(), - setAppDetailModalStateView: jest.fn(), + setAppDetailModalStateBrowse: jest.fn(), setAppDetailModalStateSuccess: jest.fn() } @@ -49,11 +49,11 @@ describe('AppConfirmInstallContent', () => { }) it('handleCloseModal', () => { const afterClose = jest.fn() - const setAppDetailModalStateView = jest.fn() - const fn = handleCloseModal(afterClose, setAppDetailModalStateView) + const setAppDetailModalStateBrowse = jest.fn() + const fn = handleCloseModal(afterClose, setAppDetailModalStateBrowse) fn() expect(afterClose).toBeCalled() - expect(setAppDetailModalStateView).toBeCalled() + expect(setAppDetailModalStateBrowse).toBeCalled() }) describe('mapDispatchToProps', () => { @@ -63,8 +63,8 @@ describe('AppConfirmInstallContent', () => { fn.installApp({ appId: '1' })() expect(dispatch).toBeCalled() }) - it('should call dispatch when involke setAppDetailModalStateView', () => { - fn.setAppDetailModalStateView() + it('should call dispatch when involke setAppDetailModalStateBrowse', () => { + fn.setAppDetailModalStateBrowse() expect(dispatch).toBeCalled() }) it('should call dispatch when involke setAppDetailModalStateSuccess', () => { diff --git a/src/components/ui/__tests__/app-confirm-uninstall.tsx b/src/components/ui/__tests__/app-confirm-uninstall.tsx index 64a60232db..cd3c953ec6 100644 --- a/src/components/ui/__tests__/app-confirm-uninstall.tsx +++ b/src/components/ui/__tests__/app-confirm-uninstall.tsx @@ -10,7 +10,7 @@ const mockProps = { installationsFormState: 'PENDING' as FormState, afterClose: jest.fn(), uninstallApp: jest.fn(), - setAppDetailModalStateView: jest.fn(), + setAppDetailModalStateBrowse: jest.fn(), setAppDetailModalStateSuccess: jest.fn() } @@ -49,11 +49,11 @@ describe('AppConfirmUninstall', () => { }) it('handleCloseModal', () => { const afterClose = jest.fn() - const setAppDetailModalStateView = jest.fn() - const fn = handleCloseModal(afterClose, setAppDetailModalStateView) + const setAppDetailModalStateBrowse = jest.fn() + const fn = handleCloseModal(afterClose, setAppDetailModalStateBrowse) fn() expect(afterClose).toBeCalled() - expect(setAppDetailModalStateView).toBeCalled() + expect(setAppDetailModalStateBrowse).toBeCalled() }) describe('mapDispatchToProps', () => { @@ -63,8 +63,8 @@ describe('AppConfirmUninstall', () => { fn.uninstallApp({ appId: '1', installationId: '1' })() expect(dispatch).toBeCalled() }) - it('should call dispatch when involke setAppDetailModalStateView', () => { - fn.setAppDetailModalStateView() + it('should call dispatch when involke setAppDetailModalStateBrowse', () => { + fn.setAppDetailModalStateBrowse() expect(dispatch).toBeCalled() }) it('should call dispatch when involke setAppDetailModalStateSuccess', () => { diff --git a/src/components/ui/__tests__/app-detail.tsx b/src/components/ui/__tests__/app-detail.tsx index 06f5939869..0e0dcdb716 100644 --- a/src/components/ui/__tests__/app-detail.tsx +++ b/src/components/ui/__tests__/app-detail.tsx @@ -2,11 +2,11 @@ import * as React from 'react' import { mount, shallow } from 'enzyme' import { AppDetail, AppDetailProps, mapDispatchToProps, SlickButtonNav } from '../app-detail' import { setDeveloperAppModalStateDelete } from '@/actions/developer-app-modal' -import { setAppDetailModalStateViewConfirm, setAppDetailModalStateUninstall } from '@/actions/app-detail-modal' +import { setAppDetailModalStateInstall, setAppDetailModalStateUninstall } from '@/actions/app-detail-modal' const props: AppDetailProps = { setDeveloperAppModalStateDelete: jest.fn(), - setAppDetailModalStateViewConfirm: jest.fn(), + setAppDetailModalStateInstall: jest.fn(), setAppDetailModalStateUninstall: jest.fn(), isCurrentLoggedUserDeveloper: true, isCurrentLoggedUserClient: false, @@ -15,26 +15,18 @@ const props: AppDetailProps = { } describe('AppDetailModalInner', () => { - it('Should show uninstall app button if isCurrentLoggedUserClient = true and installedOn exist', () => { - const modifiedProps: AppDetailProps = { ...props, isCurrentLoggedUserClient: true, data: { installedOn: 'yep' } } - const shallowAppDetailModalInner = mount() - expect(shallowAppDetailModalInner.find('[dataTest="btnAppDetailUninstallApp"]')).toHaveLength(1) - }) - - it('Should show install app button if isCurrentLoggedUserClient = true and installedOn not exist', () => { - const modifiedProps: AppDetailProps = { ...props, isCurrentLoggedUserClient: true } - const shallowAppDetailModalInner = mount() - expect(shallowAppDetailModalInner.find('[dataTest="btnAppDetailInstallApp"]')).toHaveLength(1) + it('should match a snapshot', () => { + expect(shallow()).toMatchSnapshot() }) describe('mapDispatchToProps', () => { - it('should dispatch correctly if mapped setAppDetailModalStateViewConfirm is called', () => { + it('should dispatch correctly if mapped setAppDetailModalStateInstall is called', () => { const mockedDispatch = jest.fn() - const { setAppDetailModalStateViewConfirm: mappedsetAppDetailModalStateViewConfirm } = mapDispatchToProps( + const { setAppDetailModalStateInstall: mappedsetAppDetailModalStateViewConfirm } = mapDispatchToProps( mockedDispatch ) mappedsetAppDetailModalStateViewConfirm() - expect(mockedDispatch).toHaveBeenNthCalledWith(1, setAppDetailModalStateViewConfirm()) + expect(mockedDispatch).toHaveBeenNthCalledWith(1, setAppDetailModalStateInstall()) }) it('should dispatch correctly if mapped requestUninstall is called', () => { @@ -61,7 +53,7 @@ describe('SlickButtonNav', () => { it('should match snapshot', () => { const mockProps = { currentSlide: '', - setAppDetailModalStateViewConfirm: jest.fn(), + setAppDetailModalStateInstall: jest.fn(), slideCount: jest.fn() } const wrapper = shallow( diff --git a/src/components/ui/app-confirm-install.tsx b/src/components/ui/app-confirm-install.tsx index 20271dc83f..1de778ff7a 100644 --- a/src/components/ui/app-confirm-install.tsx +++ b/src/components/ui/app-confirm-install.tsx @@ -4,7 +4,7 @@ import { FormState, ReduxState } from '@/types/core' import { ScopeModel, AppDetailModel } from '@/types/marketplace-api-schema' import appPermissionContentStyles from '@/styles/pages/app-permission-content.scss?mod' import { Button, SubTitleH6, ModalHeader, ModalBody, ModalFooter } from '@reapit/elements' -import { setAppDetailModalStateView, setAppDetailModalStateSuccess } from '@/actions/app-detail-modal' +import { setAppDetailModalStateBrowse, setAppDetailModalStateSuccess } from '@/actions/app-detail-modal' import { InstallParams, appInstallationsRequestInstall } from '@/actions/app-installations' export type AppConfirmInstallContentMappedProps = { @@ -13,7 +13,7 @@ export type AppConfirmInstallContentMappedProps = { } export interface AppConfirmInstallContentMappedActions { - setAppDetailModalStateView: () => void + setAppDetailModalStateBrowse: () => void setAppDetailModalStateSuccess: () => void installApp: (params: InstallParams) => () => void } @@ -23,9 +23,9 @@ export type AppConfirmInstallContentInnerProps = AppConfirmInstallContentMappedP afterClose?: () => void } -export const handleCloseModal = (setAppDetailModalStateView: () => void, afterClose?: () => void) => () => { +export const handleCloseModal = (setAppDetailModalStateBrowse: () => void, afterClose?: () => void) => () => { if (afterClose) afterClose() - setAppDetailModalStateView() + setAppDetailModalStateBrowse() } export const handleInstallSuccess = ({ isSuccessed, setAppDetailModalStateSuccess }) => () => { @@ -39,7 +39,7 @@ export const AppConfirmInstallContent = ({ installationsFormState, appDetailData, afterClose, - setAppDetailModalStateView, + setAppDetailModalStateBrowse, setAppDetailModalStateSuccess }: AppConfirmInstallContentInnerProps) => { const isLoading = installationsFormState === 'SUBMITTING' @@ -97,7 +97,7 @@ export const AppConfirmInstallContent = ({ className={appPermissionContentStyles.installButton} type="button" variant="danger" - onClick={handleCloseModal(setAppDetailModalStateView, afterClose)} + onClick={handleCloseModal(setAppDetailModalStateBrowse, afterClose)} > Cancel @@ -114,7 +114,7 @@ export const mapStateToProps = (state: ReduxState): AppConfirmInstallContentMapp }) export const mapDispatchToProps = (dispatch: any): AppConfirmInstallContentMappedActions => ({ - setAppDetailModalStateView: () => dispatch(setAppDetailModalStateView()), + setAppDetailModalStateBrowse: () => dispatch(setAppDetailModalStateBrowse()), setAppDetailModalStateSuccess: () => dispatch(setAppDetailModalStateSuccess()), installApp: (params: InstallParams) => () => dispatch(appInstallationsRequestInstall(params)) }) diff --git a/src/components/ui/app-confirm-uninstall.tsx b/src/components/ui/app-confirm-uninstall.tsx index 1f32331cf3..9c163b934b 100644 --- a/src/components/ui/app-confirm-uninstall.tsx +++ b/src/components/ui/app-confirm-uninstall.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { connect } from 'react-redux' import { FormState, ReduxState } from '@/types/core' import { Button, SubTitleH6, ModalFooter, ModalBody, ModalHeader } from '@reapit/elements' -import { setAppDetailModalStateView, setAppDetailModalStateSuccess } from '@/actions/app-detail-modal' +import { setAppDetailModalStateBrowse, setAppDetailModalStateSuccess } from '@/actions/app-detail-modal' import { AppDetailModel } from '@/types/marketplace-api-schema' import { appInstallationsRequestUninstall, UninstallParams } from '@/actions/app-installations' @@ -16,7 +16,7 @@ export interface AppConfirmUninstallMappedProps { } export interface AppConfirmUninstallMappedActions { - setAppDetailModalStateView: () => void + setAppDetailModalStateBrowse: () => void setAppDetailModalStateSuccess: () => void uninstallApp: (params: UninstallParams) => () => void } @@ -25,9 +25,9 @@ export type AppConfirmUninstallProps = AppConfirmUninstallInnerProps & AppConfirmUninstallMappedProps & AppConfirmUninstallMappedActions -export const handleCloseModal = (setAppDetailModalStateView: () => void, afterClose?: () => void) => () => { +export const handleCloseModal = (setAppDetailModalStateBrowse: () => void, afterClose?: () => void) => () => { if (afterClose) afterClose() - setAppDetailModalStateView() + setAppDetailModalStateBrowse() } export const handleUninstallSuccess = ({ isSuccessed, setAppDetailModalStateSuccess }) => () => { @@ -49,7 +49,7 @@ export const AppConfirmUninstall = ({ uninstallApp, installationsFormState, afterClose, - setAppDetailModalStateView, + setAppDetailModalStateBrowse, setAppDetailModalStateSuccess }: AppConfirmUninstallProps) => { const isLoading = installationsFormState === 'SUBMITTING' @@ -95,7 +95,7 @@ export const AppConfirmUninstall = ({ fullWidth type="button" variant="danger" - onClick={handleCloseModal(setAppDetailModalStateView, afterClose)} + onClick={handleCloseModal(setAppDetailModalStateBrowse, afterClose)} > Disagree @@ -112,7 +112,7 @@ export const mapStateToProps = (state: ReduxState): AppConfirmUninstallMappedPro }) export const mapDispatchToProps = (dispatch: any): AppConfirmUninstallMappedActions => ({ - setAppDetailModalStateView: () => dispatch(setAppDetailModalStateView()), + setAppDetailModalStateBrowse: () => dispatch(setAppDetailModalStateBrowse()), setAppDetailModalStateSuccess: () => dispatch(setAppDetailModalStateSuccess()), uninstallApp: (params: UninstallParams) => () => dispatch(appInstallationsRequestUninstall(params)) }) diff --git a/src/components/ui/app-detail-modal/__tests__/__snapshots__/app-detail-inner.tsx.snap b/src/components/ui/app-detail-modal/__tests__/__snapshots__/app-detail-inner.tsx.snap index ee93bda096..3073300f10 100644 --- a/src/components/ui/app-detail-modal/__tests__/__snapshots__/app-detail-inner.tsx.snap +++ b/src/components/ui/app-detail-modal/__tests__/__snapshots__/app-detail-inner.tsx.snap @@ -2,7 +2,7 @@ exports[`AppDetailInner should match a snapshot when appDetailModalState = VIEW_CONFIRM_INSTALL 1`] = ``; -exports[`AppDetailInner should match a snapshot when appDetailModalState = VIEW_DETAIL 1`] = ` +exports[`AppDetailInner should match a snapshot when appDetailModalState = VIEW_DETAIL_BROWSE 1`] = ` + Install App + + } +/> +`; + +exports[`AppDetailInner should match a snapshot when appDetailModalState = VIEW_DETAIL_MANAGE 1`] = ` + + Install App + + } /> `; diff --git a/src/components/ui/app-detail-modal/__tests__/app-detail-inner.tsx b/src/components/ui/app-detail-modal/__tests__/app-detail-inner.tsx index 00351c100a..ce56a3203c 100644 --- a/src/components/ui/app-detail-modal/__tests__/app-detail-inner.tsx +++ b/src/components/ui/app-detail-modal/__tests__/app-detail-inner.tsx @@ -5,14 +5,21 @@ import { AppDetailInner, AppDetailInnerProps } from '../app-detail-inner' import { appDetailDataStub } from '@/sagas/__stubs__/app-detail' const mockProps: AppDetailInnerProps = { - appDetailModalState: 'VIEW_DETAIL', - appDetailState: { loading: false, error: false, appDetailData: appDetailDataStub }, - setAppDetailModalStateView: jest.fn(), + appDetailModalState: 'VIEW_DETAIL_BROWSE', + appDetailData: appDetailDataStub.data, + setStateViewBrowse: jest.fn(), + setStateViewInstall: jest.fn(), + setStateViewUninstall: jest.fn(), installationsSetFormState: jest.fn() } describe('AppDetailInner', () => { - it('should match a snapshot when appDetailModalState = VIEW_DETAIL', () => { + it('should match a snapshot when appDetailModalState = VIEW_DETAIL_BROWSE', () => { + expect(shallow()).toMatchSnapshot() + }) + + it('should match a snapshot when appDetailModalState = VIEW_DETAIL_MANAGE', () => { + const props: AppDetailInnerProps = { ...mockProps, appDetailModalState: 'VIEW_DETAIL_MANAGE' } expect(shallow()).toMatchSnapshot() }) @@ -21,7 +28,7 @@ describe('AppDetailInner', () => { expect(shallow()).toMatchSnapshot() }) - it('should render AppDetail when appDetailModalState = VIEW_DETAIL', () => { + it('should render AppDetail when appDetailModalState = VIEW_DETAIL_BROWSE', () => { const wrapper = shallow() expect(wrapper.find('AppDetailWithConnect')).toHaveLength(1) }) diff --git a/src/components/ui/app-detail-modal/__tests__/app-detail-modal.tsx b/src/components/ui/app-detail-modal/__tests__/app-detail-modal.tsx index 874636fda4..81c3f9528b 100644 --- a/src/components/ui/app-detail-modal/__tests__/app-detail-modal.tsx +++ b/src/components/ui/app-detail-modal/__tests__/app-detail-modal.tsx @@ -6,7 +6,7 @@ import { AppDetailModal, AppDetailModalProps, handleAfterClose } from '../app-de const props: AppDetailModalProps = { visible: true, afterClose: jest.fn(), - setAppDetailModalStateView: jest.fn() + setAppDetailModalStateBrowse: jest.fn() } describe('AppDetailModel', () => { @@ -15,17 +15,17 @@ describe('AppDetailModel', () => { }) describe('handleAfterClose have afterClose', () => { const afterClose = jest.fn() - const setAppDetailModalStateView = jest.fn() - const fn = handleAfterClose(setAppDetailModalStateView, afterClose) + const setAppDetailModalStateBrowse = jest.fn() + const fn = handleAfterClose(setAppDetailModalStateBrowse, afterClose) fn() expect(afterClose).toBeCalled() - expect(setAppDetailModalStateView).toBeCalled() + expect(setAppDetailModalStateBrowse).toBeCalled() }) describe('handleAfterClose doesnt have afterClose', () => { const afterClose = undefined - const setAppDetailModalStateView = jest.fn() - const fn = handleAfterClose(setAppDetailModalStateView, afterClose) + const setAppDetailModalStateBrowse = jest.fn() + const fn = handleAfterClose(setAppDetailModalStateBrowse, afterClose) fn() - expect(setAppDetailModalStateView).toBeCalled() + expect(setAppDetailModalStateBrowse).toBeCalled() }) }) diff --git a/src/components/ui/app-detail-modal/app-detail-inner.tsx b/src/components/ui/app-detail-modal/app-detail-inner.tsx index 212f0031b5..e6c8b4f57a 100644 --- a/src/components/ui/app-detail-modal/app-detail-inner.tsx +++ b/src/components/ui/app-detail-modal/app-detail-inner.tsx @@ -3,31 +3,41 @@ import { connect } from 'react-redux' import { AppDetailModalState } from '@/reducers/app-detail-modal' import { ReduxState, FormState } from '@/types/core' import AppDetail from '@/components/ui/app-detail' -import { AppDetailState } from '@/reducers/app-detail' import AppInstallConfirm from '@/components/ui/app-confirm-install' import AppUninstallConfirm from '@/components/ui/app-confirm-uninstall' import CallToAction from '../call-to-action' -import { ModalBody } from '@reapit/elements' -import { setAppDetailModalStateView } from '@/actions/app-detail-modal' +import { ModalBody, Button } from '@reapit/elements' +import { + setAppDetailModalStateBrowse, + setAppDetailModalStateUninstall, + setAppDetailModalStateInstall +} from '@/actions/app-detail-modal' import { appInstallationsSetFormState } from '@/actions/app-installations' +import { AppDetailModel } from '@/types/marketplace-api-schema' +import styles from '@/styles/blocks/app-detail.scss?mod' +import { FaCheck } from 'react-icons/fa' export interface AppDetailInnerMappedProps { appDetailModalState: AppDetailModalState - appDetailState: AppDetailState + appDetailData: AppDetailModel } export interface AppDetailInnerMappedActions { - setAppDetailModalStateView: () => void + setStateViewBrowse: () => void + setStateViewInstall: () => void + setStateViewUninstall: () => void installationsSetFormState: (formState: FormState) => void } export const mapStateToProps = (state: ReduxState): AppDetailInnerMappedProps => ({ appDetailModalState: state.appDetailModal, - appDetailState: state.appDetail + appDetailData: state.appDetail.appDetailData?.data! }) export const mapDispatchToProps = (dispatch: any): AppDetailInnerMappedActions => ({ - setAppDetailModalStateView: () => dispatch(setAppDetailModalStateView()), + setStateViewBrowse: () => dispatch(setAppDetailModalStateBrowse()), + setStateViewInstall: () => dispatch(setAppDetailModalStateInstall()), + setStateViewUninstall: () => dispatch(setAppDetailModalStateUninstall()), installationsSetFormState: (formState: FormState) => dispatch(appInstallationsSetFormState(formState)) }) @@ -37,27 +47,63 @@ export type AppDetailInnerProps = AppDetailInnerMappedProps & } export const handleCloseModal = ( - setAppDetailModalStateView: () => void, + setAppDetailModalStateBrowse: () => void, afterClose?: () => void, installationsSetFormState?: (formState: FormState) => void ) => () => { afterClose && afterClose() installationsSetFormState && installationsSetFormState('DONE') - setAppDetailModalStateView() + setAppDetailModalStateBrowse() +} + +export const renderFooterAppDetailBrowse = ({ appDetailData, setStateViewInstall }) => { + return appDetailData.installedOn ? ( +
    + + Installed +
    + ) : ( + + ) +} + +export const renderFooterAppDetailManage = ({ setStateViewUninstall }) => { + return ( + + ) } export const AppDetailInner: React.FunctionComponent = ({ appDetailModalState, - appDetailState, + appDetailData, afterClose, - setAppDetailModalStateView, + setStateViewBrowse, + setStateViewInstall, + setStateViewUninstall, installationsSetFormState }) => { - if (appDetailModalState === 'VIEW_DETAIL') { - if (!appDetailState.appDetailData || !appDetailState.appDetailData.data) { - return null - } - return void} /> + if (appDetailModalState === 'VIEW_DETAIL_BROWSE') { + return ( + + ) + } + + if (appDetailModalState === 'VIEW_DETAIL_MANAGE') { + return ( + + ) } if (appDetailModalState === 'VIEW_CONFIRM_INSTALL') { @@ -69,8 +115,8 @@ export const AppDetailInner: React.FunctionComponent = ({ } if (appDetailModalState === 'VIEW_DETAIL_ACTION_SUCCESS') { - const appName = appDetailState?.appDetailData?.data?.name || 'App' - const isInstalled = !!appDetailState?.appDetailData?.data?.installationId + const appName = appDetailData.name || 'App' + const isInstalled = !!appDetailData.installationId return ( = ({ title="Success!" buttonText="Back to List" dataTest="alertInstalledSuccess" - onButtonClick={handleCloseModal(setAppDetailModalStateView, afterClose, installationsSetFormState)} + onButtonClick={handleCloseModal(setStateViewBrowse, afterClose, installationsSetFormState)} isCenter > {appName} has been successfully {isInstalled ? 'uninstalled' : 'installed'} diff --git a/src/components/ui/app-detail-modal/app-detail-modal.tsx b/src/components/ui/app-detail-modal/app-detail-modal.tsx index d2f3f95085..45598fb6bb 100644 --- a/src/components/ui/app-detail-modal/app-detail-modal.tsx +++ b/src/components/ui/app-detail-modal/app-detail-modal.tsx @@ -1,34 +1,34 @@ import * as React from 'react' import { Modal, ModalProps } from '@reapit/elements' import { connect } from 'react-redux' -import { setAppDetailModalStateView } from '@/actions/app-detail-modal' +import { setAppDetailModalStateBrowse } from '@/actions/app-detail-modal' import AppDetailInner from './app-detail-inner' import AppDetailAsyncContainer from './app-detail-async-container' export interface ActionDetailModalMappedAction { - setAppDetailModalStateView: () => void + setAppDetailModalStateBrowse: () => void } export type AppDetailModalProps = Pick & ActionDetailModalMappedAction const mapDispatchToProps = (dispatch: any): ActionDetailModalMappedAction => ({ - setAppDetailModalStateView: () => dispatch(setAppDetailModalStateView()) + setAppDetailModalStateBrowse: () => dispatch(setAppDetailModalStateBrowse()) }) -export const handleAfterClose = (setAppDetailModalStateView: () => void, afterClose?: () => void) => () => { +export const handleAfterClose = (setAppDetailModalStateBrowse: () => void, afterClose?: () => void) => () => { if (afterClose) { afterClose() } - setAppDetailModalStateView() + setAppDetailModalStateBrowse() } export const AppDetailModal: React.FunctionComponent = ({ visible = true, afterClose, - setAppDetailModalStateView + setAppDetailModalStateBrowse }) => { return ( - + diff --git a/src/components/ui/app-detail.tsx b/src/components/ui/app-detail.tsx index e19e7782f8..d398dfb8c0 100644 --- a/src/components/ui/app-detail.tsx +++ b/src/components/ui/app-detail.tsx @@ -5,7 +5,7 @@ import carouselStyles from '../../styles/elements/carousel.scss?mod' import ChevronLeftIcon from '@/components/svg/chevron-left' import '@/styles/vendor/slick.scss' import { connect } from 'react-redux' -import { setAppDetailModalStateViewConfirm, setAppDetailModalStateUninstall } from '@/actions/app-detail-modal' +import { setAppDetailModalStateInstall, setAppDetailModalStateUninstall } from '@/actions/app-detail-modal' import { AppDetailModel } from '@/types/marketplace-api-schema' import { Button, Tile, ModalHeader, ModalBody, ModalFooter, H6 } from '@reapit/elements' import { setDeveloperAppModalStateDelete } from '@/actions/developer-app-modal' @@ -14,7 +14,7 @@ import { FaCheck, FaTimes } from 'react-icons/fa' export interface AppDetailModalInnerProps { data: AppDetailModel - afterClose: () => void + afterClose?: () => void footerItems?: React.ReactNode } @@ -24,20 +24,20 @@ export interface AppDetailModalMappedProps { } export interface AppDetailModalMappedActions { - setAppDetailModalStateViewConfirm: () => void + setAppDetailModalStateInstall: () => void setAppDetailModalStateUninstall: () => void setDeveloperAppModalStateDelete: () => void } export type AppDetailProps = AppDetailModalMappedActions & AppDetailModalMappedProps & AppDetailModalInnerProps -export const SlickButtonNav = ({ currentSlide, setAppDetailModalStateViewConfirm, slideCount, children, ...props }) => ( +export const SlickButtonNav = ({ currentSlide, setAppDetailModalStateInstall, slideCount, children, ...props }) => ( ) export const AppDetail: React.FunctionComponent = ({ data, - setAppDetailModalStateViewConfirm, + setAppDetailModalStateInstall, setAppDetailModalStateUninstall, isCurrentLoggedUserClient, isCurrentLoggedUserDeveloper, @@ -72,7 +72,7 @@ export const AppDetail: React.FunctionComponent = ({ return ( <> - + void} /> @@ -88,8 +88,8 @@ export const AppDetail: React.FunctionComponent = ({ } > {isCurrentLoggedUserDeveloper && ( -
    - {isListed ? : } +
    + {isListed ? : } {isListed ? 'Is listed' : 'Not listed'}
    )} @@ -128,37 +128,7 @@ export const AppDetail: React.FunctionComponent = ({ } /> - - Uninstall App - - ) : ( - - )) - } - /> + ) } @@ -171,7 +141,7 @@ export const mapStateToProps = (state: ReduxState): AppDetailModalMappedProps => } export const mapDispatchToProps = (dispatch: any): AppDetailModalMappedActions => ({ - setAppDetailModalStateViewConfirm: () => dispatch(setAppDetailModalStateViewConfirm()), + setAppDetailModalStateInstall: () => dispatch(setAppDetailModalStateInstall()), setAppDetailModalStateUninstall: () => dispatch(setAppDetailModalStateUninstall()), setDeveloperAppModalStateDelete: () => dispatch(setDeveloperAppModalStateDelete()) }) diff --git a/src/constants/action-types.ts b/src/constants/action-types.ts index be4a6dfe65..1fa6ce25e2 100644 --- a/src/constants/action-types.ts +++ b/src/constants/action-types.ts @@ -121,8 +121,9 @@ const ActionTypes = { REVISION_DECLINE_SET_FORM_STATE: 'REVISION_DECLINE_SET_FORM_STATE', // App detail modal - SET_APP_DETAIL_MODAL_STATE_VIEW: 'SET_APP_DETAIL_MODAL_STATE_VIEW', - SET_APP_DETAIL_MODAL_STATE_CONFIRM: 'SET_APP_DETAIL_MODAL_STATE_CONFIRM', + SET_APP_DETAIL_MODAL_STATE_BROWSE: 'SET_APP_DETAIL_MODAL_STATE_BROWSE', + SET_APP_DETAIL_MODAL_STATE_MANAGE: 'SET_APP_DETAIL_MODAL_STATE_MANAGE', + SET_APP_DETAIL_MODAL_STATE_INSTALL: 'SET_APP_DETAIL_MODAL_STATE_INSTALL', SET_APP_DETAIL_MODAL_STATE_UNINSTALL: 'SET_APP_DETAIL_MODAL_STATE_UNINSTALL', SET_APP_DETAIL_MODAL_STATE_SUCCESS: 'SET_APP_DETAIL_MODAL_STATE_SUCCESS', diff --git a/src/reducers/__tests__/app-detail-modal.ts b/src/reducers/__tests__/app-detail-modal.ts index b19b88b879..5a10f3437a 100644 --- a/src/reducers/__tests__/app-detail-modal.ts +++ b/src/reducers/__tests__/app-detail-modal.ts @@ -9,18 +9,18 @@ describe('app-detail modal reducer', () => { expect(newState).toEqual(defaultState) }) - it('should set loading to true when SET_APP_DETAIL_MODAL_STATE_VIEW action is called', () => { + it('should set loading to true when SET_APP_DETAIL_MODAL_STATE_BROWSE action is called', () => { const newState = appDetailModalReducer(undefined, { - type: ActionTypes.SET_APP_DETAIL_MODAL_STATE_VIEW as ActionType, + type: ActionTypes.SET_APP_DETAIL_MODAL_STATE_BROWSE as ActionType, data: true }) - const expected = 'VIEW_DETAIL' + const expected = 'VIEW_DETAIL_BROWSE' expect(newState).toEqual(expected) }) - it('should set app-detail item data when SET_APP_DETAIL_MODAL_STATE_CONFIRM action is called', () => { + it('should set app-detail item data when SET_APP_DETAIL_MODAL_STATE_INSTALL action is called', () => { const newState = appDetailModalReducer(undefined, { - type: ActionTypes.SET_APP_DETAIL_MODAL_STATE_CONFIRM as ActionType, + type: ActionTypes.SET_APP_DETAIL_MODAL_STATE_INSTALL as ActionType, data: appDetailDataStub }) const expected = 'VIEW_CONFIRM_INSTALL' diff --git a/src/reducers/app-detail-modal.ts b/src/reducers/app-detail-modal.ts index 13a15de627..2ca1ca2071 100644 --- a/src/reducers/app-detail-modal.ts +++ b/src/reducers/app-detail-modal.ts @@ -1,26 +1,32 @@ import { Action } from '../types/core' import { isType } from '../utils/actions' import { - setAppDetailModalStateView, - setAppDetailModalStateViewConfirm, + setAppDetailModalStateBrowse, + setAppDetailModalStateInstall, setAppDetailModalStateSuccess, - setAppDetailModalStateUninstall + setAppDetailModalStateUninstall, + setAppDetailModalStateManage } from '../actions/app-detail-modal' export type AppDetailModalState = - | 'VIEW_DETAIL' + | 'VIEW_DETAIL_BROWSE' + | 'VIEW_DETAIL_MANAGE' | 'VIEW_CONFIRM_INSTALL' | 'VIEW_CONFIRM_UNINSTALL' | 'VIEW_DETAIL_ACTION_SUCCESS' -export const defaultState: AppDetailModalState = 'VIEW_DETAIL' +export const defaultState: AppDetailModalState = 'VIEW_DETAIL_BROWSE' const appDetailModalReducer = (state: AppDetailModalState = defaultState, action: Action): AppDetailModalState => { - if (isType(action, setAppDetailModalStateView)) { - return 'VIEW_DETAIL' + if (isType(action, setAppDetailModalStateBrowse)) { + return 'VIEW_DETAIL_BROWSE' } - if (isType(action, setAppDetailModalStateViewConfirm)) { + if (isType(action, setAppDetailModalStateManage)) { + return 'VIEW_DETAIL_MANAGE' + } + + if (isType(action, setAppDetailModalStateInstall)) { return 'VIEW_CONFIRM_INSTALL' } diff --git a/src/styles/blocks/app-detail.scss b/src/styles/blocks/app-detail.scss index 9294fc2386..70cddd5804 100644 --- a/src/styles/blocks/app-detail.scss +++ b/src/styles/blocks/app-detail.scss @@ -13,3 +13,31 @@ max-height: unset; } } + +.installed { + width: 100%; + display: flex; + justify-content: flex-end; + align-items: center; + span { + font-size: 1.25rem; + font-weight: 600; + margin-left: 0.75rem; + } + svg { + color: #a0c862; + } +} + +.listed { + display: flex; + align-items: center; +} + +.isListed { + color: #a0c862; +} + +.notListed { + color: #d3033d; +}