diff --git a/.changeset/helpers.md b/.changeset/helpers.md new file mode 100644 index 000000000000..e1f1f83a87fc --- /dev/null +++ b/.changeset/helpers.md @@ -0,0 +1,29 @@ +--- +"@rocket.chat/meteor": patch +--- + +chore: break down helpers.ts and create new files + +🔀 changed `handleAPIError` import in AppDetailsPage.tsx +🔀 changed `apiCurlGetter` import in AppDetailsAPIs.tsx +🔀 changed `formatPriceAndPurchaseType` import in AppStatusPriceDisplay.tsx + +❌ deleted `apiCurlGetter, handleInstallError, handleAPIError, warnAppInstall, warnEnableDisableApp, warnStatusChange, formatPriceAndPurchaseType` and moved them to new files, from helpers.ts + +✅ created apiCurlGetter.ts file +✅ created appErroredStatuses.ts file +✅ created formatPrice.ts file +✅ created formatPriceAndPurchaseType.ts file +✅ created formatPricingPlan.ts file +✅ created handleAPIError.ts file +✅ created handleInstallError.ts file +✅ created installApp.ts file +✅ created updateApp.ts file +✅ created warnAppInstal.ts file +✅ created warnEnableDisableApp.ts file +✅ created warnStatusChange.ts file + +🔀 changed `handleAPIError` import in useAppInstallationHandler.tsx +🔀 changed `handleAPIError` import in useCategories.ts +🔀 changed `handleAPIError` import in useOpenIncompatibleModal.tsx + diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx index d1688b22c6ab..83374c8f981a 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx @@ -16,7 +16,7 @@ import React, { useState, useCallback, useRef } from 'react'; import type { ISettings } from '../../../../ee/client/apps/@types/IOrchestrator'; import { Apps } from '../../../../ee/client/apps/orchestrator'; import Page from '../../../components/Page'; -import { handleAPIError } from '../helpers'; +import { handleAPIError } from '../helpers/handleAPIError'; import { useAppInfo } from '../hooks/useAppInfo'; import AppDetailsPageHeader from './AppDetailsPageHeader'; import AppDetailsPageLoading from './AppDetailsPageLoading'; diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetailsAPIs.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetailsAPIs.tsx index ebeffff5230e..ef5199d15fe5 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetailsAPIs.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppDetails/AppDetailsAPIs.tsx @@ -4,7 +4,7 @@ import { useAbsoluteUrl, useTranslation } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { Fragment } from 'react'; -import { apiCurlGetter } from '../../../helpers'; +import { apiCurlGetter } from '../../../helpers/apiCurlGetter'; type AppDetailsAPIsProps = { apis: IApiEndpointMetadata[]; 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 38d326f8cacb..78cbe7dbf4e3 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppStatus/AppStatusPriceDisplay.tsx @@ -5,7 +5,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo } from 'react'; -import { formatPriceAndPurchaseType } from '../../../helpers'; +import { formatPriceAndPurchaseType } from '../../../helpers/formatPriceAndPurchaseType'; type AppStatusPriceDisplayProps = { purchaseType: PurchaseType; diff --git a/apps/meteor/client/views/marketplace/helpers.ts b/apps/meteor/client/views/marketplace/helpers.ts index 8e20f4cd3a37..d06a1cde0cfc 100644 --- a/apps/meteor/client/views/marketplace/helpers.ts +++ b/apps/meteor/client/views/marketplace/helpers.ts @@ -1,33 +1,13 @@ import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; -import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; -import type { App, AppPricingPlan, PurchaseType } from '@rocket.chat/core-typings'; +import type { App } from '@rocket.chat/core-typings'; import semver from 'semver'; +// import { t } from '../../../app/utils/client'; import { t } from '../../../app/utils/lib/i18n'; -import { Utilities } from '../../../ee/lib/misc/Utilities'; -import { dispatchToastMessage } from '../../lib/toast'; +import { appErroredStatuses } from './helpers/appErroredStatuses'; export const appEnabledStatuses = [AppStatus.AUTO_ENABLED, AppStatus.MANUALLY_ENABLED]; -// eslint-disable-next-line @typescript-eslint/naming-convention -interface ApiError { - xhr: { - responseJSON: { - error: string; - status: string; - messages: string[]; - payload?: any; - }; - }; -} - -const appErroredStatuses = [ - AppStatus.COMPILER_ERROR_DISABLED, - AppStatus.ERROR_DISABLED, - AppStatus.INVALID_SETTINGS_DISABLED, - AppStatus.INVALID_LICENSE_DISABLED, -]; - export type Actions = 'update' | 'install' | 'purchase' | 'request'; type appButtonResponseProps = { @@ -54,116 +34,8 @@ export type appStatusSpanResponseProps = { tooltipText?: string; }; -type PlanType = 'Subscription' | 'Paid' | 'Free'; - -type FormattedPriceAndPlan = { - type: PlanType; - price: string; -}; - type appButtonPropsType = App & { isAdminUser: boolean; endUserRequested: boolean }; -export const apiCurlGetter = - (absoluteUrl: (path: string) => string) => - (method: string, api: IApiEndpointMetadata): string[] => { - const example = api.examples?.[method]; - return Utilities.curl({ - url: absoluteUrl(api.computedPath), - method, - params: example?.params, - query: example?.query, - content: example?.content, - headers: example?.headers, - auth: '', - }).split('\n'); - }; - -export function handleInstallError(apiError: ApiError | Error): void { - if (apiError instanceof Error) { - dispatchToastMessage({ type: 'error', message: apiError.message }); - return; - } - - if (!apiError.xhr || !apiError.xhr.responseJSON) { - return; - } - - const { status, messages, error, payload = null } = apiError.xhr.responseJSON; - - let message: string; - - switch (status) { - case 'storage_error': - message = messages.join(''); - break; - case 'app_user_error': - message = messages.join(''); - if (payload?.username) { - message = t('Apps_User_Already_Exists', { username: payload.username }); - } - break; - default: - if (error) { - message = error; - } else { - message = t('There_has_been_an_error_installing_the_app'); - } - } - - dispatchToastMessage({ type: 'error', message }); -} - -const shouldHandleErrorAsWarning = (message: string): boolean => { - const warnings = ['Could not reach the Marketplace']; - - return warnings.includes(message); -}; - -export const handleAPIError = (error: unknown): void => { - if (error instanceof Error) { - const { message } = error; - - if (shouldHandleErrorAsWarning(message)) { - dispatchToastMessage({ type: 'warning', message }); - return; - } - - dispatchToastMessage({ type: 'error', message }); - } -}; - -export const warnAppInstall = (appName: string, status: AppStatus): void => { - if (appErroredStatuses.includes(status)) { - dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); - return; - } - - dispatchToastMessage({ type: 'success', message: `${appName} installed` }); -}; - -export const warnEnableDisableApp = (appName: string, status: AppStatus, type: string): void => { - if (appErroredStatuses.includes(status)) { - dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); - return; - } - - if (type === 'enable') { - dispatchToastMessage({ type: 'success', message: `${appName} enabled` }); - return; - } - - dispatchToastMessage({ type: 'success', message: `${appName} disabled` }); -}; - -export const warnStatusChange = (appName: string, status: AppStatus): void => { - if (appErroredStatuses.includes(status)) { - dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); - return; - } - - dispatchToastMessage({ type: 'info', message: (t(`App_status_${status}`), appName) }); -}; - export const appButtonProps = ({ installed, version, @@ -386,45 +258,3 @@ export const appMultiStatusProps = ( return statuses; }; - -const formatPrice = (price: number): string => `\$${price.toFixed(2)}`; - -const formatPricingPlan = ({ strategy, price, tiers = [], trialDays }: AppPricingPlan): string => { - const { perUnit = false } = (Array.isArray(tiers) && tiers.find((tier) => tier.price === price)) || {}; - - const pricingPlanTranslationString = [ - 'Apps_Marketplace_pricingPlan', - Array.isArray(tiers) && tiers.length > 0 && '+*', - strategy, - trialDays && 'trialDays', - perUnit && 'perUser', - ] - .filter(Boolean) - .join('_'); - - return t(pricingPlanTranslationString, { - price: formatPrice(price), - trialDays, - }); -}; - -export const formatPriceAndPurchaseType = ( - purchaseType: PurchaseType, - pricingPlans: AppPricingPlan[], - price: number, -): FormattedPriceAndPlan => { - if (purchaseType === 'subscription') { - const type = 'Subscription'; - if (!pricingPlans || !Array.isArray(pricingPlans) || pricingPlans.length === 0) { - return { type, price: '-' }; - } - - return { type, price: formatPricingPlan(pricingPlans[0]) }; - } - - if (price > 0) { - return { type: 'Paid', price: formatPrice(price) }; - } - - return { type: 'Free', price: '-' }; -}; diff --git a/apps/meteor/client/views/marketplace/helpers/apiCurlGetter.ts b/apps/meteor/client/views/marketplace/helpers/apiCurlGetter.ts new file mode 100644 index 000000000000..8cd8974c2e55 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/apiCurlGetter.ts @@ -0,0 +1,18 @@ +import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; + +import { Utilities } from '../../../../ee/lib/misc/Utilities'; + +export const apiCurlGetter = + (absoluteUrl: (path: string) => string) => + (method: string, api: IApiEndpointMetadata): string[] => { + const example = api.examples?.[method]; + return Utilities.curl({ + url: absoluteUrl(api.computedPath), + method, + params: example?.params, + query: example?.query, + content: example?.content, + headers: example?.headers, + auth: '', + }).split('\n'); + }; diff --git a/apps/meteor/client/views/marketplace/helpers/appErroredStatuses.ts b/apps/meteor/client/views/marketplace/helpers/appErroredStatuses.ts new file mode 100644 index 000000000000..40f7c53d96dd --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/appErroredStatuses.ts @@ -0,0 +1,8 @@ +import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; + +export const appErroredStatuses = [ + AppStatus.COMPILER_ERROR_DISABLED, + AppStatus.ERROR_DISABLED, + AppStatus.INVALID_SETTINGS_DISABLED, + AppStatus.INVALID_LICENSE_DISABLED, +]; diff --git a/apps/meteor/client/views/marketplace/helpers/formatPrice.ts b/apps/meteor/client/views/marketplace/helpers/formatPrice.ts new file mode 100644 index 000000000000..91fcfe5aa917 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/formatPrice.ts @@ -0,0 +1 @@ +export const formatPrice = (price: number): string => `\$${price.toFixed(2)}`; diff --git a/apps/meteor/client/views/marketplace/helpers/formatPriceAndPurchaseType.ts b/apps/meteor/client/views/marketplace/helpers/formatPriceAndPurchaseType.ts new file mode 100644 index 000000000000..57ae4090495b --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/formatPriceAndPurchaseType.ts @@ -0,0 +1,32 @@ +import type { PurchaseType, AppPricingPlan } from '@rocket.chat/core-typings'; + +import { formatPrice } from './formatPrice'; +import { formatPricingPlan } from './formatPricingPlan'; + +type PlanType = 'Subscription' | 'Paid' | 'Free'; + +type FormattedPriceAndPlan = { + type: PlanType; + price: string; +}; + +export const formatPriceAndPurchaseType = ( + purchaseType: PurchaseType, + pricingPlans: AppPricingPlan[], + price: number, +): FormattedPriceAndPlan => { + if (purchaseType === 'subscription') { + const type = 'Subscription'; + if (!pricingPlans || !Array.isArray(pricingPlans) || pricingPlans.length === 0) { + return { type, price: '-' }; + } + + return { type, price: formatPricingPlan(pricingPlans[0]) }; + } + + if (price > 0) { + return { type: 'Paid', price: formatPrice(price) }; + } + + return { type: 'Free', price: '-' }; +}; diff --git a/apps/meteor/client/views/marketplace/helpers/formatPricingPlan.ts b/apps/meteor/client/views/marketplace/helpers/formatPricingPlan.ts new file mode 100644 index 000000000000..fcd591b8df21 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/formatPricingPlan.ts @@ -0,0 +1,23 @@ +import type { AppPricingPlan } from '@rocket.chat/core-typings'; + +import { t } from '../../../../app/utils/lib/i18n'; +import { formatPrice } from './formatPrice'; + +export const formatPricingPlan = ({ strategy, price, tiers = [], trialDays }: AppPricingPlan): string => { + const { perUnit = false } = (Array.isArray(tiers) && tiers.find((tier) => tier.price === price)) || {}; + + const pricingPlanTranslationString = [ + 'Apps_Marketplace_pricingPlan', + Array.isArray(tiers) && tiers.length > 0 && '+*', + strategy, + trialDays && 'trialDays', + perUnit && 'perUser', + ] + .filter(Boolean) + .join('_'); + + return t(pricingPlanTranslationString, { + price: formatPrice(price), + trialDays, + }); +}; diff --git a/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts b/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts new file mode 100644 index 000000000000..6226d38ac662 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/handleAPIError.ts @@ -0,0 +1,20 @@ +import { dispatchToastMessage } from '../../../lib/toast'; + +const shouldHandleErrorAsWarning = (message: string): boolean => { + const warnings = ['Could not reach the Marketplace']; + + return warnings.includes(message); +}; + +export const handleAPIError = (error: unknown): void => { + if (error instanceof Error) { + const { message } = error; + + if (shouldHandleErrorAsWarning(message)) { + dispatchToastMessage({ type: 'warning', message }); + return; + } + + dispatchToastMessage({ type: 'error', message }); + } +}; diff --git a/apps/meteor/client/views/marketplace/helpers/handleInstallError.ts b/apps/meteor/client/views/marketplace/helpers/handleInstallError.ts new file mode 100644 index 000000000000..37a2489f9c56 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/handleInstallError.ts @@ -0,0 +1,49 @@ +import { t } from '../../../../app/utils/lib/i18n'; +import { dispatchToastMessage } from '../../../lib/toast'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +interface ApiError { + xhr: { + responseJSON: { + error: string; + status: string; + messages: string[]; + payload?: any; + }; + }; +} + +export function handleInstallError(apiError: ApiError | Error): void { + if (apiError instanceof Error) { + dispatchToastMessage({ type: 'error', message: apiError.message }); + return; + } + + if (!apiError.xhr?.responseJSON) { + return; + } + + const { status, messages, error, payload = null } = apiError.xhr.responseJSON; + + let message: string; + + switch (status) { + case 'storage_error': + message = messages.join(''); + break; + case 'app_user_error': + message = messages.join(''); + if (payload?.username) { + message = t('Apps_User_Already_Exists', { username: payload.username }); + } + break; + default: + if (error) { + message = error; + } else { + message = t('There_has_been_an_error_installing_the_app'); + } + } + + dispatchToastMessage({ type: 'error', message }); +} diff --git a/apps/meteor/client/views/marketplace/helpers/installApp.ts b/apps/meteor/client/views/marketplace/helpers/installApp.ts index 9d053c8b49c8..6d51c4813b18 100644 --- a/apps/meteor/client/views/marketplace/helpers/installApp.ts +++ b/apps/meteor/client/views/marketplace/helpers/installApp.ts @@ -1,7 +1,8 @@ import type { App, AppPermission } from '@rocket.chat/core-typings'; import { Apps } from '../../../../ee/client/apps/orchestrator'; -import { handleAPIError, warnAppInstall } from '../helpers'; +import { handleAPIError } from './handleAPIError'; +import { warnAppInstall } from './warnAppInstall'; type installAppProps = App & { permissionsGranted?: AppPermission[]; diff --git a/apps/meteor/client/views/marketplace/helpers/updateApp.ts b/apps/meteor/client/views/marketplace/helpers/updateApp.ts index 6cdfae39bb2c..a7b2e31be296 100644 --- a/apps/meteor/client/views/marketplace/helpers/updateApp.ts +++ b/apps/meteor/client/views/marketplace/helpers/updateApp.ts @@ -1,7 +1,8 @@ import type { App, AppPermission } from '@rocket.chat/core-typings'; import { Apps } from '../../../../ee/client/apps/orchestrator'; -import { handleAPIError, warnStatusChange } from '../helpers'; +import { handleAPIError } from './handleAPIError'; +import { warnStatusChange } from './warnStatusChange'; type updateAppProps = App & { permissionsGranted?: AppPermission[]; diff --git a/apps/meteor/client/views/marketplace/helpers/warnAppInstall.ts b/apps/meteor/client/views/marketplace/helpers/warnAppInstall.ts new file mode 100644 index 000000000000..78d40829c0ce --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/warnAppInstall.ts @@ -0,0 +1,14 @@ +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; + +import { t } from '../../../../app/utils/lib/i18n'; +import { dispatchToastMessage } from '../../../lib/toast'; +import { appErroredStatuses } from './appErroredStatuses'; + +export const warnAppInstall = (appName: string, status: AppStatus): void => { + if (appErroredStatuses.includes(status)) { + dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); + return; + } + + dispatchToastMessage({ type: 'success', message: `${appName} installed` }); +}; diff --git a/apps/meteor/client/views/marketplace/helpers/warnEnableDisableApp.ts b/apps/meteor/client/views/marketplace/helpers/warnEnableDisableApp.ts new file mode 100644 index 000000000000..2ca50d67c784 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/warnEnableDisableApp.ts @@ -0,0 +1,19 @@ +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; + +import { t } from '../../../../app/utils/lib/i18n'; +import { dispatchToastMessage } from '../../../lib/toast'; +import { appErroredStatuses } from './appErroredStatuses'; + +export const warnEnableDisableApp = (appName: string, status: AppStatus, type: string): void => { + if (appErroredStatuses.includes(status)) { + dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); + return; + } + + if (type === 'enable') { + dispatchToastMessage({ type: 'success', message: `${appName} enabled` }); + return; + } + + dispatchToastMessage({ type: 'success', message: `${appName} disabled` }); +}; diff --git a/apps/meteor/client/views/marketplace/helpers/warnStatusChange.ts b/apps/meteor/client/views/marketplace/helpers/warnStatusChange.ts new file mode 100644 index 000000000000..3fc01514ec76 --- /dev/null +++ b/apps/meteor/client/views/marketplace/helpers/warnStatusChange.ts @@ -0,0 +1,14 @@ +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; + +import { t } from '../../../../app/utils/lib/i18n'; +import { dispatchToastMessage } from '../../../lib/toast'; +import { appErroredStatuses } from './appErroredStatuses'; + +export const warnStatusChange = (appName: string, status: AppStatus): void => { + if (appErroredStatuses.includes(status)) { + dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); + return; + } + + dispatchToastMessage({ type: 'info', message: (t(`App_status_${status}`), appName) }); +}; diff --git a/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx b/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx index 040b1d395d26..af0b04469cd5 100644 --- a/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx @@ -6,7 +6,7 @@ import { Apps } from '../../../../ee/client/apps/orchestrator'; import IframeModal from '../IframeModal'; import AppInstallModal from '../components/AppInstallModal/AppInstallModal'; import type { Actions } from '../helpers'; -import { handleAPIError } from '../helpers'; +import { handleAPIError } from '../helpers/handleAPIError'; import { isMarketplaceRouteContext, useAppsCountQuery } from './useAppsCountQuery'; import { useOpenAppPermissionsReviewModal } from './useOpenAppPermissionsReviewModal'; import { useOpenIncompatibleModal } from './useOpenIncompatibleModal'; diff --git a/apps/meteor/client/views/marketplace/hooks/useCategories.ts b/apps/meteor/client/views/marketplace/hooks/useCategories.ts index 88c0e3bcba69..27f01f91e04b 100644 --- a/apps/meteor/client/views/marketplace/hooks/useCategories.ts +++ b/apps/meteor/client/views/marketplace/hooks/useCategories.ts @@ -9,7 +9,7 @@ import type { CategoryOnSelected, selectedCategoriesList, } from '../definitions/CategoryDropdownDefinitions'; -import { handleAPIError } from '../helpers'; +import { handleAPIError } from '../helpers/handleAPIError'; import { useCategoryFlatList } from './useCategoryFlatList'; import { useCategoryToggle } from './useCategoryToggle'; diff --git a/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx b/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx index 72b7cef1c19e..41bf7716b1eb 100644 --- a/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx @@ -3,7 +3,7 @@ import React, { useCallback } from 'react'; import { Apps } from '../../../../ee/client/apps/orchestrator'; import IframeModal from '../IframeModal'; -import { handleAPIError } from '../helpers'; +import { handleAPIError } from '../helpers/handleAPIError'; export const useOpenIncompatibleModal = () => { const setModal = useSetModal();