Skip to content

Commit

Permalink
Task: Create Plans page for Jetpack App Site Creation (#82135)
Browse files Browse the repository at this point in the history
* Create a basic structure for /jetpack-app-plans page

- Created jetpack-app-plans directory
- Added an entry into sections.js with a path and module
- Created controller.jsx that displays JetpackAppPlans component and passes query parameters
- Created index.js defining a page and including jetpackAppPlans controller
- Created main.jsx that shows a loading indicator while querying plans, and PlansFeaturesMain component once plans are loaded
- Created style.scss with jetpack-app-plans style that includes plan-features-layout-switcher to support media queries when displaying plans grid

* Remove use of deprecated params

* Set intent for Jetpack app plans in site creation

* Hide yearly/monthly selector

* Pass selected plan through redirection url
- Allow redirect_to parameter to URL which is then used to callback with a selected plan
- Do not pass selected plan if free plan is selected

* Use snake case for passing cart item

* Fix plan retrieval from cart

Use getPlanCartItem to get plan

* Check if cartItems exist before fetching plan

* Hide top and sidebars in jetpack-app-plans flow

* Add a header to jetpack-app-plans page

* Refactor to improve code style

* Show a different header label when no domain name is passed

* Hide feature comparison

* Rename domain_name to paid_domain_name for more clarity

* Reuse the same translation from previous label

* Renaming plans-grid path

* Simplify redirect by passing only plan product id

* Remove redundant flow name

* Set a lighter web view color for Jetpack App Plans to match other plans page shown on the app

* Create a middleware for Jetpack App pages layout

Jetpack App pages layout do not require all the outer layouts of Calypso. We can omit them by creating a separate middleware that only displays the required component.

* Change jetpack-app-plans to jetpack-app/plans route

* Remove redundant style

* Remove Loading and Plans subcomponents to improve readability

* Use useTranslate hook

* Convert JS to TS

* Use --studio-white for Jetpack App Plans background color

* Remove explicit { yes } from parameters

* Use import type for imports only for typing purposes

* Use a consistent Context definition

* Use addQueryArgs for building redirection URL

* Use a jetpack-app__ prefix for class names

* Use grid-unit variables for spacing

* Redirect to originalUrl with select plan_id and plan_slug

* Handle an entire jetpack-app/ route

Redirect to jetpack-app/plans when accessing jetpack-app route, since it's the only page of the route for now

* Redirect to home if jetpack-app/ route is not accessed via the app

* Added README.md explaining jetpack-app/ route

* Remove forward slash from the section path

---------

Co-authored-by: Paul Von Schrottky <[email protected]>
  • Loading branch information
staskus and guarani authored Oct 12, 2023
1 parent 8bdf2a0 commit 3d91f1c
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 0 deletions.
19 changes: 19 additions & 0 deletions client/jetpack-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Jetpack App

This module is specifically designed for use within the Jetpack App to handle App-specific flows. These flows are intended to be executed only within the web views of the Jetpack App. Any access outside of this context will result in a redirection to the homepage.

## Routes

### Plans

- **URL**: `jetpack-app/plans`
- **Description**: This route opens a plans page that is specifically configured for the Jetpack App. It reuses the Plans component used in other pages.
- **Configuration**:
- The page can be configured through URL parameter `paid_domain_name`.
- Plan selection can be received by observing page redirections with `plan_id` and `plan_slug` parameters.

Example:
```
# Access the plans page with a specific domain name which was selected prior on the app.
https://www.wordpress.com/jetpack-app/plans?paid_domain_name=example.com
```
22 changes: 22 additions & 0 deletions client/jetpack-app/controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import page from 'page';
import { isWpMobileApp } from 'calypso/lib/mobile-app';
import JetpackAppPlans from './plans/main';

export function jetpackAppPlans( context: PageJS.Context, next: () => void ) {
context.primary = (
<JetpackAppPlans
paidDomainName={ context.query.paid_domain_name }
originalUrl={ context.originalUrl }
/>
);

next();
}

export function redirectIfNotJetpackApp( _context: PageJS.Context, next: () => void ) {
if ( ! isWpMobileApp() ) {
page.redirect( '/' );
} else {
next();
}
}
17 changes: 17 additions & 0 deletions client/jetpack-app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import page from 'page';
import { render as clientRender } from 'calypso/controller';
import { jetpackAppPlans, redirectIfNotJetpackApp } from './controller';
import { makeJetpackAppLayout } from './page-middleware/layout';

export default function () {
page(
'/jetpack-app/plans',
redirectIfNotJetpackApp,
jetpackAppPlans,
makeJetpackAppLayout,
clientRender
);

// Default to /plans page until we have more pages
page( '/jetpack-app', '/jetpack-app/plans' );
}
60 changes: 60 additions & 0 deletions client/jetpack-app/page-middleware/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider as ReduxProvider } from 'react-redux';
import CalypsoI18nProvider from 'calypso/components/calypso-i18n-provider';
import { RouteProvider } from 'calypso/components/route';
import { CalypsoReactQueryDevtools } from 'calypso/lib/react-query-devtools-helper';
import type { FunctionComponent } from 'react';
import type { Store } from 'redux';

