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

WIP Restyle receipt view for updated DataViews Billing History List #98567

Open
wants to merge 7 commits into
base: trunk
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion client/me/purchases/billing-history/controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createElement } from 'react';
import BillingHistoryComponent from 'calypso/me/purchases/billing-history/main';
import Receipt from './receipt';
import { Receipt } from './main';

export function billingHistory( context, next ) {
context.primary = createElement( BillingHistoryComponent );
Expand Down
33 changes: 21 additions & 12 deletions client/me/purchases/billing-history/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,30 @@ import Main from 'calypso/components/main';
import NavigationHeader from 'calypso/components/navigation-header';
import { useGeoLocationQuery } from 'calypso/data/geo/use-geolocation-query';
import PageViewTracker from 'calypso/lib/analytics/page-view-tracker';
import BillingHistoryList from 'calypso/me/purchases/billing-history/billing-history-list';
import BillingHistoryListDataView from 'calypso/me/purchases/billing-history/billing-history-list-data-view';
import { vatDetails as vatDetailsPath, billingHistoryReceipt } from 'calypso/me/purchases/paths';
import PurchasesNavigation from 'calypso/me/purchases/purchases-navigation';
import titles from 'calypso/me/purchases/titles';
import useVatDetails from 'calypso/me/purchases/vat-info/use-vat-details';
import { useTaxName } from 'calypso/my-sites/checkout/src/hooks/use-country-list';
import BillingHistoryList from './billing-history-list';
import BillingHistoryListDataView from './billing-history-list-data-view';
import ModernReceipt from './modern-receipt';
import BillingReceipt from './receipt';

import './style.scss';

const USE_DATA_VIEW = config.isEnabled( 'purchases/billing-history-data-view' );

export function BillingHistoryContent( {
siteId = null,
getReceiptUrlFor = billingHistoryReceipt,
}: {
siteId: number | null;
getReceiptUrlFor: ( receiptId: string | number ) => string;
} ) {
const useDataViewBillingHistoryList = config.isEnabled( 'purchases/billing-history-data-view' );

return (
<Card id="billing-history" className="section-content" tagName="section">
{ useDataViewBillingHistoryList ? (
{ USE_DATA_VIEW ? (
<BillingHistoryListDataView siteId={ siteId } getReceiptUrlFor={ getReceiptUrlFor } />
) : (
<BillingHistoryList header siteId={ siteId } getReceiptUrlFor={ getReceiptUrlFor } />
Expand All @@ -38,22 +40,30 @@ export function BillingHistoryContent( {
);
}

function BillingHistory() {
function BillingHistoryReceipt( { transactionId }: { transactionId: number } ) {
return USE_DATA_VIEW ? (
<ModernReceipt transactionId={ transactionId } />
) : (
<BillingReceipt transactionId={ transactionId } />
);
}

export function Receipt( { transactionId }: { transactionId: number } ) {
return <BillingHistoryReceipt transactionId={ transactionId } />;
}

export default function BillingHistory() {
const translate = useTranslate();
const { vatDetails } = useVatDetails();
const { data: geoData } = useGeoLocationQuery();
const taxName = useTaxName( vatDetails.country ?? geoData?.country_short ?? 'GB' );

const genericTaxName =
/* translators: This is a generic name for taxes to use when we do not know the user's country. */
translate( 'tax (VAT/GST/CT)' );
const genericTaxName = translate( 'tax (VAT/GST/CT)' );
const fallbackTaxName = genericTaxName;
/* translators: %s is the name of taxes in the country (eg: "VAT" or "GST"). */
const editVatText = translate( 'Edit %s details', {
textOnly: true,
args: [ taxName ?? fallbackTaxName ],
} );
/* translators: %s is the name of taxes in the country (eg: "VAT" or "GST"). */
const addVatText = translate( 'Add %s details', {
textOnly: true,
args: [ taxName ?? fallbackTaxName ],
Expand Down Expand Up @@ -86,4 +96,3 @@ function BillingHistory() {
</Main>
);
}
export default BillingHistory;
78 changes: 78 additions & 0 deletions client/me/purchases/billing-history/modern-receipt/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Card } from '@automattic/components';
import { useTranslate } from 'i18n-calypso';
import { useState } from 'react';
import { formatDisplayDate } from '../utils';
import { ReceiptLineItems } from './line-items';
import { PaymentDetails } from './payment-details';
import { ReceiptTotal } from './total';
import type { BillingTransaction } from 'calypso/state/billing-transactions/types';

interface ReceiptContentProps {
transaction: BillingTransaction;
}

export function ReceiptContent( { transaction }: ReceiptContentProps ) {
const translate = useTranslate();
const [ billingDetails, setBillingDetails ] = useState( '' );
const isEmpty = ! billingDetails.trim();
const date = new Date( transaction.date );

return (
<Card className="content">
<div className="header">
<div className="branding">
<img src={ transaction.icon } alt={ transaction.service } className="logo" />
<div className="company">
<h2>{ transaction.service }</h2>
<span className="org">{ transaction.org }</span>
<address className="address">{ transaction.address }</address>
</div>
</div>
<div className="meta">
<time className="date" dateTime={ date.toISOString() }>
{ formatDisplayDate( date ) }
</time>
</div>
</div>

<div className="body">
<div className="details-section">
<div className="label">{ translate( 'Receipt id' ) }</div>
<div className="receipt-id-value">{ transaction.id }</div>

<PaymentDetails transaction={ transaction } />

<div className="billing-details" data-is-empty={ isEmpty }>
<label className="label" htmlFor="billing-details">
{ translate( 'Billing details' ) }
</label>
<textarea
id="billing-details"
className="billing-details-input"
value={ billingDetails }
onChange={ ( e ) => setBillingDetails( e.target.value ) }
placeholder={ translate(
'Use this field to add your billing information (e.g. business address) before printing.'
) }
/>
</div>
</div>

<table className="items">
<thead>
<tr>
<th>{ translate( 'Description' ) }</th>
<th className="amount">{ translate( 'Amount' ) }</th>
</tr>
</thead>
<tbody>
<ReceiptLineItems transaction={ transaction } />
</tbody>
<tfoot>
<ReceiptTotal transaction={ transaction } />
</tfoot>
</table>
</div>
</Card>
);
}
100 changes: 100 additions & 0 deletions client/me/purchases/billing-history/modern-receipt/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Button, Card } from '@wordpress/components';
import { useTranslate } from 'i18n-calypso';
import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import DocumentHead from 'calypso/components/data/document-head';
import QueryBillingTransaction from 'calypso/components/data/query-billing-transaction';
import Main from 'calypso/components/main';
import PageViewTracker from 'calypso/lib/analytics/page-view-tracker';
import { billingHistory } from 'calypso/me/purchases/paths';
import { useDispatch } from 'calypso/state';
import { recordGoogleEvent } from 'calypso/state/analytics/actions';
import { sendBillingReceiptEmail } from 'calypso/state/billing-transactions/actions';
import getPastBillingTransaction from 'calypso/state/selectors/get-past-billing-transaction';
import isPastBillingTransactionError from 'calypso/state/selectors/is-past-billing-transaction-error';
import { ReceiptContent } from './content';
import { ReceiptPlaceholder } from './placeholder';
import type { BillingTransaction } from 'calypso/state/billing-transactions/types';
import type { IAppState } from 'calypso/state/types';

import './style.scss';

interface ReceiptProps {
transactionId: number;
}

export default function ModernReceipt( { transactionId }: ReceiptProps ) {
const translate = useTranslate();
const dispatch = useDispatch();

const transaction = useSelector( ( state: IAppState ) =>
getPastBillingTransaction( state, transactionId )
) as BillingTransaction | undefined;

const transactionError = useSelector( ( state: IAppState ) =>
isPastBillingTransactionError( state, transactionId )
);

const isLoading = ! transaction && ! transactionError;

const handlePrint = useCallback( () => {
dispatch(
recordGoogleEvent( 'Me', 'Clicked on Print Receipt Button in Billing History Receipt' )
);
window.print();
}, [ dispatch ] );

const handleEmailReceipt = useCallback( () => {
dispatch( recordGoogleEvent( 'Me', 'Clicked on Email Receipt Button' ) );
dispatch( sendBillingReceiptEmail( transactionId.toString() ) );
}, [ dispatch, transactionId ] );

return (
<Main wideLayout id="modern-receipt" className="receipt">
<DocumentHead title={ translate( 'Billing History' ) } />
<PageViewTracker
path="/me/purchases/billing/:receipt"
title="Me > Billing History > Receipt"
/>
<QueryBillingTransaction transactionId={ transactionId } />

{ isLoading && <ReceiptPlaceholder /> }

{ transactionError && (
<Card className="error">
<p>{ translate( "Sorry, we couldn't load this receipt. Please try again later." ) }</p>
<Button href={ billingHistory } variant="primary">
{ translate( 'Return to Billing History' ) }
</Button>
</Card>
) }

{ ! isLoading && ! transactionError && transaction && (
<>
<div className="page-header">
<div className="breadcrumbs">
<a href={ billingHistory } className="back-link">
{ translate( 'Receipts' ) }
</a>
</div>
<div className="title-bar">
<h1 className="title">
{ translate( 'Receipt %(id)s', { args: { id: transactionId } } ) }
</h1>
<div className="actions">
<Button variant="primary" onClick={ handlePrint }>
{ translate( 'Print Receipt' ) }
</Button>
<Button variant="primary" onClick={ handleEmailReceipt }>
{ translate( 'Email Receipt' ) }
</Button>
</div>
</div>
</div>

<ReceiptContent transaction={ transaction } />
</>
) }
</Main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { formatCurrency } from '@automattic/format-currency';
import { useTranslate } from 'i18n-calypso';
import {
groupDomainProducts,
getTransactionTermLabel,
renderTransactionQuantitySummary,
isTransactionJetpackSearch10kTier,
renderJetpackSearch10kTierBreakdown,
renderDomainTransactionVolumeSummary,
transactionIncludesTax,
} from '../utils';
import type { BillingTransaction } from 'calypso/state/billing-transactions/types';

interface ReceiptLineItemsProps {
transaction: BillingTransaction;
}

export function ReceiptLineItems( { transaction }: ReceiptLineItemsProps ) {
const translate = useTranslate();
const items = groupDomainProducts( transaction.items, translate );

return (
<>
{ items.map( ( item ) => (
<tr key={ item.id } className="item">
<td className="item-details">
<div className="item-name">{ item.variation }</div>
<div className="item-type">({ item.type_localized })</div>
{ getTransactionTermLabel( item, translate ) && (
<div className="item-term">{ getTransactionTermLabel( item, translate ) }</div>
) }
{ item.domain && <div className="item-domain">{ item.domain }</div> }
{ item.licensed_quantity && (
<div className="item-quantity">
{ renderTransactionQuantitySummary( item, translate ) }
</div>
) }
{ isTransactionJetpackSearch10kTier( item ) && (
<div className="item-tier">
{ renderJetpackSearch10kTierBreakdown( item, item.subtotal_integer, translate ) }
</div>
) }
{ item.volume && (
<div className="item-volume">
{ renderDomainTransactionVolumeSummary( item, translate ) }
</div>
) }
</td>
<td className="amount">
{ formatCurrency( item.amount_integer, item.currency, {
isSmallestUnit: true,
stripZeros: true,
} ) }
{ transaction.credit && (
<span className="credit-badge">{ translate( 'Refund' ) }</span>
) }
</td>
</tr>
) ) }
{ transactionIncludesTax( transaction ) && (
<tr className="tax">
<td>{ translate( 'Tax' ) }</td>
<td className="amount">
{ formatCurrency( transaction.tax_integer, transaction.currency, {
isSmallestUnit: true,
stripZeros: true,
} ) }
</td>
</tr>
) }
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useTranslate } from 'i18n-calypso';
import { hasValidPaymentDetails } from '../utils';
import type { BillingTransaction } from 'calypso/state/billing-transactions/types';

interface PaymentDetailsProps {
transaction: BillingTransaction;
}

export function PaymentDetails( { transaction }: PaymentDetailsProps ) {
const translate = useTranslate();

if ( ! hasValidPaymentDetails( transaction ) ) {
return null;
}

return (
<div className="payment-details">
<div className="label">{ translate( 'Payment method' ) }</div>
<div className="payment-details-content">
{ transaction.cc_display_brand !== 'Not Stored' && transaction.cc_num !== 'XXXX' && (
<div className="payment-brand">
{ translate( '%(cardType)s ending in %(cardNum)s', {
args: {
cardType: transaction.cc_display_brand ?? transaction.cc_type,
cardNum: transaction.cc_num,
},
} ) }
</div>
) }
{ transaction.cc_name !== 'Not Stored' && (
<div className="payment-name">{ transaction.cc_name }</div>
) }
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Card } from '@automattic/components';
import { useTranslate } from 'i18n-calypso';
import Main from 'calypso/components/main';

export function ReceiptPlaceholder() {
const translate = useTranslate();

return (
<Main wideLayout className="receipt">
<Card className="content is-placeholder">
<div className="header">
<div className="placeholder-title" aria-label={ translate( 'Loading receipt' ) } />
</div>
<div className="body">
<div className="placeholder-content" />
</div>
</Card>
</Main>
);
}
Loading
Loading