apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx
import type { ISetting } from '@rocket.chat/apps-engine/definition/settings';
import type { App } from '@rocket.chat/core-typings';
import { Button, ButtonGroup, Box } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useRouteParameter, useToastMessageDispatch, usePermission, useRouter } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useMemo, useCallback } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { AppClientOrchestratorInstance } from '../../../apps/orchestrator';
import { Page, PageFooter, PageHeader, PageScrollableContentWithShadow } from '../../../components/Page';
import { handleAPIError } from '../helpers/handleAPIError';
import { useAppInfo } from '../hooks/useAppInfo';
import AppDetailsPageHeader from './AppDetailsPageHeader';
import AppDetailsPageLoading from './AppDetailsPageLoading';
import AppDetailsPageTabs from './AppDetailsPageTabs';
import AppDetails from './tabs/AppDetails';
import AppLogs from './tabs/AppLogs';
import AppReleases from './tabs/AppReleases';
import AppRequests from './tabs/AppRequests/AppRequests';
import AppSecurity from './tabs/AppSecurity/AppSecurity';
import AppSettings from './tabs/AppSettings';
const AppDetailsPage = ({ id }: { id: App['id'] }): ReactElement => {
const t = useTranslation();
const router = useRouter();
const dispatchToastMessage = useToastMessageDispatch();
const isAdminUser = usePermission('manage-apps');
const tab = useRouteParameter('tab');
const context = useRouteParameter('context');
const appData = useAppInfo(id, context || '');
const handleReturn = useMutableCallback((): void => {
if (!context) {
return;
}
router.navigate({
name: 'marketplace',
params: { context, page: 'list' },
});
});
const { installed, settings, privacyPolicySummary, permissions, tosLink, privacyLink, name } = appData || {};
const isSecurityVisible = Boolean(privacyPolicySummary || permissions || tosLink || privacyLink);
const saveAppSettings = useCallback(
async (data) => {
try {
await AppClientOrchestratorInstance.setAppSettings(
id,
(Object.values(settings || {}) as ISetting[]).map((setting) => ({
...setting,
value: data[setting.id],
})),
);
dispatchToastMessage({ type: 'success', message: `${name} settings saved succesfully` });
} catch (e: any) {
handleAPIError(e);
}
},
[dispatchToastMessage, id, name, settings],
);
const reducedSettings = useMemo(() => {
return Object.values(settings || {}).reduce((ret, { id, value, packageValue }) => ({ ...ret, [id]: value ?? packageValue }), {});
}, [settings]);
const methods = useForm({ values: reducedSettings });
const {
handleSubmit,
reset,
formState: { isDirty, isSubmitting, isSubmitted },
} = methods;
return (
<Page flexDirection='column' h='full'>
<PageHeader title={t('App_Info')} onClickBack={handleReturn} />
<PageScrollableContentWithShadow pi={24} pbs={24} pbe={0} h='full'>
<Box w='full' alignSelf='center' h='full' display='flex' flexDirection='column'>
{!appData && <AppDetailsPageLoading />}
{appData && (
<>
<AppDetailsPageHeader app={appData} />
<AppDetailsPageTabs
context={context || ''}
installed={installed}
isSecurityVisible={isSecurityVisible}
settings={settings}
tab={tab}
/>
{Boolean(!tab || tab === 'details') && <AppDetails app={appData} />}
{tab === 'requests' && <AppRequests id={id} isAdminUser={isAdminUser} />}
{tab === 'security' && isSecurityVisible && (
<AppSecurity
privacyPolicySummary={privacyPolicySummary}
appPermissions={permissions}
tosLink={tosLink}
privacyLink={privacyLink}
/>
)}
{tab === 'releases' && <AppReleases id={id} />}
{Boolean(tab === 'settings' && settings && Object.values(settings).length) && (
<FormProvider {...methods}>
<AppSettings settings={settings || {}} />
</FormProvider>
)}
{tab === 'logs' && <AppLogs id={id} />}
</>
)}
</Box>
</PageScrollableContentWithShadow>
<PageFooter isDirty={isDirty}>
<ButtonGroup>
<Button onClick={() => reset()}>{t('Cancel')}</Button>
{installed && isAdminUser && (
<Button primary loading={isSubmitting || isSubmitted} onClick={handleSubmit(saveAppSettings)}>
{t('Save_changes')}
</Button>
)}
</ButtonGroup>
</PageFooter>
</Page>
);
};
export default AppDetailsPage;