diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js
index 69f0a633a5..8b6daf60da 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionController.js
+++ b/services/web/app/src/Features/Subscription/SubscriptionController.js
@@ -186,6 +186,7 @@ async function _paymentReactPage(req, res) {
currency,
countryCode,
plan,
+ planCode: req.query.planCode,
couponCode: req.query.cc,
showCouponField: !!req.query.scf,
itm_campaign: req.query.itm_campaign,
diff --git a/services/web/app/views/subscriptions/new-react.pug b/services/web/app/views/subscriptions/new-react.pug
index e34754b738..2c8c12e99d 100644
--- a/services/web/app/views/subscriptions/new-react.pug
+++ b/services/web/app/views/subscriptions/new-react.pug
@@ -14,7 +14,7 @@ block append meta
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
meta(name="ol-recommendedCurrency" content=String(currency).slice(0, 3))
meta(name="ol-plan" data-type="json" content=plan)
- meta(name="ol-currencySymbols" data-type="json" content=settings.groupPlanModalOptions.currencySymbols)
+ meta(name="ol-planCode" data-type="string" content=planCode)
meta(name="ol-showCouponField" data-type="boolean" content=showCouponField)
meta(name="ol-couponCode" content=couponCode)
meta(name="ol-itm_campaign" content=itm_campaign)
@@ -28,7 +28,7 @@ block content
main.content.content-alt#subscription-new-root
script(type="text/javascript", nonce=scriptNonce).
- ga('send', 'event', 'pageview', 'payment_form', "#{plan_code}")
+ ga('send', 'event', 'pageview', 'payment_form', "#{planCode}")
script(
type="text/ng-template"
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index b17b9abb6a..d6e3b8f29f 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -32,6 +32,7 @@
"additional_licenses": "",
"address_line_1": "",
"address_second_line_optional": "",
+ "all_premium_features_including": "",
"all_projects": "",
"also": "",
"an_error_occurred_when_verifying_the_coupon_code": "",
@@ -69,6 +70,7 @@
"can_link_your_institution_acct_2": "",
"can_now_relink_dropbox": "",
"cancel": "",
+ "cancel_anytime": "",
"cancel_your_subscription": "",
"cannot_invite_non_user": "",
"cannot_invite_self": "",
@@ -84,6 +86,7 @@
"category_operators": "",
"category_relations": "",
"change": "",
+ "change_currency": "",
"change_or_cancel-cancel": "",
"change_or_cancel-change": "",
"change_or_cancel-or": "",
@@ -108,6 +111,7 @@
"code_check_failed_explanation": "",
"collaborate_online_and_offline": "",
"collabs_per_proj": "",
+ "collabs_per_proj_single": "",
"collapse": "",
"commit": "",
"common": "",
@@ -169,6 +173,7 @@
"did_you_know_institution_providing_professional": "",
"did_you_know_that_overleaf_offers": "",
"disable_stop_on_first_error": "",
+ "discount_of": "",
"dismiss": "",
"dismiss_error_popup": "",
"do_you_want_to_overwrite_them": "",
@@ -239,6 +244,7 @@
"find_out_more_nt": "",
"find_the_symbols_you_need_with_premium": "",
"first_name": "",
+ "first_x_days_free_after_that_y_per_month": "",
"fold_line": "",
"following_paths_conflict": "",
"font_family": "",
@@ -360,6 +366,7 @@
"importing_and_merging_changes_in_github": "",
"in_order_to_match_institutional_metadata_2": "",
"in_order_to_match_institutional_metadata_associated": "",
+ "increased_compile_timeout": "",
"institution": "",
"institution_account": "",
"institution_acct_successfully_linked_2": "",
@@ -488,6 +495,8 @@
"no_search_results": "",
"no_symbols_found": "",
"normal": "",
+ "normally_x_price_per_month": "",
+ "normally_x_price_per_year": "",
"notification_project_invite_accepted_message": "",
"notification_project_invite_message": "",
"oauth_orcid_description": "",
@@ -515,6 +524,7 @@
"password_managed_externally": "",
"password_was_detected_on_a_public_list_of_known_compromised_passwords": "",
"payment_provider_unreachable_error": "",
+ "payment_summary": "",
"pdf_compile_in_progress_error": "",
"pdf_compile_rate_limit_hit": "",
"pdf_compile_try_again": "",
@@ -722,7 +732,9 @@
"sure_you_want_to_delete": "",
"switch_to_editor": "",
"switch_to_pdf": "",
+ "symbol_palette": "",
"sync": "",
+ "sync_dropbox_github": "",
"sync_project_to_github_explanation": "",
"sync_to_dropbox": "",
"sync_to_github": "",
@@ -741,6 +753,8 @@
"thank_you_exclamation": "",
"thanks_settings_updated": "",
"the_following_files_already_exist_in_this_project": "",
+ "then_x_price_per_month": "",
+ "then_x_price_per_year": "",
"this_action_cannot_be_undone": "",
"this_address_will_be_shown_on_the_invoice": "",
"this_field_is_required": "",
@@ -762,7 +776,10 @@
"too_many_requests": "",
"too_many_search_results": "",
"too_recently_compiled": "",
+ "total_per_month": "",
+ "total_per_year": "",
"total_words": "",
+ "track_changes": "",
"trash": "",
"trash_projects": "",
"trashed": "",
@@ -784,6 +801,7 @@
"unconfirmed": "",
"unfold_line": "",
"university": "",
+ "unlimited_collabs": "",
"unlimited_projects": "",
"unlink": "",
"unlink_dropbox_folder": "",
@@ -815,6 +833,7 @@
"user_deletion_password_reset_tip": "",
"user_sessions": "",
"validation_issue_entry_description": "",
+ "vat": "",
"vat_number": "",
"view_all": "",
"view_logs": "",
@@ -831,6 +850,9 @@
"word_count": "",
"work_offline": "",
"work_with_non_overleaf_users": "",
+ "x_price_for_first_month": "",
+ "x_price_for_first_year": "",
+ "x_price_for_y_months": "",
"year": "",
"you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
"you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "",
diff --git a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx b/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
index 6b20653094..1c3431b7f4 100644
--- a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
+++ b/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
@@ -34,7 +34,8 @@ function CheckoutPanel() {
const { t } = useTranslation()
const {
couponError,
- plan,
+ planCode,
+ planName,
pricingFormState,
pricing,
recurlyLoadError,
@@ -150,7 +151,7 @@ function CheckoutPanel() {
eventTracking.send(
'subscription-funnel',
'subscription-submission-success',
- plan.planCode
+ planCode
)
window.location.assign('/user/subscription/thank-you')
} catch (error) {
@@ -173,7 +174,7 @@ function CheckoutPanel() {
ITMReferrer,
isAddCompanyDetailsChecked,
isPayPalPaymentMethod,
- plan.planCode,
+ planCode,
pricing,
pricingFormState,
t,
@@ -184,7 +185,7 @@ function CheckoutPanel() {
useEffect(() => {
payPal.current = recurly.PayPal({
- display: { displayName: plan.name },
+ display: { displayName: planName },
})
payPal.current.on('token', token => {
@@ -202,7 +203,7 @@ function CheckoutPanel() {
return () => {
payPalCopy.destroy()
}
- }, [completeSubscription, plan.name])
+ }, [completeSubscription, planName])
const handleCardChange = useCallback((state: CardElementChangeState) => {
setCardIsValid(state.valid)
@@ -291,7 +292,7 @@ function CheckoutPanel() {
)}
+}
+
+type CollaboratorsProps = {
+ count: NonNullable['collaborators']
+}
+
+function Collaborators({ count }: CollaboratorsProps) {
+ const { t } = useTranslation()
+
+ if (count === 1) {
+ return (
+
+ {t('collabs_per_proj_single', { collabcount: 1 })}
+
+ )
+ }
+
+ if (count > 1) {
+ return (
+
+ {t('collabs_per_proj', { collabcount: count })}
+
+ )
+ }
+
+ if (count === -1) {
+ return {t('unlimited_collabs')}
+ }
+
+ return null
+}
+
+export default Collaborators
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/currency-dropdown.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/currency-dropdown.tsx
new file mode 100644
index 0000000000..99b0e1cb9e
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/currency-dropdown.tsx
@@ -0,0 +1,40 @@
+import { useTranslation } from 'react-i18next'
+import { Dropdown, DropdownProps, MenuItem } from 'react-bootstrap'
+import Icon from '../../../../../shared/components/icon'
+import { usePaymentContext } from '../../../context/payment-context'
+
+function CurrencyDropdown(props: DropdownProps) {
+ const { t } = useTranslation()
+ const { currencyCode, limitedCurrencies, changeCurrency } =
+ usePaymentContext()
+
+ return (
+
+
+ {t('change_currency')}
+
+
+ {Object.entries(limitedCurrencies).map(([currency, symbol]) => (
+
+ ))}
+
+
+ )
+}
+
+export default CurrencyDropdown
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/features-list.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/features-list.tsx
new file mode 100644
index 0000000000..3524c9bbda
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/features-list.tsx
@@ -0,0 +1,33 @@
+import { useTranslation } from 'react-i18next'
+import { Plan } from '../../../../../../../types/subscription/plan'
+
+type FeaturesListProps = {
+ features: NonNullable
+}
+
+function FeaturesList({ features }: FeaturesListProps) {
+ const { t } = useTranslation()
+
+ return (
+ <>
+ {t('all_premium_features_including')}
+
+ {features.compileTimeout > 1 && (
+ - {t('increased_compile_timeout')}
+ )}
+ {features.dropbox && features.github && (
+ - {t('sync_dropbox_github')}
+ )}
+ {features.versioning && - {t('full_doc_history')}
}
+ {features.trackChanges && - {t('track_changes')}
}
+ {features.references && - {t('reference_search')}
}
+ {(features.mendeley || features.zotero) && (
+ - {t('reference_sync')}
+ )}
+ {features.symbolPalette && - {t('symbol_palette')}
}
+
+ >
+ )
+}
+
+export default FeaturesList
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/no-discount-price.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/no-discount-price.tsx
new file mode 100644
index 0000000000..964d5b8165
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/no-discount-price.tsx
@@ -0,0 +1,38 @@
+import { useTranslation } from 'react-i18next'
+import { usePaymentContext } from '../../../context/payment-context'
+
+function NoDiscountPrice() {
+ const { t } = useTranslation()
+ const { currencySymbol, monthlyBilling, coupon } = usePaymentContext()
+
+ if (coupon?.normalPrice === undefined) {
+ return null
+ }
+
+ const price = `${currencySymbol}${coupon.normalPrice.toFixed(2)}`
+
+ return (
+
+ {!coupon.singleUse &&
+ coupon.discountMonths &&
+ coupon.discountMonths > 0 &&
+ monthlyBilling &&
+ t('then_x_price_per_month', { price })}
+ {!coupon.singleUse &&
+ !coupon.discountMonths &&
+ monthlyBilling &&
+ t('normally_x_price_per_month', { price })}
+ {!coupon.singleUse &&
+ !monthlyBilling &&
+ t('normally_x_price_per_year', { price })}
+ {coupon.singleUse &&
+ monthlyBilling &&
+ t('then_x_price_per_month', { price })}
+ {coupon.singleUse &&
+ !monthlyBilling &&
+ t('then_x_price_per_year', { price })}
+
+ )
+}
+
+export default NoDiscountPrice
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/payment-preview-panel.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/payment-preview-panel.tsx
index 871611fc7f..e6fe4fe770 100644
--- a/services/web/frontend/js/features/subscription/components/new/payment-preview/payment-preview-panel.tsx
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/payment-preview-panel.tsx
@@ -1,5 +1,43 @@
+import { useTranslation } from 'react-i18next'
+import Collaborators from './collaborators'
+import FeaturesList from './features-list'
+import PriceSummary from './price-summary'
+import TrialPrice from './trial-price'
+import NoDiscountPrice from './no-discount-price'
+import PriceForFirstXPeriod from './price-for-first-x-period'
+import { usePaymentContext } from '../../../context/payment-context'
+
function PaymentPreviewPanel() {
- return Preview panel
+ const { t } = useTranslation()
+ const { plan, planName } = usePaymentContext()
+ const trialPrice =
+ const priceForFirstXPeriod =
+ const noDiscountPrice =
+
+ return (
+
+
{planName}
+ {plan.features && (
+ <>
+
+
+ >
+ )}
+
+ {(trialPrice || priceForFirstXPeriod || noDiscountPrice) && (
+ <>
+
+
+ {trialPrice}
+ {priceForFirstXPeriod}
+ {noDiscountPrice}
+
+ >
+ )}
+
+
{t('cancel_anytime')}
+
+ )
}
export default PaymentPreviewPanel
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/price-for-first-x-period.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/price-for-first-x-period.tsx
new file mode 100644
index 0000000000..c32d830f39
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/price-for-first-x-period.tsx
@@ -0,0 +1,49 @@
+import { Trans } from 'react-i18next'
+import { usePaymentContext } from '../../../context/payment-context'
+
+function PriceForFirstXPeriod() {
+ const { currencySymbol, monthlyBilling, coupon, recurlyPrice } =
+ usePaymentContext()
+
+ if (!recurlyPrice || !coupon) {
+ return null
+ }
+
+ const price = `${currencySymbol}${recurlyPrice.total}`
+
+ return (
+
+ {coupon.discountMonths &&
+ coupon.discountMonths > 0 &&
+ !coupon.singleUse &&
+ monthlyBilling && (
+
]} // eslint-disable-line react/jsx-key
+ values={{
+ discountMonths: coupon.discountMonths,
+ price,
+ }}
+ />
+ )}
+ {coupon.singleUse && monthlyBilling && (
+
]} // eslint-disable-line react/jsx-key
+ values={{ price }}
+ />
+ )}
+ {coupon.singleUse && !monthlyBilling && (
+
+ ]} // eslint-disable-line react/jsx-key
+ values={{ price }}
+ />
+
+ )}
+
+ )
+}
+
+export default PriceForFirstXPeriod
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/price-summary.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/price-summary.tsx
new file mode 100644
index 0000000000..7962df736c
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/price-summary.tsx
@@ -0,0 +1,78 @@
+import { useTranslation } from 'react-i18next'
+import { usePaymentContext } from '../../../context/payment-context'
+import CurrencyDropdown from './currency-dropdown'
+
+function PriceSummary() {
+ const { t } = useTranslation()
+ const {
+ coupon,
+ currencySymbol,
+ recurlyPrice,
+ planName,
+ taxes,
+ monthlyBilling,
+ } = usePaymentContext()
+
+ if (!recurlyPrice) {
+ return null
+ }
+
+ const rate = parseFloat(taxes?.[0]?.rate)
+ const subtotal =
+ coupon?.normalPriceWithoutTax.toFixed(2) ?? recurlyPrice.subtotal
+
+ return (
+ <>
+
+
+
{t('payment_summary')}
+
+
+ {planName}
+
+ {currencySymbol}
+ {subtotal}
+
+
+ {coupon && (
+
+ {coupon.name}
+
+ –{currencySymbol}
+ {recurlyPrice.discount}
+
+
+ {t('discount_of', {
+ amount: `${currencySymbol}${recurlyPrice.discount}`,
+ })}
+
+
+ )}
+ {rate > 0 && (
+
+
+ {t('vat')} {rate * 100}%
+
+
+ {currencySymbol}
+ {recurlyPrice.tax}
+
+
+ )}
+
+ {monthlyBilling ? t('total_per_month') : t('total_per_year')}
+
+ {currencySymbol}
+ {recurlyPrice.total}
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default PriceSummary
diff --git a/services/web/frontend/js/features/subscription/components/new/payment-preview/trial-price.tsx b/services/web/frontend/js/features/subscription/components/new/payment-preview/trial-price.tsx
new file mode 100644
index 0000000000..7bd62ad893
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/new/payment-preview/trial-price.tsx
@@ -0,0 +1,25 @@
+import { Trans } from 'react-i18next'
+import { usePaymentContext } from '../../../context/payment-context'
+
+function TrialPrice() {
+ const { currencySymbol, trialLength, recurlyPrice } = usePaymentContext()
+
+ if (!trialLength || !recurlyPrice) {
+ return null
+ }
+
+ return (
+
+ , ]} // eslint-disable-line react/jsx-key
+ values={{
+ trialLen: trialLength,
+ price: `${currencySymbol}${recurlyPrice.total}`,
+ }}
+ />
+
+ )
+}
+
+export default TrialPrice
diff --git a/services/web/frontend/js/features/subscription/context/payment-context.tsx b/services/web/frontend/js/features/subscription/context/payment-context.tsx
index 4f71388a63..65059dfbe1 100644
--- a/services/web/frontend/js/features/subscription/context/payment-context.tsx
+++ b/services/web/frontend/js/features/subscription/context/payment-context.tsx
@@ -8,6 +8,7 @@ import {
useContext,
createContext,
} from 'react'
+import { currencies, CurrencyCode, CurrencySymbol } from '../data/currency'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../utils/meta'
import * as eventTracking from '../../../infrastructure/event-tracking'
@@ -33,14 +34,12 @@ function usePayment({ publicKey }: RecurlyOptions) {
'ol-couponCode',
''
)
- const currencySymbols: Record<
- PaymentContextValue['currencyCode'],
- PaymentContextValue['currencySymbol']
- > = getMeta('ol-currencySymbols')
- const initiallySelectedCurrencyCode: string = getMeta(
+ const initiallySelectedCurrencyCode: CurrencyCode = getMeta(
'ol-recommendedCurrency'
)
+ const planCode: string = getMeta('ol-planCode')
+ const [planName, setPlanName] = useState(plan.name)
const [recurlyLoading, setRecurlyLoading] = useState(true)
const [recurlyLoadError, setRecurlyLoadError] = useState(false)
const [recurlyPrice, setRecurlyPrice] = useState<{
@@ -83,12 +82,12 @@ function usePayment({ publicKey }: RecurlyOptions) {
const pricing = useRef()
const limitedCurrencyCodes = Array.from(
- new Set([initiallySelectedCurrencyCode, 'USD', 'EUR', 'GBP'])
+ new Set([initiallySelectedCurrencyCode, 'USD', 'EUR', 'GBP'])
)
const limitedCurrencies = limitedCurrencyCodes.reduce((prev, cur) => {
- return { ...prev, [cur]: currencySymbols[cur] }
- }, {} as Record)
- const currencySymbol = limitedCurrencies[currencyCode]
+ return { ...prev, [cur]: currencies[cur] }
+ }, {} as Partial)
+ const currencySymbol = limitedCurrencies[currencyCode] as CurrencySymbol
useLayoutEffect(() => {
if (typeof recurly === 'undefined' || !recurly) {
@@ -96,11 +95,11 @@ function usePayment({ publicKey }: RecurlyOptions) {
return
}
- eventTracking.sendMB('payment-page-view', { plan: plan.planCode })
+ eventTracking.sendMB('payment-page-view', { plan: planCode })
eventTracking.send(
'subscription-funnel',
'subscription-form-viewed',
- plan.planCode
+ planCode
)
recurly.configure({ publicKey })
@@ -111,7 +110,7 @@ function usePayment({ publicKey }: RecurlyOptions) {
setRecurlyLoading(true)
pricing.current
- ?.plan(plan.planCode, { quantity: 1 })
+ ?.plan(planCode, { quantity: 1 })
.address({
first_name: '',
last_name: '',
@@ -146,7 +145,7 @@ function usePayment({ publicKey }: RecurlyOptions) {
initialCountry,
initialCouponCode,
initiallySelectedCurrencyCode,
- plan.planCode,
+ planCode,
publicKey,
t,
])
@@ -155,6 +154,11 @@ function usePayment({ publicKey }: RecurlyOptions) {
pricing.current?.on('change', function () {
if (!pricing.current) return
+ const planName = pricing.current.items.plan?.name
+ if (planName) {
+ setPlanName(planName)
+ }
+
const trialLength = pricing.current.items.plan?.trial?.length
setTrialLength(trialLength)
@@ -168,14 +172,6 @@ function usePayment({ publicKey }: RecurlyOptions) {
setTaxes(pricing.current.price.taxes)
- // TODO availableCurrencies for preview section (limitedCurrencies is implemented)
- // for (const currencyCode in pricing.items.plan.price) {
- // if (MultiCurrencyPricing.plans[currencyCode]) {
- // $scope.availableCurrencies[currencyCode] =
- // MultiCurrencyPricing.plans[currencyCode]
- // }
- // }
-
const couponData = (() => {
if (pricing.current.items.coupon?.discount.type === 'percent') {
const coupon = pricing.current.items.coupon
@@ -262,9 +258,8 @@ function usePayment({ publicKey }: RecurlyOptions) {
[]
)
- // TODO - for preview panel
const changeCurrency = useCallback(
- (newCurrency: string) => {
+ (newCurrency: CurrencyCode) => {
setRecurlyLoading(true)
setCurrencyCode(newCurrency)
@@ -293,6 +288,8 @@ function usePayment({ publicKey }: RecurlyOptions) {
pricingFormState,
setPricingFormState,
plan,
+ planCode,
+ planName,
pricing,
recurlyLoading,
recurlyLoadError,
@@ -315,6 +312,8 @@ function usePayment({ publicKey }: RecurlyOptions) {
pricingFormState,
setPricingFormState,
plan,
+ planCode,
+ planName,
pricing,
recurlyLoading,
recurlyLoadError,
diff --git a/services/web/frontend/js/features/subscription/context/types/payment-context-value.tsx b/services/web/frontend/js/features/subscription/context/types/payment-context-value.tsx
index ff21ef317b..28e94a4c57 100644
--- a/services/web/frontend/js/features/subscription/context/types/payment-context-value.tsx
+++ b/services/web/frontend/js/features/subscription/context/types/payment-context-value.tsx
@@ -2,6 +2,7 @@ import countries from '../../data/countries'
import { Plan } from '../../../../../../types/subscription/plan'
import { SubscriptionPricingStateTax } from 'recurly__recurly-js'
import { SubscriptionPricingInstanceCustom } from '../../../../../../types/recurly/pricing/subscription'
+import { currencies, CurrencyCode, CurrencySymbol } from '../../data/currency'
export type PricingFormState = {
first_name: string
@@ -18,20 +19,19 @@ export type PricingFormState = {
}
export type PaymentContextValue = {
- currencyCode: string
+ currencyCode: CurrencyCode
setCurrencyCode: React.Dispatch<
React.SetStateAction
>
- currencySymbol: string
- limitedCurrencies: Record<
- PaymentContextValue['currencyCode'],
- PaymentContextValue['currencySymbol']
- >
+ currencySymbol: CurrencySymbol
+ limitedCurrencies: Partial
pricingFormState: PricingFormState
setPricingFormState: React.Dispatch<
React.SetStateAction
>
plan: Plan
+ planCode: string
+ planName: string
pricing: React.MutableRefObject
recurlyLoading: boolean
recurlyLoadError: boolean
@@ -62,6 +62,6 @@ export type PaymentContextValue = {
trialLength: number | undefined
applyVatNumber: (vatNumber: PricingFormState['vat_number']) => void
addCoupon: (coupon: PricingFormState['coupon']) => void
- changeCurrency: (newCurrency: string) => void
+ changeCurrency: (newCurrency: CurrencyCode) => void
updateCountry: (country: PricingFormState['country']) => void
}
diff --git a/services/web/frontend/js/features/subscription/data/currency.ts b/services/web/frontend/js/features/subscription/data/currency.ts
index ad82a701ce..db1b8542cc 100644
--- a/services/web/frontend/js/features/subscription/data/currency.ts
+++ b/services/web/frontend/js/features/subscription/data/currency.ts
@@ -12,4 +12,6 @@ export const currencies = {
SGD: '$',
}
-export type CurrencyCode = keyof typeof currencies
+type Currency = typeof currencies
+export type CurrencyCode = keyof Currency
+export type CurrencySymbol = Currency[CurrencyCode]
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index fdcb24d852..37755f8d2b 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -188,6 +188,7 @@
"category_operators": "Operators",
"category_relations": "Relations",
"change": "Change",
+ "change_currency": "Change currency",
"change_or_cancel-cancel": "cancel",
"change_or_cancel-change": "Change",
"change_or_cancel-or": "or",
@@ -334,6 +335,7 @@
"direct_link": "Direct Link",
"disable_stop_on_first_error": "Disable “Stop on first error”",
"disconnected": "Disconnected",
+ "discount_of": "Discount of __amount__",
"discounted_group_accounts": "discounted group accounts",
"dismiss": "Dismiss",
"dismiss_error_popup": "Dismiss first error alert",