@@ -134,16 +149,24 @@ export const PlusInfo = ({
const { giftOneYear, isOrganization, checkoutItemsLoading } =
usePaymentContext();
const { openModal } = useLazyModal();
- const { logSubscriptionEvent } = usePlusSubscription();
+ const { isPlus, logSubscriptionEvent } = usePlusSubscription();
const { giftToUser } = useGiftUserContext();
const [itemQuantity, setItemQuantity] = useState
(1);
+ const { value: isApiLanding } = useConditionalFeature({
+ feature: featurePlusApiLanding,
+ shouldEvaluate: !isPlus,
+ });
+
const plusType = getPlusType({
isGift: !!giftToUser,
isOrganization,
});
- const defaultCopy = defaultPlusInfoCopyControl[plusType];
+ const copySource = isApiLanding
+ ? plusInfoCopyApi
+ : defaultPlusInfoCopyControl;
+ const defaultCopy = copySource[plusType];
const titleCopy = title || defaultCopy.title;
const descriptionCopy = description || defaultCopy.description;
const subtitleCopy = subtitle || defaultCopy.subtitle;
@@ -151,11 +174,14 @@ export const PlusInfo = ({
const isOnboarding = router.pathname.startsWith('/onboarding');
const showBuyAsAGiftButton =
!giftToUser && showGiftButton && !!giftOneYear && !isOnboarding;
- const plusListContent = isOrganization ? (
-
- ) : (
-
- );
+ let plusListContent: ReactElement;
+ if (isOrganization) {
+ plusListContent = ;
+ } else if (isApiLanding && plusType === PlusType.Self) {
+ plusListContent = ;
+ } else {
+ plusListContent = ;
+ }
return (
<>
@@ -187,7 +213,7 @@ export const PlusInfo = ({
label="Team size"
className="mb-4"
itemQuantity={itemQuantity}
- selectedOption={selectedOption}
+ selectedOption={selectedOption ?? ''}
checkoutItemsLoading={checkoutItemsLoading ?? false}
setItemQuantity={setItemQuantity}
onChange={onChange}
@@ -247,9 +273,9 @@ export const PlusInfo = ({
giftToUser ? 'min-h-[3rem]' : 'min-h-[6.125rem]',
)}
>
- {giftToUser ? (
+ {giftToUser && giftOneYear ? (
diff --git a/packages/shared/src/components/plus/PlusList.tsx b/packages/shared/src/components/plus/PlusList.tsx
index 53fd5c64f51..afd541d243c 100644
--- a/packages/shared/src/components/plus/PlusList.tsx
+++ b/packages/shared/src/components/plus/PlusList.tsx
@@ -211,6 +211,63 @@ export const plusFeatureListControl: Array = [
export const plusFeatureList = plusFeatureListControl;
+const reframeControlItem = (
+ baseId: string,
+ label: string,
+ tooltip: string,
+): PlusItem => {
+ const base = plusFeatureListControl.find((item) => item.id === baseId);
+ if (!base) {
+ throw new Error(
+ `plusFeatureListControl is missing item with id: ${baseId}`,
+ );
+ }
+ return {
+ ...base,
+ label,
+ tooltip,
+ modalProps: base.modalProps
+ ? { ...base.modalProps, title: label, description: tooltip }
+ : undefined,
+ };
+};
+
+export const plusFeatureListApiFirst: Array = [
+ {
+ id: 'public-api',
+ label: 'Public API access',
+ status: PlusItemStatus.Ready,
+ highlight: true,
+ tooltip: `Endpoints for your feed, search, posts, and bookmarks. Plus pre-built integrations for Claude Code, Cursor, and Codex.`,
+ },
+ reframeControlItem(
+ 'custom feeds',
+ 'Custom feeds you can query',
+ `Filter feeds by tools, languages, and topics. Pull them into your agent or dashboard through the feeds endpoint.`,
+ ),
+ reframeControlItem(
+ 'clean titles',
+ 'AI-cleaned titles',
+ `AI rewrites clickbait and low-signal titles so your agents and digests ingest accurate metadata, not ragebait.`,
+ ),
+ reframeControlItem(
+ 'bookmark folders',
+ 'Bookmark folders',
+ `Organize posts into folders, then pull them via the bookmarks endpoint. Great for read-later apps, digests, or Notion mirrors.`,
+ ),
+ reframeControlItem(
+ 'keyword filter',
+ 'Keyword filters',
+ `Mute buzzwords once. They apply to every feed you query, so agents don't waste tokens on noise.`,
+ ),
+ {
+ id: 'plus-everything-else',
+ label: 'Everything else in daily.dev Plus',
+ status: PlusItemStatus.Ready,
+ tooltip: `Ad-free reading, auto-translate, presidential briefings, members-only Squad, and more. The full Plus experience, bundled with your API access.`,
+ },
+];
+
export const plusOrganizationFeatureList: Array = [
{
label: 'All premium features for every seat',
diff --git a/packages/shared/src/components/plus/PlusListItem.tsx b/packages/shared/src/components/plus/PlusListItem.tsx
index 1efffa44b5c..ac508f2ce31 100644
--- a/packages/shared/src/components/plus/PlusListItem.tsx
+++ b/packages/shared/src/components/plus/PlusListItem.tsx
@@ -27,6 +27,7 @@ export interface PlusItem {
id?: string;
icon?: ReactElement;
iconClasses?: string;
+ highlight?: boolean;
modalProps?: {
title: string;
description: string;
@@ -81,6 +82,7 @@ export const PlusListItem = ({
{...iconProps}
className={classNames(
'mr-1 mt-px inline-block',
+ item.highlight && 'text-action-plus-default',
iconProps?.className,
)}
/>
@@ -90,6 +92,10 @@ export const PlusListItem = ({
type={TypographyType.Body}
color={TypographyColor.Primary}
{...typographyProps}
+ {...(item.highlight && {
+ color: TypographyColor.Plus,
+ bold: true,
+ })}
className={classNames(
'-mt-px flex flex-1 flex-wrap items-baseline gap-2',
typographyProps?.className,
diff --git a/packages/shared/src/components/plus/PlusMobile.tsx b/packages/shared/src/components/plus/PlusMobile.tsx
index 90ce8304da9..1bd6ec2bcd9 100644
--- a/packages/shared/src/components/plus/PlusMobile.tsx
+++ b/packages/shared/src/components/plus/PlusMobile.tsx
@@ -11,19 +11,25 @@ import { plusUrl } from '../../lib/constants';
import { objectToQueryParams } from '../../lib';
import { PlusProductToggle } from './PlusProductToggle';
import { PurchaseType } from '../../graphql/paddle';
+import { useFeature } from '../GrowthBookProvider';
+import { featurePlusApiLanding } from '../../lib/featureManagement';
const PlusTrustRefund = dynamic(() =>
import('./PlusTrustRefund').then((mod) => mod.PlusTrustRefund),
);
const PlusFAQs = dynamic(() => import('./PlusFAQ').then((mod) => mod.PlusFAQ));
+const PlusApiShowcase = dynamic(() =>
+ import('./PlusApiShowcase').then((mod) => mod.PlusApiShowcase),
+);
export const PlusMobile = ({
shouldShowPlusHeader,
}: CommonPlusPageProps): ReactElement => {
const router = useRouter();
const { giftToUser } = useGiftUserContext();
- const { productOptions } = usePaymentContext();
+ const { productOptions, isOrganization } = usePaymentContext();
+ const isApiLanding = useFeature(featurePlusApiLanding);
const [selectedOption, setSelectedOption] = useState(null);
const selectionChange: OpenCheckoutFn = useCallback(({ priceId }) => {
@@ -31,10 +37,14 @@ export const PlusMobile = ({
}, []);
const onContinue = useCallback(() => {
- const params = objectToQueryParams({
- pid: selectedOption,
- gift: giftToUser?.id,
- });
+ const query: Record = {};
+ if (selectedOption) {
+ query.pid = selectedOption;
+ }
+ if (giftToUser?.id) {
+ query.gift = giftToUser.id;
+ }
+ const params = objectToQueryParams(query);
router.push(`${plusUrl}/payment?${params}`);
}, [router, giftToUser, selectedOption]);
@@ -66,13 +76,14 @@ export const PlusMobile = ({
/>
)}
+ {isApiLanding && !isOrganization && !giftToUser && }
);
diff --git a/packages/shared/src/components/plus/common.tsx b/packages/shared/src/components/plus/common.tsx
index b1faf4dbde8..24bb4a87aa0 100644
--- a/packages/shared/src/components/plus/common.tsx
+++ b/packages/shared/src/components/plus/common.tsx
@@ -11,7 +11,19 @@ type FAQItem = {
answer: React.ReactNode;
};
-export const plusFAQItems: FAQItem[] = [
+export const plusFAQItemsControl: FAQItem[] = [
+ {
+ question: 'Can I use my Plus membership on multiple devices?',
+ answer: `Yes! Your Plus benefits are tied to your account, so you can use them across all your devices (desktop, mobile, or tablet) just by logging in.`,
+ },
+ {
+ question: 'How does billing work?',
+ answer: `We offer monthly and yearly plans. Your subscription automatically renews, but you can cancel anytime before your next billing cycle.`,
+ },
+ {
+ question: 'What forms of payment do you accept?',
+ answer: `We accept all major credit and debit cards, as well as PayPal, Apple Pay, and Google Pay. Payments are securely processed through Paddle, our payment provider. Additional local payment methods may be available in some countries.`,
+ },
{
question: 'Can I cancel anytime?',
answer: `Yes! You can cancel your subscription at any time, and your Plus benefits will remain active until the end of your billing cycle.`,
@@ -41,19 +53,31 @@ export const plusFAQItems: FAQItem[] = [
),
},
{
- question: 'Can I use my Plus membership on multiple devices?',
- answer: `Yes! Your Plus benefits are tied to your account, so you can use them across all your devices (desktop, mobile, or tablet) just by logging in.`,
+ question: 'What happens to my data if I cancel?',
+ answer: `Your preferences and bookmarks will stay in your account, but some premium features (like custom feeds, advanced filtering, and bookmark folders) will no longer be accessible.`,
},
+];
+
+const apiFAQItems: FAQItem[] = [
{
- question: 'How does billing work?',
- answer: `We offer monthly and yearly plans. Your subscription automatically renews, but you can cancel anytime before your next billing cycle.`,
+ question: 'What can I build with the API?',
+ answer: `Coding agents, Slack bots, dashboards, personalized digests, anything that needs a feed of what developers are actually reading. Check the API docs for available endpoints and pre-built integrations with Claude Code, Cursor, and Codex.`,
},
{
- question: 'What forms of payment do you accept?',
- answer: `We accept all major credit and debit cards, as well as PayPal, Apple Pay, and Google Pay. Payments are securely processed through Paddle, our payment provider. Additional local payment methods may be available in some countries.`,
+ question: 'What endpoints does the API have?',
+ answer: `Personalized feed, search, post details, bookmarks. Full OpenAPI spec at api.daily.dev/public/v1/docs/json.`,
},
{
- question: 'What happens to my data if I cancel?',
- answer: `Your preferences and bookmarks will stay in your account, but some premium features (like custom feeds, advanced filtering, and bookmark folders) will no longer be accessible.`,
+ question: 'How does authentication work?',
+ answer: `You create personal access tokens from your account settings after subscribing. Pick an expiration (30 days, 90 days, 1 year, or never) and use it as a Bearer token.`,
},
+ {
+ question: 'Are there usage limits?',
+ answer: `There are rate limits designed for personal use and small integrations. If you're planning something heavier, email support@daily.dev and we'll figure it out together.`,
+ },
+];
+
+export const plusFAQItemsApi: FAQItem[] = [
+ ...apiFAQItems,
+ ...plusFAQItemsControl,
];
diff --git a/packages/shared/src/components/sidebar/SidebarTablet.tsx b/packages/shared/src/components/sidebar/SidebarTablet.tsx
index 9df51969c90..9973030d2d7 100644
--- a/packages/shared/src/components/sidebar/SidebarTablet.tsx
+++ b/packages/shared/src/components/sidebar/SidebarTablet.tsx
@@ -33,7 +33,7 @@ import { getFeedName } from '../../lib/feed';
import { useAlertsContext } from '../../contexts/AlertContext';
import HeaderLogo from '../layout/HeaderLogo';
import { useConditionalFeature, useActions } from '../../hooks';
-import { featurePlusCtaCopy } from '../../lib/featureManagement';
+import { featurePlusApiLanding } from '../../lib/featureManagement';
import { Bubble } from '../tooltips/utils';
import { NewOpportunityPopover } from '../opportunity/NewOpportunityPopover';
import { SimpleTooltip } from '../tooltips';
@@ -67,11 +67,14 @@ export const SidebarTablet = ({
hasFiltered: !alerts?.filter,
});
const isPlus = user?.isPlus;
- const { value: ctaCopy } = useConditionalFeature({
- feature: featurePlusCtaCopy,
+ const { value: isApiLanding } = useConditionalFeature({
+ feature: featurePlusApiLanding,
shouldEvaluate: !isPlus,
});
- const hasSquads = squads?.length > 0;
+ const ctaCopy = isApiLanding
+ ? { full: 'Get API Access', short: 'API access' }
+ : { full: 'Level Up with Plus', short: 'Upgrade' };
+ const hasSquads = (squads?.length ?? 0) > 0;
const squadsUrl = hasSquads
? `${webappUrl}${squadCategoriesPaths['My Squads'].substring(1)}`
: `${webappUrl}${squadCategoriesPaths.discover.substring(1)}`;
@@ -172,7 +175,9 @@ export const SidebarTablet = ({
variant={ButtonVariant.Option}
className={classNames(
buttonProps.className,
- '!text-accent-avocado-default',
+ isApiLanding
+ ? '!text-action-plus-default'
+ : '!text-accent-avocado-default',
)}
>
{ctaCopy.short}
@@ -208,7 +213,7 @@ export const SidebarTablet = ({
- {isLoggedIn && (
+ {isLoggedIn && user && (