fix: show correct highlighting in pricing comparison table
This commit is contained in:
@@ -8,10 +8,10 @@ type FrequencyValue = "monthly" | "annually";
|
|||||||
interface PlanFeature {
|
interface PlanFeature {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
free: string | boolean | { text: string; highlight?: string };
|
free: string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean };
|
||||||
teams: string | boolean | { text: string; highlight?: string };
|
teams: string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean };
|
||||||
pro: string | boolean | { text: string; highlight?: string };
|
pro: string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean };
|
||||||
enterprise: string | boolean | { text: string; highlight?: string };
|
enterprise: string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeatureSection {
|
interface FeatureSection {
|
||||||
@@ -71,34 +71,34 @@ const FeatureComparisonTable = ({
|
|||||||
{
|
{
|
||||||
key: "boards",
|
key: "boards",
|
||||||
label: t`Boards`,
|
label: t`Boards`,
|
||||||
free: { text: t`Unlimited boards`, highlight: "Unlimited" },
|
free: { text: t`Unlimited boards`, highlightFirstWord: true },
|
||||||
teams: { text: t`Unlimited boards`, highlight: "Unlimited" },
|
teams: { text: t`Unlimited boards`, highlightFirstWord: true },
|
||||||
pro: { text: t`Unlimited boards`, highlight: "Unlimited" },
|
pro: { text: t`Unlimited boards`, highlightFirstWord: true },
|
||||||
enterprise: { text: t`Unlimited boards`, highlight: "Unlimited" },
|
enterprise: { text: t`Unlimited boards`, highlightFirstWord: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "members",
|
key: "members",
|
||||||
label: t`Members`,
|
label: t`Members`,
|
||||||
free: { text: t`1 user`, highlight: "1" },
|
free: { text: t`1 user`, highlightFirstWord: true },
|
||||||
teams: { text: t`Per-seat pricing`, highlight: "Per-seat" },
|
teams: { text: t`Per-seat pricing`, highlightFirstWord: true },
|
||||||
pro: { text: t`Unlimited members`, highlight: "Unlimited" },
|
pro: { text: t`Unlimited members`, highlightFirstWord: true },
|
||||||
enterprise: { text: t`Unlimited members`, highlight: "Unlimited" },
|
enterprise: { text: t`Unlimited members`, highlightFirstWord: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "file-uploads",
|
key: "file-uploads",
|
||||||
label: t`File uploads`,
|
label: t`File uploads`,
|
||||||
free: { text: t`10mb file uploads`, highlight: "10mb" },
|
free: { text: t`10mb file uploads`, highlightFirstWord: true },
|
||||||
teams: { text: t`Unlimited file uploads`, highlight: "Unlimited" },
|
teams: { text: t`Unlimited file uploads`, highlightFirstWord: true },
|
||||||
pro: { text: t`Unlimited file uploads`, highlight: "Unlimited" },
|
pro: { text: t`Unlimited file uploads`, highlightFirstWord: true },
|
||||||
enterprise: { text: t`Unlimited file uploads`, highlight: "Unlimited" },
|
enterprise: { text: t`Unlimited file uploads`, highlightFirstWord: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "workspace-username",
|
key: "workspace-username",
|
||||||
label: t`Workspace username`,
|
label: t`Workspace username`,
|
||||||
free: { text: t`Default username`, highlight: "Default" },
|
free: { text: t`Default username`, highlightFirstWord: true },
|
||||||
teams: { text: t`Default username`, highlight: "Default" },
|
teams: { text: t`Default username`, highlightFirstWord: true },
|
||||||
pro: { text: t`Custom username`, highlight: "Custom" },
|
pro: { text: t`Custom username`, highlightFirstWord: true },
|
||||||
enterprise: { text: t`Custom username`, highlight: "Custom" },
|
enterprise: { text: t`Custom username`, highlightFirstWord: true },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -236,9 +236,9 @@ const FeatureComparisonTable = ({
|
|||||||
key: "rest-api",
|
key: "rest-api",
|
||||||
label: t`API`,
|
label: t`API`,
|
||||||
free: { text: t`Limited API access` },
|
free: { text: t`Limited API access` },
|
||||||
teams: { text: t`Full API access` },
|
teams: { text: t`Full API access`, highlight: true },
|
||||||
pro: { text: t`Full API access` },
|
pro: { text: t`Full API access`, highlight: true },
|
||||||
enterprise: { text: t`Full API access` },
|
enterprise: { text: t`Full API access`, highlight: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "self-hostable",
|
key: "self-hostable",
|
||||||
@@ -254,18 +254,18 @@ const FeatureComparisonTable = ({
|
|||||||
{
|
{
|
||||||
key: "sso",
|
key: "sso",
|
||||||
label: t`SSO`,
|
label: t`SSO`,
|
||||||
free: { text: t`Google SSO` },
|
free: { text: t`Google SSO`, highlight: true },
|
||||||
teams: { text: t`Google SSO` },
|
teams: { text: t`Google SSO`, highlight: true },
|
||||||
pro: { text: t`Google SSO` },
|
pro: { text: t`Google SSO`, highlight: true },
|
||||||
enterprise: { text: t`Google SSO + SAML` },
|
enterprise: { text: t`Google SSO + SAML`, highlight: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "admin-roles",
|
key: "admin-roles",
|
||||||
label: t`Admin roles`,
|
label: t`Admin roles`,
|
||||||
free: { text: t`Admin roles` },
|
free: { text: t`Admin roles`, highlight: true },
|
||||||
teams: { text: t`Admin roles` },
|
teams: { text: t`Admin roles`, highlight: true },
|
||||||
pro: { text: t`Admin roles` },
|
pro: { text: t`Admin roles`, highlight: true },
|
||||||
enterprise: { text: t`Advanced admin roles` },
|
enterprise: { text: t`Advanced admin roles`, highlight: true },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -334,51 +334,29 @@ const FeatureComparisonTable = ({
|
|||||||
{ id: "enterprise", name: t`Enterprise`, tierId: "tier-enterprise" },
|
{ id: "enterprise", name: t`Enterprise`, tierId: "tier-enterprise" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const isHighlightValue = (
|
|
||||||
value: string | boolean | { text: string; highlight?: string },
|
|
||||||
): value is { text: string; highlight?: string } => {
|
|
||||||
return typeof value === "object" && value !== null && "text" in value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldHighlight = (text: string): boolean => {
|
|
||||||
const lowerText = text.toLowerCase();
|
|
||||||
return ["unlimited", "multiple", "per-seat", "custom", "mentions", "full", "sso", "admin"].some(
|
|
||||||
(keyword) => lowerText.includes(keyword),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CellValue = ({
|
const CellValue = ({
|
||||||
value,
|
value,
|
||||||
label,
|
label,
|
||||||
}: {
|
}: {
|
||||||
value: string | boolean | { text: string; highlight?: string };
|
value: string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean };
|
||||||
label: string;
|
label: string;
|
||||||
}) => {
|
}) => {
|
||||||
// Handle object with text and highlight
|
if (typeof value === "object" && value !== null) {
|
||||||
if (isHighlightValue(value)) {
|
const { text, highlight, highlightFirstWord } = value;
|
||||||
const { text, highlight } = value;
|
|
||||||
const isUnlimited = shouldHighlight(text);
|
|
||||||
|
|
||||||
let displayText;
|
if (highlightFirstWord) {
|
||||||
if (highlight) {
|
const spaceIndex = text.indexOf(" ");
|
||||||
const parts = text.split(new RegExp(`(${highlight})`, "gi"));
|
const firstWord = spaceIndex === -1 ? text : text.slice(0, spaceIndex);
|
||||||
displayText = (
|
const rest = spaceIndex === -1 ? "" : text.slice(spaceIndex);
|
||||||
<>
|
return (
|
||||||
{parts.map((part, index) =>
|
<div className="flex items-center gap-2.5">
|
||||||
part.toLowerCase() === highlight.toLowerCase() ? (
|
<HiCheckCircle className="mt-0.5 h-4 w-4 shrink-0 text-light-1000 dark:text-dark-1000" />
|
||||||
<span key={index} className="text-light-1000 dark:text-dark-1000">
|
<span className="text-sm font-medium">
|
||||||
{part}
|
<span className="text-light-1000 dark:text-dark-1000">{firstWord}</span>
|
||||||
</span>
|
<span className="text-light-950 dark:text-dark-800">{rest}</span>
|
||||||
) : (
|
</span>
|
||||||
<span key={index} className="text-light-950 dark:text-dark-800">
|
</div>
|
||||||
{part}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
displayText = text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -386,7 +364,7 @@ const FeatureComparisonTable = ({
|
|||||||
<HiCheckCircle
|
<HiCheckCircle
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"mt-0.5 h-4 w-4 shrink-0",
|
"mt-0.5 h-4 w-4 shrink-0",
|
||||||
isUnlimited
|
highlight
|
||||||
? "text-light-1000 dark:text-dark-1000"
|
? "text-light-1000 dark:text-dark-1000"
|
||||||
: "text-light-400 dark:text-dark-600",
|
: "text-light-400 dark:text-dark-600",
|
||||||
)}
|
)}
|
||||||
@@ -394,44 +372,18 @@ const FeatureComparisonTable = ({
|
|||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"text-sm font-medium",
|
"text-sm font-medium",
|
||||||
isUnlimited
|
highlight
|
||||||
? "text-light-1000 dark:text-dark-950"
|
? "text-light-1000 dark:text-dark-950"
|
||||||
: "text-light-950 dark:text-dark-800",
|
: "text-light-950 dark:text-dark-800",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{displayText}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "string") {
|
|
||||||
const isUnlimited = shouldHighlight(value);
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2.5">
|
|
||||||
<HiCheckCircle
|
|
||||||
className={twMerge(
|
|
||||||
"mt-0.5 h-4 w-4 shrink-0",
|
|
||||||
isUnlimited
|
|
||||||
? "text-light-1000 dark:text-dark-1000"
|
|
||||||
: "text-light-400 dark:text-dark-600",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={twMerge(
|
|
||||||
"text-sm font-medium",
|
|
||||||
isUnlimited
|
|
||||||
? "text-light-1000 dark:text-dark-950"
|
|
||||||
: "text-light-950 dark:text-dark-800",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// Boolean true - show checkmark icon
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<HiCheckCircle className="mt-0.5 h-4 w-4 shrink-0 text-light-1000 dark:text-dark-1000" />
|
<HiCheckCircle className="mt-0.5 h-4 w-4 shrink-0 text-light-1000 dark:text-dark-1000" />
|
||||||
@@ -441,7 +393,7 @@ const FeatureComparisonTable = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Boolean false - show cross icon
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<HiXCircle className="mt-0.5 h-4 w-4 shrink-0 text-light-400 dark:text-dark-600" />
|
<HiXCircle className="mt-0.5 h-4 w-4 shrink-0 text-light-400 dark:text-dark-600" />
|
||||||
@@ -455,7 +407,7 @@ const FeatureComparisonTable = ({
|
|||||||
const getFeatureValue = (
|
const getFeatureValue = (
|
||||||
feature: PlanFeature,
|
feature: PlanFeature,
|
||||||
planId: string,
|
planId: string,
|
||||||
): string | boolean | { text: string; highlight?: string } => {
|
): string | boolean | { text: string; highlight?: boolean; highlightFirstWord?: boolean } => {
|
||||||
switch (planId) {
|
switch (planId) {
|
||||||
case "free":
|
case "free":
|
||||||
return feature.free;
|
return feature.free;
|
||||||
|
|||||||
Reference in New Issue
Block a user