diff --git a/client/me/purchases/billing-history/billing-history-list-data-view.tsx b/client/me/purchases/billing-history/billing-history-list-data-view.tsx index ee856657a03ae..14fc386afdfdf 100644 --- a/client/me/purchases/billing-history/billing-history-list-data-view.tsx +++ b/client/me/purchases/billing-history/billing-history-list-data-view.tsx @@ -27,7 +27,7 @@ export default function BillingHistoryListDataView( { const transactions = useSelector( getPastBillingTransactions ); const isLoading = useSelector( isRequestingBillingTransactions ); const viewState = useViewStateUpdate(); - const receiptActions = useReceiptActions( getReceiptUrlFor ); + const receiptActions = useReceiptActions(); const actions = receiptActions.map( ( action ) => ( { ...action, diff --git a/client/me/purchases/billing-history/hooks/use-receipt-actions.ts b/client/me/purchases/billing-history/hooks/use-receipt-actions.ts index d17133826e876..07359bc124951 100644 --- a/client/me/purchases/billing-history/hooks/use-receipt-actions.ts +++ b/client/me/purchases/billing-history/hooks/use-receipt-actions.ts @@ -1,37 +1,23 @@ -import pageRedirect from '@automattic/calypso-router'; import { useTranslate } from 'i18n-calypso'; -import { useMemo } from 'react'; +import { createElement, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { recordGoogleEvent } from 'calypso/state/analytics/actions'; import { sendBillingReceiptEmail } from 'calypso/state/billing-transactions/actions'; +import type { Action, ActionModal } from '@wordpress/dataviews'; import type { BillingTransaction } from 'calypso/state/billing-transactions/types'; import type { IAppState } from 'calypso/state/types'; -import type { Action } from 'redux'; +import type { Action as ReduxAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; function recordClickEvent( eventAction: string ): void { recordGoogleEvent( 'Me', eventAction ); } -export type ReceiptAction = { - id: 'view-receipt' | 'email-receipt'; - label: string; - isPrimary: boolean; +export type ReceiptAction = ( Action< BillingTransaction > | ActionModal< BillingTransaction > ) & { iconName: string; - callback: ( items: BillingTransaction[] ) => void; }; -type AppDispatch = ThunkDispatch< IAppState, undefined, Action >; - -function handleViewReceipt( - items: BillingTransaction[], - getReceiptUrlFor: ( receiptId: string ) => string -): void { - if ( ! items?.length || ! items[ 0 ]?.id ) { - return; - } - pageRedirect.redirect( getReceiptUrlFor( items[ 0 ].id ) ); -} +type AppDispatch = ThunkDispatch< IAppState, undefined, ReduxAction >; function handleEmailReceipt( items: BillingTransaction[], dispatch: AppDispatch ): void { if ( ! items?.length || ! items[ 0 ]?.id ) { @@ -42,7 +28,7 @@ function handleEmailReceipt( items: BillingTransaction[], dispatch: AppDispatch } export function useReceiptActions( - getReceiptUrlFor: ( receiptId: string ) => string + getReceiptUrlFor?: ( receiptId: string ) => string ): ReceiptAction[] { const dispatch = useDispatch< AppDispatch >(); const translate = useTranslate(); @@ -54,7 +40,15 @@ export function useReceiptActions( label: translate( 'View receipt' ), isPrimary: true, iconName: 'pages', - callback: ( items: BillingTransaction[] ) => handleViewReceipt( items, getReceiptUrlFor ), + modalHeader: translate( 'View receipt' ), + modalProps: { + size: 'large', + }, + RenderModal: ( props ) => { + const ReceiptModal = require( '../receipt-modal' ).default; + const [ item ] = props.items; + return createElement( ReceiptModal, { item, closeModal: props.closeModal } ); + }, }, { id: 'email-receipt', @@ -64,6 +58,6 @@ export function useReceiptActions( callback: ( items: BillingTransaction[] ) => handleEmailReceipt( items, dispatch ), }, ], - [ dispatch, getReceiptUrlFor, translate ] + [ dispatch, translate ] ); } diff --git a/client/me/purchases/billing-history/receipt-modal.tsx b/client/me/purchases/billing-history/receipt-modal.tsx new file mode 100644 index 0000000000000..1e815490cec81 --- /dev/null +++ b/client/me/purchases/billing-history/receipt-modal.tsx @@ -0,0 +1,202 @@ +import { FormLabel } from '@automattic/components'; +import { formatCurrency } from '@automattic/format-currency'; +import { Button } from '@wordpress/components'; +import clsx from 'clsx'; +import { useTranslate } from 'i18n-calypso'; +import { useState, useCallback } from 'react'; +import { useLocalizedMoment } from 'calypso/components/localized-moment'; +import TextareaAutosize from 'calypso/components/textarea-autosize'; +import { useTaxName } from 'calypso/my-sites/checkout/src/hooks/use-country-list'; +import { useDispatch } from 'calypso/state'; +import { recordGoogleEvent } from 'calypso/state/analytics/actions'; +import { getTransactionTermLabel, transactionIncludesTax } from './utils'; +import type { BillingTransaction } from 'calypso/state/billing-transactions/types'; + +import './style-modal.scss'; + +interface ReceiptModalProps { + item: BillingTransaction; + closeModal: () => void; +} + +function ReceiptLabels( { hideDetailsLabelOnPrint }: { hideDetailsLabelOnPrint?: boolean } ) { + const translate = useTranslate(); + + const labelContent = translate( + 'Use this field to add your billing information (eg. business address) before printing.' + ); + + return ( +
+ + { translate( 'Billing Details' ) } + +
+ { labelContent } +
+
+ ); +} + +function BillingDetails( { transaction }: { transaction: BillingTransaction } ) { + const [ hideDetailsLabelOnPrint, setHideDetailsLabelOnPrint ] = useState( true ); + const onChange = useCallback( + ( e: React.ChangeEvent< HTMLTextAreaElement > ) => { + const value = e.target.value.trim(); + if ( hideDetailsLabelOnPrint && value.length > 0 ) { + setHideDetailsLabelOnPrint( false ); + } else if ( ! hideDetailsLabelOnPrint && value.length === 0 ) { + setHideDetailsLabelOnPrint( true ); + } + }, + [ hideDetailsLabelOnPrint, setHideDetailsLabelOnPrint ] + ); + + const defaultValue = + transaction.cc_name === 'Not Stored' && transaction.cc_email === 'Not Stored' + ? '' + : `${ transaction.cc_name !== 'Not Stored' ? transaction.cc_name || '' : '' }\n${ + transaction.cc_email !== 'Not Stored' ? transaction.cc_email || '' : '' + }`.trim(); + + return ( +
  • + + +
  • + ); +} + +export default function ReceiptModal( { item }: ReceiptModalProps ) { + const translate = useTranslate(); + const moment = useLocalizedMoment(); + const taxName = useTaxName( item.tax_country_code ); + const reduxDispatch = useDispatch(); + + const handlePrintClick = () => { + reduxDispatch( + recordGoogleEvent( 'Me', 'Clicked on Print Receipt Button in Billing History Receipt' ) + ); + window.print(); + }; + + return ( +
    +
    + { +

    + { item.service } + + { translate( 'by %(organization)s', { + args: { organization: item.org }, + } ) } + + { item.address } +

    + + { moment( item.date ).format( 'll' ) } + +
    + +
    +
    + { translate( 'Receipt ID' ) } + { item.id } +
    + { item.pay_ref && ( +
    + { translate( 'Transaction ID' ) } + { item.pay_ref } +
    + ) } +
    + { translate( 'Payment Method' ) } + + { item.cc_type } { item.cc_num } + +
    + +
    + +
    +

    { translate( 'Order summary' ) }

    + + + + + + + + + { item.items.map( ( receiptItem ) => { + const termLabel = getTransactionTermLabel( receiptItem, translate ); + return ( + + + + + ); + } ) } + { transactionIncludesTax( item ) && ( + + + + ) } + + + + + + + +
    { translate( 'Description' ) }{ translate( 'Amount' ) }
    + { receiptItem.variation } + ({ receiptItem.type_localized }) + { termLabel && { termLabel } } + { receiptItem.domain && { receiptItem.domain } } + + { formatCurrency( receiptItem.amount_integer, item.currency, { + isSmallestUnit: true, + stripZeros: true, + } ) } +
    +
    + { taxName ?? translate( 'Tax' ) } + + { formatCurrency( item.tax_integer, item.currency, { + isSmallestUnit: true, + stripZeros: true, + } ) } + +
    +
    + { translate( 'Total paid:' ) } + + { formatCurrency( item.amount_integer, item.currency, { + isSmallestUnit: true, + stripZeros: true, + } ) } +
    +
    + +
    + +
    +
    + ); +} diff --git a/client/me/purchases/billing-history/style-modal.scss b/client/me/purchases/billing-history/style-modal.scss new file mode 100644 index 0000000000000..aa2abae2349b3 --- /dev/null +++ b/client/me/purchases/billing-history/style-modal.scss @@ -0,0 +1,140 @@ +@media print { + + body > *:not(.components-modal__screen-overlay), + .components-modal__screen-overlay > *:not(.components-modal__frame), + .components-modal__frame > *:not(.components-modal__content), + .components-modal__header, + .components-modal__header-heading-container, + .interface-interface-skeleton__header, + .interface-interface-skeleton__footer, + .interface-interface-skeleton__sidebar, + .interface-interface-skeleton__secondary-sidebar { + display: none !important; + } + + .components-modal__content { + position: static !important; + transform: none !important; + margin: 0 !important; + padding: 0 !important; + height: auto !important; + box-shadow: none !important; + background: none !important; + overflow: visible !important; + width: 100% !important; + } + + .components-modal__frame { + position: static !important; + transform: none !important; + height: auto !important; + max-height: none !important; + background: none !important; + box-shadow: none !important; + overflow: visible !important; + width: 100% !important; + margin: 0 !important; + top: 0 !important; + } + + .billing-history-receipt-modal { + margin: 0; + padding: 20px; + width: 100%; + max-width: none; + position: static !important; + overflow: visible !important; + + .billing-history__receipt-links { + display: none !important; + } + + .billing-history__billing-details-description { + display: none !important; + } + + .billing-history__billing-details-editable { + background: transparent; + border: 0; + padding: 0; + margin: 0; + } + + .receipt__no-print { + display: none !important; + } + + .billing-history__receipt-line-items { + width: 100%; + border-collapse: collapse; + } + } + + body { + font-family: $sans; + margin: 0 !important; + padding: 0 !important; + } + + @page { + margin: 20px; + } +} + +.billing-history-receipt-modal { + .billing-history__billing-details { + margin: 15px 0; + } + + .billing-history__receipt-detail { + color: var(--color-neutral-50); + display: block; + font-size: 0.75rem; + margin: 0 5px 0 0; + text-transform: uppercase; + } + + .billing-history__receipt-detail strong { + display: block; + font-weight: 600; + } + + .billing-history__receipt-detail span { + display: block; + margin-bottom: 10px; + } + + .billing-history__billing-details-description { + display: block; + font-size: $font-body-small; + font-style: italic; + margin-bottom: 5px; + } + + .billing-history__billing-details-editable { + width: 100%; + min-height: 20px; + padding: 8px; + border: 1px solid var(--color-neutral-20); + border-radius: 2px; + font-size: $font-body-small; + resize: vertical; + } + +} + +.dataviews-action-modal__view-receipt { + @media ( min-width: 600px ) { + .components-modal__frame.has-size-small { + max-width: 840vw; + width: 80vw; + } + } + + @media (min-width: 960px) { + .components-modal__frame { + max-height: 80vh; + height: 80vh; + } + } +} \ No newline at end of file