From c79db66739022f3d8c618800f069df87ab46db9d Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:52:35 +0700 Subject: [PATCH] - Details Column - Expandable chevron button for each row- Expandable Transaction Details - Click chevron to show/hide details- Payment Information Section:- Stripe Transaction IDs Section- Copy to Clipboard - One-click copy for all transaction IDs- Update Stripe webhook event permission on Stripe Config page. --- src/pages/admin/AdminDonations.js | 256 ++++++++++++++++---- src/pages/admin/AdminSettings.js | 36 ++- src/pages/admin/AdminSubscriptions.js | 327 +++++++++++++++++++++----- 3 files changed, 503 insertions(+), 116 deletions(-) diff --git a/src/pages/admin/AdminDonations.js b/src/pages/admin/AdminDonations.js index 248ecc5..4e53094 100644 --- a/src/pages/admin/AdminDonations.js +++ b/src/pages/admin/AdminDonations.js @@ -28,7 +28,13 @@ import { Loader2, Download, FileDown, - Calendar + Calendar, + ChevronDown, + ChevronUp, + ExternalLink, + Copy, + CreditCard, + Info } from 'lucide-react'; const AdminDonations = () => { @@ -43,6 +49,7 @@ const AdminDonations = () => { const [statusFilter, setStatusFilter] = useState('all'); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); + const [expandedRows, setExpandedRows] = useState(new Set()); useEffect(() => { fetchData(); @@ -157,6 +164,27 @@ const AdminDonations = () => { }); }; + const toggleRowExpansion = (donationId) => { + setExpandedRows((prev) => { + const newExpanded = new Set(prev); + if (newExpanded.has(donationId)) { + newExpanded.delete(donationId); + } else { + newExpanded.add(donationId); + } + return newExpanded; + }); + }; + + const copyToClipboard = async (text, label) => { + try { + await navigator.clipboard.writeText(text); + toast.success(`${label} copied to clipboard`); + } catch (error) { + toast.error('Failed to copy to clipboard'); + } + }; + const getStatusBadgeVariant = (status) => { const variants = { completed: 'default', @@ -385,12 +413,15 @@ const AdminDonations = () => { Payment Method + + Details + {filteredDonations.length === 0 ? ( - +

@@ -400,51 +431,182 @@ const AdminDonations = () => { ) : ( - filteredDonations.map((donation) => ( - - -

-

- {donation.donor_name || 'Anonymous'} -

-

- {donation.donor_email || 'No email'} -

-
- - - - {donation.donation_type === 'member' ? 'Member' : 'Public'} - - - -

- {donation.amount} -

- - - - {donation.status.charAt(0).toUpperCase() + donation.status.slice(1)} - - - -
- - - {formatDate(donation.created_at)} - -
- - -

- {donation.payment_method || 'N/A'} -

- - - )) + filteredDonations.map((donation) => { + const isExpanded = expandedRows.has(donation.id); + return ( + + + +
+

+ {donation.donor_name || 'Anonymous'} +

+

+ {donation.donor_email || 'No email'} +

+
+ + + + {donation.donation_type === 'member' ? 'Member' : 'Public'} + + + +

+ {donation.amount} +

+ + + + {donation.status.charAt(0).toUpperCase() + donation.status.slice(1)} + + + +
+ + + {formatDate(donation.created_at)} + +
+ + +

+ {donation.payment_method || 'N/A'} +

+ + + + + + {/* Expandable Details Row */} + {isExpanded && ( + + +
+

+ Transaction Details +

+
+ {/* Payment Information */} +
+
+ + Payment Information +
+
+ {donation.payment_completed_at && ( +
+ Payment Date: + {formatDate(donation.payment_completed_at)} +
+ )} + {donation.payment_method && ( +
+ Payment Method: + {donation.payment_method} +
+ )} + {donation.card_brand && donation.card_last4 && ( +
+ Card: + {donation.card_brand} ****{donation.card_last4} +
+ )} +
+
+ + {/* Stripe Transaction IDs */} +
+
+ + Stripe Transaction IDs +
+
+ {donation.stripe_payment_intent_id && ( +
+ Payment Intent: +
+ + {donation.stripe_payment_intent_id.substring(0, 20)}... + + +
+
+ )} + {donation.stripe_charge_id && ( +
+ Charge ID: +
+ + {donation.stripe_charge_id.substring(0, 20)}... + + +
+
+ )} + {donation.stripe_customer_id && ( +
+ Customer ID: +
+ + {donation.stripe_customer_id.substring(0, 20)}... + + +
+
+ )} + {donation.stripe_receipt_url && ( +
+ Receipt: + +
+ )} +
+
+
+
+ + + )} +
+ ); + }) )} diff --git a/src/pages/admin/AdminSettings.js b/src/pages/admin/AdminSettings.js index 6967086..5d106c6 100644 --- a/src/pages/admin/AdminSettings.js +++ b/src/pages/admin/AdminSettings.js @@ -342,16 +342,26 @@ export default function AdminSettings() {
-

Webhook Events:

+

Webhook Events to Configure:

-

✅ Currently Handled:

+

✅ Required (Configure in Stripe):

    -
  • checkout.session.completed - Subscription & donation payments
  • +
  • checkout.session.completed - Handles subscriptions & donations
+
+

🔔 Automatically Triggered:

+
    +
  • payment_intent.created
  • +
  • payment_intent.succeeded
  • +
  • charge.succeeded
  • +
  • charge.updated
  • +
+

These fire automatically with checkout.session.completed

+
-

🔄 Coming Soon:

+

🔄 Coming Soon (Recurring Subscriptions):

  • invoice.payment_succeeded
  • invoice.payment_failed
  • @@ -417,16 +427,26 @@ export default function AdminSettings() {
-

Webhook Events:

+

Webhook Events to Configure:

-

✅ Currently Handled:

+

✅ Required (Configure in Stripe):

    -
  • checkout.session.completed - Subscription & donation payments
  • +
  • checkout.session.completed - Handles subscriptions & donations
+
+

🔔 Automatically Triggered:

+
    +
  • payment_intent.created
  • +
  • payment_intent.succeeded
  • +
  • charge.succeeded
  • +
  • charge.updated
  • +
+

These fire automatically with checkout.session.completed

+
-

🔄 Coming Soon:

+

🔄 Coming Soon (Recurring Subscriptions):

  • invoice.payment_succeeded
  • invoice.payment_failed
  • diff --git a/src/pages/admin/AdminSubscriptions.js b/src/pages/admin/AdminSubscriptions.js index bb0e4c5..815580d 100644 --- a/src/pages/admin/AdminSubscriptions.js +++ b/src/pages/admin/AdminSubscriptions.js @@ -35,7 +35,11 @@ import { Download, FileDown, AlertTriangle, - Info + Info, + ChevronDown, + ChevronUp, + ExternalLink, + Copy } from 'lucide-react'; import { DropdownMenu, @@ -55,6 +59,7 @@ const AdminSubscriptions = () => { const [statusFilter, setStatusFilter] = useState('all'); const [planFilter, setPlanFilter] = useState('all'); const [exporting, setExporting] = useState(false); + const [expandedRows, setExpandedRows] = useState(new Set()); // Edit subscription dialog state const [editDialogOpen, setEditDialogOpen] = useState(false); @@ -265,6 +270,38 @@ Proceed with activation?`; }); }; + const formatDateTime = (dateString) => { + if (!dateString) return 'N/A'; + return new Date(dateString).toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const toggleRowExpansion = (subscriptionId) => { + setExpandedRows((prev) => { + const newExpanded = new Set(prev); + if (newExpanded.has(subscriptionId)) { + newExpanded.delete(subscriptionId); + } else { + newExpanded.add(subscriptionId); + } + return newExpanded; + }); + }; + + const copyToClipboard = async (text, label) => { + try { + await navigator.clipboard.writeText(text); + toast.success(`${label} copied to clipboard`); + } catch (error) { + toast.error('Failed to copy to clipboard'); + } + }; + const getStatusBadgeVariant = (status) => { const variants = { active: 'default', @@ -566,6 +603,9 @@ Proceed with activation?`; Total + + Details + Actions @@ -573,73 +613,238 @@ Proceed with activation?`; {filteredSubscriptions.length > 0 ? ( - filteredSubscriptions.map((sub) => ( - - -
    - {sub.user.first_name} {sub.user.last_name} -
    -
    - {sub.user.email} -
    - - -
    - {sub.plan.name} -
    -
    - {sub.plan.billing_cycle} -
    - - - - {sub.status} - - - -
    -
    {formatDate(sub.start_date)}
    -
    to {formatDate(sub.end_date)}
    -
    - - - {formatPrice(sub.base_subscription_cents || 0)} - - - {formatPrice(sub.donation_cents || 0)} - - - {formatPrice(sub.amount_paid_cents || 0)} - - -
    - {hasPermission('subscriptions.edit') && ( + filteredSubscriptions.map((sub) => { + const isExpanded = expandedRows.has(sub.id); + return ( + + + +
    + {sub.user.first_name} {sub.user.last_name} +
    +
    + {sub.user.email} +
    + + +
    + {sub.plan.name} +
    +
    + {sub.plan.billing_cycle} +
    + + + + {sub.status} + + + +
    +
    {formatDate(sub.start_date)}
    +
    to {formatDate(sub.end_date)}
    +
    + + + {formatPrice(sub.base_subscription_cents || 0)} + + + {formatPrice(sub.donation_cents || 0)} + + + {formatPrice(sub.amount_paid_cents || 0)} + + - )} - {sub.status === 'active' && hasPermission('subscriptions.cancel') && ( - - )} -
    - - - )) + + +
    + {hasPermission('subscriptions.edit') && ( + + )} + {sub.status === 'active' && hasPermission('subscriptions.cancel') && ( + + )} +
    + + + {/* Expandable Details Row */} + {isExpanded && ( + + +
    +

    + Transaction Details +

    +
    + {/* Payment Information */} +
    +
    + + Payment Information +
    +
    + {sub.payment_completed_at && ( +
    + Payment Date: + {formatDateTime(sub.payment_completed_at)} +
    + )} + {sub.payment_method && ( +
    + Payment Method: + {sub.payment_method} +
    + )} + {sub.card_brand && sub.card_last4 && ( +
    + Card: + {sub.card_brand} ****{sub.card_last4} +
    + )} +
    +
    + + {/* Stripe Transaction IDs */} +
    +
    + + Stripe Transaction IDs +
    +
    + {sub.stripe_payment_intent_id && ( +
    + Payment Intent: +
    + + {sub.stripe_payment_intent_id.substring(0, 20)}... + + +
    +
    + )} + {sub.stripe_charge_id && ( +
    + Charge ID: +
    + + {sub.stripe_charge_id.substring(0, 20)}... + + +
    +
    + )} + {sub.stripe_subscription_id && ( +
    + Subscription ID: +
    + + {sub.stripe_subscription_id.substring(0, 20)}... + + +
    +
    + )} + {sub.stripe_invoice_id && ( +
    + Invoice ID: +
    + + {sub.stripe_invoice_id.substring(0, 20)}... + + +
    +
    + )} + {sub.stripe_customer_id && ( +
    + Customer ID: +
    + + {sub.stripe_customer_id.substring(0, 20)}... + + +
    +
    + )} + {sub.stripe_receipt_url && ( +
    + Receipt: + +
    + )} +
    +
    +
    +
    + + + )} + + ); + }) ) : ( - + No subscriptions found