Skip to content

Commit

Permalink
fix: finalizing missing configuration access
Browse files Browse the repository at this point in the history
  • Loading branch information
carere committed Feb 28, 2025
1 parent 5290331 commit aedb83a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 88 deletions.
14 changes: 7 additions & 7 deletions packages/app-builder/src/models/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
}

Expand Down
46 changes: 29 additions & 17 deletions packages/app-builder/src/routes/_builder+/settings+/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -101,7 +103,7 @@ export async function loader({ request }: LoaderFunctionArgs) {

export default function Settings() {
const { t } = useTranslation(handle.i18n);
const { sections } = useLoaderData<typeof loader>();
const { sections, entitlements } = useLoaderData<typeof loader>();

return (
<Page.Main>
Expand All @@ -128,22 +130,32 @@ export default function Settings() {
<p className="font-bold">{t(`settings:${section}`)}</p>
</div>
<ul className="flex flex-col gap-1 pb-6">
{settings.map((setting) => (
<NavLink
key={setting.title}
className={({ isActive }) =>
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}`)}
</NavLink>
))}
{settings.map((setting) =>
setting.title === 'webhooks' && !isAccessible(entitlements.webhooks) ? (
<span
key={setting.title}
className="text-s bg-grey-100 text-grey-80 inline-flex w-full gap-2 p-2 font-medium first-letter:capitalize"
>
{t(`settings:${setting.title}`)}
<Nudge content="" kind={entitlements.webhooks} className="size-5" />
</span>
) : (
<NavLink
key={setting.title}
className={({ isActive }) =>
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}`)}
</NavLink>
),
)}
</ul>
</nav>
);
Expand Down
129 changes: 68 additions & 61 deletions packages/app-builder/src/routes/_builder+/settings+/scenarios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof editOrganizationSchema>;
Expand Down Expand Up @@ -89,7 +94,7 @@ export async function action({ request }: LoaderFunctionArgs) {

export default function Users() {
const { t } = useTranslation(['settings', 'common']);
const { organization, user } = useLoaderData<typeof loader>();
const { organization, user, entitlements } = useLoaderData<typeof loader>();
const fetcher = useFetcher<typeof action>();

const form = useForm<EditOrganizationForm>({
Expand Down Expand Up @@ -151,61 +156,63 @@ export default function Users() {
</form.Field>
</CollapsiblePaper.Content>
</CollapsiblePaper.Container>
<CollapsiblePaper.Container>
<CollapsiblePaper.Title>
<span className="flex-1">{t('settings:scenario_sanction_settings')}</span>
</CollapsiblePaper.Title>
<CollapsiblePaper.Content>
<div className="flex flex-col gap-6 lg:gap-8">
<form.Field name="sanctionLimit">
{(field) => (
<div className="flex flex-col gap-4">
<FormLabel
name={field.name}
className="text-m"
valid={field.state.meta.errors.length === 0}
>
{t('settings:scenario_sanction_limit')}
</FormLabel>
<FormInput
defaultValue={field.state.value}
type="number"
name={field.name}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(+e.currentTarget.value)}
placeholder={t('settings:scenario_sanction_limit_placeholder')}
valid={field.state.meta.errors.length === 0}
/>
<FormErrorOrDescription errors={field.state.meta.errors} />
</div>
)}
</form.Field>
<form.Field name="sanctionThreshold">
{(field) => (
<div className="flex flex-col gap-4">
<FormLabel
name={field.name}
className="text-m"
valid={field.state.meta.errors.length === 0}
>
{t('settings:scenario_sanction_threshold')}
</FormLabel>
<FormInput
defaultValue={field.state.value}
type="number"
name={field.name}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(+e.currentTarget.value)}
placeholder={t('settings:scenario_sanction_threshold_placeholder')}
valid={field.state.meta.errors.length === 0}
/>
<FormErrorOrDescription errors={field.state.meta.errors} />
</div>
)}
</form.Field>
</div>
</CollapsiblePaper.Content>
</CollapsiblePaper.Container>
{entitlements.sanctions !== 'restricted' ? (
<CollapsiblePaper.Container>
<CollapsiblePaper.Title>
<span className="flex-1">{t('settings:scenario_sanction_settings')}</span>
</CollapsiblePaper.Title>
<CollapsiblePaper.Content>
<div className="flex flex-col gap-6 lg:gap-8">
<form.Field name="sanctionLimit">
{(field) => (
<div className="flex flex-col gap-4">
<FormLabel
name={field.name}
className="text-m"
valid={field.state.meta.errors.length === 0}
>
{t('settings:scenario_sanction_limit')}
</FormLabel>
<FormInput
defaultValue={field.state.value}
type="number"
name={field.name}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(+e.currentTarget.value)}
placeholder={t('settings:scenario_sanction_limit_placeholder')}
valid={field.state.meta.errors.length === 0}
/>
<FormErrorOrDescription errors={field.state.meta.errors} />
</div>
)}
</form.Field>
<form.Field name="sanctionThreshold">
{(field) => (
<div className="flex flex-col gap-4">
<FormLabel
name={field.name}
className="text-m"
valid={field.state.meta.errors.length === 0}
>
{t('settings:scenario_sanction_threshold')}
</FormLabel>
<FormInput
defaultValue={field.state.value}
type="number"
name={field.name}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(+e.currentTarget.value)}
placeholder={t('settings:scenario_sanction_threshold_placeholder')}
valid={field.state.meta.errors.length === 0}
/>
<FormErrorOrDescription errors={field.state.meta.errors} />
</div>
)}
</form.Field>
</div>
</CollapsiblePaper.Content>
</CollapsiblePaper.Container>
) : null}
</form>
</Page.Content>
</Page.Container>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -62,6 +64,11 @@ export function CreateSanction({
const { t } = useTranslation(['scenarios']);
const fetcher = useFetcher<typeof action>();

const disabled = useMemo(
() => hasAlreadyASanction || !isAccessible(isSanctionAvailable),
[hasAlreadyASanction, isSanctionAvailable],
);

return (
<fetcher.Form
method="POST"
Expand All @@ -74,7 +81,7 @@ export function CreateSanction({
type="submit"
variant="dropdown"
size="dropdown"
disabled={hasAlreadyASanction || isSanctionAvailable === 'restricted'}
disabled={disabled}
className="w-full"
>
<div className="flex items-center gap-4">
Expand All @@ -83,7 +90,7 @@ export function CreateSanction({
<span className="text-s font-normal">{t('scenarios:create_sanction.title')}</span>
<span
className={clsx('text-grey-50 font-normal', {
'text-grey-80': isSanctionAvailable === 'restricted' || hasAlreadyASanction,
'text-grey-80': disabled,
})}
>
{hasAlreadyASanction
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder/src/services/feature-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down

0 comments on commit aedb83a

Please sign in to comment.