give
/
src
/
Subscriptions
/
resources
/
admin
/
components
/
SubscriptionDetailsPage
Last commit date
Tabs
10 months ago
components
10 months ago
SubscriptionDetailsPage.module.scss
10 months ago
index.tsx
10 months ago
slots.ts
10 months ago
index.tsx
219 lines
| 1 | /** |
| 2 | * External Dependencies |
| 3 | */ |
| 4 | import { useState } from 'react'; |
| 5 | import cx from 'classnames'; |
| 6 | import { useFormContext } from 'react-hook-form'; |
| 7 | |
| 8 | /** |
| 9 | * WordPress Dependencies |
| 10 | */ |
| 11 | import { __, _n, sprintf } from '@wordpress/i18n'; |
| 12 | |
| 13 | /** |
| 14 | * Internal Dependencies |
| 15 | */ |
| 16 | import { CancelIcon, TrashIcon } from '@givewp/components/AdminDetailsPage/Icons'; |
| 17 | import AdminDetailsPage from '@givewp/components/AdminDetailsPage'; |
| 18 | import ConfirmationDialog from '@givewp/components/AdminDetailsPage/ConfirmationDialog'; |
| 19 | import { getSubscriptionOptionsWindowData, useSubscriptionEntityRecord } from '@givewp/subscriptions/utils'; |
| 20 | import tabDefinitions from './Tabs/definitions'; |
| 21 | import { useSubscriptionAmounts } from '@givewp/subscriptions/hooks'; |
| 22 | import { useDispatch } from '@wordpress/data'; |
| 23 | import { store as coreDataStore } from '@wordpress/core-data'; |
| 24 | import useSubscriptionSync from '@givewp/subscriptions/hooks/useSubscriptionSync'; |
| 25 | import SubscriptionSyncList from '../SubscriptionSyncList'; |
| 26 | import styles from './SubscriptionDetailsPage.module.scss'; |
| 27 | import CancelSubscriptionDialog from './components/CancelSubscriptionDialog'; |
| 28 | |
| 29 | const { subscriptionStatuses } = getSubscriptionOptionsWindowData(); |
| 30 | |
| 31 | /** |
| 32 | * @since 4.8.0 |
| 33 | */ |
| 34 | const StatusBadge = ({ status, isTest }: { status: string, isTest: boolean }) => { |
| 35 | const statusMap = subscriptionStatuses; |
| 36 | |
| 37 | if (!statusMap[status]) { |
| 38 | return null; |
| 39 | } |
| 40 | |
| 41 | return ( |
| 42 | <> |
| 43 | <div className={`${styles.statusBadge} ${styles[`statusBadge--${status}`]}`}> |
| 44 | {statusMap[status]} |
| 45 | </div> |
| 46 | {isTest && ( |
| 47 | <div className={`${styles.statusBadge} ${styles.testBadge}`}> |
| 48 | {__('Test Subscription', 'give')} |
| 49 | </div> |
| 50 | )} |
| 51 | </> |
| 52 | ); |
| 53 | }; |
| 54 | |
| 55 | /** |
| 56 | * @since 4.8.0 |
| 57 | */ |
| 58 | export default function SubscriptionDetailsPage() { |
| 59 | const { adminUrl, subscriptionsAdminUrl } = getSubscriptionOptionsWindowData(); |
| 60 | const [showConfirmationDialog, setShowConfirmationDialog] = useState<string | null>(null); |
| 61 | const [hasSyncBeenPerformed, setHasSyncBeenPerformed] = useState(false); |
| 62 | |
| 63 | // Get subscription ID from URL parameters |
| 64 | const urlParams = new URLSearchParams(window.location.search); |
| 65 | const subscriptionId = urlParams.get('id'); |
| 66 | |
| 67 | const { record: subscription } = useSubscriptionEntityRecord(subscriptionId ? parseInt(subscriptionId) : undefined); |
| 68 | const { formatter } = useSubscriptionAmounts(subscription); |
| 69 | const { deleteEntityRecord } = useDispatch(coreDataStore); |
| 70 | const { syncSubscription, isLoading, hasResolved, syncResult } = useSubscriptionSync(); |
| 71 | const subscriptionCanSync = subscription?.gateway?.canSync; |
| 72 | |
| 73 | const PageTitle = () => { |
| 74 | if (subscription?.amount?.value == null) { |
| 75 | return null; |
| 76 | } |
| 77 | const periodsLabels = { |
| 78 | day: _n('day', 'days', subscription?.frequency, 'give'), |
| 79 | week: _n('week', 'weeks', subscription?.frequency, 'give'), |
| 80 | month: _n('month', 'months', subscription?.frequency, 'give'), |
| 81 | quarter: _n('quarter', 'quarters', subscription?.frequency, 'give'), |
| 82 | year: _n('year', 'years', subscription?.frequency, 'give'), |
| 83 | }; |
| 84 | |
| 85 | const period = [ |
| 86 | __('every', 'give'), |
| 87 | subscription?.frequency > 1 ? subscription?.frequency : '', |
| 88 | periodsLabels[subscription?.period], |
| 89 | ].filter(Boolean).join(' '); |
| 90 | |
| 91 | return ( |
| 92 | <> |
| 93 | {formatter.format(subscription?.amount?.value)} <span className={styles.period}>{period}</span> |
| 94 | </> |
| 95 | ); |
| 96 | }; |
| 97 | |
| 98 | function SecondaryActionButton({ className }: { className: string }) { |
| 99 | return ( |
| 100 | <button |
| 101 | type="button" |
| 102 | className={className} |
| 103 | onClick={() => { |
| 104 | setShowConfirmationDialog('sync'); |
| 105 | setHasSyncBeenPerformed(false); |
| 106 | }} |
| 107 | > |
| 108 | {__('Sync subscription', 'give')} |
| 109 | </button> |
| 110 | ); |
| 111 | } |
| 112 | |
| 113 | const ContextMenuItems = ({ className }: { className: string }) => { |
| 114 | return ( |
| 115 | <> |
| 116 | {subscription?.status !== 'cancelled' ? ( |
| 117 | <a |
| 118 | href="#" |
| 119 | className={cx(className, styles.archive)} |
| 120 | onClick={() => setShowConfirmationDialog('cancel')} |
| 121 | > |
| 122 | <CancelIcon /> {__('Cancel subscription', 'give')} |
| 123 | </a> |
| 124 | ) : ( |
| 125 | <a |
| 126 | href="#" |
| 127 | className={cx(className, styles.archive)} |
| 128 | onClick={() => setShowConfirmationDialog('delete')} |
| 129 | > |
| 130 | <TrashIcon /> {__('Trash subscription', 'give')} |
| 131 | </a> |
| 132 | )} |
| 133 | </> |
| 134 | ); |
| 135 | }; |
| 136 | |
| 137 | /** |
| 138 | * @since 4.8.0 |
| 139 | */ |
| 140 | const handleDelete = async () => { |
| 141 | try { |
| 142 | await deleteEntityRecord('givewp', 'subscription', subscription?.id, { force: false }) |
| 143 | window.location.href = subscriptionsAdminUrl; |
| 144 | } catch (error) { |
| 145 | setShowConfirmationDialog(null); |
| 146 | } |
| 147 | }; |
| 148 | |
| 149 | /** |
| 150 | * @since 4.8.0 |
| 151 | */ |
| 152 | const handleSyncSubscription = async () => { |
| 153 | try { |
| 154 | await syncSubscription(subscription); |
| 155 | setHasSyncBeenPerformed(true); |
| 156 | console.log('Sync result:', syncResult); |
| 157 | } catch (error) { |
| 158 | console.error('Sync failed:', error); |
| 159 | setShowConfirmationDialog(null); |
| 160 | } |
| 161 | }; |
| 162 | |
| 163 | return ( |
| 164 | <AdminDetailsPage |
| 165 | objectId={subscription?.id} |
| 166 | objectType="subscription" |
| 167 | objectTypePlural="subscriptions" |
| 168 | useObjectEntityRecord={useSubscriptionEntityRecord} |
| 169 | tabDefinitions={tabDefinitions} |
| 170 | breadcrumbUrl={`${adminUrl}edit.php?post_type=give_forms&page=give-subscriptions`} |
| 171 | breadcrumbTitle={subscription?.id && sprintf('#%s', subscription?.id)} |
| 172 | pageTitle={<PageTitle />} |
| 173 | SecondaryActionButton={subscriptionCanSync && SecondaryActionButton} |
| 174 | StatusBadge={() => <StatusBadge status={subscription?.status} isTest={subscription?.mode === 'test'} />} |
| 175 | ContextMenuItems={ContextMenuItems} |
| 176 | > |
| 177 | <CancelSubscriptionDialog |
| 178 | subscription={subscription} |
| 179 | showConfirmationDialog={showConfirmationDialog} |
| 180 | setShowConfirmationDialog={setShowConfirmationDialog} |
| 181 | /> |
| 182 | <ConfirmationDialog |
| 183 | title={__('Move subscription to trash', 'give')} |
| 184 | actionLabel={__('Trash Subscription', 'give')} |
| 185 | isOpen={showConfirmationDialog === 'delete'} |
| 186 | handleClose={() => setShowConfirmationDialog(null)} |
| 187 | handleConfirm={handleDelete} |
| 188 | > |
| 189 | {__('Are you sure you want to move this subscription to the trash? You can restore it later if needed.', 'give')} |
| 190 | </ConfirmationDialog> |
| 191 | <ConfirmationDialog |
| 192 | variant={isLoading ? 'syncing' : null} |
| 193 | spinner={'arc'} |
| 194 | isConfirming={isLoading} |
| 195 | title={__('Sync subscription details', 'give')} |
| 196 | actionLabel={isLoading ? __('Syncing', 'give') : !hasSyncBeenPerformed ? __('Proceed to sync', 'give') : __('Resync', 'give')} |
| 197 | showCancelButton={false} |
| 198 | isOpen={showConfirmationDialog === 'sync'} |
| 199 | handleClose={() => { |
| 200 | setShowConfirmationDialog(null); |
| 201 | }} |
| 202 | handleConfirm={handleSyncSubscription} |
| 203 | footer={ |
| 204 | hasSyncBeenPerformed && hasResolved && syncResult?.notice && ( |
| 205 | <div className={styles.syncModalFooter}> |
| 206 | <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 207 | <path fillRule="evenodd" clipRule="evenodd" d="M10 .832a9.167 9.167 0 1 0 0 18.333A9.167 9.167 0 0 0 10 .832zm0 5a.833.833 0 1 0 0 1.667h.008a.833.833 0 0 0 0-1.667H10zm.833 4.167a.833.833 0 0 0-1.666 0v3.333a.833.833 0 1 0 1.666 0V9.999z" fill="#0C7FF2"/> |
| 208 | </svg> |
| 209 | {syncResult?.notice} |
| 210 | </div> |
| 211 | ) |
| 212 | } |
| 213 | > |
| 214 | {hasSyncBeenPerformed && hasResolved ? <SubscriptionSyncList syncResult={syncResult} /> : __('This will update the subscription details using the most recent data from the gateway. However, no changes will be made to existing payments.', 'give')} |
| 215 | </ConfirmationDialog> |
| 216 | </AdminDetailsPage> |
| 217 | ); |
| 218 | } |
| 219 |