Merge pull request #11919 from overleaf/ii-react-subscription-dash-user-email-not-matching-recurly-email

[web] Subscription dash user email not matching recurly email section - react migration

GitOrigin-RevId: 8d89dbc804694afffea2a92cebdeb5d4e9389810
This commit is contained in:
ilkin-overleaf 2023-02-28 11:15:33 +02:00 committed by Copybot
parent 41b5bb5bae
commit 9f95c7c2ab
9 changed files with 150 additions and 26 deletions

View file

@ -636,6 +636,8 @@
"recompile_from_scratch": "",
"recompile_pdf": "",
"reconnect": "",
"recurly_email_update_needed": "",
"recurly_email_updated": "",
"redirect_to_editor": "",
"reduce_costs_group_licenses": "",
"reference_error_relink_hint": "",
@ -876,6 +878,7 @@
"update_account_info": "",
"update_dropbox_settings": "",
"update_your_billing_details": "",
"updating": "",
"upgrade": "",
"upgrade_cc_btn": "",
"upgrade_for_longer_compiles": "",

View file

@ -20,32 +20,32 @@ function InstitutionMemberships() {
)
}
if (!institutionMemberships.length) return null
return (
<>
<div>
{institutionMemberships.map((institution: Institution) => (
<div key={`${institution.id}`}>
<Trans
i18nKey="you_are_on_x_plan_as_a_confirmed_member_of_institution_y"
values={{
planName: 'Professional',
institutionName: institution.name || '',
}}
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a href="/user/subscription/plans" rel="noopener" />,
// eslint-disable-next-line react/jsx-key
<strong />,
// eslint-disable-next-line react/jsx-key
<strong />,
]}
/>
<hr />
</div>
))}
{institutionMemberships.length > 0 && <PremiumFeaturesLink />}
</div>
</>
<div>
{institutionMemberships.map((institution: Institution) => (
<div key={`${institution.id}`}>
<Trans
i18nKey="you_are_on_x_plan_as_a_confirmed_member_of_institution_y"
values={{
planName: 'Professional',
institutionName: institution.name || '',
}}
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a href="/user/subscription/plans" rel="noopener" />,
// eslint-disable-next-line react/jsx-key
<strong />,
// eslint-disable-next-line react/jsx-key
<strong />,
]}
/>
<hr />
</div>
))}
<PremiumFeaturesLink />
</div>
)
}

View file

@ -0,0 +1,58 @@
import { useTranslation, Trans } from 'react-i18next'
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
import { FormGroup, Alert } from 'react-bootstrap'
import getMeta from '../../../../utils/meta'
import useAsync from '../../../../shared/hooks/use-async'
import { postJSON } from '../../../../infrastructure/fetch-json'
function PersonalSubscriptionRecurlySyncEmail() {
const { t } = useTranslation()
const { personalSubscription } = useSubscriptionDashboardContext()
const userEmail = getMeta('ol-usersEmail') as string
const { isLoading, isSuccess, runAsync } = useAsync()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
runAsync(postJSON('/user/subscription/account/email'))
}
if (!personalSubscription || !('recurly' in personalSubscription)) return null
const recurlyEmail = personalSubscription.recurly.account.email
if (!userEmail || recurlyEmail === userEmail) return null
return (
<>
<form onSubmit={handleSubmit}>
<FormGroup>
{isSuccess ? (
<Alert bsStyle="success">{t('recurly_email_updated')}</Alert>
) : (
<>
<p>
<Trans
i18nKey="recurly_email_update_needed"
components={[<em />, <em />]} // eslint-disable-line react/jsx-key
values={{ recurlyEmail, userEmail }}
/>
</p>
<div>
<button
className="btn btn-primary"
type="submit"
disabled={isLoading}
>
{isLoading ? <>{t('updating')}</> : t('update')}
</button>
</div>
</>
)}
</FormGroup>
</form>
<hr />
</>
)
}
export default PersonalSubscriptionRecurlySyncEmail

View file

@ -4,6 +4,7 @@ import { ActiveSubscription } from './states/active/active'
import { CanceledSubscription } from './states/canceled'
import { ExpiredSubscription } from './states/expired'
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
import PersonalSubscriptionRecurlySyncEmail from './personal-subscription-recurly-sync-email'
function PastDueSubscriptionAlert({
subscription,
@ -79,6 +80,7 @@ function PersonalSubscription() {
</div>
)}
<hr />
<PersonalSubscriptionRecurlySyncEmail />
</>
)
}

