Add upgrade to org plan card to RLS (#17455)
## Context <img width="564" height="270" alt="Screenshot 2026-01-26 at 18 19 39" src="https://github.com/user-attachments/assets/9a6bdd69-2fa1-449b-9985-bfc7a401780e" />
This commit is contained in:
+46
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
StyledSettingsOptionCardContent,
|
||||
StyledSettingsOptionCardDescription,
|
||||
StyledSettingsOptionCardIcon,
|
||||
StyledSettingsOptionCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
type SettingsOptionCardContentButtonProps = {
|
||||
Icon?: IconComponent;
|
||||
title: React.ReactNode;
|
||||
description?: string | React.ReactNode;
|
||||
disabled?: boolean;
|
||||
Button?: React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
export const SettingsOptionCardContentButton = ({
|
||||
Icon,
|
||||
title,
|
||||
description,
|
||||
disabled = false,
|
||||
Button,
|
||||
}: SettingsOptionCardContentButtonProps) => {
|
||||
return (
|
||||
<StyledSettingsOptionCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsOptionCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsOptionCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsOptionCardDescription>
|
||||
{description}
|
||||
</StyledSettingsOptionCardDescription>
|
||||
</div>
|
||||
{Button && <StyledButtonContainer>{Button}</StyledButtonContainer>}
|
||||
</StyledSettingsOptionCardContent>
|
||||
);
|
||||
};
|
||||
+2
-3
@@ -60,9 +60,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
|
||||
) ?? false;
|
||||
|
||||
const isRowLevelPermissionPredicatesEnabled =
|
||||
featureFlagsMap[
|
||||
FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED
|
||||
] && isRLSBillingEntitlementEnabled;
|
||||
featureFlagsMap[FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED];
|
||||
|
||||
const objectMetadataItem = objectMetadata.objectMetadataItem;
|
||||
|
||||
@@ -170,6 +168,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
|
||||
<SettingsRolePermissionsObjectLevelRecordLevelSection
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
roleId={roleId}
|
||||
hasOrganizationPlan={isRLSBillingEntitlementEnabled}
|
||||
/>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
|
||||
+60
-2
@@ -2,26 +2,84 @@
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { Pill } from 'twenty-ui/components';
|
||||
import { H2Title, IconArrowUp, IconLock } from 'twenty-ui/display';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
|
||||
import { SettingsRolePermissionsObjectLevelRecordLevelPermissionFilterBuilder } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFilterBuilder';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledContent = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledPill = styled(Pill)`
|
||||
border-radius: 40px;
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectLevelRecordLevelSectionProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
roleId: string;
|
||||
hasOrganizationPlan: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelRecordLevelSection = ({
|
||||
objectMetadataItem,
|
||||
roleId,
|
||||
hasOrganizationPlan,
|
||||
}: SettingsRolePermissionsObjectLevelRecordLevelSectionProps) => {
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const billing = useRecoilValue(billingState);
|
||||
const isBillingEnabled = billing?.isBillingEnabled ?? false;
|
||||
|
||||
if (!hasOrganizationPlan) {
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Record-level`}
|
||||
description={t`Ability to filter the records a user can interact with`}
|
||||
adornment={<StyledPill label={t`Organization`} Icon={IconLock} />}
|
||||
/>
|
||||
<StyledCard rounded>
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconLock}
|
||||
title={t`Upgrade to access`}
|
||||
description={t`This feature is part of the Organization Plan`}
|
||||
Button={
|
||||
isBillingEnabled && (
|
||||
<Button
|
||||
title={t`Upgrade`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconArrowUp}
|
||||
onClick={() => navigateSettings(SettingsPath.Billing)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledCard>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
|
||||
type PillProps = {
|
||||
className?: string;
|
||||
label?: string;
|
||||
Icon?: IconComponent;
|
||||
};
|
||||
|
||||
const StyledPill = styled.span`
|
||||
@@ -11,17 +12,22 @@ const StyledPill = styled.span`
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.pill};
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: flex-end;
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||
padding: ${({ theme }) => `0 ${theme.spacing(2)}`};
|
||||
`;
|
||||
|
||||
export const Pill = ({ className, label }: PillProps) => {
|
||||
return <StyledPill className={className}>{label}</StyledPill>;
|
||||
export const Pill = ({ className, label, Icon }: PillProps) => {
|
||||
return (
|
||||
<StyledPill className={className}>
|
||||
{Icon && <Icon size={12} />}
|
||||
{label}
|
||||
</StyledPill>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user