From 1e5217f3ee353be5e4cf1f13d48fdf1f266085fe Mon Sep 17 00:00:00 2001 From: David Remer Date: Thu, 22 Jun 2023 10:28:01 +0300 Subject: [PATCH] Add order tab [PREMIUM-224] --- .../components/tabs/orders/cells/customer.tsx | 17 +++ .../components/tabs/orders/cells/email.tsx | 24 ++++ .../components/tabs/orders/cells/order.tsx | 11 ++ .../tabs/orders/cells/order_status.tsx | 15 +++ .../components/tabs/orders/cells/products.tsx | 28 ++++ .../components/tabs/orders/index.tsx | 121 +++++++++++++++++- .../analytics/components/tabs/orders/rows.tsx | 48 +++++++ .../components/tabs/orders/summary.ts | 53 ++++++++ .../components/tabs/orders/upgrade.tsx | 36 ++++++ 9 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/customer.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/email.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order_status.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/products.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/rows.tsx create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/summary.ts create mode 100644 mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/upgrade.tsx diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/customer.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/customer.tsx new file mode 100644 index 0000000000..91f2faab67 --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/customer.tsx @@ -0,0 +1,17 @@ +import { CustomerData } from '../../../../store'; + +export function CustomerCell({ + customer, +}: { + customer: CustomerData; +}): JSX.Element { + return ( + + {customer.last_name} + {`${customer.first_name} ${customer.last_name}`} + + ); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/email.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/email.tsx new file mode 100644 index 0000000000..5bd1c34a0e --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/email.tsx @@ -0,0 +1,24 @@ +import { Tooltip } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; +import { OrderData, storeName } from '../../../../store'; +import { MailPoet } from '../../../../../../../../mailpoet'; + +export function EmailCell({ order }: { order: OrderData }): JSX.Element { + const { automation } = useSelect((s) => ({ + automation: s(storeName).getAutomation(), + })); + + return ( + + + {`${order.email.subject}`} + + + ); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order.tsx new file mode 100644 index 0000000000..70dc8f78e0 --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order.tsx @@ -0,0 +1,11 @@ +import { Tooltip } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { OrderDetails } from '../../../../store'; + +export function OrderCell({ order }: { order: OrderDetails }): JSX.Element { + return ( + + {`${order.id}`} + + ); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order_status.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order_status.tsx new file mode 100644 index 0000000000..279f5b6a63 --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/order_status.tsx @@ -0,0 +1,15 @@ +export function OrderStatusCell({ + id, + name, +}: { + id: string; + name: string; +}): JSX.Element { + return ( + + {name} + + ); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/products.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/products.tsx new file mode 100644 index 0000000000..5999d3a475 --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/cells/products.tsx @@ -0,0 +1,28 @@ +import { ViewMoreList } from '@woocommerce/components/build'; +import { Fragment } from '@wordpress/element'; +import { OrderDetails } from '../../../../store'; + +export function ProductsCell({ order }: { order: OrderDetails }) { + const items = + order.products.length > 0 + ? order.products.map((item) => ( + + {item.name}  + ({item.quantity}×) + + )) + : []; + + if (!items.length) { + return ; + } + + const visibleItem = items.slice(0, 1); + + return ( +
+ {visibleItem} + {items.length > 1 && } +
+ ); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/index.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/index.tsx index 3a81305431..c3a4d364c1 100644 --- a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/index.tsx +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/index.tsx @@ -1,3 +1,122 @@ +import { dispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { TableCard } from '@woocommerce/components/build'; +import { MailPoet } from '../../../../../../../mailpoet'; +import { OrderSection, storeName } from '../../../store'; +import { transformOrdersToRows } from './rows'; +import { calculateSummary } from './summary'; +import { Upgrade } from './upgrade'; + +const headers = [ + { + key: 'created_at', + isSortable: true, + label: __('Date', 'mailpoet'), + }, + { + key: 'id', + label: __('Order #', 'mailpoet'), + }, + { + key: 'last_name', + isSortable: true, + label: __('Customer', 'mailpoet'), + }, + { + key: 'items', + label: __('Product(s)', 'mailpoet'), + }, + { + key: 'subject', + isSortable: true, + label: __('Email clicked', 'mailpoet'), + }, + { + key: 'status', + isSortable: true, + label: __('Status', 'mailpoet'), + }, + { + key: 'revenue', + isSortable: true, + label: __('Revenue', 'mailpoet'), + }, +]; + export function Orders(): JSX.Element { - return

Orders

; + const { ordersSection } = useSelect((s) => ({ + ordersSection: s(storeName).getSection('orders') as OrderSection, + })); + + const orders = + ordersSection.data !== undefined ? ordersSection.data.items : undefined; + const rows = transformOrdersToRows(orders); + const summary = calculateSummary(orders ?? []); + + return ( +
+ {!MailPoet.premiumActive && ( + + {__("You're viewing sample data.", 'mailpoet')} +   + {__( + 'To use data from your email activity, upgrade to a premium plan.', + 'mailpoet', + )} + + } + /> + )} + + (param: unknown) => { + let customQuery = {}; + if (type === 'paged') { + customQuery = { page: param }; + } else if (type === 'per_page') { + customQuery = { + page: 1, + limit: param, + }; + } else if (type === 'sort') { + customQuery = { + page: 1, + order_by: param, + order: + ordersSection.customQuery.order_by === param && + ordersSection.customQuery.order === 'asc' + ? 'desc' + : 'asc', + }; + } + dispatch(storeName).updateSection({ + ...ordersSection, + customQuery: { + ...ordersSection.customQuery, + ...customQuery, + }, + }); + }} + query={{ + paged: ordersSection.customQuery.page, + orderby: ordersSection.customQuery.order_by, + order: ordersSection.customQuery.order, + }} + rows={rows} + headers={headers} + showMenu={false} + rowsPerPage={ordersSection.customQuery.limit} + onRowClick={() => {}} + totalRows={ + ordersSection.data !== undefined ? ordersSection.data.results : 0 + } + summary={summary} + isLoading={orders === undefined} + /> +
+ ); } diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/rows.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/rows.tsx new file mode 100644 index 0000000000..0e1162ba97 --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/rows.tsx @@ -0,0 +1,48 @@ +import { OrderData } from '../../../store'; +import { OrderCell } from './cells/order'; +import { CustomerCell } from './cells/customer'; +import { ProductsCell } from './cells/products'; +import { EmailCell } from './cells/email'; +import { OrderStatusCell } from './cells/order_status'; +import { formattedPrice } from '../../../formatter'; +import { MailPoet } from '../../../../../../../mailpoet'; + +export function transformOrdersToRows(orders: OrderData[] | undefined) { + return orders === undefined + ? [] + : orders.map((order) => [ + { + display: MailPoet.Date.format(new Date(order.date)), + value: order.date, + }, + { + display: , + value: order.details.id, + }, + { + display: , + value: order.customer.last_name, + }, + { + display: , + value: null, + }, + { + display: , + value: order.email.subject, + }, + { + display: ( + + ), + value: order.details.status.id, + }, + { + display: formattedPrice(order.details.total), + value: order.details.total, + }, + ]); +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/summary.ts b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/summary.ts new file mode 100644 index 0000000000..06cc9c0a0e --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/summary.ts @@ -0,0 +1,53 @@ +import { __ } from '@wordpress/i18n'; +import { OrderData } from '../../../store'; +import { formattedPrice } from '../../../formatter'; + +function calculateDaysPassed(data: OrderData[]): number { + const dates = data + .map((order) => { + const date = new Date(order.date); + return date.getTime(); + }) + .filter((value, index, self) => self.indexOf(value) === index); + + const latestDate = Math.max.apply(null, dates); + const earliestDate = Math.min.apply(null, dates); + + return Math.max.apply(null, [ + 1, + Math.round((latestDate - earliestDate) / (1000 * 3600 * 24)), + ]) as number; +} + +export function calculateSummary(data: OrderData[]) { + const subscribers = data + .map((order) => order.customer.id) + .filter((value, index, self) => self.indexOf(value) === index).length; + + const products = data + .map((order) => order.details.products.map((item) => item.id)) + .flat() + .filter((value, index, self) => self.indexOf(value) === index).length; + + const revenue = data + .map((order) => order.details.total) + .reduce((a, b) => a + b, 0); + return [ + { + label: __('days', 'mailpoet'), + value: calculateDaysPassed(data), + }, + { + label: __('subscribers', 'mailpoet'), + value: subscribers, + }, + { + label: __('products', 'mailpoet'), + value: products, + }, + { + label: __('revenue', 'mailpoet'), + value: formattedPrice(revenue), + }, + ]; +} diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/upgrade.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/upgrade.tsx new file mode 100644 index 0000000000..724cf9cbec --- /dev/null +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/orders/upgrade.tsx @@ -0,0 +1,36 @@ +import { Notice } from '@wordpress/components/build'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; +import { MailPoet } from '../../../../../../../mailpoet'; + +function getUpgradeLink(): string { + const utmArgs = { + utm_source: 'plugin', + utm_medium: 'upsell_modal', + utm_campaign: 'automation-analytics', + }; + const url = MailPoet.hasValidApiKey + ? `https://account.mailpoet.com/orders/upgrade/${MailPoet.pluginPartialKey}` + : `https://account.mailpoet.com/?s=${MailPoet.subscribersCount}&g=business&billing=monthly&email=${MailPoet.currentWpUserEmail}`; + + return addQueryArgs(url, utmArgs); +} + +export function Upgrade({ text }: { text: string | JSX.Element }): JSX.Element { + return ( + + + {text} + + + + + ); +}