Skip to content
9 changes: 4 additions & 5 deletions packages/shared/src/components/CustomFeedEmptyScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@ import { LogEvent, TargetId } from '../lib/log';
import { Button } from './buttons/Button';
import { useConditionalFeature, usePlusSubscription } from '../hooks';
import { IconSize } from './Icon';
import { featurePlusCtaCopy } from '../lib/featureManagement';
import { featurePlusApiLanding } from '../lib/featureManagement';
import Link from './utilities/Link';

export const CustomFeedEmptyScreen = (): ReactElement => {
const { logSubscriptionEvent, isPlus } = usePlusSubscription();
const {
value: { full: plusCta },
} = useConditionalFeature({
feature: featurePlusCtaCopy,
const { value: isApiLanding } = useConditionalFeature({
feature: featurePlusApiLanding,
shouldEvaluate: !isPlus,
});
const plusCta = isApiLanding ? 'Get API Access' : 'Level Up with Plus';
const [selectedAlgo, setSelectedAlgo] = usePersistentContext(
DEFAULT_ALGORITHM_KEY,
DEFAULT_ALGORITHM_INDEX,
Expand Down
9 changes: 4 additions & 5 deletions packages/shared/src/components/PlusUserBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { DateFormat } from './utilities';
import { TimeFormatType } from '../lib/dateFormat';
import { usePlusSubscription } from '../hooks/usePlusSubscription';
import { LogEvent, TargetId } from '../lib/log';
import { featurePlusCtaCopy } from '../lib/featureManagement';
import { featurePlusApiLanding } from '../lib/featureManagement';
import { useConditionalFeature } from '../hooks';
import { IconSize } from './Icon';

Expand All @@ -31,12 +31,11 @@ export const PlusUserBadge = ({
size = IconSize.Size16,
}: Props): ReactElement | null => {
const { isPlus, logSubscriptionEvent } = usePlusSubscription();
const {
value: { full: plusCta },
} = useConditionalFeature({
feature: featurePlusCtaCopy,
const { value: isApiLanding } = useConditionalFeature({
feature: featurePlusApiLanding,
shouldEvaluate: !isPlus,
});
const plusCta = isApiLanding ? 'Get API Access' : 'Level Up with Plus';

if (!user.isPlus) {
return null;
Expand Down
12 changes: 8 additions & 4 deletions packages/shared/src/components/UpgradeToPlus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { LogEvent } from '../lib/log';
import { useAuthContext } from '../contexts/AuthContext';
import { AuthTriggers } from '../lib/auth';
import type { WithClassNameProps } from './utilities';
import { featurePlusCtaCopy } from '../lib/featureManagement';
import { featurePlusApiLanding } from '../lib/featureManagement';

type Props = {
iconOnly?: boolean;
Expand All @@ -37,11 +37,15 @@ export const UpgradeToPlus = ({
const isLaptopXL = useViewSize(ViewSize.LaptopXL);
const isFullCTAText = !isLaptop || isLaptopXL;
const { isPlus, logSubscriptionEvent } = usePlusSubscription();
const { value: ctaCopy } = useConditionalFeature({
feature: featurePlusCtaCopy,
const { value: isApiLanding } = useConditionalFeature({
feature: featurePlusApiLanding,
shouldEvaluate: !isPlus,
});
const ctaCopy = isApiLanding
? { full: 'Get API Access', short: 'API access' }
: { full: 'Level Up with Plus', short: 'Upgrade' };
const content = isFullCTAText ? ctaCopy.full : ctaCopy.short;
const defaultColor = isApiLanding ? ButtonColor.Bacon : ButtonColor.Avocado;

const onClick = useCallback(
(e: React.MouseEvent) => {
Expand Down Expand Up @@ -70,7 +74,7 @@ export const UpgradeToPlus = ({
className={classNames(!iconOnly && 'flex-1', className)}
icon={<DevPlusIcon />}
size={size}
color={ButtonColor.Avocado}
color={defaultColor}
variant={ButtonVariant.Primary}
onClick={onClick}
{...(variant && { variant, color })}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type { TargetType } from '../../lib/log';
import { LogEvent } from '../../lib/log';
import { useLogContext } from '../../contexts/LogContext';
import { useBoot } from '../../hooks';
import { useFeature } from '../GrowthBookProvider';
import { featurePlusApiLanding } from '../../lib/featureManagement';

type PlusBannerProps = Omit<MarketingCta, 'flags'> & {
targetType: TargetType;
Expand All @@ -31,10 +33,12 @@ const PlusMobileEntryBanner = ({
}: PlusBannerProps): ReactElement | null => {
const { logEvent } = useLogContext();
const { clearMarketingCta } = useBoot();
const isApiLanding = useFeature(featurePlusApiLanding);
if (!flags) {
return null;
}
const { leadIn, description, ctaText, ctaUrl } = flags;
const ctaColor = isApiLanding ? ButtonColor.Bacon : ButtonColor.Avocado;

const handleClose = () => {
logEvent({
Expand Down Expand Up @@ -85,7 +89,7 @@ const PlusMobileEntryBanner = ({
tag="a"
href={ctaUrl || '/plus'}
variant={ButtonVariant.Primary}
color={ButtonColor.Avocado}
color={ctaColor}
onClick={handleClick}
>
<Typography
Expand Down
6 changes: 5 additions & 1 deletion packages/shared/src/components/cards/plus/PlusGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { useBoot } from '../../../hooks';
import { LogEvent, TargetType } from '../../../lib/log';
import { useLogContext } from '../../../contexts/LogContext';
import { PlusItemStatus, PlusListItem } from '../../plus/PlusListItem';
import { useFeature } from '../../GrowthBookProvider';
import { featurePlusApiLanding } from '../../../lib/featureManagement';

const bulletPointsControl = [
{
Expand Down Expand Up @@ -40,11 +42,13 @@ const bulletPointsControl = [
const PlusGrid = ({ flags, campaignId }: MarketingCta) => {
const { logEvent } = useLogContext();
const { clearMarketingCta } = useBoot();
const isApiLanding = useFeature(featurePlusApiLanding);

if (!flags) {
return null;
}
const { title, description, ctaText, ctaUrl } = flags;
const ctaColor = isApiLanding ? ButtonColor.Bacon : ButtonColor.Avocado;

const handleClose = () => {
logEvent({
Expand Down Expand Up @@ -109,7 +113,7 @@ const PlusGrid = ({ flags, campaignId }: MarketingCta) => {
tag="a"
href={ctaUrl || '/plus'}
variant={ButtonVariant.Primary}
color={ButtonColor.Avocado}
color={ctaColor}
onClick={handleClick}
>
{ctaText}
Expand Down
107 changes: 107 additions & 0 deletions packages/shared/src/components/plus/PlusApiShowcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { ComponentType, ReactElement } from 'react';
import React, { useId } from 'react';
import {
Typography,
TypographyColor,
TypographyTag,
TypographyType,
} from '../typography/Typography';
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
import { anchorDefaultRel } from '../../lib/strings';
import { plusPublicApiDocs } from '../../lib/constants';
import type { IconProps } from '../Icon';
import { IconSize } from '../Icon';
import { AiIcon, MagicIcon, TerminalIcon } from '../icons';

type ShowcaseCard = {
title: string;
body: string;
icon: ComponentType<IconProps>;
iconClasses: string;
};

const showcaseCards: Array<ShowcaseCard> = [
{
title: 'Keep your coding agent current',
body: `LLMs stop learning the day they ship. Wire daily.dev in so your agent can reference current libraries, recent CVEs, and what senior devs are actually reading. Pre-built integrations for Claude Code, Cursor, Codex, and OpenClaw.`,
icon: AiIcon,
iconClasses: 'bg-overlay-float-water text-accent-water-default',
},
{
title: `Ship the internal tool you've been putting off`,
body: `The Slack bot for #engineering. The weekly team digest. The tech radar dashboard. Whatever's been sitting in your Notes doc is a few endpoints away.`,
icon: TerminalIcon,
iconClasses: 'bg-overlay-float-bun text-accent-bun-default',
},
{
title: 'Automate your own reading exactly how you want it',
body: `Mirror your personalized feed to Notion, Obsidian, or email. Get alerts when the topics you follow start trending. Your workflow, no copy-paste.`,
icon: MagicIcon,
iconClasses: 'bg-overlay-float-cabbage text-accent-cabbage-default',
},
];

const ShowcaseCardView = ({ card }: { card: ShowcaseCard }): ReactElement => {
const { icon: Icon, iconClasses } = card;

return (
<div className="flex flex-1 flex-col rounded-16 border border-border-subtlest-tertiary bg-surface-float p-6">
<div
className={`mb-4 flex size-12 items-center justify-center rounded-12 ${iconClasses}`}
>
<Icon secondary size={IconSize.Medium} />
</div>
<Typography
bold
className="mb-2"
color={TypographyColor.Primary}
tag={TypographyTag.H3}
type={TypographyType.Title3}
>
{card.title}
</Typography>
<Typography
color={TypographyColor.Tertiary}
type={TypographyType.Callout}
>
{card.body}
</Typography>
</div>
);
};

export const PlusApiShowcase = (): ReactElement => {
const id = useId();
const titleId = `${id}-title`;

return (
<section aria-labelledby={titleId} className="my-10">
<Typography
bold
className="mb-10 text-center"
id={titleId}
tag={TypographyTag.H2}
type={TypographyType.Title3}
>
What you can build with the API
</Typography>
<div className="mx-auto flex max-w-5xl flex-col gap-4 laptop:flex-row">
{showcaseCards.map((card) => (
<ShowcaseCardView key={card.title} card={card} />
))}
</div>
<div className="mt-8 flex justify-center">
<Button
tag="a"
href={plusPublicApiDocs}
target="_blank"
rel={anchorDefaultRel}
size={ButtonSize.Medium}
variant={ButtonVariant.Secondary}
>
Read the API docs
</Button>
</div>
</section>
);
};
17 changes: 12 additions & 5 deletions packages/shared/src/components/plus/PlusDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ import { usePlusSubscription } from '../../hooks';

import { PurchaseType } from '../../graphql/paddle';
import { PlusProductToggle } from './PlusProductToggle';
import { useFeature } from '../GrowthBookProvider';
import { featurePlusApiLanding } from '../../lib/featureManagement';

const PlusFAQs = dynamic(() => import('./PlusFAQ').then((mod) => mod.PlusFAQ));
const PlusApiShowcase = dynamic(() =>
import('./PlusApiShowcase').then((mod) => mod.PlusApiShowcase),
);

export const PlusDesktop = ({
shouldShowPlusHeader,
Expand All @@ -32,14 +37,15 @@ export const PlusDesktop = ({
query: { selectedPlan },
} = useRouter();
const { isPlus } = usePlusSubscription();
const isApiLanding = useFeature(featurePlusApiLanding);
const initialPaymentOption = selectedPlan ? `${selectedPlan}` : null;
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const ref = useRef();
const ref = useRef<HTMLDivElement>(null);

const onChangeCheckoutOption: OpenCheckoutFn = useCallback(
({ priceId, giftToUserId, quantity }) => {
setSelectedOption(priceId);
openCheckout({ priceId, giftToUserId, quantity });
openCheckout?.({ priceId, giftToUserId, quantity });
},
[openCheckout],
);
Expand All @@ -56,7 +62,7 @@ export const PlusDesktop = ({

const { priceId } = giftOneYear;
setSelectedOption(priceId);
openCheckout({ priceId, giftToUserId: giftToUser.id });
openCheckout?.({ priceId, giftToUserId: giftToUser.id });

return;
}
Expand All @@ -66,7 +72,7 @@ export const PlusDesktop = ({
// Auto-select if user is not plus or it is organization checkout
if (option && (!isPlus || isOrganization)) {
setSelectedOption(option);
openCheckout({ priceId: option });
openCheckout?.({ priceId: option });
}
}, [
giftOneYear,
Expand Down Expand Up @@ -98,7 +104,7 @@ export const PlusDesktop = ({
/>
)}
<PlusInfo
productOptions={productOptions}
productOptions={productOptions ?? []}
selectedOption={selectedOption}
onChange={onChangeCheckoutOption}
shouldShowPlusHeader={shouldShowPlusHeader}
Expand All @@ -120,6 +126,7 @@ export const PlusDesktop = ({
)}
</div>
</div>
{isApiLanding && !isOrganization && !giftToUser && <PlusApiShowcase />}
<PlusFAQs />
</>
);
Expand Down
13 changes: 11 additions & 2 deletions packages/shared/src/components/plus/PlusFAQ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import {
import { Accordion } from '../accordion';
import { anchorDefaultRel } from '../../lib/strings';
import { feedback } from '../../lib/constants';
import { plusFAQItems } from './common';
import { plusFAQItemsApi, plusFAQItemsControl } from './common';
import { useLogContext } from '../../contexts/LogContext';
import { LogEvent } from '../../lib/log';
import { useConditionalFeature } from '../../hooks';
import { featurePlusApiLanding } from '../../lib/featureManagement';
import { usePlusSubscription } from '../../hooks/usePlusSubscription';

interface FAQ {
question: string;
Expand Down Expand Up @@ -50,6 +53,12 @@ const FAQItem = ({ item }: { item: FAQ }): ReactElement => {
export const PlusFAQ = (): ReactElement => {
const id = useId();
const titleId = `${id}-title`;
const { isPlus } = usePlusSubscription();
const { value: isApiLanding } = useConditionalFeature({
feature: featurePlusApiLanding,
shouldEvaluate: !isPlus,
});
const items = isApiLanding ? plusFAQItemsApi : plusFAQItemsControl;
return (
<section aria-labelledby={titleId} className="my-10">
<Typography
Expand All @@ -62,7 +71,7 @@ export const PlusFAQ = (): ReactElement => {
Frequently asked questions
</Typography>
<div className="mx-auto flex max-w-3xl flex-col gap-4">
{plusFAQItems.map((item) => (
{items.map((item) => (
<FAQItem key={item.question} item={item} />
))}
</div>
Expand Down
Loading
Loading