Skip to content

Commit

Permalink
feat: #1125: Update standalone app detail layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nphivu414 committed May 5, 2020
1 parent 4ec9e74 commit 406b6c0
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 21 deletions.
76 changes: 59 additions & 17 deletions packages/marketplace/src/components/pages/developer-app-detail.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,66 @@
import * as React from 'react'
import { Grid, GridItem } from '@reapit/elements'
import { Dispatch } from 'redux'
import { ReduxState } from '@/types/core'
import { useDispatch, useSelector } from 'react-redux'
import { RouteComponentProps } from 'react-router'
import { appDetailRequestData } from '@/actions/app-detail'
import { selectAppDetailData, selectAppDetailLoading } from '@/selector/developer-app-detail'
import { selectLoginType } from '@/selector/auth'
import { LoginType } from '@reapit/cognito-auth'
import { AppDetailModel } from '@reapit/foundations-ts-definitions'
import AppHeader from '../ui/developer-app-detail/app-header'
import AppContent from '../ui/developer-app-detail/app-content'
import { Loader } from '@reapit/elements'
import styles from '@/styles/pages/developer-app-detail.scss?mod'

type DeveloperAppDetailProps = {}
export type DeveloperAppDetailProps = {} & RouteComponentProps<{ id: string }>
export type MapState = {
appDetailData: AppDetailModel & {
apiKey?: string | undefined
}
isLoadingAppDetail: boolean
loginType: LoginType
}

export const mapState = (state: ReduxState): MapState => {
return {
appDetailData: selectAppDetailData(state),
isLoadingAppDetail: selectAppDetailLoading(state),
loginType: selectLoginType(state),
}
}

export const fetchDeveloperAppDetail = (dispatch: Dispatch<any>, appId: string) => {
return () => {
dispatch(
appDetailRequestData({
id: appId,
}),
)
}
}

