Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions src/custom/SubscriptionTable/SubscriptionTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import { Box, Button, Table, TableBody, TableHead, TableRow, Typography } from '@mui/material';
import React from 'react';
Comment on lines +1 to +4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import the custom styled components from ./style and clean up the unused MUI imports (Paper, TableCell, TableContainer, TableRow) to keep the code clean and leverage the custom styling defined for this component.

Suggested change
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import {
Box,
Button,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography
} from '@mui/material';
import React from 'react';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import {
Box,
Button,
Table,
TableBody,
TableHead,
Typography
} from '@mui/material';
import React from 'react';
import {
StyledTableContainer,
StyledHeaderRow,
StyledTableCell,
FeatureHeaderCell
} from './style';

import { FeatureHeaderCell, StyledHeaderRow, StyledTableCell, StyledTableContainer } from './style';

export interface PlanFeature {
featureName: string;
freePlan: boolean | string;
teamPlan: boolean | string;
enterprisePlan: boolean | string;
}

export interface SubscriptionTableProps {
title?: string;
features?: PlanFeature[];
onPlanSelect?: (planType: 'free' | 'team' | 'enterprise') => void;
featuresLabel?: string;
freePlanLabel?: string;
freePlanButtonLabel?: string;
teamPlanLabel?: string;
teamPlanButtonLabel?: string;
enterprisePlanLabel?: string;
enterprisePlanButtonLabel?: string;
}

