Skip to content

Commit

Permalink
Wip 4
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriellsh committed May 26, 2020
1 parent 7fc4af0 commit f2af7ec
Show file tree
Hide file tree
Showing 15 changed files with 912 additions and 89 deletions.
117 changes: 117 additions & 0 deletions client/admin/apps/AppDetailsPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState, useCallback } from 'react';
import { Button, ButtonGroup, Icon, Avatar, Box, Divider, Chip, Margins } from '@rocket.chat/fuselage';

import Page from '../../components/basic/Page';
import { useRoute } from '../../contexts/RouterContext';
import { useMethod } from '../../contexts/ServerContext';
import PriceDisplay from './PriceDisplay';
import { AppStatus } from './AppStatus';
import { useTranslation } from '../../contexts/TranslationContext';
import { useAppInfo } from './hooks/useAppInfo';
import { AppMenu } from './AppMenu';

const objectFit = { objectFit: 'contain' };

export default function AppDetailsPage({ id }) {
const t = useTranslation();
const data = useAppInfo(id);
const [modal, setModal] = useState(null);

const router = useRoute('admin-apps');
const handleReturn = useCallback(() => router.push({}));

const {
iconFileData = '',
name,
author: { name: authorName, homepage, support } = {},
description,
categories = [],
version,
price,
purchaseType,
pricingPlans,
iconFileContent,
installed,
bundledIn,
} = data;

const getLoggedInCloud = useMethod('cloud:checkUserLoggedIn');
const isLoggedIn = getLoggedInCloud();

return <><Page flexDirection='column'>
<Page.Header title={t('App_Details')}>
<ButtonGroup>
<Button primary disabled>
{t('Save_changes')}
</Button>
<Button onClick={handleReturn}>
<Icon name='back'/>
{t('Back')}
</Button>
</ButtonGroup>
</Page.Header>
<Page.ScrollableContent maxWidth='x600' w='full' alignSelf='center'>
<Box display='flex' flexDirection='row' mbe='x20' w='full'>
<Avatar style={objectFit} size='x120' mie='x20' url={iconFileContent || `data:image/png;base64,${ iconFileData }`}/>
<Box display='flex' flexDirection='column' justifyContent='space-between' flexGrow={1}>
<Box fontScale='h1' fontWeight='500'>{name}</Box>
<Box display='flex' flexDirection='row' color='hint' alignItems='center'>
<Box fontScale='p2' mie='x4'>{`${ t('By') } ${ authorName }`}</Box>
|
<Box mis= 'x4'>{`${ t('Version') } ${ version }`}</Box>
</Box>
<Box display='flex' flexDirection='row' alignItems='center' justifyContent='space-between'>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center'>
<AppStatus mie='x4' app={data} setModal={setModal} isLoggedIn={isLoggedIn}/>
{!installed && <PriceDisplay mis='x4' purchaseType={purchaseType} pricingPlans={pricingPlans} price={price} showType={false}/>}
</Box>
{installed && <AppMenu app={data} setModal={setModal} isLoggedIn={isLoggedIn} />}
</Box>
</Box>
</Box>
<Divider />
<Box display='flex' flexDirection='column'>

<Margins block='x12'>
<Box fontScale='h1' textTransform='uppercase'>{t('Categories')}</Box>
<Box display='flex' flexDirection='row'>
{categories && categories.map((current) => <Chip key={current} textTransform='uppercase' mie='x8'><Box color='hint'>{current}</Box></Chip>)}
</Box>

<Box fontScale='h1' textTransform='uppercase'>{t('Contact')}</Box>
<Box display='flex' flexDirection='row'>
<Box display='flex' flexDirection='column' mie='x12'>
<Box fontScale='s1' color='hint' textTransform='uppercase'>{t('Author_Site')}</Box>
<Box withRichContent><a href={homepage}>{homepage}</a></Box>
</Box>
<Box display='flex' flexDirection='column'>
<Box fontScale='s1' color='hint' textTransform='uppercase'>{t('Support')}</Box>
<Box withRichContent><a href={support}>{support}</a></Box>
</Box>
</Box>

<Box fontScale='h1' textTransform='uppercase'>{t('Details')}</Box>
<Box display='flex' flexDirection='row'>{description}</Box>
</Margins>

</Box>
{bundledIn && <>
<Divider />
<Box display='flex' flexDirection='column'>
<Margins block='x12'>
<Box fontScale='h1' textTransform='uppercase'>{t('Bundles')}</Box>
{bundledIn.map((bundle) => <Box key={bundle.bundleId} display='flex' flexDirection='row' alignItems='center'>
<Box width='x80' height='x80' display='flex' flexDirection='row' justifyContent='space-around' flexWrap='wrap' flexShrink={0}>
{bundle.apps.map((app) => <Avatar size='x36' url={app.latest.iconFileContent || `data:image/png;base64,${ app.latest.iconFileData }`} />)}
</Box>
<Box display='flex' flexDirection='column' mis='x12'>
<Box fontScale='p2'>{bundle.bundleName}</Box>
{bundle.apps.map((app) => <Box key={app.latest.name}>{app.latest.name},</Box>)}
</Box>
</Box>)}
</Margins>
</Box>
</>}
</Page.ScrollableContent>
</Page>{modal}</>;
}
10 changes: 10 additions & 0 deletions client/admin/apps/AppMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { Menu } from '@rocket.chat/fuselage';

