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
132 changes: 132 additions & 0 deletions src/custom/SubscriptionTable/SubscriptionTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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';
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';


// Strict TypeScript interfaces to handle dynamic table data
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 +7 to +31

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>
);
};

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}`
}));

// 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'
}));
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 @@ -44,6 +44,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 @@ -111,6 +112,7 @@ export {
StyledDialogActions,
StyledDialogContent,
StyledDialogTitle,
SubscriptionTable,
TeamTable,
TeamTableConfiguration,
TooltipIcon,
Expand Down Expand Up @@ -173,5 +175,6 @@ export * from './permissions';
export * from './ResourceDetailFormatters';
export * from './RJSFFormWrapper';
export * from './ShareModal';
export * from './SubscriptionTable';
export * from './UserSearchField';
export * from './Workspaces';
Loading