export { render, hydrate } from 'calypso/controller/web-util';

interface ProviderWrappedLayoutProps {
store: Store;
queryClient: QueryClient;
currentRoute: string;
currentQuery: object;
primary: React.ReactNode;
redirectUri: string;
}

export const ProviderWrappedLayout: FunctionComponent< ProviderWrappedLayoutProps > = ( {
store,
queryClient,
currentRoute,
currentQuery,
primary,
} ) => {
return (
<CalypsoI18nProvider>
{ /* TS incorrectly infers RouteProvider types; ignore errors here. */ }
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ }
<RouteProvider currentRoute={ currentRoute } currentQuery={ currentQuery }>
<QueryClientProvider client={ queryClient }>
<ReduxProvider store={ store }>{ primary }</ReduxProvider>
<CalypsoReactQueryDevtools />
</QueryClientProvider>
</RouteProvider>
</CalypsoI18nProvider>
);
};

export function makeJetpackAppLayoutMiddleware( LayoutComponent: typeof ProviderWrappedLayout ) {
return ( context: PageJS.Context, next: () => void ) => {
const { store, queryClient, pathname, query, primary } = context;

context.layout = (
<LayoutComponent
store={ store }
queryClient={ queryClient }
currentRoute={ pathname }
currentQuery={ query }
primary={ primary }
redirectUri={ context.originalUrl }
/>
);
next();
};
}

export const makeJetpackAppLayout = makeJetpackAppLayoutMiddleware( ProviderWrappedLayout );
111 changes: 111 additions & 0 deletions client/jetpack-app/plans/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { getPlan, PLAN_FREE } from '@automattic/calypso-products';
import { addQueryArgs } from '@wordpress/url';
import { useTranslate } from 'i18n-calypso';
import { useSelector } from 'react-redux';
import QueryPlans from 'calypso/components/data/query-plans';
import FormattedHeader from 'calypso/components/formatted-header';
import { LoadingEllipsis } from 'calypso/components/loading-ellipsis';
import Main from 'calypso/components/main';
import { getPlanCartItem } from 'calypso/lib/cart-values/cart-items';
import PlansFeaturesMain from 'calypso/my-sites/plans-features-main';
import { getPlanSlug } from 'calypso/state/plans/selectors';
import type { Plan } from '@automattic/calypso-products';
import type { MinimalRequestCartProduct } from '@automattic/shopping-cart';
import type { AppState } from 'calypso/types';

import './style.scss';

interface HeaderProps {
paidDomainName?: string;
}

interface JetpackAppPlansProps {
paidDomainName?: string;
originalUrl: string;
}

const Header: React.FC< HeaderProps > = ( { paidDomainName } ) => {
const translate = useTranslate();

return (
<div className="jetpack-app__plans-header">
<FormattedHeader
brandFont
headerText={ translate( 'Choose the perfect plan' ) }
align="center"
/>
{ paidDomainName ? (
<>
<p>
{ translate(
'With your annual plan, you’ll get %(domainName)s {{strong}}free for the first year{{/strong}}.',
{
args: {
domainName: paidDomainName,
},
components: { strong: <strong /> },
}
) }
</p>
<p>
{ translate(
'You’ll also unlock advanced features that make it easy to build and grow your site.'
) }
</p>
</>
) : (
<p>{ translate( 'See and compare the features available on each WordPress.com plan.' ) }</p>
) }
</div>
);
};

