diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetails.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetails.tsx index 7935ff3ddaadce7e76f4170fac1f98a1340e6c96..efdfbf46f64a7e68b90d230855b4ea69edba6b3f 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetails.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetails.tsx @@ -8,6 +8,7 @@ import React from 'react'; import ScreenshotCarouselAnchor from '../../../components/ScreenshotCarouselAnchor'; import type { AppInfo } from '../../../definitions/AppInfo'; import AppDetailsAPIs from './AppDetailsAPIs'; +import { normalizeUrl } from './normalizeUrl'; const AppDetails = ({ app }: { app: AppInfo }): ReactElement => { const t = useTranslation(); @@ -18,12 +19,15 @@ const AppDetails = ({ app }: { app: AppInfo }): ReactElement => { categories = [], screenshots, apis, - documentationUrl, + documentationUrl: documentation, } = app; const isMarkdown = detailedDescription && Object.keys(detailedDescription).length !== 0 && detailedDescription.rendered; const isCarouselVisible = screenshots && Boolean(screenshots.length); - const normalizeDocumentationUrl = documentationUrl?.startsWith('http') ? documentationUrl : `https://${documentationUrl}`; + + const normalizedHomepageUrl = normalizeUrl(homepage); + const normalizedSupportUrl = normalizeUrl(support); + const normalizedDocumentationUrl = normalizeUrl(documentation); return ( <Box maxWidth='x640' w='full' marginInline='auto' color='default'> @@ -76,24 +80,21 @@ const AppDetails = ({ app }: { app: AppInfo }): ReactElement => { <Box fontScale='h4' color='hint'> {t('Author_Site')} </Box> - <ExternalLink to={homepage} /> + {normalizedHomepageUrl ? <ExternalLink to={normalizedHomepageUrl}>{homepage}</ExternalLink> : homepage} </Box> <Box display='flex' flexDirection='column' flexGrow={1}> <Box fontScale='h4' color='hint'> {t('Support')} </Box> - <ExternalLink to={support} /> + {normalizedSupportUrl ? <ExternalLink to={normalizedSupportUrl}>{support}</ExternalLink> : support} </Box> </Box> - - {documentationUrl && ( - <> - <Box fontScale='h4' color='hint'> - {t('Documentation')} - </Box> - <ExternalLink to={normalizeDocumentationUrl} /> - </> - )} + <> + <Box fontScale='h4' color='hint'> + {t('Documentation')} + </Box> + {normalizedDocumentationUrl ? <ExternalLink to={normalizedDocumentationUrl}>{documentation}</ExternalLink> : documentation} + </> </Box> {apis?.length ? ( diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.spec.ts b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..45b2863a79d92f137a1f4c7a41606916d9d0d57d --- /dev/null +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.spec.ts @@ -0,0 +1,13 @@ +import { it } from '@jest/globals'; + +import { normalizeUrl } from './normalizeUrl'; + +it.each([ + ['https://rocket.chat', 'https://rocket.chat'], + ['//rocket.chat', 'https://rocket.chat'], + ['rocket.chat', 'https://rocket.chat'], + ['rocketchat@rocket.chat', 'mailto:rocketchat@rocket.chat'], + ['plain_text', undefined], +])('should normalize %o as %o', (input, output) => { + expect(normalizeUrl(input)).toBe(output); +}); diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.tsx new file mode 100644 index 0000000000000000000000000000000000000000..69c12fa20d50f45a6c432f2882c83a08512928c3 --- /dev/null +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/normalizeUrl.tsx @@ -0,0 +1,25 @@ +import { parse } from '@rocket.chat/message-parser'; + +export const normalizeUrl = (url: string): string | undefined => { + if (url.startsWith('http')) { + return url; + } + + if (url.startsWith('//')) { + return `https:${url}`; + } + + const parsedUrl = parse(url); + + if (parsedUrl[0].type === 'PARAGRAPH') { + if (parsedUrl[0].value[0].type === 'LINK') { + if (parsedUrl[0].value[0].value.src.value.startsWith('//')) { + return `https:${parsedUrl[0].value[0].value.src.value}`; + } + + return parsedUrl[0].value[0].value.src.value; + } + } + + return undefined; +}; diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx index 78cbe7dbf4e359182fd7764956599325a72933a9..1487eb144ea4ad69133eeb2118eea8f086ef7f55 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx @@ -1,6 +1,5 @@ import type { AppPricingPlan, PurchaseType } from '@rocket.chat/core-typings'; -import { Box, Tag } from '@rocket.chat/fuselage'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import { Box, Margins, Tag } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo } from 'react'; @@ -24,10 +23,12 @@ const AppStatusPriceDisplay: FC<AppStatusPriceDisplayProps> = ({ purchaseType, p ); return ( - <Tag> - {showType && <Box color='default'>{t(type as TranslationKey)}</Box>} - <Box>{!showType && type === 'Free' ? t(type) : formattedPrice}</Box> - </Tag> + <Margins inline={4}> + <Tag> + {showType && <Box color='default'>{t.has(type) ? t(type) : type}</Box>} + <Box>{!showType && type === 'Free' ? t(type) : formattedPrice}</Box> + </Tag> + </Margins> ); };