Skip to content

Feature: add donation overview page #7985

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fce4a15
feature: add scaffold with header and tabs for donation details page
pauloiankoski Jun 11, 2025
67942dd
fix: set correct argument type
pauloiankoski Jun 12, 2025
6a3945f
Merge branch 'epic/donation-details-admin-page' into feature/donation…
JoshuaHungDinh Jun 18, 2025
39b768e
feature: add overview panel
JoshuaHungDinh Jun 23, 2025
097b0fa
feature: update stat widget
JoshuaHungDinh Jun 23, 2025
6908a8d
Feautre: update timestamp formatter
JoshuaHungDinh Jun 23, 2025
43c6a63
feature: add overview components
JoshuaHungDinh Jun 23, 2025
2e577b5
fix: remove placeholder
JoshuaHungDinh Jun 23, 2025
b9ca6fe
feature: add initial hook
JoshuaHungDinh Jun 23, 2025
23110d7
feature: update donation options and types
JoshuaHungDinh Jun 23, 2025
7a25d58
refactor: pass data from parent component
JoshuaHungDinh Jun 23, 2025
fa1661a
fix: update feerecovery link
JoshuaHungDinh Jun 23, 2025
6305311
fix: get event ticket amount
JoshuaHungDinh Jun 23, 2025
29a0e97
refactor: improve summarygrid styles
JoshuaHungDinh Jun 24, 2025
92cb747
refactor: improve component props
JoshuaHungDinh Jun 24, 2025
ccff0c0
refactor: check donation resolved
JoshuaHungDinh Jun 24, 2025
6228c17
refactor: map donation type to formatted text display
JoshuaHungDinh Jun 24, 2025
555dffa
Merge branch 'epic/donation-details-admin-page' into feature/donation…
JoshuaHungDinh Jun 24, 2025
6186054
fix: update import
JoshuaHungDinh Jun 24, 2025
97ae832
fix: update resolved conditional
JoshuaHungDinh Jun 24, 2025
341e463
doc: add unreleased tags
JoshuaHungDinh Jun 24, 2025
6691201
feature: add gaetway view url
JoshuaHungDinh Jun 24, 2025
a04c0f3
refactor event tickets stat widget to rely on amount rather than enab…
JoshuaHungDinh Jun 24, 2025
2e02c25
fix: hide gateway url link when not available
JoshuaHungDinh Jun 24, 2025
3628235
fix: update unit tests
JoshuaHungDinh Jun 24, 2025
bdc91e7
Merge branch 'epic/donation-details-admin-page' into feature/donation…
JoshuaHungDinh Jun 24, 2025
ab1f398
fix: handle upgrade link :visited styles
JoshuaHungDinh Jun 24, 2025
d9bc900
fix: One-time pill text change
JoshuaHungDinh Jun 24, 2025
48697bf
fix: use readable payment method value. update accessing values from …
JoshuaHungDinh Jun 24, 2025
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
32 changes: 32 additions & 0 deletions src/API/REST/V3/Routes/Donations/DonationStatisticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public function get_item($request)
'date' => $donation->createdAt->format('Y-m-d H:i:s'),
'paymentMethod' => $donation->gatewayId,
'mode' => $donation->mode->getValue(),
'gatewayViewUrl' => $this->getGatewayViewUrl($donation),
],
'donor' => [
'id' => $donation->donorId,
Expand Down Expand Up @@ -131,4 +132,35 @@ public function prepare_item_for_response($item, $request): WP_REST_Response

return $response;
}

/**
* Generate a dashboard URL to view this donation on the gateway, if possible.
*
* @param Donation $donation
* @return string|null
*/
private function getGatewayViewUrl(Donation $donation): ?string
{
$gatewayId = $donation->gatewayId;
$transactionId = $donation->gatewayTransactionId ?? null;
$mode = $donation->mode->getValue();

if (!$transactionId) {
return null;
}

switch ($gatewayId) {
case 'paypal-commerce':
$base = 'https://www.paypal.com/';
return $base . 'activity/payment/' . urlencode($transactionId);
case 'stripe_payment_element':
case 'stripe':
$base = $mode === 'live'
? 'https://dashboard.stripe.com/payments/'
: 'https://dashboard.stripe.com/test/payments/';
return $base . urlencode($transactionId);
default:
return null;
}
}
}
21 changes: 21 additions & 0 deletions src/Admin/components/OverviewPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import styles from './styles.module.scss';

