Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A4A: Display Pressable plan usage information #95485

Merged
merged 13 commits into from
Oct 18, 2024
Merged
124 changes: 124 additions & 0 deletions client/a8c-for-agencies/components/pressable-usage-details/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { ProgressBar } from '@automattic/components';
import formatNumber, {
DEFAULT_LOCALE,
STANDARD_FORMATTING_OPTIONS,
} from '@automattic/components/src/number-formatters/lib/format-number';
import clsx from 'clsx';
import { useTranslate } from 'i18n-calypso';
import { useSelector } from 'react-redux';
import getPressablePlan from 'calypso/a8c-for-agencies/sections/marketplace/pressable-overview/lib/get-pressable-plan';
import { getActiveAgency } from 'calypso/state/a8c-for-agencies/agency/selectors';
import { APIProductFamilyProduct } from 'calypso/state/partner-portal/types';

import './style.scss';

type Props = {
existingPlan: APIProductFamilyProduct | null;
};

export default function PressableUsageDetails( { existingPlan }: Props ) {
const translate = useTranslate();
const agency = useSelector( getActiveAgency );
const planUsage = agency?.third_party?.pressable?.usage;

const planInfo = existingPlan?.slug ? getPressablePlan( existingPlan?.slug ) : null;

if ( ! planInfo ) {
return null;
}

return (
<div
className={ clsx( 'pressable-usage-details__card', {
'is-empty': ! planUsage,
} ) }
>
{ ! planUsage && (
<div className="pressable-usage-details__empty-message">
{ translate( "View your usage data here when it's available." ) }
</div>
) }
<div className="pressable-usage-details__info">
<div className="pressable-usage-details__info-item">
<div className="pressable-usage-details__info-header">
<div className="pressable-usage-details__info-label">{ translate( 'Storage' ) }</div>
<div className="pressable-usage-details__info-top-right storage">
{ planUsage &&
translate( 'Using %(used_storage)s of %(max_storage)s GB', {
args: {
used_storage: planUsage ? planUsage.storage_gb : '?',
max_storage: planInfo.storage,
},
comment: '%(used_storage)s and %(max_storage)d are the storage values in GB.',
} ) }
</div>
</div>
<div className="pressable-usage-details__info-value">
<ProgressBar
className="pressable-usage-details__storage-bar"
compact
value={ planUsage ? planUsage.storage_gb : 0 }
total={ planInfo.storage }
/>
</div>
</div>
</div>

<div className="pressable-usage-details__info">
<div className="pressable-usage-details__info-item sites">
<div className="pressable-usage-details__info-header">
<div className="pressable-usage-details__info-label">{ translate( 'Sites' ) }</div>
<div className="pressable-usage-details__info-top-right">
{ translate( 'up to %(max_sites)s sites', {
args: {
max_sites: planInfo.install,
},
comment: '%(max_sites)s is the maximum number of sites.',
} ) }
</div>
</div>
<div className="pressable-usage-details__info-value">
{ planUsage &&
translate( '%(total_sites)s installed sites', {
args: {
total_sites: planUsage.sites_count,
},
comment: '%(total_sites)s is the number of installed sites.',
} ) }
</div>
</div>

<div className="pressable-usage-details__info-item visits">
<div className="pressable-usage-details__info-header">
<div className="pressable-usage-details__info-label">{ translate( 'Visits' ) }</div>
<div className="pressable-usage-details__info-top-right">
{ translate( '%(max_visits)s per month', {
args: {
max_visits: formatNumber(
planInfo.visits,
DEFAULT_LOCALE,
STANDARD_FORMATTING_OPTIONS
),
},
comment: '%(max_visits)s is the number of traffic visits of the site.',
} ) }
</div>
</div>
<div className="pressable-usage-details__info-value">
{ planUsage &&
translate( '%(visits_count)s visits this month', {
args: {
visits_count: formatNumber(
planUsage.visits_count,
DEFAULT_LOCALE,
STANDARD_FORMATTING_OPTIONS
),
},
comment: '%(visits_count)s is the number of month visits of the site.',
} ) }
</div>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@import "@wordpress/base-styles/breakpoints";
@import "@wordpress/base-styles/mixins";

.pressable-usage-details__card {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
margin: 0 auto;

.pressable-usage-details__empty-message {
font-size: 0.875rem;
margin-bottom: 16px;
}

.pressable-usage-details__title {
font-size: 1.5rem;
margin-bottom: 10px;
}

.pressable-usage-details__info {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 8px;

@include break-large {
flex-direction: row;
}
}

.pressable-usage-details__storage-bar {
margin-top: 16px;
background-color: var(--color-neutral-5);
border-radius: 4px;
height: 8px;

.progress-bar__progress {
border-radius: 4px;
height: 100%;
background-color: var(--color-primary-50);
}
}

.pressable-usage-details__info-item {
background: var(--color-neutral-0);
padding: 16px;
border-radius: 4px;
width: auto;
min-height: 45px;

@include break-large {
width: 100%;
}
}

.pressable-usage-details__info-header {
display: flex;
justify-content: space-between;
align-items: center;
}

.pressable-usage-details__info-label {
font-size: 0.875rem;
text-transform: uppercase;
}

&.is-empty {
.pressable-usage-details__info-top-right,
.pressable-usage-details__info-label {
color: var(--studio-gray-40);
}
}

.pressable-usage-details__info-top-right {
font-size: 0.875rem;
color: var(--color-neutral-60);

&.storage {
font-weight: bold;
font-size: 1rem;
}
}

.pressable-usage-details__info-value {
font-size: 1rem;
font-weight: bold;
margin-top: 5px;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { JetpackLogo } from '@automattic/components';
import { layout, blockMeta, shuffle, help, keyboardReturn, tip } from '@wordpress/icons';
import { useTranslate } from 'i18n-calypso';
import PressableUsageDetails from 'calypso/a8c-for-agencies/components/pressable-usage-details';
import useProductAndPlans from 'calypso/a8c-for-agencies/sections/marketplace/hooks/use-product-and-plans';
import useExistingPressablePlan from 'calypso/a8c-for-agencies/sections/marketplace/pressable-overview/hooks/use-existing-pressable-plan';
import ProfileAvatar1 from 'calypso/assets/images/a8c-for-agencies/hosting/premier-testimonial-1.png';
import ProfileAvatar2 from 'calypso/assets/images/a8c-for-agencies/hosting/premier-testimonial-2.png';
import { APIProductFamilyProduct } from 'calypso/state/partner-portal/types';
Expand All @@ -20,8 +23,37 @@ type Props = {
export default function PremierAgencyHosting( { onAddToCart }: Props ) {
const translate = useTranslate();

const { pressablePlans } = useProductAndPlans( {
selectedSite: null,
productSearchQuery: '',
} );

const { existingPlan, isReady: isExistingPlanFetched } = useExistingPressablePlan( {
plans: pressablePlans,
} );

return (
<div className="premier-agency-hosting">
{ isExistingPlanFetched && existingPlan && (
cleacos marked this conversation as resolved.
Show resolved Hide resolved
<section className="pressable-overview-plan-existing">
<div className="pressable-overview-plan-existing__details-card">
<div className="pressable-overview-plan-existing__header">
<div className="pressable-overview-plan-existing__owned-plan">
{ translate( 'You own the' ) }
</div>
<div className="pressable-overview-plan-existing__name">
{ translate( '%(plan_name)s plan', {
args: {
plan_name: existingPlan.name,
},
comment: '%(plan_name)s is the plan name.',
} ) }
</div>
</div>
<PressableUsageDetails existingPlan={ existingPlan } />
</div>
</section>
) }
<PressableOverviewPlanSelection onAddToCart={ onAddToCart } />
<HostingAdditionalFeaturesSection
icon={ <JetpackLogo size={ 16 } /> }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,47 @@
}
}
}

.pressable-overview-plan-existing {
background-image: linear-gradient(0deg, var(--color-primary-0) 0%, #fff 100%);
padding: 0 16px 48px;
margin-bottom: 32px;

@include break-large {
padding: 0 32px 48px;
}

.pressable-overview-plan-existing__details-card{
flex: 1;
border: 1px solid var(--color-neutral-5);
border-radius: 4px;
padding: 16px;
justify-content: space-between;
max-width: 1000px;
margin: 0 auto;
background-color: #fff;

@include break-large {
padding: 24px;
}
}

.pressable-overview-plan-existing__owned-plan {
font-size: 0.75rem;
color: var(--color-neutral-60);
}
.pressable-overview-plan-existing__name {
font-size: 1.50rem;
font-weight: bold;
color: var(--color-accent-100);
}

.pressable-overview-plan-existing__header {
margin-bottom: 16px;
text-align: center;

@include break-large {
text-align: left;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Button } from '@wordpress/components';
import { Icon, info } from '@wordpress/icons';
import clsx from 'clsx';
import { useTranslate } from 'i18n-calypso';
import { useCallback, useMemo, useState } from 'react';
Expand All @@ -15,7 +14,6 @@ import { FilterType } from '../types';
type Props = {
selectedPlan: APIProductFamilyProduct | null;
plans: APIProductFamilyProduct[];
existingPlan?: APIProductFamilyProduct | null;
pressablePlan?: PressablePlan | null;
onSelectPlan: ( plan: APIProductFamilyProduct | null ) => void;
isLoading?: boolean;
Expand All @@ -25,7 +23,6 @@ export default function PlanSelectionFilter( {
selectedPlan,
plans,
onSelectPlan,
existingPlan,
pressablePlan,
isLoading,
}: Props ) {
Expand Down Expand Up @@ -113,26 +110,6 @@ export default function PlanSelectionFilter( {

return (
<section className={ wrapperClass }>
{ !! existingPlan && (
<div className="pressable-overview-plan-selection__filter-owned-plan">
<div className="badge">
<Icon icon={ info } size={ 24 } />

<span>
{ translate( 'You own {{b}}%(planName)s plan{{/b}}', {
args: {
planName: existingPlan.name,
},
components: {
b: <strong />,
},
comment: '%(planName)s is the name of the Pressable plan the user owns.',
} ) }
</span>
</div>
</div>
) }

<div className="pressable-overview-plan-selection__filter-type">
<p className="pressable-overview-plan-selection__filter-label">
{ translate( 'Filter by:' ) }
Expand Down
Loading
Loading