AddonRowActions.jsx
1 week ago
AddonsList.jsx
1 week ago
AllAccessAddonsSummary.jsx
1 week ago
AutoUpdateModal.jsx
1 week ago
DownloadActivateButton.jsx
1 week ago
EmptyState.jsx
1 week ago
FeatureList.jsx
1 week ago
LicenseField.jsx
1 week ago
LicenseItem.jsx
1 week ago
LicenseNotices.jsx
1 week ago
LicenseStatus.jsx
1 week ago
Loader.jsx
1 week ago
PricingModal.jsx
1 week ago
PricingTable.jsx
1 week ago
PricingTableItem.jsx
1 week ago
SitesModal.jsx
1 week ago
AddonsList.jsx
219 lines
| 1 | /** |
| 2 | * External Dependencies |
| 3 | */ |
| 4 | import { __, sprintf } from '@wordpress/i18n'; |
| 5 | import { ChevronDown } from 'lucide-react'; |
| 6 | import { useId, useState } from '@wordpress/element'; |
| 7 | import { useSelect } from '@wordpress/data'; |
| 8 | |
| 9 | /** |
| 10 | * Internal Dependencies |
| 11 | */ |
| 12 | import { getIncludedAddonsForLicense } from '../addon-catalog'; |
| 13 | import { |
| 14 | getLicenseNameForKey, |
| 15 | isAddonLicensedByKey, |
| 16 | isAddonManagedByOtherLicense, |
| 17 | isAllAccessBundleName, |
| 18 | isLicenseAppliedOnThisSite, |
| 19 | isRichLicenseEntitled, |
| 20 | } from '../utils'; |
| 21 | import { STORE_NAME } from '@admin/store'; |
| 22 | import { AddonRowActions } from './AddonRowActions'; |
| 23 | |
| 24 | function AddonItem( { |
| 25 | addon, |
| 26 | licenseKey, |
| 27 | licenses, |
| 28 | isApplied, |
| 29 | canUseAddonActions, |
| 30 | managedByOtherLicense, |
| 31 | managingLicenseName, |
| 32 | licenseAppliedOnSite, |
| 33 | noticesContext, |
| 34 | addonInstallStates, |
| 35 | installingAddonId, |
| 36 | failedAddonIds, |
| 37 | onInstallStart, |
| 38 | onInstallEnd, |
| 39 | } ) { |
| 40 | const { id, title, description, icon, learnMore, manualUrl, downloadUrl } = |
| 41 | addon; |
| 42 | |
| 43 | return ( |
| 44 | <div className="relative border border-border bg-white p-3 rounded-lg flex flex-wrap flex-row gap-3 md:items-center"> |
| 45 | <div className="flex items-center justify-center p-2 size-8 bg-gray-200 rounded-lg grow-0"> |
| 46 | <img src={ icon } alt="" className="size-4" /> |
| 47 | </div> |
| 48 | <div className="shrink-0 flex-1"> |
| 49 | <strong>{ title }</strong> |
| 50 | <p className="flex flex-wrap items-center gap-x-1 m-0 mt-1 text-gray-500"> |
| 51 | { description } |
| 52 | <a |
| 53 | href={ learnMore } |
| 54 | target="_blank" |
| 55 | rel="noopener noreferrer" |
| 56 | className="no-underline" |
| 57 | > |
| 58 | { __( 'Learn more', 'advanced-ads' ) } |
| 59 | </a> |
| 60 | <span>|</span> |
| 61 | <a |
| 62 | href={ manualUrl } |
| 63 | target="_blank" |
| 64 | rel="noopener noreferrer" |
| 65 | className="no-underline" |
| 66 | > |
| 67 | { __( 'Manual', 'advanced-ads' ) } |
| 68 | </a> |
| 69 | </p> |
| 70 | </div> |
| 71 | <div className="basis-full sm:basis-auto sm:ml-auto"> |
| 72 | <AddonRowActions |
| 73 | addonId={ id } |
| 74 | licenseKey={ licenseKey } |
| 75 | licenses={ licenses } |
| 76 | downloadUrl={ downloadUrl } |
| 77 | isApplied={ isApplied } |
| 78 | disabled={ ! canUseAddonActions } |
| 79 | managedByOtherLicense={ managedByOtherLicense } |
| 80 | managingLicenseName={ managingLicenseName } |
| 81 | licenseAppliedOnSite={ licenseAppliedOnSite } |
| 82 | addonInstallStates={ addonInstallStates } |
| 83 | noticesContext={ noticesContext } |
| 84 | isInstalling={ installingAddonId === id } |
| 85 | installFailed={ failedAddonIds.has( id ) } |
| 86 | onInstallStart={ onInstallStart } |
| 87 | onInstallEnd={ onInstallEnd } |
| 88 | /> |
| 89 | </div> |
| 90 | </div> |
| 91 | ); |
| 92 | } |
| 93 | |
| 94 | export function AddonsList( { |
| 95 | license, |
| 96 | allLicenses = [], |
| 97 | appliedAddonKeyMap = {}, |
| 98 | currentHostname = '', |
| 99 | noticesContext, |
| 100 | canUseAddonActions = false, |
| 101 | } ) { |
| 102 | const addonsId = useId(); |
| 103 | const [ addOnsOpen, setAddOnsOpen ] = useState( true ); |
| 104 | const [ installingAddonId, setInstallingAddonId ] = useState( '' ); |
| 105 | const [ failedAddonIds, setFailedAddonIds ] = useState( () => new Set() ); |
| 106 | |
| 107 | const addonInstallStates = useSelect( |
| 108 | ( select ) => select( STORE_NAME ).getAddonInstallStates(), |
| 109 | [] |
| 110 | ); |
| 111 | |
| 112 | if ( ! isAllAccessBundleName( license?.name ) ) { |
| 113 | return null; |
| 114 | } |
| 115 | |
| 116 | const includedAddons = getIncludedAddonsForLicense( license ); |
| 117 | if ( ! includedAddons.length ) { |
| 118 | return null; |
| 119 | } |
| 120 | |
| 121 | const licenseKey = String( license?.licenseKey ?? '' ); |
| 122 | const licenseAppliedOnSite = isLicenseAppliedOnThisSite( |
| 123 | license, |
| 124 | allLicenses, |
| 125 | currentHostname, |
| 126 | appliedAddonKeyMap, |
| 127 | addonInstallStates |
| 128 | ); |
| 129 | |
| 130 | function handleInstallStart( addonId ) { |
| 131 | setInstallingAddonId( addonId ); |
| 132 | } |
| 133 | |
| 134 | function handleInstallEnd( addonId, failed ) { |
| 135 | setInstallingAddonId( '' ); |
| 136 | if ( failed ) { |
| 137 | setFailedAddonIds( ( prev ) => new Set( prev ).add( addonId ) ); |
| 138 | } else { |
| 139 | setFailedAddonIds( ( prev ) => { |
| 140 | const next = new Set( prev ); |
| 141 | next.delete( addonId ); |
| 142 | return next; |
| 143 | } ); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | return ( |
| 148 | <div className="mt-6 xl:ml-30"> |
| 149 | <button |
| 150 | type="button" |
| 151 | id={ addonsId } |
| 152 | className="is-ghost" |
| 153 | onClick={ () => setAddOnsOpen( ( open ) => ! open ) } |
| 154 | aria-expanded={ addOnsOpen } |
| 155 | > |
| 156 | { sprintf( |
| 157 | /* translators: %d: Number of included add-ons. */ |
| 158 | __( 'Included add-ons (%d)', 'advanced-ads' ), |
| 159 | includedAddons.length |
| 160 | ) } |
| 161 | <ChevronDown |
| 162 | className={ `h-4 w-4 shrink-0 transition-transform ${ |
| 163 | addOnsOpen ? 'rotate-180' : '' |
| 164 | }` } |
| 165 | strokeWidth={ 2 } |
| 166 | aria-hidden |
| 167 | /> |
| 168 | </button> |
| 169 | { addOnsOpen ? ( |
| 170 | <section |
| 171 | className="mt-6 space-y-6" |
| 172 | aria-labelledby={ addonsId } |
| 173 | > |
| 174 | { includedAddons.map( ( addon ) => { |
| 175 | const isLicensedByThisKey = isAddonLicensedByKey( |
| 176 | addon.id, |
| 177 | licenseKey, |
| 178 | appliedAddonKeyMap |
| 179 | ); |
| 180 | const managedByOther = isAddonManagedByOtherLicense( |
| 181 | addon.id, |
| 182 | licenseKey, |
| 183 | appliedAddonKeyMap |
| 184 | ); |
| 185 | const managingKey = managedByOther |
| 186 | ? String( |
| 187 | appliedAddonKeyMap?.[ addon.id ] ?? '' |
| 188 | ) |
| 189 | : ''; |
| 190 | |
| 191 | return ( |
| 192 | <AddonItem |
| 193 | key={ addon.id } |
| 194 | addon={ addon } |
| 195 | licenseKey={ licenseKey } |
| 196 | licenses={ allLicenses } |
| 197 | isApplied={ isLicensedByThisKey } |
| 198 | canUseAddonActions={ canUseAddonActions } |
| 199 | managedByOtherLicense={ managedByOther } |
| 200 | managingLicenseName={ getLicenseNameForKey( |
| 201 | managingKey, |
| 202 | allLicenses |
| 203 | ) } |
| 204 | licenseAppliedOnSite={ licenseAppliedOnSite } |
| 205 | noticesContext={ noticesContext } |
| 206 | addonInstallStates={ addonInstallStates } |
| 207 | installingAddonId={ installingAddonId } |
| 208 | failedAddonIds={ failedAddonIds } |
| 209 | onInstallStart={ handleInstallStart } |
| 210 | onInstallEnd={ handleInstallEnd } |
| 211 | /> |
| 212 | ); |
| 213 | } ) } |
| 214 | </section> |
| 215 | ) : null } |
| 216 | </div> |
| 217 | ); |
| 218 | } |
| 219 |