Skip to content

Commit

Permalink
Gutenboarding: Integrate PlansTable & PlansDetails into PlansGrid. (#…
Browse files Browse the repository at this point in the history
…41935)

* Integrate mock prototype into plans grid.

* Remove mock files and /new/plans route.

* Fixed type errors.

* Integrate the plans store with the grid ✨

* Update PlanItem key and remove unused plans-table wrappers.

* Remove lib/plans.ts from gutenboarding

* Update plans store.
- Add resetPlan action
- Use undefined instead of PLAN_FREE as selected plan
- extract defaultPaidPlan and freePlan as constants

* Use resetPlan when ending Gutenboarding flow.
- update PlanAction type

Co-authored-by: Omar Alshaker <omar@omaralshaker.com>
Co-authored-by: Razvan Papadopol <razvan.papadopol@automattic.com>
  • Loading branch information
3 people authored May 8, 2020
1 parent e816f74 commit 1b7437d
Show file tree
Hide file tree
Showing 21 changed files with 316 additions and 374 deletions.
4 changes: 4 additions & 0 deletions client/landing/gutenboarding/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useHistory } from 'react-router-dom';
* Internal dependencies
*/
import { STORE_KEY as ONBOARD_STORE } from '../../stores/onboard';
import { STORE_KEY as PLANS_STORE } from '../../stores/plans';
import { USER_STORE } from '../../stores/user';
import { SITE_STORE } from '../../stores/site';
import './style.scss';
Expand Down Expand Up @@ -81,6 +82,7 @@ const Header: React.FunctionComponent = () => {
const { createSite, resetOnboardStore, setDomain, setIsRedirecting } = useDispatch(
ONBOARD_STORE
);
const { resetPlan } = useDispatch( PLANS_STORE );

const allSuggestions = useDomainSuggestions( { searchOverride: siteTitle, locale: i18nLocale } );
const paidSuggestions = getPaidDomainSuggestions( allSuggestions )?.slice(
Expand Down Expand Up @@ -193,6 +195,7 @@ const Header: React.FunctionComponent = () => {
blogId: newSite.blogid,
} );
resetOnboardStore();
resetPlan();

window.location.replace( `/block-editor/page/${ newSite.site_slug }/home` );
}
Expand All @@ -203,6 +206,7 @@ const Header: React.FunctionComponent = () => {
newSite,
newUser,
resetOnboardStore,
resetPlan,
setIsRedirecting,
] );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import * as React from 'react';
import { Button } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { useViewportMatch } from '@wordpress/compose';
import { sprintf } from '@wordpress/i18n';
import { useI18n } from '@automattic/react-i18n';
Expand All @@ -14,13 +14,7 @@ import config from 'config';
*/
import JetpackLogo from 'components/jetpack-logo'; // @TODO: extract to @automattic package
import { STORE_KEY as ONBOARD_STORE } from '../../../stores/onboard';
import {
freePlan,
defaultPaidPlan,
getPlanSlugByPath,
getPlanTitle,
Plan,
} from '../../../lib/plans';
import { STORE_KEY as PLANS_STORE } from '../../../stores/plans';
import { usePlanRouteParam } from '../../../path';
import PlansModal from '../plans-modal';

