diff --git a/packages/app-builder/src/models/license.ts b/packages/app-builder/src/models/license.ts index bb498d422..a077003c5 100644 --- a/packages/app-builder/src/models/license.ts +++ b/packages/app-builder/src/models/license.ts @@ -15,13 +15,13 @@ export interface LicenseEntitlements { export function emptyLicenseEntitlements(): LicenseEntitlements { return { - workflows: 'missing_configuration', - analytics: 'missing_configuration', - userRoles: 'missing_configuration', - webhooks: 'missing_configuration', - ruleSnoozes: 'missing_configuration', - testRun: 'missing_configuration', - sanctions: 'missing_configuration', + workflows: 'restricted', + analytics: 'restricted', + userRoles: 'restricted', + webhooks: 'restricted', + ruleSnoozes: 'restricted', + testRun: 'restricted', + sanctions: 'restricted', }; } diff --git a/packages/app-builder/src/routes/_builder+/settings+/_layout.tsx b/packages/app-builder/src/routes/_builder+/settings+/_layout.tsx index ad0617a3f..5cd7e549b 100644 --- a/packages/app-builder/src/routes/_builder+/settings+/_layout.tsx +++ b/packages/app-builder/src/routes/_builder+/settings+/_layout.tsx @@ -4,8 +4,10 @@ import { type BreadCrumbProps, BreadCrumbs, } from '@app-builder/components/Breadcrumbs'; +import { Nudge } from '@app-builder/components/Nudge'; import { type CurrentUser } from '@app-builder/models'; import { + isAccessible, isReadAllInboxesAvailable, isReadApiKeyAvailable, isReadTagAvailable, @@ -101,7 +103,7 @@ export async function loader({ request }: LoaderFunctionArgs) { export default function Settings() { const { t } = useTranslation(handle.i18n); - const { sections } = useLoaderData(); + const { sections, entitlements } = useLoaderData(); return ( @@ -128,22 +130,32 @@ export default function Settings() {

{t(`settings:${section}`)}

    - {settings.map((setting) => ( - - clsx( - 'text-s flex w-full cursor-pointer flex-row rounded p-2 font-medium first-letter:capitalize', - isActive - ? 'bg-purple-96 text-purple-65' - : 'bg-grey-100 text-grey-00 hover:bg-purple-96 hover:text-purple-65', - ) - } - to={setting.to} - > - {t(`settings:${setting.title}`)} - - ))} + {settings.map((setting) => + setting.title === 'webhooks' && !isAccessible(entitlements.webhooks) ? ( + + {t(`settings:${setting.title}`)} + + + ) : ( + + clsx( + 'text-s flex w-full cursor-pointer flex-row rounded p-2 font-medium first-letter:capitalize', + isActive + ? 'bg-purple-96 text-purple-65' + : 'bg-grey-100 text-grey-00 hover:bg-purple-96 hover:text-purple-65', + ) + } + to={setting.to} + > + {t(`settings:${setting.title}`)} + + ), + )}
); diff --git a/packages/app-builder/src/routes/_builder+/settings+/scenarios.tsx b/packages/app-builder/src/routes/_builder+/settings+/scenarios.tsx index 8343beb12..0ff605f7f 100644 --- a/packages/app-builder/src/routes/_builder+/settings+/scenarios.tsx +++ b/packages/app-builder/src/routes/_builder+/settings+/scenarios.tsx @@ -18,21 +18,26 @@ import { z } from 'zod'; export async function loader({ request }: LoaderFunctionArgs) { const { authService } = serverServices; - const { organization: repository, user } = await authService.isAuthenticated(request, { + const { + organization: repository, + user, + entitlements, + } = await authService.isAuthenticated(request, { failureRedirect: getRoute('/sign-in'), }); - return json({ + return { organization: await repository.getCurrentOrganization(), + entitlements, user, - }); + }; } const editOrganizationSchema = z.object({ organizationId: z.string().min(1), defaultScenarioTimezone: z.string(), - sanctionThreshold: z.coerce.number().min(0).max(100), - sanctionLimit: z.coerce.number().min(0), + sanctionThreshold: z.coerce.number().min(0).max(100).optional(), + sanctionLimit: z.coerce.number().min(0).optional(), }); type EditOrganizationForm = z.infer; @@ -89,7 +94,7 @@ export async function action({ request }: LoaderFunctionArgs) { export default function Users() { const { t } = useTranslation(['settings', 'common']); - const { organization, user } = useLoaderData(); + const { organization, user, entitlements } = useLoaderData(); const fetcher = useFetcher(); const form = useForm({ @@ -151,61 +156,63 @@ export default function Users() { - - - {t('settings:scenario_sanction_settings')} - - -
- - {(field) => ( -
- - {t('settings:scenario_sanction_limit')} - - field.handleChange(+e.currentTarget.value)} - placeholder={t('settings:scenario_sanction_limit_placeholder')} - valid={field.state.meta.errors.length === 0} - /> - -
- )} -
- - {(field) => ( -
- - {t('settings:scenario_sanction_threshold')} - - field.handleChange(+e.currentTarget.value)} - placeholder={t('settings:scenario_sanction_threshold_placeholder')} - valid={field.state.meta.errors.length === 0} - /> - -
- )} -
-
-
-
+ {entitlements.sanctions !== 'restricted' ? ( + + + {t('settings:scenario_sanction_settings')} + + +
+ + {(field) => ( +
+ + {t('settings:scenario_sanction_limit')} + + field.handleChange(+e.currentTarget.value)} + placeholder={t('settings:scenario_sanction_limit_placeholder')} + valid={field.state.meta.errors.length === 0} + /> + +
+ )} +
+ + {(field) => ( +
+ + {t('settings:scenario_sanction_threshold')} + + field.handleChange(+e.currentTarget.value)} + placeholder={t('settings:scenario_sanction_threshold_placeholder')} + valid={field.state.meta.errors.length === 0} + /> + +
+ )} +
+
+
+
+ ) : null} diff --git a/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/sanctions+/create.tsx b/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/sanctions+/create.tsx index 39bac1350..a9cd5d5d6 100644 --- a/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/sanctions+/create.tsx +++ b/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/sanctions+/create.tsx @@ -1,4 +1,5 @@ import { Nudge } from '@app-builder/components/Nudge'; +import { isAccessible } from '@app-builder/services/feature-access'; import { serverServices } from '@app-builder/services/init.server'; import { getRoute } from '@app-builder/utils/routes'; import { fromParams, fromUUID } from '@app-builder/utils/short-uuid'; @@ -7,6 +8,7 @@ import { useFetcher } from '@remix-run/react'; import clsx from 'clsx'; import { type Namespace } from 'i18next'; import { type FeatureAccessDto } from 'marble-api/generated/license-api'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from 'ui-design-system'; import { Icon } from 'ui-icons'; @@ -62,6 +64,11 @@ export function CreateSanction({ const { t } = useTranslation(['scenarios']); const fetcher = useFetcher(); + const disabled = useMemo( + () => hasAlreadyASanction || !isAccessible(isSanctionAvailable), + [hasAlreadyASanction, isSanctionAvailable], + ); + return (
@@ -83,7 +90,7 @@ export function CreateSanction({ {t('scenarios:create_sanction.title')} {hasAlreadyASanction diff --git a/packages/app-builder/src/services/feature-access.ts b/packages/app-builder/src/services/feature-access.ts index c0351df7c..8aced4b86 100644 --- a/packages/app-builder/src/services/feature-access.ts +++ b/packages/app-builder/src/services/feature-access.ts @@ -2,7 +2,7 @@ import { type CurrentUser } from '@app-builder/models'; import { type LicenseEntitlements } from '@app-builder/models/license'; import { type FeatureAccessDto } from 'marble-api/generated/license-api'; -const isAccessible = (featureAccess: FeatureAccessDto) => +export const isAccessible = (featureAccess: FeatureAccessDto) => featureAccess !== 'restricted' && featureAccess !== 'missing_configuration'; export const isAnalyticsAvailable = (