export const SubscriptionTable: React.FC<SubscriptionTableProps> = ({
title = 'Subscription Plans Comparison',
features = [],
onPlanSelect,
featuresLabel = 'Features',
freePlanLabel = 'Free Plan',
freePlanButtonLabel = 'Get Started',
teamPlanLabel = 'Team Plan',
teamPlanButtonLabel = 'Upgrade',
enterprisePlanLabel = 'Enterprise Plan',
enterprisePlanButtonLabel = 'Contact Us'
}) => {
Comment on lines +14 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support internationalization (i18n) and localization, avoid hardcoding UI strings (such as table headers and button labels) in shared components. Expose these strings as configurable props with default values to maintain backward compatibility. Additionally, default the features prop to an empty array [] to prevent runtime errors if it is not provided.

export interface SubscriptionTableProps {
  title?: string;
  features?: PlanFeature[];
  onPlanSelect?: (planType: 'free' | 'team' | 'enterprise') => void;
  featuresLabel?: string;
  freePlanLabel?: string;
  freePlanButtonLabel?: string;
  teamPlanLabel?: string;
  teamPlanButtonLabel?: string;
  enterprisePlanLabel?: string;
  enterprisePlanButtonLabel?: string;
}

export const SubscriptionTable: React.FC<SubscriptionTableProps> = ({
  title = 'Subscription Plans Comparison',
  features = [],
  onPlanSelect,
  featuresLabel = 'Features',
  freePlanLabel = 'Free Plan',
  freePlanButtonLabel = 'Get Started',
  teamPlanLabel = 'Team Plan',
  teamPlanButtonLabel = 'Upgrade',
  enterprisePlanLabel = 'Enterprise Plan',
  enterprisePlanButtonLabel = 'Contact Us'
}) => {
References
  1. Avoid hardcoding UI strings (such as button labels) in shared components. Expose these strings as configurable props to support internationalization (i18n) and localization.
  2. When exposing UI strings as configurable props in shared components to support localization, provide a default value to maintain backward compatibility.

// Helper function to render true/false values as Crisp Icons or Text
const renderValue = (value: boolean | string) => {
if (typeof value === 'boolean') {
return value ? (
<CheckIcon color="success" data-testid="check-icon" />
) : (
<CloseIcon color="error" data-testid="close-icon" />
);
}
return (
<Typography variant="body2" sx={{ fontWeight: 500 }}>
{value}
</Typography>
);
Comment on lines +40 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,220p' src/custom/SubscriptionTable/SubscriptionTable.tsx

Repository: layer5io/sistent

Length of output: 4928


🏁 Script executed:

cat -n src/custom/SubscriptionTable/SubscriptionTable.tsx | sed -n '1,220p'

Repository: layer5io/sistent

Length of output: 5887


Expose a text alternative for the boolean icons in src/custom/SubscriptionTable/SubscriptionTable.tsx:40-46. The check/close icons need an accessible name so screen readers can tell whether a feature is included; add titleAccess, aria-label, or hidden text for both states.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/custom/SubscriptionTable/SubscriptionTable.tsx` around lines 40 - 52, Add
an accessible text alternative to both boolean branches in renderValue: label
the CheckIcon for the included state and the CloseIcon for the excluded state
using titleAccess, aria-label, or visually hidden text, while preserving their
existing visual styling and test IDs.

};

return (
<Box sx={{ width: '100%', my: 4 }}>
{title && (
<Typography
variant="h4"
component="h2"
sx={{
mb: 3,
fontWeight: 700,
fontFamily: 'Qanelas Soft, sans-serif' // Figma semantic typography mapping
}}
>
{title}
</Typography>
)}
<StyledTableContainer>
<Table sx={{ minWidth: 650 }} aria-label="subscription comparison table">
<TableHead>
<StyledHeaderRow>
<StyledTableCell sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
{featuresLabel}
</StyledTableCell>
<StyledTableCell align="center" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
{freePlanLabel}
<Box sx={{ mt: 1 }}>
<Button size="small" variant="outlined" onClick={() => onPlanSelect?.('free')}>
{freePlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
<StyledTableCell align="center" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
{teamPlanLabel}
<Box sx={{ mt: 1 }}>
<Button
size="small"
variant="contained"
color="primary"
onClick={() => onPlanSelect?.('team')}
>
{teamPlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
<StyledTableCell align="center" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
{enterprisePlanLabel}
<Box sx={{ mt: 1 }}>
<Button
size="small"
variant="contained"
color="secondary"
onClick={() => onPlanSelect?.('enterprise')}
>
{enterprisePlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
</StyledHeaderRow>
</TableHead>
<TableBody>
{features.map((row, index) => (
<TableRow
key={index}
sx={{
'&:last-child td, &:last-child th': { border: 0 },
'&:hover': { backgroundColor: 'action.hover' }
}}
>
<FeatureHeaderCell component="th" scope="row">
{row.featureName}
</FeatureHeaderCell>
<StyledTableCell align="center">{renderValue(row.freePlan)}</StyledTableCell>
<StyledTableCell align="center">{renderValue(row.teamPlan)}</StyledTableCell>
<StyledTableCell align="center">{renderValue(row.enterprisePlan)}</StyledTableCell>
</TableRow>
))}
</TableBody>
</Table>
</StyledTableContainer>
</Box>
);
};

SubscriptionTable.displayName = 'SubscriptionTable';
1 change: 1 addition & 0 deletions src/custom/SubscriptionTable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SubscriptionTable';
29 changes: 29 additions & 0 deletions src/custom/SubscriptionTable/style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Paper, TableCell, TableRow } from '@mui/material';
import { styled } from '@mui/material/styles';

// Custom Styled Container aligned with Sistent Theme
export const StyledTableContainer = styled(Paper)(({ theme }) => ({
borderRadius: theme.shape.borderRadius * 2,
overflow: 'hidden',
boxShadow: theme.shadows[2],
border: `1px solid ${theme.palette.divider}`
}));
Comment on lines +5 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow narrow viewports to scroll the table horizontally.

overflow: 'hidden' clips the 650px-minimum-width table on small screens, preventing access to later plan columns.

Proposed fix
-  overflow: 'hidden',
+  overflowX: 'auto',
+  overflowY: 'hidden',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const StyledTableContainer = styled(Paper)(({ theme }) => ({
borderRadius: theme.shape.borderRadius * 2,
overflow: 'hidden',
boxShadow: theme.shadows[2],
border: `1px solid ${theme.palette.divider}`
}));
export const StyledTableContainer = styled(Paper)(({ theme }) => ({
borderRadius: theme.shape.borderRadius * 2,
overflowX: 'auto',
overflowY: 'hidden',
boxShadow: theme.shadows[2],
border: `1px solid ${theme.palette.divider}`
}));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/custom/SubscriptionTable/style.tsx` around lines 5 - 10, Update the
StyledTableContainer styles to allow horizontal scrolling on narrow viewports
instead of clipping the table with overflow hidden. Preserve the existing visual
styles and ensure the 650px-minimum-width table remains accessible, including
later plan columns.


// Custom Styled Header Row
export const StyledHeaderRow = styled(TableRow)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'light' ? '#f9fafb' : '#1e1e1e'
}));