View file

@ -1607,6 +1607,7 @@
"update_account_info": "Update Account Info",
"update_dropbox_settings": "Update Dropbox Settings",
"update_your_billing_details": "Update Your Billing Details",
"updating": "Updating",
"updating_site": "Updating Site",
"upgrade": "Upgrade",
"upgrade_cc_btn": "Upgrade now, pay after 7 days",

View file

@ -1,5 +1,10 @@
import { expect } from 'chai'
import { screen } from '@testing-library/react'
import {
screen,
fireEvent,
waitForElementToBeRemoved,
within,
} from '@testing-library/react'
import PersonalSubscription from '../../../../../../frontend/js/features/subscription/components/dashboard/personal-subscription'
import {
annualActiveSubscription,
@ -11,6 +16,7 @@ import {
cleanUpContext,
renderWithSubscriptionDashContext,
} from '../../helpers/render-with-subscription-dash-context'
import fetchMock from 'fetch-mock'
describe('<PersonalSubscription />', function () {
afterEach(function () {
@ -145,4 +151,45 @@ describe('<PersonalSubscription />', function () {
screen.getByText('Change plan')
})
})
it('shows different recurly email address section', async function () {
fetchMock.post('/user/subscription/account/email', 200)
const usersEmail = 'foo@example.com'
renderWithSubscriptionDashContext(<PersonalSubscription />, {
metaTags: [
{ name: 'ol-subscription', value: annualActiveSubscription },
{ name: 'ol-usersEmail', value: usersEmail },
],
})
const billingText = screen.getByText(
/your billing email address is currently/i
).textContent
expect(billingText).to.contain(
`Your billing email address is currently ${annualActiveSubscription.recurly.account.email}.` +
` If needed you can update your billing address to ${usersEmail}`
)
const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
name: /update/i,
})
expect(submitBtn.disabled).to.be.false
fireEvent.click(submitBtn)
expect(submitBtn.disabled).to.be.true
expect(
screen.getByRole<HTMLButtonElement>('button', { name: /updating/i })
.disabled
).to.be.true
await waitForElementToBeRemoved(() =>
screen.getByText(/your billing email address is currently/i)
)
within(screen.getByRole('alert')).getByText(
/your billing email address was successfully updated/i
)
expect(screen.queryByRole('button', { name: /update/i })).to.be.null
expect(screen.queryByRole('button', { name: /updating/i })).to.be.null
})
})

View file

@ -3,6 +3,7 @@ import {
GroupSubscription,
RecurlySubscription,
} from '../../../../../types/subscription/dashboard/subscription'
const dateformat = require('dateformat')
const today = new Date()
const oneYearFromToday = new Date().setFullYear(today.getFullYear() + 1)
@ -45,6 +46,7 @@ export const annualActiveSubscription: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -84,6 +86,7 @@ export const annualActiveSubscriptionEuro: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -122,6 +125,7 @@ export const annualActiveSubscriptionPro: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -161,6 +165,7 @@ export const pastDueExpiredSubscription: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'true', $: { type: 'boolean' } },
},
@ -200,6 +205,7 @@ export const canceledSubscription: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'true', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -239,6 +245,7 @@ export const pendingSubscriptionChange: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -289,6 +296,7 @@ export const groupActiveSubscription: GroupSubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},
@ -333,6 +341,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription
trial_ends_at: null,
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: {
_: 'false',
$: {
@ -395,6 +404,7 @@ export const trialSubscription: RecurlySubscription = {
trial_ends_at: new Date(sevenDaysFromToday).toString(),
activeCoupons: [],
account: {
email: 'fake@example.com',
has_canceled_subscription: {
_: 'false',
$: {

View file

@ -2,6 +2,7 @@ import { render } from '@testing-library/react'
import _ from 'lodash'
import { SubscriptionDashboardProvider } from '../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
import { groupPriceByUsageTypeAndSize, plans } from '../fixtures/plans'
import fetchMock from 'fetch-mock'
export function renderWithSubscriptionDashContext(
component: React.ReactElement,
@ -89,4 +90,5 @@ export function cleanUpContext() {
// @ts-ignore
delete global.recurly
window.metaAttributesCache = new Map()
fetchMock.reset()
}

View file

@ -19,6 +19,7 @@ type Recurly = {
trial_ends_at: Nullable<string>
activeCoupons: any[] // TODO: confirm type in array
account: {
email: string
// data via Recurly API
has_canceled_subscription: {
_: 'false' | 'true'