const DeveloperAppDetail: React.FC<DeveloperAppDetailProps> = props => {
const dispatch = useDispatch()

const {
match: { params },
} = props
const { id: appId } = params
React.useEffect(fetchDeveloperAppDetail(dispatch, appId), [dispatch, appId])
const { appDetailData, isLoadingAppDetail, loginType } = useSelector(mapState)
const { developer, media, name } = appDetailData
const appIcon = media?.filter(({ type }) => type === 'icon')[0]

if (isLoadingAppDetail) {
return <Loader />
}

const DeveloperAppDetail: React.FC<DeveloperAppDetailProps> = () => {
return (
<Grid>
<GridItem>
<Grid>
<GridItem>Avatar</GridItem>
<GridItem>Title</GridItem>
</Grid>
</GridItem>
<GridItem>
<Grid>
<GridItem>SideBar</GridItem>
<GridItem>Description</GridItem>
</Grid>
</GridItem>
</Grid>
<div className={styles.appDetailContainer}>
<AppHeader appIcon={appIcon} appName={name} developer={developer} />
<AppContent appDetailData={appDetailData} loginType={loginType} />
</div>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { httpTrafficPerDayRequestData } from '@/actions/app-http-traffic-event'
import { appInstallationsRequestData, appInstallationsFilterRequestData } from '@/actions/app-installations'
import { InstallationModel, AppSummaryModel } from '@reapit/foundations-ts-definitions'
import { getAppUsageStats, getAppHttpTraffic } from '@/selector/analytics'
import { getDevelopers } from '@/selector/developer'
import { selectDeveloper } from '@/selector/developer'
import { getInstallations } from '@/selector/installations'

import { FlexContainerBasic, Grid, GridItem, FlexContainerResponsive, DATE_TIME_FORMAT } from '@reapit/elements'
Expand Down Expand Up @@ -120,7 +120,7 @@ export type MapState = {
export const mapState = (state: ReduxState): MapState => {
return {
installations: getInstallations(state),
developer: getDevelopers(state),
developer: selectDeveloper(state),
appUsageStats: getAppUsageStats(state),
appHttpTraffic: getAppHttpTraffic(state),
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import * as React from 'react'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import Slider, { Settings } from 'react-slick'
import ChevronLeftIcon from '@/components/svg/chevron-left'
import { FaCheck, FaTimes, FaCopy } from 'react-icons/fa'
import { Grid, GridItem, SubTitleH6, H6, GridThreeColItem } from '@reapit/elements'
import { AppDetailModel } from '@reapit/foundations-ts-definitions'
import AuthFlow from '@/constants/app-auth-flow'
import AppAuthenticationDetail from '../../app-authentication-detail'
import styles from '@/styles/blocks/app-detail.scss?mod'
import carouselStyles from '@/styles/elements/carousel.scss?mod'

type AppContentProps = {
appDetailData: AppDetailModel & {
apiKey?: string | undefined
}
loginType: string
}

export const SlickButtonNav = ({ children, ...props }) => <button {...props}>{children}</button>

export type HandleShowApiKey = {
setIsShowApikey: React.Dispatch<React.SetStateAction<boolean>>
isShowApiKey: boolean
}

export const handleShowApiKey = ({ setIsShowApikey, isShowApiKey }: HandleShowApiKey) => () => {
setIsShowApikey(!isShowApiKey)
}

export const handleCopyAlert = () => alert('Copied')

export type RenderShowApiKeyForWebComponent = HandleShowApiKey & {
isWebComponent?: boolean
isCurrentLoggedUserDeveloper: boolean
apiKey?: string
}

export const renderShowApiKeyForWebComponent = ({
isWebComponent,
setIsShowApikey,
isShowApiKey,
apiKey,
isCurrentLoggedUserDeveloper,
}: RenderShowApiKeyForWebComponent) => {
const isShow = isWebComponent && !isCurrentLoggedUserDeveloper
if (!isShow) {
return null
}
return (
<>
<SubTitleH6 className="mb-0">API key</SubTitleH6>
<p>
To obtain your unique API key, click on &apos;Show API Key&apos; below. For installation instructions, please
click here
</p>
<span>Authentication:</span>&nbsp;
<span>
<a onClick={handleShowApiKey({ setIsShowApikey, isShowApiKey })}>
<u>{!isShowApiKey ? 'Show' : 'Hide'} API key</u>
</a>
</span>
{isShowApiKey && (
<CopyToClipboard text={apiKey} onCopy={handleCopyAlert}>
<div className="control has-icons-right">
<input className="input is-primary" id="apiKey" type="text" name="apiKey" value={apiKey || ''} readOnly />
<span className="icon is-right">
<FaCopy />
</span>
</div>
</CopyToClipboard>
)}
</>
)
}

const AppContent: React.FC<AppContentProps> = ({ appDetailData, loginType }) => {
const {
externalId,
developer,
isListed,
id,
authFlow,
isWebComponent,
apiKey,
media = [],
scopes = [],
description,
installedOn,
} = appDetailData
const [isShowApiKey, setIsShowApikey] = React.useState<boolean>(false)

const isCurrentLoggedUserClient = loginType === 'CLIENT'
const isCurrentLoggedUserDeveloper = loginType === 'DEVELOPER'
const carouselImages = media.filter(({ type }) => type === 'image')
const settings: Settings = {
dots: false,
speed: 500,
variableWidth: true,
prevArrow: (
// @ts-ignore
<SlickButtonNav>
<ChevronLeftIcon />
</SlickButtonNav>
),
nextArrow: (
// @ts-ignore
<SlickButtonNav>
<ChevronLeftIcon />
</SlickButtonNav>
),
}

return (
<Grid>
<GridItem className="is-3">
<div className={styles.listed}>
<H6>{developer}</H6>
</div>
{isCurrentLoggedUserDeveloper && (
<>
<p className={styles.appInfo}>App Information</p>
<div key="app-id" className={styles.appInfoRow}>
<p className={styles.appInfoProperty}>Client ID:</p>
<p>{externalId}</p>
</div>
<div key="app-listed" className={styles.appInfoRow}>
<p className={styles.appInfoProperty}>Status:</p>
<p className={styles.appInfoSpace}>{isListed ? 'Listed' : 'Not listed'}</p>
{isListed ? <FaCheck className={styles.isListed} /> : <FaTimes className={styles.notListed} />}
</div>
{authFlow === AuthFlow.CLIENT_SECRET && id && <AppAuthenticationDetail appId={id} />}
</>
)}
{renderShowApiKeyForWebComponent({
isWebComponent,
isShowApiKey,
setIsShowApikey,
apiKey: apiKey,
isCurrentLoggedUserDeveloper,
})}
</GridItem>
<GridItem className="is-9">
{carouselImages.length > 0 && (
<div className={carouselStyles.container}>
<Slider {...settings}>
{carouselImages.map(({ uri }, index) => (
<div key={index} className={carouselStyles.slide}>
<img src={uri} />
</div>
))}
</Slider>
</div>
)}
<br />
<p>{description}</p>
<br />

<H6>
{isCurrentLoggedUserDeveloper && 'Permissions requested'}
{isCurrentLoggedUserClient && (installedOn ? 'Permissions granted' : 'Permissions required')}
</H6>
<Grid isMultiLine>
{scopes.map(item => (
<GridThreeColItem key={item.name}>
<li>{item.description}</li>
</GridThreeColItem>
))}
</Grid>
</GridItem>
</Grid>
)
}

export default AppContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import AppContent from './app-content'
export default AppContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react'
import { Grid, GridItem, H3 } from '@reapit/elements'
import { MediaModel } from '@reapit/foundations-ts-definitions'
import styles from '@/styles/pages/developer-app-detail.scss?mod'

type AppHeaderProps = {
appIcon?: MediaModel
appName?: string
developer?: string
}

const AppHeader: React.FC<AppHeaderProps> = ({ appIcon, appName, developer }) => {
return (
<Grid className="is-vcentered ">
<GridItem className="is-3">
<div className={styles.appIconContainer}>
<img
className="image"
src={(appIcon && appIcon.uri) || 'https://bulma.io/images/placeholders/48x48.png'}
alt={name}
/>
</div>
</GridItem>
<GridItem className="is-9">
<H3 className={styles.appName}>{appName}</H3>
<p>{developer}</p>
</GridItem>
</Grid>
)
}

export default AppHeader
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import AppHeader from './app-header'
export default AppHeader
2 changes: 1 addition & 1 deletion packages/marketplace/src/core/private-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const PrivateRoute = ({
}
const Component = component

return <Component />
return <Component {...props} />
}}
/>
)
Expand Down
13 changes: 13 additions & 0 deletions packages/marketplace/src/selector/developer-app-detail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ReduxState } from '@/types/core'

export const selectAppDetailData = (state: ReduxState) => {
return state.appDetail.appDetailData?.data || {}
}

export const selectAppDetailAuthentication = (state: ReduxState) => {
return state.appDetail.authentication
}

export const selectAppDetailLoading = (state: ReduxState) => {
return state.appDetail.loading
}
2 changes: 1 addition & 1 deletion packages/marketplace/src/selector/developer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export const selectDeveloperEmail = (state: ReduxState) => {
return state?.settings?.developerInfomation?.email
}

export const getDevelopers = (state: ReduxState) => {
export const selectDeveloper = (state: ReduxState) => {
return state.developer
}
18 changes: 18 additions & 0 deletions packages/marketplace/src/styles/pages/developer-app-detail.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@import '../base/colors.scss';

.appDetailContainer {
width: 100%;
max-width: 1012px;
margin: 0 auto;
padding: 0 16px;
}
.appIconContainer {
width: 128px;
height: 128px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
border-radius: 50%;
margin: 0 auto;
}

0 comments on commit 406b6c0

Please sign in to comment.