diff --git a/packages/shared/src/components/CustomFeedEmptyScreen.tsx b/packages/shared/src/components/CustomFeedEmptyScreen.tsx index cfa0e40f449..4454b51e8e9 100644 --- a/packages/shared/src/components/CustomFeedEmptyScreen.tsx +++ b/packages/shared/src/components/CustomFeedEmptyScreen.tsx @@ -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, diff --git a/packages/shared/src/components/PlusUserBadge.tsx b/packages/shared/src/components/PlusUserBadge.tsx index aee4c6515fd..fefdff87d4c 100644 --- a/packages/shared/src/components/PlusUserBadge.tsx +++ b/packages/shared/src/components/PlusUserBadge.tsx @@ -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'; @@ -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; diff --git a/packages/shared/src/components/UpgradeToPlus.tsx b/packages/shared/src/components/UpgradeToPlus.tsx index 5fd66e7dae4..047bb7146e6 100644 --- a/packages/shared/src/components/UpgradeToPlus.tsx +++ b/packages/shared/src/components/UpgradeToPlus.tsx @@ -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; @@ -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) => { @@ -70,7 +74,7 @@ export const UpgradeToPlus = ({ className={classNames(!iconOnly && 'flex-1', className)} icon={} size={size} - color={ButtonColor.Avocado} + color={defaultColor} variant={ButtonVariant.Primary} onClick={onClick} {...(variant && { variant, color })} diff --git a/packages/shared/src/components/banners/PlusMobileEntryBanner.tsx b/packages/shared/src/components/banners/PlusMobileEntryBanner.tsx index 6aa62a0ad44..f04d87a6f21 100644 --- a/packages/shared/src/components/banners/PlusMobileEntryBanner.tsx +++ b/packages/shared/src/components/banners/PlusMobileEntryBanner.tsx @@ -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 & { targetType: TargetType; @@ -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({ @@ -85,7 +89,7 @@ const PlusMobileEntryBanner = ({ tag="a" href={ctaUrl || '/plus'} variant={ButtonVariant.Primary} - color={ButtonColor.Avocado} + color={ctaColor} onClick={handleClick} > { 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({ @@ -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} diff --git a/packages/shared/src/components/plus/PlusApiShowcase.tsx b/packages/shared/src/components/plus/PlusApiShowcase.tsx new file mode 100644 index 00000000000..a81022a72a9 --- /dev/null +++ b/packages/shared/src/components/plus/PlusApiShowcase.tsx @@ -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; + iconClasses: string; +}; + +const showcaseCards: Array = [ + { + 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 ( +
+
+ +
+ + {card.title} + + + {card.body} + +
+ ); +}; + +export const PlusApiShowcase = (): ReactElement => { + const id = useId(); + const titleId = `${id}-title`; + + return ( +
+ + What you can build with the API + +
+ {showcaseCards.map((card) => ( + + ))} +
+
+ +
+
+ ); +}; diff --git a/packages/shared/src/components/plus/PlusDesktop.tsx b/packages/shared/src/components/plus/PlusDesktop.tsx index cb459b5fd06..2df07846800 100644 --- a/packages/shared/src/components/plus/PlusDesktop.tsx +++ b/packages/shared/src/components/plus/PlusDesktop.tsx @@ -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, @@ -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(null); - const ref = useRef(); + const ref = useRef(null); const onChangeCheckoutOption: OpenCheckoutFn = useCallback( ({ priceId, giftToUserId, quantity }) => { setSelectedOption(priceId); - openCheckout({ priceId, giftToUserId, quantity }); + openCheckout?.({ priceId, giftToUserId, quantity }); }, [openCheckout], ); @@ -56,7 +62,7 @@ export const PlusDesktop = ({ const { priceId } = giftOneYear; setSelectedOption(priceId); - openCheckout({ priceId, giftToUserId: giftToUser.id }); + openCheckout?.({ priceId, giftToUserId: giftToUser.id }); return; } @@ -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, @@ -98,7 +104,7 @@ export const PlusDesktop = ({ /> )} + {isApiLanding && !isOrganization && !giftToUser && } ); diff --git a/packages/shared/src/components/plus/PlusFAQ.tsx b/packages/shared/src/components/plus/PlusFAQ.tsx index 6e4efc37593..cbaa7a6601b 100644 --- a/packages/shared/src/components/plus/PlusFAQ.tsx +++ b/packages/shared/src/components/plus/PlusFAQ.tsx @@ -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; @@ -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 (
{ Frequently asked questions
- {plusFAQItems.map((item) => ( + {items.map((item) => ( ))}
diff --git a/packages/shared/src/components/plus/PlusInfo.tsx b/packages/shared/src/components/plus/PlusInfo.tsx index f7dbac50616..38dae64d443 100644 --- a/packages/shared/src/components/plus/PlusInfo.tsx +++ b/packages/shared/src/components/plus/PlusInfo.tsx @@ -8,11 +8,16 @@ import { TypographyTag, TypographyType, } from '../typography/Typography'; -import { PlusList, plusOrganizationFeatureList } from './PlusList'; +import { + PlusList, + plusFeatureListApiFirst, + plusOrganizationFeatureList, +} from './PlusList'; +import { useConditionalFeature, usePlusSubscription } from '../../hooks'; +import { featurePlusApiLanding } from '../../lib/featureManagement'; import { usePaymentContext } from '../../contexts/payment/context'; import type { OpenCheckoutFn } from '../../contexts/payment/context'; import { Button, ButtonSize, ButtonVariant } from '../buttons/Button'; -import { usePlusSubscription } from '../../hooks'; import { LogEvent, TargetId } from '../../lib/log'; import { useGiftUserContext } from './GiftUserContext'; import { PlusOptionRadio } from './PlusOptionRadio'; @@ -79,6 +84,16 @@ export const defaultPlusInfoCopyControl: Record = { }, }; +export const plusInfoCopyApi: Record = { + ...defaultPlusInfoCopyControl, + [PlusType.Self]: { + title: 'A real API for the dev content you already trust', + description: + "Your coding agent doesn't know what shipped last week. The daily.dev API feeds it curated, upvote-ranked dev content through pre-built integrations for Claude Code, Cursor, and Codex.", + subtitle: 'Billing cycle', + }, +}; + const skeletonItems = Array.from({ length: 3 }, (_, i) => i); const RadioGroupSkeleton = () => (
@@ -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 && (