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
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Welcome screen: Confirm podcasting is included instead of showing an upgrade prompt when the site's plan already covers it.
12 changes: 11 additions & 1 deletion projects/packages/podcast/src/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ const ConnectPrompt = lazy( () => import( './connect-prompt' ) );
// grandfathered users out of the Episodes tab.
const hasProductAccess = (): boolean => getScriptData()?.podcast?.has_product_access !== false;

// Fail-closed counterpart for entitlement *claims* (the welcome screen's
// "included with your plan" copy): only treat the site as entitled when the
// flag is explicitly true, so a missing flag never asserts entitlement we
// haven't confirmed or hides the upgrade path from a non-entitled user.
const hasConfirmedProductAccess = (): boolean =>
getScriptData()?.podcast?.has_product_access === true;

const TabFallback = () => (
<div className="podcast__loading">
<Spinner />
Expand Down Expand Up @@ -70,6 +77,9 @@ const App = () => {
const { data: settings, isLoading } = usePodcastSettings();
const isSetUp = !! settings && settings.podcasting_category_id > 0;
const hasAccess = hasProductAccess();
// Confirmed entitlement gates the welcome "included" claim; the fail-open
// `hasAccess` keeps gating the tabs so a missing flag never locks anyone out.
const hasConfirmedAccess = hasConfirmedProductAccess();
const connected = isSiteConnected();

// `?tab=` owns the active tab; absent `?tab=` falls back to `defaultTab`.
Expand Down Expand Up @@ -179,7 +189,7 @@ const App = () => {
<div className="podcast__tab-content podcast__tab-content--wide">
<ErrorBoundary>
<Suspense fallback={ <TabFallback /> }>
<Welcome onEnable={ handleEnable } />
<Welcome onEnable={ handleEnable } hasAccess={ hasConfirmedAccess } />
</Suspense>
</ErrorBoundary>
</div>
Expand Down
181 changes: 104 additions & 77 deletions projects/packages/podcast/src/dashboard/welcome/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import './style.scss';

interface WelcomeProps {
onEnable: () => void;
/** Whether the site already includes the paid podcast surfaces. */
hasAccess: boolean;
}

const CHECKOUT_SOURCE = 'jetpack-podcast-welcome';
Expand Down Expand Up @@ -142,9 +144,9 @@ const STEPS: ReadonlyArray< { number: string; title: string; body: string } > =
},
];

const Welcome = ( { onEnable }: WelcomeProps ) => {
const upgradeCheckoutUrl = getUpgradeCheckoutUrl();
const planName = getUpgradePlanName();
const Welcome = ( { onEnable, hasAccess }: WelcomeProps ) => {
const upgradeCheckoutUrl = ! hasAccess ? getUpgradeCheckoutUrl() : '';
const planName = ! hasAccess ? getUpgradePlanName() : '';
const isWpcom = isWpcomPlatformSite();

const freeFeatures = isWpcom ? FREE_FEATURES_WPCOM : FREE_FEATURES_SELF_HOSTED;
Expand All @@ -158,6 +160,17 @@ const Welcome = ( { onEnable }: WelcomeProps ) => {
'Unlock podcast stats, the episode dashboard, and the episode block.',
'jetpack-podcast'
);
// Shown when the site already owns the paid surfaces, so the plan comparison
// is replaced by confirmation copy instead of a checkout CTA.
const includedDescription = isWpcom
? __(
'Audio hosting, stats, the episode dashboard, and the episode block are all unlocked.',
'jetpack-podcast'
)
: __(
'Podcast stats, the episode dashboard, and the episode block are all unlocked.',
'jetpack-podcast'
);

// Fire-and-forget Tracks; the anchor handles navigation so middle/cmd-click
// still opens checkout in a new tab and "copy link address" shows the URL.
Expand All @@ -172,15 +185,31 @@ const Welcome = ( { onEnable }: WelcomeProps ) => {
<VStack spacing={ 8 }>
<section className="podcast__welcome-hero">
<VStack spacing={ 4 } className="podcast__welcome-hero-copy">
<h2 className="podcast__welcome-title">
{ __( 'Your podcast belongs with your blog', 'jetpack-podcast' ) }
</h2>
<Text variant="muted">
{ __(
'Publish your show on the same site as your blog and newsletter. Reach fans on Apple, Spotify, Pocket Casts, and every major podcast app.',
'jetpack-podcast'
) }
</Text>
{ hasAccess ? (
<VStack spacing={ 2 }>
<HStack justify="flex-start" alignment="center" spacing={ 2 } expanded={ false }>
<span className="podcast__welcome-plan-check" aria-hidden="true">
<Icon icon={ check } size={ 24 } />
</span>
<Text as="h2" size="title" weight={ 500 }>
{ __( 'Podcast is included with your plan', 'jetpack-podcast' ) }
</Text>
</HStack>
<Text variant="muted">{ includedDescription }</Text>
</VStack>
) : (
<>
<h2 className="podcast__welcome-title">
{ __( 'Your podcast belongs with your blog', 'jetpack-podcast' ) }
</h2>
<Text variant="muted">
{ __(
'Publish your show on the same site as your blog and newsletter. Reach fans on Apple, Spotify, Pocket Casts, and every major podcast app.',
'jetpack-podcast'
) }
</Text>
</>
) }
<HStack justify="flex-start" expanded={ false }>
<Button variant="primary" onClick={ onEnable }>
{ __( 'Enable podcasting', 'jetpack-podcast' ) }
Expand All @@ -189,82 +218,80 @@ const Welcome = ( { onEnable }: WelcomeProps ) => {
</VStack>
</section>

<section className="podcast__welcome-plans">
<HStack alignment="stretch" spacing={ 4 } wrap>
<Card className="podcast__welcome-plan" style={ { flex: '1 1 320px' } }>
<CardBody>
<VStack spacing={ 4 }>
<VStack spacing={ 2 }>
<Text size="title" weight={ 500 }>
{ __( 'Free', 'jetpack-podcast' ) }
</Text>
<Text variant="muted">
{ __(
'Publish your podcast alongside your blog and newsletter.',
'jetpack-podcast'
) }
</Text>
</VStack>
<HStack justify="flex-start" expanded={ false }>
{ ! hasAccess && (
<section className="podcast__welcome-plans">
<HStack alignment="stretch" spacing={ 4 } wrap>
<Card className="podcast__welcome-plan" style={ { flex: '1 1 320px' } }>
<CardBody>
<VStack spacing={ 4 }>
<VStack spacing={ 2 }>
<Text size="title" weight={ 500 }>
{ __( 'Free', 'jetpack-podcast' ) }
</Text>
<Text variant="muted">
{ __(
'Publish your podcast alongside your blog and newsletter.',
'jetpack-podcast'
) }
</Text>
</VStack>
<Button variant="secondary" onClick={ onEnable }>
{ __( 'Start your podcast', 'jetpack-podcast' ) }
</Button>
</HStack>
<ul className="podcast__welcome-plan-features">
{ freeFeatures.map( feature => (
<li key={ feature } className="podcast__welcome-plan-feature">
<span aria-hidden="true">
<Icon icon={ check } size={ 20 } />
</span>
<Text>{ feature }</Text>
</li>
) ) }
</ul>
</VStack>
</CardBody>
</Card>

<Card
className="podcast__welcome-plan podcast__welcome-plan--premium"
style={ { flex: '1 1 320px' } }
>
<CardBody>
<VStack spacing={ 4 }>
<VStack spacing={ 2 }>
<HStack justify="space-between" alignment="center">
<Text size="title" weight={ 500 }>
{ planName }
</Text>
<span className="podcast__welcome-plan-badge">
{ __( 'Popular', 'jetpack-podcast' ) }
</span>
</HStack>
<Text variant="muted">{ paidDescription }</Text>
<ul className="podcast__welcome-plan-features">
{ freeFeatures.map( feature => (
<li key={ feature } className="podcast__welcome-plan-feature">
<span aria-hidden="true">
<Icon icon={ check } size={ 20 } />
</span>
<Text>{ feature }</Text>
</li>
) ) }
</ul>
</VStack>
<HStack justify="flex-start" expanded={ false }>
</CardBody>
</Card>

<Card
className="podcast__welcome-plan podcast__welcome-plan--premium"
style={ { flex: '1 1 320px' } }
>
<CardBody>
<VStack spacing={ 4 }>
<VStack spacing={ 2 }>
<HStack justify="space-between" alignment="center">
<Text size="title" weight={ 500 }>
{ planName }
</Text>
<span className="podcast__welcome-plan-badge">
{ __( 'Popular', 'jetpack-podcast' ) }
</span>
</HStack>
<Text variant="muted">{ paidDescription }</Text>
</VStack>
<Button variant="primary" href={ upgradeCheckoutUrl } onClick={ onUpgradeClick }>
{ sprintf(
/* translators: %s is the plan name, e.g. "Growth" or "Premium". */
__( 'Start your %s podcast', 'jetpack-podcast' ),
planName
) }
</Button>
</HStack>
<ul className="podcast__welcome-plan-features">
{ paidFeatures.map( feature => (
<li key={ feature } className="podcast__welcome-plan-feature">
<span aria-hidden="true">
<Icon icon={ check } size={ 20 } />
</span>
<Text>{ feature }</Text>
</li>
) ) }
</ul>
</VStack>
</CardBody>
</Card>
</HStack>
</section>
<ul className="podcast__welcome-plan-features">
{ paidFeatures.map( feature => (
<li key={ feature } className="podcast__welcome-plan-feature">
<span aria-hidden="true">
<Icon icon={ check } size={ 20 } />
</span>
<Text>{ feature }</Text>
</li>
) ) }
</ul>
</VStack>
</CardBody>
</Card>
</HStack>
</section>
) }

<HStack alignment="stretch" spacing={ 4 } wrap>
{ BENEFITS.map( b => (
Expand Down
10 changes: 10 additions & 0 deletions projects/packages/podcast/src/dashboard/welcome/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
outline-offset: -2px;
}

.podcast__welcome-plan-check {
display: inline-flex;
align-items: center;
color: #008a20;

svg {
fill: currentColor;
}
}

.podcast__welcome-plan-badge {
display: inline-block;
padding: 2px 10px;
Expand Down
Loading