/**
* @unreleased
*/
interface OverviewPanelProps {
children: React.ReactNode;
className?: string;
}

/**
* @unreleased
*/
export default function OverviewPanel({ children, className = '' }: OverviewPanelProps) {
return (
<section className={`${styles.panel} ${className}`}>
{children}
</section>
);
}
12 changes: 12 additions & 0 deletions src/Admin/components/OverviewPanel/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.panel {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding: 1rem 1.5rem 1.5rem;
border-radius: 8px;
border: solid 1px #f3f4f6;
background-color: var(--givewp-shades-white);
width: 100%;
box-sizing: border-box;
}
14 changes: 12 additions & 2 deletions src/Admin/components/StatWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import {Spinner} from '@givewp/components';
import {HeaderText} from '../Header';
import PercentChangePill
from '@givewp/campaigns/admin/components/CampaignDetailsPage/Components/CampaignStats/PercentChangePill';
import classnames from 'classnames';
import styles from './styles.module.scss';
import { __ } from '@wordpress/i18n';

/**
* @unreleased add href & inActive props to handle Fee Recovery widget.
* @since 4.4.0
*/
export type StatWidgetProps = {
Expand All @@ -14,33 +17,39 @@ export type StatWidgetProps = {
formatter: Intl.NumberFormat;
loading?: boolean;
previousValue?: number;
inActive?: boolean;
href?: string;
};

/**
* @unreleased use new props to handle Fee Recovery widget.
* @since 4.4.0
*/
export default function StatWidget({
label,
value,
description,
href,
formatter = null,
loading = false,
previousValue = null,
inActive = false,
}: StatWidgetProps) {
return (
<div className={styles.statWidget}>
<div className={classnames(styles.statWidget)}>
<header>
<HeaderText>{label}</HeaderText>
</header>
<div className={styles.statWidgetAmount}>
<div className={styles.statWidgetDisplay}>
<div className={classnames(styles.statWidgetDisplay, {[styles.inActive]: inActive})}>
{!loading ? (
formatter?.format(value) ?? value
) : (
<span>
<Spinner size="small" />
</span>
)}
{inActive && (<a className={styles.upgradeLink} href={href}>{__('Upgrade', 'give')}</a>)}
</div>
{previousValue !== null && <PercentChangePill value={value} comparison={previousValue} />}
</div>
Expand All @@ -49,6 +58,7 @@ export default function StatWidget({
<div>{description}</div>
</footer>
)}

</div>
);
}
66 changes: 33 additions & 33 deletions src/Admin/components/StatWidget/styles.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.statWidget {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
Expand All @@ -8,6 +9,7 @@
color: var(--givewp-neutral-700);
font-weight: 600;
border-radius: var(--givewp-rounded-8);
width: 100%;
}

.statWidget header, .statWidget footer {
Expand All @@ -30,6 +32,9 @@
}

.statWidgetDisplay {
display: flex;
justify-content: space-between;
align-items: flex-end;
font-size: 2.25rem;
font-weight: 600;
line-height: 44px;
Expand All @@ -44,6 +49,34 @@
}
}

.inActive {
color: var(--givewp-neutral-300);
}

.upgradeLink{
display: flex;
align-items: center;
justify-content: center;
height: 1.375rem;
background: var(--givewp-green-500);
color: var(--givewp-shades-white);
padding: 0.125rem var(--givewp-spacing-3);
border-radius: 999px;;
text-decoration: none;
font-size: 0.75rem;
text-transform: uppercase;
cursor: pointer;

&:hover, &:active, &:focus {
color: var(--givewp-shades-white);
background: var(--givewp-green-600);
}
}

.upgradeLink:visited {
background: var(--givewp-green-500);
}

.revenueWidget {
flex: 2;
background-color: var(--givewp-shades-white);
Expand Down Expand Up @@ -71,41 +104,8 @@
border-radius: var(--givewp-rounded-8);
}

.headerSpacing {
display: flex;
flex-direction: column;
gap: .25rem;
}

.mainGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
row-gap: var(--givewp-spacing-6);
column-gap: var(--givewp-spacing-4);
}

.nestedGrid {
grid-column: span 1;
display: grid;
gap: var(--givewp-spacing-6);
}

