Thanks to visit codestin.com
Credit goes to github.com

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
113 changes: 78 additions & 35 deletions site/src/components/Paywall/Paywall.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { PremiumBadge } from "#/components/Badges/Badges";
import { expect, within } from "storybook/test";
import {
Paywall,
PaywallContent,
Expand All @@ -8,11 +8,13 @@ import {
PaywallFeature,
PaywallFeatures,
PaywallHeading,
PaywallSeparator,
PaywallStack,
PaywallSupergraphic,
PaywallTitle,
} from "./Paywall";

const CTA_HREF = "https://coder.com/pricing#compare-plans";

const meta: Meta<typeof Paywall> = {
title: "components/Paywall",
component: Paywall,
Expand All @@ -21,40 +23,81 @@ const meta: Meta<typeof Paywall> = {
export default meta;
type Story = StoryObj<typeof Paywall>;

const paywallChildren = (
<>
<PaywallContent>
<PaywallHeading>
<PaywallTitle>Black Lotus</PaywallTitle>
</PaywallHeading>
<PaywallDescription>
Adds 3 mana of any single color of your choice to your mana pool, then
is discarded. Tapping this artifact can be played as an interrupt.
</PaywallDescription>
</PaywallContent>
<PaywallStack>
<PaywallFeatures>
<PaywallFeature>High availability & workspace proxies</PaywallFeature>
<PaywallFeature>Multi-org & role-based access control</PaywallFeature>
<PaywallFeature>24x7 global support with SLA</PaywallFeature>
<PaywallFeature>
Unlimited Git & external auth integrations
</PaywallFeature>
</PaywallFeatures>
<PaywallCTA href={CTA_HREF}>Start trial for free</PaywallCTA>
</PaywallStack>
</>
);

const withSupergraphicChildren = (
<>
<PaywallSupergraphic />
{paywallChildren}
</>
);

const expectPaywallContent = async (canvasElement: HTMLElement) => {
const canvas = within(canvasElement);

await expect(
canvas.getByRole("heading", { name: "Black Lotus" }),
).toBeVisible();
await expect(
canvas.getByText(/Adds 3 mana of any single color/),
).toBeVisible();

await expect(canvas.getAllByRole("listitem")).toHaveLength(4);
await expect(canvas.getByText("24x7 global support with SLA")).toBeVisible();

const cta = canvas.getByRole("link", { name: "Start trial for free" });
await expect(cta).toBeVisible();
await expect(cta).toHaveAttribute("href", CTA_HREF);
await expect(cta).toHaveAttribute("target", "_blank");
};

export const Default: Story = {
args: {
children: (
<>
<PaywallContent>
<PaywallHeading>
<PaywallTitle>Black Lotus</PaywallTitle>
<PremiumBadge />
</PaywallHeading>
<PaywallDescription>
Adds 3 mana of any single color of your choice to your mana pool,
then is discarded. Tapping this artifact can be played as an
interrupt.
</PaywallDescription>
</PaywallContent>
<PaywallSeparator />
<PaywallStack>
<PaywallFeatures>
<PaywallFeature>
High availability & workspace proxies
</PaywallFeature>
<PaywallFeature>
Multi-org & role-based access control
</PaywallFeature>
<PaywallFeature>24x7 global support with SLA</PaywallFeature>
<PaywallFeature>
Unlimited Git & external auth integrations
</PaywallFeature>
</PaywallFeatures>
<PaywallCTA href="https://coder.com/pricing#compare-plans">
Learn about Premium
</PaywallCTA>
</PaywallStack>
</>
),
children: paywallChildren,
},
play: async ({ canvasElement }) => {
await expectPaywallContent(canvasElement);
},
};

export const WithSupergraphic: Story = {
args: {
children: withSupergraphicChildren,
},
play: async ({ canvasElement }) => {
await expectPaywallContent(canvasElement);
},
};

export const WithSupergraphicLight: Story = {
args: {
children: withSupergraphicChildren,
},
parameters: { themes: { themeOverride: "light" } },
play: async ({ canvasElement }) => {
await expectPaywallContent(canvasElement);
},
};
73 changes: 52 additions & 21 deletions site/src/components/Paywall/Paywall.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { CircleCheckBigIcon } from "lucide-react";
import { ArrowRightIcon, CheckIcon, ExternalLinkIcon } from "lucide-react";
import type React from "react";
import type { FC } from "react";
import { type LinkProps, Link as RouterLink } from "react-router";
import { Button } from "#/components/Button/Button";
import { Supergraphic } from "#/components/Supergraphic/Supergraphic";
import { cn } from "#/utils/cn";

export const PREMIUM_FEATURES = [
"High availability & workspace proxies",
"Multi-org & role-based access control",
"24x7 global support with SLA",
"Unlimited Git & external auth integrations",
];

export const PREMIUM_PAGE_PATH = "/deployment/premium";
export const PREMIUM_PRICING_LINK = "https://coder.com/pricing";
export const PREMIUM_DEFAULT_DESCRIPTION =
"You need a Premium license to use this feature.";
export const PREMIUM_DEFAULT_HERO = "Get Access with Coder Premium";

export type PaywallProps = React.ComponentProps<"div"> & {
message: string;
description?: string;
compact?: boolean;
canViewPremium: boolean;
features?: string[];
};

export const Paywall = ({
className,
children,
Expand All @@ -13,8 +35,9 @@ export const Paywall = ({
return (
<div
className={cn(
"relative isolate overflow-hidden",
"flex flex-row items-center justify-center min-h-[280px] p-4 rounded-lg gap-8",
"border border-solid border-border-magenta bg-[linear-gradient(160deg,transparent,hsl(var(--surface-magenta)))]",
"border border-solid border-border-default bg-surface-secondary",
className,
)}
{...props}
Expand All @@ -26,9 +49,20 @@ export const Paywall = ({

export const PaywallContent: FC<React.ComponentProps<"div">> = ({
children,
className,
...props
}) => {
return <div {...props}>{children}</div>;
return (
<div
className={cn(
"flex w-1/2 flex-col items-center justify-center px-6 text-center",
className,
)}
{...props}
>
{children}
</div>
);
};

export const PaywallHeading: FC<React.ComponentProps<"div">> = ({
Expand All @@ -38,7 +72,10 @@ export const PaywallHeading: FC<React.ComponentProps<"div">> = ({
}) => {
return (
<div
className={cn("flex flex-row gap-4 items-center mb-6", className)}
className={cn(
"flex flex-row gap-4 items-center justify-center mb-6",
className,
)}
{...props}
>
{children}
Expand All @@ -53,7 +90,7 @@ export const PaywallTitle: FC<React.ComponentProps<"h5">> = ({
}) => {
return (
<h5
className={cn("font-semibold font-inherit text-xl m-0", className)}
className={cn("font-semibold font-inherit text-xl mr-4", className)}
{...props}
>
{children}
Expand All @@ -68,7 +105,7 @@ export const PaywallDescription: FC<React.ComponentProps<"p">> = ({
}) => {
return (
<p
className={cn("font-inherit max-w-md text-sm mb-4", className)}
className={cn("font-inherit max-w-md text-sm mb-4 mr-4", className)}
{...props}
>
{children}
Expand All @@ -87,27 +124,19 @@ export const PaywallDocumentationLink: FC<React.ComponentProps<"a">> = ({
href={href}
target="_blank"
rel="noreferrer"
className={cn("text-content-link font-medium", className)}
className={cn("text-content-link font-medium mr-4", className)}
{...props}
>
{children}
{children} <ExternalLinkIcon className="size-icon-xs" />
</a>
);
};

export const PaywallSeparator: FC<React.ComponentProps<"hr">> = ({
export const PaywallSupergraphic: FC<React.ComponentProps<"div">> = ({
className,
...props
}) => {
return (
<hr
className={cn(
"w-0 h-[220px] border-0 border-l border-highlight-magenta/50 ml-2 mr-0",
className,
)}
{...props}
/>
);
return <Supergraphic className={cn("left-0 w-1/2", className)} {...props} />;
};

export const PaywallStack: FC<React.ComponentProps<"div">> = ({
Expand All @@ -117,7 +146,7 @@ export const PaywallStack: FC<React.ComponentProps<"div">> = ({
}) => {
return (
<div
className={cn("flex flex-col items-start gap-6", className)}
className={cn("flex flex-1 flex-col items-start gap-6", className)}
{...props}
>
{children}
Expand Down Expand Up @@ -170,6 +199,7 @@ export const PaywallCTA: FC<React.ComponentProps<"a">> = ({
className={cn("mx-7", className)}
{...props}
>
<ArrowRightIcon aria-hidden="true" />
{children}
</a>
</Button>
Expand All @@ -184,6 +214,7 @@ export const PaywallCTALink: FC<LinkProps> = ({
return (
<Button asChild>
<RouterLink className={cn("mx-7", className)} {...props}>
<ArrowRightIcon aria-hidden="true" />
{children}
</RouterLink>
</Button>
Expand Down Expand Up @@ -213,9 +244,9 @@ const FeatureIcon: FC<React.ComponentProps<"svg">> = ({
...props
}) => {
return (
<CircleCheckBigIcon
<CheckIcon
aria-hidden="true"
className={cn("size-icon-sm text-border-magenta", className)}
className={cn("size-icon-sm", className)}
{...props}
/>
);
Expand Down
73 changes: 15 additions & 58 deletions site/src/components/Paywall/PaywallAIGovernance.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,21 @@
import { PremiumBadge } from "#/components/Badges/Badges";
import { docs } from "#/utils/docs";
import {
Paywall,
PaywallContent,
PaywallCTA,
PaywallDescription,
PaywallDocumentationLink,
PaywallFeature,
PaywallFeatures,
PaywallHeading,
PaywallSeparator,
PaywallStack,
PaywallTitle,
} from "./Paywall";
import { PaywallSmall } from "#/components/Paywall/PaywallSmall";

const PAYWALL_AIGATEWAY_DESCRIPTION =
"AI Gateway provides auditable visibility into user prompts and LLM tool calls from developer tools within Coder Workspaces. AI Gateway requires a Premium license with AI Governance add-on.";

const PaywallAIGovernance = () => {
return (
<Paywall>
<PaywallContent>
<PaywallHeading>
<PaywallTitle>AI Gateway</PaywallTitle>
<PremiumBadge>AI Governance</PremiumBadge>
</PaywallHeading>
<PaywallDescription>
AI Gateway provides auditable visibility into user prompts and LLM
tool calls from developer tools within Coder Workspaces. AI Gateway
requires a Premium license with AI Governance add-on.
</PaywallDescription>
<PaywallDocumentationLink href={docs("/ai-coder/ai-governance")}>
Learn about AI Governance
</PaywallDocumentationLink>
</PaywallContent>
<PaywallSeparator />
<PaywallStack>
<PaywallFeatures>
<PaywallFeature>Auditable history of user prompts</PaywallFeature>
<PaywallFeature>Logged LLM tool invocations</PaywallFeature>
<PaywallFeature>
Token usage and consumption visibility
</PaywallFeature>
<PaywallFeature>Centrally-managed MCP servers</PaywallFeature>
<PaywallFeature>
<span>
Visit{" "}
<a
href={docs("/ai-coder/ai-gateway")}
target="_blank"
rel="noreferrer"
className="text-content-link"
>
AI Gateway Docs
</a>
</span>
</PaywallFeature>
</PaywallFeatures>
<PaywallCTA href="https://coder.com/contact/sales">
Contact Sales
</PaywallCTA>
</PaywallStack>
</Paywall>
<PaywallSmall
message="AI Gateway"
canViewPremium
description={PAYWALL_AIGATEWAY_DESCRIPTION}
features={[
"Auditable history of user prompts",
"Logged LLM tool invocations",
"Token usage and consumption visibility",
"Centrally-managed MCP servers",
]}
/>
);
};

Expand Down
Loading
Loading