JezK
Edit File: sc-subscription-details_2.cjs.entry.js.map
{"file":"sc-subscription-details.sc-subscription-status-badge.entry.cjs.js","mappings":";;;;;;;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAiCA;;AAEA;AACA;AACA;AACA;AACA;;ACzEA,MAAM,wBAAwB,GAAG,wiCAAwiC,CAAC;AAC1kC,oCAAe,wBAAwB;;;;;;;;;;;;;;;;;;eC4B5B,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oDAqCS,eAAO;;gBAEjB,UAAE;;YAOF,eAAO,CAAC,UAAE;;;;;;;wDAcE,eAAO;;oBAEjB,UAAE,ieAKH,UAAE,umBASF,UAAE;;oDAMO,eAAO;;gBAEjB,UAAE,oJAKH,UAAE,0GAAuG,UAAE;;;oDAShG,eAAO;;gBAEjB,UAAE,4GAKH,eAAO;;YAEN,UAAE;;;oDASQ,eAAO;;gBAEjB,UAAE;;oBAOA,eAAO,CAAC,UAAE;;oBAEV,eAAO,CAAC,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oGA+Db,eAAO;;QAEN,UAAE,ycAWO,eAAO;;QAEN,UAAE;;;;;;;;;;;;;;;;;;;;wTAoCT,UAAE,qPAOO,eAAO;;YAEjB,UAAE,gdAgBU,UAAE;;sZAyBX,UAAE;;;;;;;;AChUnB,MAAM,4BAA4B,GAAG,mDAAmD,CAAC;AACzF,wCAAe,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBCmD9B,UAAE;;;;uBAKA,UAAE;;uBAEF,UAAE;;uBAEF,UAAE;;uBAEF,UAAE;;;;;uBAKF,UAAE;;uBAEF,UAAE;;uBAEF,UAAE;;;;mGAMS,eAAO,CAAC,UAAE;;;;;;;;","names":[],"sources":["../admin/util.js","src/components/controllers/dashboard/subscription-details/sc-subscription-details.css?tag=sc-subscription-details&encapsulation=shadow","src/components/controllers/dashboard/subscription-details/sc-subscription-details.tsx","src/components/ui/subscription-status-badge/sc-subscription-status-badge.css?tag=sc-subscription-status-badge&encapsulation=shadow","src/components/ui/subscription-status-badge/sc-subscription-status-badge.tsx"],"sourcesContent":["import { sprintf, __ } from '@wordpress/i18n';\n\nexport const maybeConvertAmount = (amount, currency) => {\n\treturn [\n\t\t'BIF',\n\t\t'BYR',\n\t\t'CLP',\n\t\t'DJF',\n\t\t'GNF',\n\t\t'ISK',\n\t\t'JPY',\n\t\t'KMF',\n\t\t'KRW',\n\t\t'PYG',\n\t\t'RWF',\n\t\t'UGX',\n\t\t'VND',\n\t\t'VUV',\n\t\t'XAF',\n\t\t'XAG',\n\t\t'XAU',\n\t\t'XBA',\n\t\t'XBB',\n\t\t'XBC',\n\t\t'XBD',\n\t\t'XDR',\n\t\t'XOF',\n\t\t'XPD',\n\t\t'XPF',\n\t\t'XPT',\n\t\t'XTS',\n\t].includes(currency?.toUpperCase())\n\t\t? amount\n\t\t: amount / 100;\n};\n\nexport const getHumanDiscount = (coupon, currency = 'usd') => {\n\tif (coupon?.amount_off && currency) {\n\t\treturn sprintf(\n\t\t\t__('%s off', 'surecart'),\n\t\t\tgetFormattedPrice({\n\t\t\t\tamount: coupon.amount_off,\n\t\t\t\tcurrency,\n\t\t\t})\n\t\t);\n\t}\n\n\tif (coupon?.percent_off) {\n\t\tconst percent = `${parseInt(coupon.percent_off, 10) || 0}%`;\n\t\t// translators: %s is the discount percentage (e.g. \"10%\").\n\t\treturn sprintf(__('%s off', 'surecart'), percent);\n\t}\n\n\treturn '';\n};\n\nexport const getFormattedPrice = ({ amount, currency = 'usd' }) => {\n\tconst converted = maybeConvertAmount(parseFloat(amount), currency);\n\n\tconst minimumFractionDigits = amount % 1 == 0 ? 0 : 2;\n\n\treturn `${new Intl.NumberFormat(undefined, {\n\t\tstyle: 'currency',\n\t\tcurrency,\n\t\tminimumFractionDigits,\n\t}).format(parseFloat(converted.toFixed(2)))}`;\n};\n\nexport const formatNumber = (value, currency = '') =>\n\tnew Intl.NumberFormat([], {\n\t\tstyle: 'currency',\n\t\tcurrency: currency.toUpperCase(),\n\t\tcurrencyDisplay: 'symbol',\n\t}).format(maybeConvertAmount(value, currency.toUpperCase()));\n\n// get the currency symbol for a currency code.\nexport const getCurrencySymbol = (code = 'usd') => {\n\tconst formattedParts = new Intl.NumberFormat(undefined, {\n\t\tstyle: 'currency',\n\t\tcurrency: code,\n\t}).formatToParts();\n\treturn formattedParts.find((part) => part.type === 'currency')?.value;\n};\n\nexport const translate = (key) => {\n\tconst map = {\n\t\tday: __('Day', 'surecart'),\n\t\tmonth: __('Month', 'surecart'),\n\t\tyear: __('Year', 'surecart'),\n\t\tnever: __('Lifetime', 'surecart'),\n\t\tarchived: __('Archived', 'surecart'),\n\t\tdraft: __('Draft', 'surecart'),\n\t\tactive: __('Active', 'surecart'),\n\t\tinactive: __('Inactive', 'surecart'),\n\t\tpending: __('Pending', 'surecart'),\n\t\tapproved: __('Approved', 'surecart'),\n\t\tdenied: __('Denied', 'surecart'),\n\t\treviewing: __('Reviewing', 'surecart'),\n\t\tprocessing: __('Processing', 'surecart'),\n\t\tpaid: __('Paid', 'surecart'),\n\t};\n\treturn map?.[key] || key;\n};\n\nexport const filterObject = (obj, predicate) =>\n\tObject.keys(obj)\n\t\t.filter((key) => predicate(obj[key]))\n\t\t.reduce((res, key) => ((res[key] = obj[key]), res), {});\n\nexport const snakeToCamel = (str) =>\n\tstr\n\t\t.toLowerCase()\n\t\t.replace(/([-_][a-z])/g, (group) =>\n\t\t\tgroup.toUpperCase().replace('-', '').replace('_', '')\n\t\t);\n\nexport const camelName = (name) => {\n\tconst camelName = snakeToCamel(name);\n\treturn camelName.charAt(0).toUpperCase() + camelName.toLowerCase().slice(1);\n};\n\nexport const createErrorString = (error) => {\n\tconst additionalErrors = (error?.additional_errors || [])\n\t\t.map((error) => error?.message)\n\t\t.filter((n) => n);\n\treturn `${error?.message || __('Something went wrong.', 'surecart')}${\n\t\t!!additionalErrors?.length ? ` ${additionalErrors.join('. ')}` : ''\n\t}`;\n};\n",":host {\n display: block;\n}\n\n.subscription-details {\n display: grid;\n gap: 0.25em;\n color: var(--sc-input-label-color);\n}\n\n.subscription-details__missing-method {\n display: flex;\n align-items: center;\n gap: var(--sc-spacing-x-small);\n}\n\n.subscription-details__bundle-toggle {\n display: inline-flex;\n align-items: center;\n gap: var(--sc-spacing-xx-small);\n background: transparent;\n border: 0;\n padding: 0;\n margin: 0;\n color: var(--sc-color-primary-500, var(--sc-input-label-color));\n font: inherit;\n font-size: var(--sc-font-size-small);\n cursor: pointer;\n}\n\n.subscription-details__bundle-toggle:focus-visible {\n outline: 2px solid var(--sc-color-primary-500);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n.subscription-details__bundle-components {\n display: grid;\n gap: var(--sc-spacing-xx-small);\n margin-top: var(--sc-spacing-xx-small);\n padding-left: var(--sc-spacing-medium);\n border-left: 2px solid var(--sc-color-gray-200);\n font-size: var(--sc-font-size-small);\n color: var(--sc-color-gray-600);\n}\n\n.subscription-details__bundle-component {\n display: flex;\n align-items: center;\n gap: var(--sc-spacing-xx-small);\n}\n\n.subscription-details__bundle-qty {\n color: var(--sc-color-gray-500);\n}\n","import { Component, h, Prop, State, Watch } from '@stencil/core';\nimport { __, _n, sprintf } from '@wordpress/i18n';\nimport { addQueryArgs } from '@wordpress/url';\n\nimport apiFetch from '../../../../functions/fetch';\nimport { intervalString } from '../../../../functions/price';\nimport { getBundleComponentRowsFromBundleItems } from '../../../../functions/line-items';\nimport { BundleItem, License, Price, Product, Purchase, Subscription } from '../../../../types';\nimport { productNameWithPrice } from '../../../../functions/price';\nimport { formatNumber } from '../../../../../../admin/util';\n@Component({\n tag: 'sc-subscription-details',\n styleUrl: 'sc-subscription-details.css',\n shadow: true,\n})\nexport class ScSubscriptionDetails {\n @Prop() subscription: Subscription;\n @Prop() pendingPrice: Price;\n @Prop() hideRenewalText: boolean;\n\n @State() activationsModal: boolean;\n @State() loading: boolean;\n @State() hasPendingUpdate: boolean;\n @State() bundleExpanded: boolean = false;\n\n renderName() {\n if (typeof this.subscription?.price?.product !== 'string') {\n return productNameWithPrice(this.subscription?.price);\n }\n return __('Subscription', 'surecart');\n }\n\n @Watch('subscription')\n async handleSubscriptionChange() {\n this.hasPendingUpdate = !!Object.keys(this?.subscription?.pending_update || {})?.length;\n if (this?.subscription?.pending_update?.price && !this?.pendingPrice && !this.hideRenewalText) {\n this.pendingPrice = await this.fetchPrice(this.subscription.pending_update.price);\n }\n }\n\n componentWillLoad() {\n this.handleSubscriptionChange();\n }\n\n async fetchPrice(price_id: string) {\n try {\n this.loading = true;\n const price = await apiFetch({\n path: addQueryArgs(`surecart/v1/prices/${price_id}`, {\n expand: ['product'],\n }),\n });\n return price as Price;\n } catch (e) {\n console.error(e);\n } finally {\n this.loading = false;\n }\n }\n\n renderRenewalText() {\n const tag = <sc-subscription-status-badge subscription={this?.subscription}></sc-subscription-status-badge>;\n\n if (this?.subscription?.cancel_at_period_end && this?.subscription?.current_period_end_at) {\n return (\n <span\n aria-label={sprintf(\n /* translators: %s: current period end date */\n __('Renewal Update - Your plan will be canceled on %s', 'surecart'),\n this.subscription.current_period_end_at_date,\n )}\n >\n {tag}{' '}\n {\n /* translators: %s: current period end date */\n sprintf(__('Your plan will be canceled on %s', 'surecart'), this.subscription.current_period_end_at_date)\n }\n </span>\n );\n }\n\n if (this.hasPendingUpdate) {\n if (!this.pendingPrice && !this.subscription?.pending_update?.ad_hoc_amount) {\n return <sc-skeleton></sc-skeleton>;\n }\n\n if (this.subscription?.pending_update?.ad_hoc_amount) {\n return (\n <span\n aria-label={sprintf(\n /* translators: 1: new price, 2: current period end date */\n __('Renewal Update - Your plan switches to %1s on %2s', 'surecart'),\n formatNumber(this.subscription?.pending_update?.ad_hoc_amount, this.pendingPrice?.currency || this.subscription?.price?.currency),\n this.subscription.current_period_end_at_date,\n )}\n >\n {__('Your plan switches to', 'surecart')}{' '}\n <strong>\n <sc-format-number\n type=\"currency\"\n currency={this.pendingPrice?.currency || this.subscription?.price?.currency}\n value={this.subscription?.pending_update?.ad_hoc_amount}\n ></sc-format-number>{' '}\n {intervalString(this.pendingPrice || this.subscription?.price)}\n </strong>{' '}\n {__('on', 'surecart')} {this.subscription.current_period_end_at_date}\n </span>\n );\n }\n return (\n <span\n aria-label={sprintf(\n /* translators: 1: new plan name, 2: current period end date */\n __('Renewal Update - Your plan switches to %1s on %2s', 'surecart'),\n (this.pendingPrice.product as Product).name,\n this.subscription.current_period_end_at_date,\n )}\n >\n {__('Your plan switches to', 'surecart')} <strong>{(this.pendingPrice.product as Product).name}</strong> {__('on', 'surecart')}{' '}\n {this.subscription.current_period_end_at_date}\n </span>\n );\n }\n\n if (this?.subscription?.status === 'trialing' && this?.subscription?.trial_end_at) {\n return (\n <span\n aria-label={sprintf(\n /* translators: %s: trial end date */\n __('Renewal Update - Your plan begins on %s.', 'surecart'),\n this.subscription.trial_end_at_date,\n )}\n >\n {tag}{' '}\n {sprintf(\n /* translators: %s: trial end date */\n __('Your plan begins on %s', 'surecart'),\n this?.subscription?.trial_end_at_date,\n )}\n </span>\n );\n }\n if (this.subscription?.status === 'active' && this.subscription?.current_period_end_at) {\n return (\n <span\n aria-label={sprintf(\n /* translators: %s: current period end date */\n __('Renewal Update - Your next payment is on %s', 'surecart'),\n this.subscription.current_period_end_at_date,\n )}\n >\n {tag}{' '}\n {this.subscription?.remaining_period_count === null\n ? /* translators: %s: current period end date */\n sprintf(__('Your plan renews on %s', 'surecart'), this.subscription.current_period_end_at_date)\n : /* translators: %s: current period end date */\n sprintf(__('Your next payment is on %s', 'surecart'), this.subscription.current_period_end_at_date)}\n </span>\n );\n }\n\n return tag;\n }\n\n getActivations() {\n return (((this.subscription?.purchase as Purchase)?.license as License)?.activations?.data || []).filter(activation => {\n return activation?.counted;\n });\n }\n\n renderActivations() {\n const activations = this.getActivations();\n if (!activations?.length) return null;\n return (\n <sc-flex justifyContent=\"flex-start\" alignItems=\"center\">\n <sc-tag size=\"small\">{activations?.[0]?.name}</sc-tag>\n {activations?.length > 1 && (\n <sc-text\n style={{ '--font-size': 'var(--sc-font-size-small)', 'cursor': 'pointer' }}\n onClick={e => {\n e.preventDefault();\n e.stopImmediatePropagation();\n this.activationsModal = true;\n }}\n >\n + {activations?.length - 1} More\n </sc-text>\n )}\n </sc-flex>\n );\n }\n\n /**\n * Bundle items attached to the subscription's product (if it is a bundle).\n * Bundle is a Product attribute post-refactor — read it via price.product.\n */\n getBundleItems(): BundleItem[] {\n const product = (this.subscription?.price as Price)?.product as Product;\n if (!product?.bundle) return [];\n return (product?.bundle_items?.data || []) as BundleItem[];\n }\n\n renderBundleComponents() {\n const rows = getBundleComponentRowsFromBundleItems(this.getBundleItems());\n if (!rows.length) return null;\n\n return (\n <div>\n <button\n type=\"button\"\n class=\"subscription-details__bundle-toggle\"\n aria-expanded={this.bundleExpanded ? 'true' : 'false'}\n onClick={e => {\n e.preventDefault();\n e.stopPropagation();\n this.bundleExpanded = !this.bundleExpanded;\n }}\n >\n <sc-icon name={this.bundleExpanded ? 'chevron-up' : 'chevron-down'}></sc-icon>\n {sprintf(\n /* translators: %d: number of bundle component items */\n _n('Includes %d item', 'Includes %d items', rows.length, 'surecart'),\n rows.length,\n )}\n </button>\n {this.bundleExpanded && (\n <div class=\"subscription-details__bundle-components\">\n {rows.map(row => (\n <div class=\"bundle-component subscription-details__bundle-component\" key={row.id}>\n <span class=\"bundle-component__label\">{row.name}</span>\n {row.qty > 1 && (\n <span class=\"bundle-component__qty subscription-details__bundle-qty\">\n {sprintf(\n /* translators: %d: quantity */\n __('× %d', 'surecart'),\n row.qty,\n )}\n </span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n\n showWarning() {\n // no payment method.\n if (this.subscription?.payment_method || this.subscription?.manual_payment) {\n return false;\n }\n // don't show if not looking for payment.\n if (!['active', 'past_due', 'unpaid', 'incomplete'].includes(this.subscription?.status)) {\n return false;\n }\n // handle ad_hoc.\n if (this.subscription?.price?.ad_hoc) {\n return this.subscription?.ad_hoc_amount !== 0;\n }\n // show the warning if the subscription is not free.\n return this.subscription?.price?.amount !== 0;\n }\n\n render() {\n return (\n <div class=\"subscription-details\">\n {this.hasPendingUpdate && (\n <div>\n <sc-tag size=\"small\" type=\"warning\">\n {__('Update Scheduled', 'surecart')}\n </sc-tag>\n </div>\n )}\n\n <sc-flex alignItems=\"center\" justifyContent=\"flex-start\">\n <sc-text\n aria-label={sprintf(\n /* translators: %s: plan name */\n __('Plan name - %s', 'surecart'),\n this.renderName(),\n )}\n style={{ '--font-weight': 'var(--sc-font-weight-bold)' }}\n >\n {this.renderName()}\n </sc-text>\n {this.renderActivations()}\n </sc-flex>\n\n {!this.hideRenewalText && <div>{this.renderRenewalText()} </div>}\n\n {this.renderBundleComponents()}\n\n <slot />\n\n <sc-dialog label={__('Activations', 'surecart')} onScRequestClose={() => (this.activationsModal = false)} open={!!this.activationsModal}>\n <sc-card no-padding style={{ '--overflow': 'hidden' }}>\n <sc-stacked-list>\n {(this.getActivations() || []).map(activation => {\n return (\n <sc-stacked-list-row style={{ '--columns': '2' }} mobileSize={0}>\n <sc-text style={{ '--line-height': 'var(--sc-line-height-dense)' }}>\n <strong>{activation?.name}</strong>\n <div>\n <sc-text style={{ '--color': 'var(--sc-color-gray-500)' }}>{activation?.fingerprint}</sc-text>\n </div>\n </sc-text>\n <sc-text style={{ '--color': 'var(--sc-color-gray-500)' }}>{activation?.created_at_date}</sc-text>\n </sc-stacked-list-row>\n );\n })}\n </sc-stacked-list>\n </sc-card>\n </sc-dialog>\n\n {this.showWarning() && (\n <div>\n <sc-tag type=\"warning\">\n <div class=\"subscription-details__missing-method\">\n <sc-icon name=\"alert-triangle\"></sc-icon>\n {__('Payment Method Missing', 'surecart')}\n </div>\n </sc-tag>\n </div>\n )}\n </div>\n );\n }\n}\n",":host {\n display: inline-block;\n vertical-align: middle;\n}\n","import { Component, h, Prop } from '@stencil/core';\nimport { __, sprintf } from '@wordpress/i18n';\nimport { Subscription, SubscriptionStatus } from '../../../types';\n\n@Component({\n tag: 'sc-subscription-status-badge',\n styleUrl: 'sc-subscription-status-badge.css',\n shadow: true,\n})\nexport class ScSubscriptionStatusBadge {\n /** Subscription status */\n @Prop() status: SubscriptionStatus;\n\n /** The tag's status type. */\n @Prop() subscription: Subscription;\n\n /** The tag's size. */\n @Prop({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n /** Draws a pill-style tag with rounded edges. */\n @Prop({ reflect: true }) pill: boolean = false;\n\n /** Makes the tag clearable. */\n @Prop() clearable: boolean = false;\n\n getType() {\n if (this.subscription?.cancel_at_period_end) {\n return 'info';\n }\n switch (this.status || this.subscription?.status) {\n case 'incomplete':\n return 'warning';\n case 'trialing':\n return 'info';\n case 'active':\n return 'success';\n case 'completed':\n return 'success';\n case 'past_due':\n return 'warning';\n case 'canceled':\n if (this.subscription?.restore_at) {\n return 'info';\n }\n return 'danger';\n case 'unpaid':\n return 'warning';\n }\n }\n\n getText() {\n if (this.subscription?.cancel_at_period_end && this.subscription.current_period_end_at && this.subscription?.status !== 'canceled') {\n return __('Cancelling', 'surecart');\n }\n\n switch (this.status || this.subscription?.status) {\n case 'incomplete':\n return __('Incomplete', 'surecart');\n case 'trialing':\n return __('Trialing', 'surecart');\n case 'active':\n return __('Active', 'surecart');\n case 'past_due':\n return __('Past Due', 'surecart');\n case 'canceled':\n if (this.subscription?.restore_at) {\n return 'Paused';\n }\n return __('Canceled', 'surecart');\n case 'completed':\n return __('Completed', 'surecart');\n case 'unpaid':\n return __('Unpaid', 'surecart');\n }\n }\n\n render() {\n return (\n <sc-tag aria-label={sprintf(__('Plan Status - %s', 'surecart'), this.getText())} type={this.getType()}>\n {this.getText()}\n </sc-tag>\n );\n }\n}\n"],"version":3}