import { useMenuOptions } from './hooks/useMenuOptions';

export const AppMenu = ({ app, setModal, isLoggedIn, ...props }) => {
const menuOptions = useMenuOptions({ app, setModal, isLoggedIn });

return <Menu options={menuOptions} placement='bottom left' {...props}/>;
};
87 changes: 87 additions & 0 deletions client/admin/apps/AppStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useCallback, useState } from 'react';
import { Box, Button, Icon, Throbber } from '@rocket.chat/fuselage';

import { useTranslation } from '../../contexts/TranslationContext';
import { appButtonProps, appStatusSpanProps, handleAPIError, warnStatusChange } from './helpers';
import { Apps } from '../../../app/apps/client/orchestrator';
import { IframeModal } from './IframeModal';
import { CloudLoginModal } from './CloudLoginModal';

const installApp = async ({ id, name, version }) => {
try {
const { status } = await Apps.installApp(id, version);
warnStatusChange(name, status);
} catch (error) {
handleAPIError(error);
}
};

const actions = {
purchase: installApp,
install: installApp,
update: async ({ id, name, version }) => {
try {
const { status } = await Apps.updateApp(id, version);
warnStatusChange(name, status);
} catch (error) {
handleAPIError(error);
}
},
};

export const AppStatus = React.memo(({ app, setModal, isLoggedIn, ...props }) => {
const t = useTranslation();
const [loading, setLoading] = useState();

const button = appButtonProps(app);
const status = !button && appStatusSpanProps(app);

const { id } = app;

const confirmAction = () => {
setModal(null);

actions[button.action](app).then(() => {
setLoading(false);
});
};

const cancelAction = () => {
setModal(null);
setLoading(false);
};

const openModal = async () => {
try {
const data = await Apps.buildExternalUrl(app.id, app.purchaseType, false);

setModal(() => <IframeModal url={data.url} cancel={cancelAction} confirm={confirmAction}/>);
} catch (error) {
handleAPIError(error);
}
};

const handleClick = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
if (isLoggedIn) {
setLoading(true);

button.action === 'purchase' ? openModal() : confirmAction();
return;
}
setModal(<CloudLoginModal cancel={() => setModal(null)} />);
}, [id, isLoggedIn, button && button.action]);

return <Box {...props}>
{button && <Button primary disabled={loading} onClick={handleClick}>
{loading && <Throbber />}
{!loading && button.icon && <Icon name={button.icon} />}
{!loading && t(button.label)}
</Button>}
{status && <Box color={status.label === 'Disabled' ? 'warning' : 'hint'} display='flex' alignItems='center'>
<Icon size='x20' name={status.icon} mie='x4'/>
{t(status.label)}
</Box>}
</Box>;
});
11 changes: 10 additions & 1 deletion client/admin/apps/AppsRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, { useState, useEffect } from 'react';
import { Box } from '@rocket.chat/fuselage';

import { Apps } from '../../../app/apps/client/orchestrator';
import { useRouteParameter } from '../../contexts/RouterContext';
import { usePermission } from '../../contexts/AuthorizationContext';
import { useTranslation } from '../../contexts/TranslationContext';
import NotAuthorizedPage from '../NotAuthorizedPage';
import AppDetailsPage from './AppDetailsPage';
import MarketplacePage from './MarketplacePage';

