Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const CommercialArt = memo(
return (
<CommercialRive
className={clsx(
"w-full max-w-[100px] mx-auto h-[80px]",
"w-full max-w-[100px] mx-auto h-[90px]",
props.className,
)}
/>
Expand Down
57 changes: 35 additions & 22 deletions apps/web/components/pages/HomePage/Pricing/CommercialCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Button, Switch } from "@cap/ui";
import {
faBriefcase,
faDownload,
faEdit,
faMinus,
faPlus,
faVideo,
Expand Down Expand Up @@ -115,30 +116,30 @@ export const CommercialCard = () => {
</div>

<div className="flex flex-wrap gap-5 justify-center items-center p-5 my-8 w-full rounded-xl border xs:gap-3 xs:p-3 xs:rounded-full xs:justify-between bg-gray-3 border-gray-4">
<div className="flex gap-3 justify-center items-center">
<p className="text-base text-gray-12">
<div className="flex gap-2 justify-center items-center">
<p className="text-sm text-gray-12">
{homepageCopy.pricing.commercial.labels.licenses}
</p>
<div className="flex items-center">
<Button
onClick={decrementLicenses}
className="px-1.5 py-1.5 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit"
className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit"
aria-label="Decrease license count"
>
<FontAwesomeIcon
icon={faMinus}
className="text-gray-1 size-3"
className="text-gray-1 size-2"
/>
</Button>
<span className="w-8 font-medium tabular-nums text-center text-gray-12">
<span className="w-5 font-medium tabular-nums text-center text-gray-12">
<NumberFlow value={licenses} />
</span>
<Button
onClick={incrementLicenses}
className="px-1.5 py-1.5 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit"
className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit"
aria-label="Increase license count"
>
<FontAwesomeIcon icon={faPlus} className="text-gray-1 size-3" />
<FontAwesomeIcon icon={faPlus} className="text-gray-1 size-2" />
</Button>
</div>
</div>
Expand All @@ -147,7 +148,7 @@ export const CommercialCard = () => {
<div className="flex gap-2 items-center">
<span
className={clsx(
"text-md",
"text-sm",
isYearly ? "font-medium text-gray-12" : "text-gray-10",
)}
>
Expand All @@ -161,7 +162,7 @@ export const CommercialCard = () => {
/>
<span
className={clsx(
"text-md",
"text-sm",
!isYearly ? "font-medium text-gray-12" : "text-gray-10",
)}
>
Expand All @@ -172,8 +173,16 @@ export const CommercialCard = () => {
</div>
</div>

<div className="mb-6">
<div className="space-y-6">
<ul className="space-y-3">
<li className="flex items-center text-sm text-gray-12">
<FontAwesomeIcon
icon={faEdit}
className="flex-shrink-0 mr-3 text-gray-12"
style={{ fontSize: "14px", minWidth: "14px" }}
/>
<span>Studio Mode with full editor</span>
</li>
<li className="flex items-center text-sm text-gray-12">
<FontAwesomeIcon
icon={faBriefcase}
Expand All @@ -197,7 +206,9 @@ export const CommercialCard = () => {
className="flex-shrink-0 mr-3 text-gray-12"
style={{ fontSize: "14px", minWidth: "14px" }}
/>
<span>Unlimited local recordings</span>
<span>
Unlimited local recordings, shareable links up to 5 minutes
</span>
</li>
<li className="flex items-center text-sm text-gray-12">
<FontAwesomeIcon
Expand All @@ -208,18 +219,20 @@ export const CommercialCard = () => {
<span>Export to MP4 or GIF</span>
</li>
</ul>
</div>

<Button
disabled={commercialLoading}
onClick={openCommercialCheckout}
variant="dark"
size="lg"
className="w-full font-medium"
aria-label="Purchase Commercial License"
>
{commercialLoading ? "Loading..." : homepageCopy.pricing.commercial.cta}
</Button>
<Button
disabled={commercialLoading}
onClick={openCommercialCheckout}
variant="dark"
size="lg"
className="w-full font-medium"
aria-label="Purchase Commercial License"
>
{commercialLoading
? "Loading..."
: homepageCopy.pricing.commercial.cta}
</Button>
</div>
</div>
);
};
45 changes: 45 additions & 0 deletions apps/web/components/pages/HomePage/Pricing/EnterpriseArt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Fit, Layout, useRive } from "@rive-app/react-canvas";
import clsx from "clsx";
import { forwardRef, memo, useImperativeHandle } from "react";

export interface EnterpriseArtRef {
playHoverAnimation: () => void;
playDefaultAnimation: () => void;
}

interface EnterpriseArtProps {
className?: string;
}

export const EnterpriseArt = memo(
forwardRef<EnterpriseArtRef, EnterpriseArtProps>(({ className }, ref) => {
const { rive, RiveComponent: EnterpriseRive } = useRive({
src: "/rive/pricing.riv",
artboard: "enterprise",
animations: "idle",
autoplay: false,
layout: new Layout({
fit: Fit.Contain,
}),
});

useImperativeHandle(ref, () => ({
playHoverAnimation: () => {
if (rive) {
rive.play("out");
}
},
playDefaultAnimation: () => {
if (rive) {
rive.play("idle");
}
},
}));

return (
<EnterpriseRive
className={clsx(className, "mx-auto w-full max-w-[200px] h-[120px]")}
/>
);
}),
);
109 changes: 109 additions & 0 deletions apps/web/components/pages/HomePage/Pricing/EnterpriseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Button } from "@cap/ui";
import {
faDownload,
faHandshake,
faServer,
faShield,
faUsers,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRef } from "react";
import { EnterpriseArt, type EnterpriseArtRef } from "./EnterpriseArt";

export const EnterpriseCard = () => {
const enterpriseArtRef = useRef<EnterpriseArtRef>(null);
// Enterprise features data
const enterpriseFeatures = [
Comment on lines +15 to +16
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove inline comment (repo rule: no comments in code)

Inline comments are disallowed per repo guidelines.

-  // Enterprise features data
   const enterpriseFeatures = [
📝 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
// Enterprise features data
const enterpriseFeatures = [
const enterpriseFeatures = [
🤖 Prompt for AI Agents
In apps/web/components/pages/HomePage/Pricing/EnterpriseCard.tsx around lines 15
to 16, remove the inline comment "// Enterprise features data" and, if needed
for clarity, rename the variable or add a descriptive variable name (e.g.,
enterpriseFeatures) so the code is self-explanatory; ensure no other inline
comments remain in that block to comply with the repo rule banning comments in
code.

{
icon: faShield,
label: "SLAs & Priority Support",
},
{
icon: faDownload,
label: "Loom Video Importer",
},
{
icon: faHandshake,
label: "Bulk Discounts",
},
{
icon: faServer,
label: "Managed self-hosting",
},
{
icon: faUsers,
label: "SAML SSO Login",
},
{
icon: faShield,
label: "Advanced Security Controls",
},
];

const handleBookCall = () => {
window.open("https://cal.com/cap.so/15min", "_blank");
};
Comment on lines +43 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Harden new‑tab open against tabnabbing

Use noopener/noreferrer (or clear opener) when opening external sites.

-  const handleBookCall = () => {
-    window.open("https://cal.com/cap.so/15min", "_blank");
-  };
+  const handleBookCall = () => {
+    const w = window.open(
+      "https://cal.com/cap.so/15min",
+      "_blank",
+      "noopener,noreferrer",
+    );
+    if (w) w.opener = null;
+  };
📝 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
const handleBookCall = () => {
window.open("https://cal.com/cap.so/15min", "_blank");
};
const handleBookCall = () => {
const w = window.open(
"https://cal.com/cap.so/15min",
"_blank",
"noopener,noreferrer",
);
if (w) w.opener = null;
};
🤖 Prompt for AI Agents
In apps/web/components/pages/HomePage/Pricing/EnterpriseCard.tsx around lines 43
to 45, the handler opens an external URL in a new tab without protection against
tabnabbing; update the handler to open the URL with noopener and noreferrer (or
clear window.opener) so the newly opened page cannot access the opener.
Concretely, call window.open with the appropriate features (e.g., include
"noopener,noreferrer") or immediately null out the returned window's opener (if
window.open returns a Window) to harden against tabnabbing and ensure external
links are safe.


return (
<div
onMouseEnter={() => {
enterpriseArtRef.current?.playHoverAnimation();
}}
onMouseLeave={() => {
enterpriseArtRef.current?.playDefaultAnimation();
}}
className="flex overflow-hidden relative flex-col flex-1 justify-between p-8 text-black rounded-2xl border shadow-lg bg-gray-1 border-gray-5"
>
<div className="flex relative z-10 flex-col flex-1 justify-between space-y-8 h-full">
<div>
<div className="space-y-5 min-h-fit">
<EnterpriseArt ref={enterpriseArtRef} />
<div>
<h3 className="mb-2 text-xl font-semibold text-center text-gray-12">
Cap for Enterprise
</h3>
<p className="mb-4 text-sm font-medium text-center text-gray-11">
Deploy Cap across your organization with enterprise-grade
features, dedicated support, and custom integrations.
</p>
</div>
</div>
</div>

<div className="flex flex-wrap items-center p-3 w-full rounded-full border bg-gray-3 border-gray-4">
<p className="w-full text-lg font-medium text-center text-black">
Contact us for a quote
</p>
</div>

<div className="space-y-6">
<ul className="space-y-3">
{enterpriseFeatures.map((feature) => (
<li
key={feature.label}
className="flex items-center text-sm text-gray-12"
>
<FontAwesomeIcon
icon={feature.icon}
className="flex-shrink-0 mr-3 text-gray-10"
style={{ fontSize: "14px", minWidth: "14px" }}
/>
<span className="text-gray-11">{feature.label}</span>
</li>
))}
</ul>

<Button
variant="gray"
size="lg"
onClick={handleBookCall}
className="w-full font-medium"
aria-label="Book a Call for Enterprise"
>
Book a Call
</Button>
</div>
</div>
</div>
);
};
Loading
Loading