// Custom Styled Table Cell for Typography token enforcement
export const StyledTableCell = styled(TableCell)(({ theme }) => ({
fontFamily: 'Open Sans, sans-serif',
borderColor: theme.palette.divider,
fontWeight: 500
}));

// Feature Name Column styling (High Emphasis)
export const FeatureHeaderCell = styled(StyledTableCell)(() => ({
fontFamily: 'Qanelas Soft, sans-serif',
fontWeight: 700,
color: 'text.primary'
}));
Comment on lines +13 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use Sistent semantic palette and typography tokens throughout this component. color: 'text.primary' in styled() is emitted as a literal CSS value and is not resolved through MUI’s sx token parser, while the raw hex colors and font-family overrides bypass theme configuration.

  • src/custom/SubscriptionTable/style.tsx#L13-L29: derive header colors, feature-cell color, and typography from Sistent theme exports; use theme.palette.text.primary (or the Sistent equivalent) rather than the literal text.primary.
  • src/custom/SubscriptionTable/SubscriptionTable.tsx#L49-L65: replace literal typography weight/family styling with the appropriate Sistent typography token.
  • src/custom/SubscriptionTable/SubscriptionTable.tsx#L74-L104: apply the same tokenized typography to plan header cells.

As per coding guidelines, “Theme-aware UI must use Sistent theme exports and semantic palette tokens rather than raw MUI defaults.”

📍 Affects 2 files
  • src/custom/SubscriptionTable/style.tsx#L13-L29 (this comment)
  • src/custom/SubscriptionTable/SubscriptionTable.tsx#L49-L65
  • src/custom/SubscriptionTable/SubscriptionTable.tsx#L74-L104
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/custom/SubscriptionTable/style.tsx` around lines 13 - 29, Replace raw
colors and font-family/weight overrides with Sistent theme palette and
typography tokens across all affected sites: in
src/custom/SubscriptionTable/style.tsx lines 13-29, update StyledHeaderRow,
StyledTableCell, and FeatureHeaderCell to derive colors and typography from the
theme, including theme.palette.text.primary or its Sistent equivalent; in
src/custom/SubscriptionTable/SubscriptionTable.tsx lines 49-65 and 74-104, apply
the appropriate Sistent typography token to the feature and plan header cells
instead of literal family and weight values.

Source: Coding guidelines

1 change: 1 addition & 0 deletions src/custom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './Markdown';
export * from './Modal';
export * from './RJSFFormWrapper';
export * from './StyledAccordion';
export * from './SubscriptionTable';
3 changes: 3 additions & 0 deletions src/custom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import ResponsiveDataTable, {
} from './ResponsiveDataTable';
import SearchBar, { SearchBarProps } from './SearchBar';
import { StyledCardProps } from './StyledCard/StyledCard';
import { SubscriptionTable } from './SubscriptionTable';
import { getCopyDeepLinkAction, TableAction } from './TableActions';
import { TeamTable, TeamTableConfiguration } from './TeamTable';
import { TooltipIcon } from './TooltipIconButton';
Expand Down Expand Up @@ -124,6 +125,7 @@ export {
StyledDialogActions,
StyledDialogContent,
StyledDialogTitle,
SubscriptionTable,
TeamTable,
TeamTableConfiguration,
TooltipIcon,
Expand Down Expand Up @@ -187,5 +189,6 @@ export * from './permissions';
export * from './ResourceDetailFormatters';
export * from './RJSFFormWrapper';
export * from './ShareModal';
export * from './SubscriptionTable';
export * from './UserSearchField';
export * from './Workspaces';
Loading