const JetpackAppPlans: React.FC< JetpackAppPlansProps > = ( { paidDomainName, originalUrl } ) => {
const planSlug = useSelector( ( state: AppState ) =>
getPlanSlug( state, getPlan( PLAN_FREE )?.getProductId() || 0 )
) as string | null;
const plansLoaded = Boolean( planSlug );

const onUpgradeClick = ( cartItems?: MinimalRequestCartProduct[] | null | undefined ) => {
const productSlug = getPlanCartItem( cartItems )?.product_slug;

type PlansParameters = { plan_id?: number; plan_slug: string };
let args: PlansParameters;

if ( ! productSlug ) {
args = { plan_slug: PLAN_FREE };
} else {
const plan = getPlan( productSlug ) as Plan;
args = { plan_id: plan.getProductId(), plan_slug: productSlug };
}

window.location.href = addQueryArgs( originalUrl, args );
};

return (
<Main className="jetpack-app__plans">
<QueryPlans />
{ plansLoaded ? (
<>
<Header paidDomainName={ paidDomainName } />
<PlansFeaturesMain
paidDomainName={ paidDomainName }
intent="plans-jetpack-app-site-creation"
isInSignup
intervalType="yearly"
onUpgradeClick={ onUpgradeClick }
plansWithScroll={ false }
hidePlanTypeSelector
hidePlansFeatureComparison
/>
</>
) : (
<div className="jetpack-app__plans-loading">
<LoadingEllipsis />
</div>
) }
</Main>
);
};

export default JetpackAppPlans;
43 changes: 43 additions & 0 deletions client/jetpack-app/plans/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import "calypso/my-sites/plans-grid/media-queries";
@import "@wordpress/base-styles/variables";
@import "@automattic/typography/styles/fonts";

body {
background-color: var(--studio-white);
}

.jetpack-app__plans {
@include plan-features-layout-switcher;

.jetpack-app__plans-header {
display: block;
overflow: auto;

.formatted-header__title,
.formatted-header__subtitle {
max-width: unset;
}

h1 {
font-family: $brand-serif;
font-size: $font-headline-small;
margin-bottom: $grid-unit-20;
}

p {
font-size: $font-body;
color: var(--color-text-subtle);
text-align: center;
padding: 0 $grid-unit-20;
margin-bottom: 0;
}
}

.jetpack-app__plans-loading {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export type PlansIntent =
| 'plans-new-hosted-site'
| 'plans-plugins'
| 'plans-jetpack-app'
| 'plans-jetpack-app-site-creation'
| 'plans-import'
| 'plans-woocommerce'
| 'plans-paid-media'
Expand Down Expand Up @@ -217,6 +218,9 @@ const usePlanTypesWithIntent = ( {
case 'plans-jetpack-app':
planTypes = [ TYPE_PERSONAL, TYPE_PREMIUM, TYPE_BUSINESS, TYPE_ECOMMERCE ];
break;
case 'plans-jetpack-app-site-creation':
planTypes = [ TYPE_FREE, TYPE_PERSONAL, TYPE_PREMIUM, TYPE_BUSINESS, TYPE_ECOMMERCE ];
break;
case 'plans-paid-media':
planTypes = [ TYPE_PERSONAL, TYPE_PREMIUM, TYPE_BUSINESS, TYPE_ECOMMERCE ];
break;
Expand Down
5 changes: 5 additions & 0 deletions client/sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ const sections = [
enableLoggedOut: true,
isomorphic: true,
},
{
name: 'jetpack-app',
paths: [ '/jetpack-app' ],
module: 'calypso/jetpack-app',
},
{
name: 'stats',
paths: [ '/stats' ],
Expand Down

0 comments on commit 3d91f1c

Please sign in to comment.