export default function AppsRoute() {
Expand All @@ -13,6 +15,10 @@ export default function AppsRoute() {
const canViewAppsAndMarketplace = usePermission('manage-apps');
const [isEnabled, setEnabled] = useState(false);

const context = useRouteParameter('context');
const id = useRouteParameter('id');
const version = useRouteParameter('version');

useEffect(() => {
(async () => setEnabled(await Apps.isEnabled()))();
}, []);
Expand All @@ -25,5 +31,8 @@ export default function AppsRoute() {
return <Box>{t('Apps_disabled')}</Box>;
}

return <MarketplacePage />;
return <>
{!context && <MarketplacePage />}
{context === 'details' && <AppDetailsPage id={id} marketplaceVersion={version}/>}
</>;
}
29 changes: 29 additions & 0 deletions client/admin/apps/CloudLoginModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

import React from 'react';
import { Icon, Button, ButtonGroup } from '@rocket.chat/fuselage';

import { useTranslation } from '../../contexts/TranslationContext';
import { Modal } from '../../components/basic/Modal';
import { useRoute } from '../../contexts/RouterContext';

export const CloudLoginModal = ({ cancel, ...props }) => {
const t = useTranslation();
const router = useRoute('cloud');

return <Modal {...props}>
<Modal.Header>
<Icon color='danger' name='info-circled' size={20}/>
<Modal.Title>{t('Apps_Marketplace_Login_Required_Title')}</Modal.Title>
<Modal.Close onClick={cancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{t('Apps_Marketplace_Login_Required_Description')}
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button ghost onClick={cancel}>{t('Cancel')}</Button>
<Button primary danger onClick={() => router.push({})}>{t('Login')}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
};
29 changes: 29 additions & 0 deletions client/admin/apps/IframeModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useEffect } from 'react';
import { Box } from '@rocket.chat/fuselage';

import { Modal } from '../../components/basic/Modal';

const iframeMsgListener = (confirm, cancel) => (e) => {
let data;
try {
data = JSON.parse(e.data);
} catch (e) {
return;
}

data.result ? confirm(data) : cancel();
};

export const IframeModal = ({ url, confirm, cancel, ...props }) => {
useEffect(() => {
const listener = iframeMsgListener(confirm, cancel);
window.addEventListener('message', listener);

return () => window.removeEventListener('message', listener);
}, []);
return <Modal height='x360' {...props}>
<Box padding='x12' w='full' h='full' flexGrow={1}>
<iframe style={{ border: 'none', height: '100%', width: '100%' }} src={url}/>
</Box>
</Modal>;
};
29 changes: 7 additions & 22 deletions client/admin/apps/MarketplacePage.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
import { Button, ButtonGroup, Icon, Tabs } from '@rocket.chat/fuselage';
import React, { useEffect, useCallback } from 'react';
import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage';
import React, { useState } from 'react';

import Page from '../../components/basic/Page';
import { Apps } from '../../../app/apps/client/orchestrator';
import { useTranslation } from '../../contexts/TranslationContext';
import { useRoute, useRouteParameter } from '../../contexts/RouterContext';
import { useRoute } from '../../contexts/RouterContext';
import { useMethod } from '../../contexts/ServerContext';
import MarketplaceTable from './MarketplaceTable';

function MarketplacePage() {
const t = useTranslation();
const [modal, setModal] = useState(null);

const cloudRouter = useRoute('cloud');

// const handleNewButtonClick = useCallback(() => {
// router.push({ context: 'new', type: 'incoming' });
// }, []);

// const context = useRouteParameter('context');
// useEffect(() => {
// if (!context) {
// router.push({ context: 'webhook-incoming' });
// }
// }, [context]);

const getLoggedInCloud = useMethod('cloud:checkUserLoggedIn');
const isLoggedInCloud = getLoggedInCloud();

useEffect(() => {
(async () => console.log(await Apps.getAppsFromMarketplace()))();
}, []);

return <Page flexDirection='column'>
return <><Page flexDirection='column'>
<Page.Header title={t('Marketplace')}>
{isLoggedInCloud && <ButtonGroup>
<Button onClick={() => { cloudRouter.push({}); }}>
Expand All @@ -40,9 +25,9 @@ function MarketplacePage() {
</ButtonGroup>}
</Page.Header>
<Page.Content>
{/* <MarketplaceTable /> */}
<MarketplaceTable setModal={setModal}/>
</Page.Content>
</Page>;
</Page>{ modal }</>;
}

export default MarketplacePage;
Loading

0 comments on commit f2af7ec

Please sign in to comment.