@media (max-width: 900px) {
.mainGrid {
grid-template-columns: 1fr;
gap: var(--givewp-spacing-3);
}

.revenueWidget {
grid-column: span 1;
}

.nestGrid {
grid-template-columns: 1fr;
gap: var(--givewp-spacing-3);
}

/* Only center content when grid becomes single column */
.statWidgetAmount {
justify-content: center;
text-align: center;
Expand Down
5 changes: 3 additions & 2 deletions src/Admin/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function amountFormatter(currency: Intl.NumberFormatOptions['currency'],
/**
* @since unreleased
*/
export function formatTimestamp(timestamp: string): string {
export function formatTimestamp(timestamp: string, useComma: boolean = false): string {
const date = new Date(timestamp);

const day = date.getDate();
Expand All @@ -32,8 +32,9 @@ export function formatTimestamp(timestamp: string): string {
const month = date.toLocaleString('en-US', { month: 'long' });
const year = date.getFullYear();
const time = date.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
const separator = useComma ? ', ' : ' • ';

return `${dayWithOrdinal} ${month} ${year}${time}`;
return `${dayWithOrdinal} ${month} ${year}${separator}${time}`;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/Donations/Actions/LoadDonationOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Give\API\REST\V3\Routes\Donations\ValueObjects\DonationRoute;
use Give\Donations\ValueObjects\DonationStatus;
use Give\Framework\Database\DB;
use Give\Helpers\IntlTelInput;
use Give\BetaFeatures\Facades\FeatureFlag;
use Give\Framework\Database\DB;

/**
* The purpose of this action is to have a centralized place for localizing options used on many different places
Expand Down Expand Up @@ -47,6 +48,9 @@ private function getDonationOptions(): array
'campaignsWithForms' => $this->getCampaignsWithForms(),
'donors' => $this->getDonors(),
'isRecurringEnabled' => defined('GIVE_RECURRING_VERSION') ? GIVE_RECURRING_VERSION : null,
'admin' => $isAdmin ? [] : null,
'eventTicketsEnabled' => FeatureFlag::eventTickets(),
'isFeeRecoveryEnabled' => defined('GIVE_FEE_RECOVERY_VERSION'),
'mode' => give_is_test_mode() ? 'test' : 'live',
];
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { Fragment } from 'react';
import { __ } from '@wordpress/i18n';

/**
* @unreleased
*/
type BillingInformationProps = {
name: string;
email: string;
address: string[];
};

/**
* @unreleased
*/
export default function BillingInformation({ name, email, address }: BillingInformationProps) {
return (
<address>
<p>
{name} (<a href={`mailto:${email}`}>{email}</a>)<br />
{address.map((line, index) => (
<Fragment key={index}>
{line}
{index < address.length - 1 && <br />}
</Fragment>
))}
</p>
</address>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import styles from './styles.module.scss';

/**
* @unreleased
*/
export default function DonationBreakdown() {
return (
<>
<Row label={__("Donation amount", 'give')} value="$270.00" underline/>
<Row
label={
<div className={styles.eventLabel}>
{__("Standard (x1)", 'give')}
<a href="#" className={styles.eventLink}>{__("Event name", 'give')}</a>
</div>
}
value="$30.00"
underline
/>
<Row label={__("Total", 'give')} value="$300.00" isTotal />
<Row link={{ href: '#', label: __('Close', 'give')}}/>
<Row label={__("Exchange rate", 'give')} value="1.14" />
<Row label={__("Base currency", 'give')} value={__("Euro (€)", 'give')} />
<Row label={__("Base total", 'give')} value="€264.14" />
</>
);
}

/**
* @unreleased
*/
type RowProps = {
label?: React.ReactNode;
value?: React.ReactNode;
isTotal?: boolean;
children?: React.ReactNode;
link?: { href: string; label: string;};
underline?: boolean;
};

/**
* @unreleased
*/
function Row({ label, value, isTotal = false, children, link }: RowProps) {
return (
<div className={`${styles.row} ${isTotal ? styles.rowTotal : ''}`}>
<dt className={styles.label}>
{label}
{children}
</dt>
<dd className={styles.value}>
{isTotal ? <strong>{value}</strong> : value}
{link && (
<a
href={link.href}
className={styles.link}
>
{link.label}
</a>
)}
</dd>
</div>
);
}
Loading