Expand All @@ -34,44 +28,50 @@ const PlansButton: React.FunctionComponent< Button.ButtonProps > = ( { ...button

// mobile first to match SCSS media query https://github.com/Automattic/wp-calypso/pull/41471#discussion_r415678275
const isDesktop = useViewportMatch( 'mobile', '>=' );

// When no plan is selected we determine the default plan by checking the selected domain, if any
const hasPaidDomain = useSelect( ( select ) => select( ONBOARD_STORE ).hasPaidDomain() );
const defaultPlan = hasPaidDomain ? defaultPaidPlan : freePlan;
const { setPlan } = useDispatch( PLANS_STORE );
const defaultPlan = useSelect( ( select ) =>
select( PLANS_STORE ).getDefaultPlan( hasPaidDomain )
);
const selectedPlan = useSelect( ( select ) => select( PLANS_STORE ).getSelectedPlan() );

const planPath = usePlanRouteParam();

// @TODO: move to Onboard store once we use it for cart redirect at the end of the flow
const [ plan, setPlan ] = React.useState< Plan >( getPlanSlugByPath( planPath ) );

const planFromPath = useSelect( ( select ) => select( PLANS_STORE ).getPlanByPath( planPath ) );
const [ isPlansModalVisible, setIsPlanModalVisible ] = React.useState( false );

const handleModalClose = () => setIsPlanModalVisible( false );
const handleButtonClick = () => {
if ( config.isEnabled( 'gutenboarding/plans-grid' ) ) {
setIsPlanModalVisible( ( isVisible ) => ! isVisible );
}
};

/**
* Plan is decided in this order
* 1. selected from PlansGrid (by dispatching setPlan)
* 2. having the plan slug in the URL
* 3. selecting a paid domain
*/
const plan = selectedPlan || planFromPath || defaultPlan;

/* translators: Button label where %s is the WordPress.com plan name (eg: Free, Personal, Premium, Business) */
const planLabel = sprintf( __( '%s Plan' ), getPlanTitle( plan || defaultPlan ) );
const planLabel = sprintf( __( '%s Plan' ), plan.getTitle() );

return (
<>
<Button
onClick={ handleButtonClick }
label={ __( planLabel ) }
className="plans-button"
onClick={ handleButtonClick }
{ ...buttonProps }
>
{ isDesktop && planLabel }
<JetpackLogo className="plans-button__jetpack-logo" size={ 16 } monochrome />
</Button>
<PlansModal
isOpen={ isPlansModalVisible }
currentPlan={ plan || defaultPlan }
selectedPlanSlug={ plan.getStoreSlug() }
onConfirm={ setPlan }
onClose={ handleModalClose }
onClose={ () => setIsPlanModalVisible( false ) }
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@
import React from 'react';
import { useI18n } from '@automattic/react-i18n';
import { Icon } from '@wordpress/components';
import { STORE_KEY as PLANS_STORE } from '../../../stores/plans';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { details, PlanFeature, PlanDetail } from './mock-data';
import './style.scss';

const PlansDetails: React.FunctionComponent = () => {
const plansDetails = useSelect( ( select ) => select( PLANS_STORE ).getPlansDetails() );

const PlanDetails: React.FunctionComponent = () => {
const { __ } = useI18n();

return (
<table className="plan-details">
<table className="plans-details">
<thead>
<tr className="plan-details__header-row">
<tr className="plans-details__header-row">
<th>{ __( 'Feature' ) }</th>
<th>{ __( 'Free' ) }</th>
<th>{ __( 'Personal' ) }</th>
Expand All @@ -25,15 +29,15 @@ const PlanDetails: React.FunctionComponent = () => {
<th>{ __( 'Commerce' ) }</th>
</tr>
</thead>
{ details.map( ( detail: PlanDetail ) => (
{ plansDetails.map( ( detail ) => (
<tbody key={ detail.id }>
{ detail.name && (
<tr className="plan-details__header-row">
<tr className="plans-details__header-row">
<th colSpan={ 6 }>{ detail.name }</th>
</tr>
) }
{ detail.features.map( ( feature: PlanFeature, i ) => (
<tr className="plan-details__feature-row" key={ i }>
{ detail.features.map( ( feature, i ) => (
<tr className="plans-details__feature-row" key={ i }>
<th>{ feature.name }</th>
{ feature.data.map( ( value, j ) => (
<td key={ j }>
Expand Down Expand Up @@ -61,4 +65,4 @@ const PlanDetails: React.FunctionComponent = () => {
);
};

export default PlanDetails;
export default PlansDetails;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@import 'assets/stylesheets/gutenberg-base-styles';
@import '../../../mixins.scss';
@import '../../../variables.scss';

.plans-details {
width: 100%;
table-layout: fixed;
margin-bottom: 110px;

th,
td {
padding: 16px 24px;

&:first-child {
padding-left: 0;
width: 40%;
}

&:not( :first-child ) {
white-space: nowrap;
}
}

// TODO: Deal with a11y later.
.hidden {
display: none;
}
}

.plans-details__header-row {
th {
font-weight: 600;
font-size: 14px;
line-height: 20px;
text-transform: uppercase;
color: var( --studio-gray-20 );
padding-top: 5px;
padding-bottom: 5px;
border-bottom: 1px solid #eaeaeb;
}
}

.plans-details__feature-row {
th,
td {
font-size: 14px;
line-height: 17px;
letter-spacing: 0.2px;
border-bottom: 1px solid #eaeaeb;
vertical-align: top;
}
}
64 changes: 42 additions & 22 deletions client/landing/gutenboarding/components/plans/plans-grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,49 @@
* External dependencies
*/
import * as React from 'react';
import { Button } from '@wordpress/components';
import { Button, Icon } from '@wordpress/components';
import { useI18n } from '@automattic/react-i18n';

/**
* Internal dependencies
*/

import { Plan, supportedPlans, getPlanTitle } from '../../../lib/plans';
import { Title, SubTitle } from '../../titles';
import ActionButtons from '../../action-buttons';
import PlansTable from '../plans-table';
import PlansDetails from '../plans-details';

/**
* Style dependencies
*/
import './style.scss';

export interface Props {
currentPlan: Plan;
renderConfirmButton: ( plan: Plan ) => React.ReactElement;
selectedPlanSlug: string;
renderConfirmButton: ( plan: string ) => React.ReactElement;
cancelButton?: React.ReactElement;
onPlanChange?: ( plan: Plan ) => void;
onPlanChange?: ( plan: string ) => void;
}

const PlansGrid: React.FunctionComponent< Props > = ( {
currentPlan,
selectedPlanSlug,
renderConfirmButton,
cancelButton,
onPlanChange = () => undefined,
} ) => {
const { __ } = useI18n();
const [ selectedPlan, setSelectedPlan ] = React.useState< Plan >( currentPlan );
const [ currentPlan, setCurrentPlan ] = React.useState< string >( selectedPlanSlug );
const [ showDetails, setShowDetails ] = React.useState( false );

const handlePlanSelect = ( plan: Plan ) => {
setSelectedPlan( plan );
const handlePlanSelect = ( plan: string ) => {
setCurrentPlan( plan );
onPlanChange( plan );
};

const handleDetailsToggleButtonClick = () => {
setShowDetails( ! showDetails );
};

return (
<div className="plans-grid">
<div className="plans-grid__header">
Expand All @@ -51,25 +57,39 @@ const PlansGrid: React.FunctionComponent< Props > = ( {
</SubTitle>
</div>
<ActionButtons
primaryButton={ renderConfirmButton( selectedPlan ) }
primaryButton={ renderConfirmButton( currentPlan ) }
secondaryButton={ cancelButton }
/>
</div>

{ /* @TODO: Replace with real grid */ }
<div className="plans-grid__table">
{ supportedPlans.map( ( plan, index ) => (
<div key={ index } className="plans-grid__column">
{ getPlanTitle( plan ) }
<Button
isPrimary
className={ selectedPlan === plan ? 'plans-grid__button--active' : '' }
onClick={ () => handlePlanSelect( plan ) }
>
Select { getPlanTitle( plan ) }
</Button>
<PlansTable selectedPlanSlug={ currentPlan } onPlanSelect={ handlePlanSelect }></PlansTable>
</div>

<div className="plans-grid__details">
{ showDetails && (
<div className="plans-grid__details-heading">
<Title>{ __( 'Detailed comparison' ) }</Title>
<PlansDetails />
</div>
) ) }
) }
<Button
className="plans-grid__details-toggle-button"
isLarge
onClick={ handleDetailsToggleButtonClick }
>
{ showDetails ? (
<>
<span>{ __( 'Less details' ) } </span>
<Icon icon="arrow-up" size={ 20 }></Icon>
</>
) : (
<>
<span>{ __( 'More details' ) } </span>
<Icon icon="arrow-down" size={ 20 }></Icon>
</>
) }
</Button>
</div>
</div>
);
Expand Down
Loading

0 comments on commit 1b7437d

Please sign in to comment.