mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-22 16:42:00 +00:00
[web] Migrate /user/subscription
to BS5 (#20513)
* [web] Initialize BS5 in subscription page * [web] Update subscription-dashboard.tsx for BS5 * [web] Update row-link.tsx for BS5 * [web] Update modals * [web] Add `btn` to `btn-inline-link` classes * [web] Update circle change-to-group circle price element * [web] Replace `list-item-with-margin-bottom` with `mb-3` * [web] Update form elements to BS5 * [web] Use `useContactUsModal` * [web] Adjust tables margin/padding, and more * [web] Update change-to-group-modal.tsx * [web] Add gap to subscription buttons * [web] Remove subscription page colspan for md and above * [web] Use Notification component * [web] Update "leave group" buttons * [web] Fix tests: add `ol-user` meta tag * [web] Nest .hover-highlight in #subscription-dashboard-root * [web] Update to OLRow/OLCol * [web] Update to OLButtons * [web] Update to OLFormGroup * [web] Naming: use BSversion prefix * [web] Set CancelSubscriptionButton as ghost directly in component * [web] Set "Plan" font size * [web] Simplify cancel-subscription buttons * [web] Remove `--neutral-10` ModalFooter background * [web] Simplify circle styles * [web] Center discount badge * [web] Update fieldset label * [web] Add `<ul>` around RowLink * [web] Define SCSS for row-link component * [web] Remove some use of utility classes * [web] Revert and update `fieldset` changes (fixes tests) * [web] Fixup some more OLButtons * [web] Fixup use of OLRow/OLCol * [web] Reduce spacing below "legend-as-label" * [web] Use h5 instead of small in OLModalTitle * [web] Revert OLCol removal on lg screens I had removed them by mistake because I wasn't using the proper breakpoints * [web] Add backdrop to nested modal ContactUsModal * [web] Don't prefill project URL in ContactUsModal * [web] Fix lint * [web] Share `className` prop in BS5 and BS3 modals * [web] Set sub-title font sans serif (BS3) * [web] Update remaining Alerts to OLNotification GitOrigin-RevId: 7fd975ae3e992cebfaf71d4e182f8e13ec886d09
This commit is contained in:
parent
d4bf47932e
commit
9997c4874f
36 changed files with 763 additions and 492 deletions
|
@ -23,6 +23,7 @@ block append meta
|
|||
meta(name="ol-fromPlansPage" data-type="boolean" content=fromPlansPage)
|
||||
meta(name="ol-plans", data-type="json" content=plans)
|
||||
meta(name="ol-groupSettingsEnabledFor", data-type="json" content=groupSettingsEnabledFor)
|
||||
meta(name="ol-user" data-type="json" content=user)
|
||||
if (personalSubscription && personalSubscription.recurly)
|
||||
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
||||
meta(name="ol-recommendedCurrency" content=personalSubscription.recurly.currency)
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function ActionButtonText({
|
||||
inflight,
|
||||
buttonText,
|
||||
}: {
|
||||
inflight: boolean
|
||||
buttonText: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
return <>{!inflight ? buttonText : t('processing_uppercase') + '…'}</>
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
export default function GenericErrorAlert({
|
||||
className,
|
||||
|
@ -7,12 +7,18 @@ export default function GenericErrorAlert({
|
|||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const alertClassName = classNames('alert', 'alert-danger', className)
|
||||
|
||||
return (
|
||||
<div className={alertClassName} aria-live="polite">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</div>
|
||||
<OLNotification
|
||||
className={className}
|
||||
aria-live="polite"
|
||||
type="error"
|
||||
content={
|
||||
<>
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { MemberGroupSubscription } from '../../../../../../types/subscription/dashboard/subscription'
|
||||
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
|
||||
import { LEAVE_GROUP_MODAL_ID } from './leave-group-modal'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
type GroupSubscriptionMembershipProps = {
|
||||
subscription: MemberGroupSubscription
|
||||
|
@ -52,9 +52,9 @@ export default function GroupSubscriptionMembership({
|
|||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<Button bsStyle="danger" onClick={leaveGroup}>
|
||||
<OLButton variant="danger" onClick={leaveGroup}>
|
||||
{t('leave_group')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</span>
|
||||
)}
|
||||
<hr />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import { Institution } from '../../../../../../types/institution'
|
||||
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
function InstitutionMemberships() {
|
||||
const { institutionMemberships } = useSubscriptionDashboardContext()
|
||||
|
@ -9,13 +10,16 @@ function InstitutionMemberships() {
|
|||
|
||||
if (!institutionMemberships) {
|
||||
return (
|
||||
<div className="alert alert-warning">
|
||||
<p>
|
||||
Sorry, something went wrong. Subscription information related to
|
||||
institutional affiliations may not be displayed. Please try again
|
||||
later.
|
||||
</p>
|
||||
</div>
|
||||
<OLNotification
|
||||
type="warning"
|
||||
content={
|
||||
<p>
|
||||
Sorry, something went wrong. Subscription information related to
|
||||
institutional affiliations may not be displayed. Please try again
|
||||
later.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { deleteJSON } from '../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../shared/components/accessible-modal'
|
||||
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
|
||||
import { useLocation } from '../../../../shared/hooks/use-location'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export const LEAVE_GROUP_MODAL_ID = 'leave-group'
|
||||
|
||||
|
@ -37,38 +42,43 @@ export default function LeaveGroupModal() {
|
|||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
id={LEAVE_GROUP_MODAL_ID}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('leave_group')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('leave_group')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
<p>{t('sure_you_want_to_leave_group')}</p>
|
||||
</Modal.Body>
|
||||
</OLModalBody>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
bsStyle={null}
|
||||
className="btn-secondary"
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
onClick={handleCloseModal}
|
||||
disabled={inflight}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
bsStyle="danger"
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="danger"
|
||||
onClick={handleConfirmLeaveGroup}
|
||||
disabled={inflight}
|
||||
isLoading={inflight}
|
||||
bs3Props={{
|
||||
loading: inflight
|
||||
? t('processing_uppercase') + '…'
|
||||
: t('leave_now'),
|
||||
}}
|
||||
>
|
||||
{inflight ? t('processing_uppercase') + '…' : t('leave_now')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('processing_uppercase')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -100,27 +100,29 @@ export default function ManagedGroupSubscriptions() {
|
|||
<p>
|
||||
<ManagedGroupAdministrator subscription={subscription} />
|
||||
</p>
|
||||
<RowLink
|
||||
href={`/manage/groups/${subscription._id}/members`}
|
||||
heading={t('manage_members')}
|
||||
subtext={t('manage_group_members_subtext')}
|
||||
icon="groups"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/groups/${subscription._id}/managers`}
|
||||
heading={t('manage_group_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
{groupSettingsEnabledFor?.includes(subscription._id) && (
|
||||
<GroupSettingsButton subscription={subscription} />
|
||||
)}
|
||||
<RowLink
|
||||
href={`/metrics/groups/${subscription._id}`}
|
||||
heading={t('view_metrics')}
|
||||
subtext={t('view_metrics_group_subtext')}
|
||||
icon="insights"
|
||||
/>
|
||||
<ul className="list-group p-0">
|
||||
<RowLink
|
||||
href={`/manage/groups/${subscription._id}/members`}
|
||||
heading={t('manage_members')}
|
||||
subtext={t('manage_group_members_subtext')}
|
||||
icon="groups"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/groups/${subscription._id}/managers`}
|
||||
heading={t('manage_group_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
{groupSettingsEnabledFor?.includes(subscription._id) && (
|
||||
<GroupSettingsButton subscription={subscription} />
|
||||
)}
|
||||
<RowLink
|
||||
href={`/metrics/groups/${subscription._id}`}
|
||||
heading={t('view_metrics')}
|
||||
subtext={t('view_metrics_group_subtext')}
|
||||
icon="insights"
|
||||
/>
|
||||
</ul>
|
||||
<hr />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ManagedInstitution as Institution } from '../../../../../../types/subsc
|
|||
import { RowLink } from './row-link'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
type ManagedInstitutionProps = {
|
||||
institution: Institution
|
||||
|
@ -53,33 +54,35 @@ export default function ManagedInstitution({
|
|||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
<RowLink
|
||||
href={`/metrics/institutions/${institution.v1Id}`}
|
||||
heading={t('view_metrics')}
|
||||
subtext={t('view_metrics_commons_subtext')}
|
||||
icon="insights"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/institutions/${institution.v1Id}/hub`}
|
||||
heading={t('view_hub')}
|
||||
subtext={t('view_hub_subtext')}
|
||||
icon="account_circle"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/institutions/${institution.v1Id}/managers`}
|
||||
heading={t('manage_institution_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
<ul className="list-group p-0">
|
||||
<RowLink
|
||||
href={`/metrics/institutions/${institution.v1Id}`}
|
||||
heading={t('view_metrics')}
|
||||
subtext={t('view_metrics_commons_subtext')}
|
||||
icon="insights"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/institutions/${institution.v1Id}/hub`}
|
||||
heading={t('view_hub')}
|
||||
subtext={t('view_hub_subtext')}
|
||||
icon="account_circle"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/institutions/${institution.v1Id}/managers`}
|
||||
heading={t('manage_institution_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
</ul>
|
||||
<div>
|
||||
<p>
|
||||
<span>Monthly metrics emails: </span>
|
||||
{subscriptionChanging ? (
|
||||
<i className="fa fa-spin fa-refresh" />
|
||||
) : (
|
||||
<button
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
style={{ border: 0 }}
|
||||
onClick={e =>
|
||||
changeInstitutionalEmailSubscription(e, institution.v1Id)
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ export default function ManagedInstitution({
|
|||
)
|
||||
? t('subscribe')
|
||||
: t('unsubscribe')}
|
||||
</button>
|
||||
</OLButton>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -22,18 +22,20 @@ export default function ManagedPublisher({ publisher }: ManagedPublisherProps) {
|
|||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
<RowLink
|
||||
href={`/publishers/${publisher.slug}/hub`}
|
||||
heading={t('view_hub')}
|
||||
subtext={t('view_hub_subtext')}
|
||||
icon="account_circle"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/publishers/${publisher.slug}/managers`}
|
||||
heading={t('manage_publisher_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
<ul className="list-group p-0">
|
||||
<RowLink
|
||||
href={`/publishers/${publisher.slug}/hub`}
|
||||
heading={t('view_hub')}
|
||||
subtext={t('view_hub_subtext')}
|
||||
icon="account_circle"
|
||||
/>
|
||||
<RowLink
|
||||
href={`/manage/publishers/${publisher.slug}/managers`}
|
||||
heading={t('manage_publisher_managers')}
|
||||
subtext={t('manage_managers_subtext')}
|
||||
icon="manage_accounts"
|
||||
/>
|
||||
</ul>
|
||||
<hr />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
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'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
|
||||
function PersonalSubscriptionRecurlySyncEmail() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -25,9 +27,12 @@ function PersonalSubscriptionRecurlySyncEmail() {
|
|||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<OLFormGroup>
|
||||
{isSuccess ? (
|
||||
<Alert bsStyle="success">{t('recurly_email_updated')}</Alert>
|
||||
<OLNotification
|
||||
type="success"
|
||||
content={t('recurly_email_updated')}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
|
@ -40,17 +45,21 @@ function PersonalSubscriptionRecurlySyncEmail() {
|
|||
/>
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
<OLButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? t('updating') + '…' : t('update'),
|
||||
}}
|
||||
>
|
||||
{isLoading ? <>{t('updating')}…</> : t('update')}
|
||||
</button>
|
||||
{t('update')}
|
||||
</OLButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</FormGroup>
|
||||
</OLFormGroup>
|
||||
</form>
|
||||
<hr />
|
||||
</>
|
||||
|
|
|
@ -5,6 +5,7 @@ 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'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
function PastDueSubscriptionAlert({
|
||||
subscription,
|
||||
|
@ -13,18 +14,21 @@ function PastDueSubscriptionAlert({
|
|||
}) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{t('account_has_past_due_invoice_change_plan_warning')}{' '}
|
||||
<a
|
||||
href={subscription.recurly.accountManagementLink}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{t('view_your_invoices')}
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<>
|
||||
{t('account_has_past_due_invoice_change_plan_warning')}{' '}
|
||||
<a
|
||||
href={subscription.recurly.accountManagementLink}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{t('view_your_invoices')}
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -75,9 +79,10 @@ function PersonalSubscription() {
|
|||
subscription={personalSubscription as RecurlySubscription}
|
||||
/>
|
||||
{recurlyLoadError && (
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<strong>{t('payment_provider_unreachable_error')}</strong>
|
||||
</div>
|
||||
<OLNotification
|
||||
type="warning"
|
||||
content={<strong>{t('payment_provider_unreachable_error')}</strong>}
|
||||
/>
|
||||
)}
|
||||
<hr />
|
||||
<PersonalSubscriptionRecurlySyncEmail />
|
||||
|
|
|
@ -5,6 +5,7 @@ import useAsync from '../../../../shared/hooks/use-async'
|
|||
import { useLocation } from '../../../../shared/hooks/use-location'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
function ReactivateSubscription() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -25,14 +26,14 @@ function ReactivateSubscription() {
|
|||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
<OLButton
|
||||
variant="primary"
|
||||
disabled={isLoading || isSuccess}
|
||||
onClick={handleReactivate}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{t('reactivate_subscription')}
|
||||
</button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import MaterialIcon from '../../../../shared/components/material-icon'
|
||||
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type RowLinkProps = {
|
||||
href: string
|
||||
|
@ -7,7 +8,11 @@ type RowLinkProps = {
|
|||
icon: string
|
||||
}
|
||||
|
||||
export function RowLink({ href, heading, subtext, icon }: RowLinkProps) {
|
||||
export function RowLink(props: RowLinkProps) {
|
||||
return isBootstrap5() ? <BS5RowLink {...props} /> : <BS3RowLink {...props} />
|
||||
}
|
||||
|
||||
function BS3RowLink({ href, heading, subtext, icon }: RowLinkProps) {
|
||||
return (
|
||||
<a href={href} className="row-link">
|
||||
<div className="icon">
|
||||
|
@ -23,3 +28,18 @@ export function RowLink({ href, heading, subtext, icon }: RowLinkProps) {
|
|||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function BS5RowLink({ href, heading, subtext, icon }: RowLinkProps) {
|
||||
return (
|
||||
<li className="list-group-item row-link">
|
||||
<a href={href}>
|
||||
<MaterialIcon type={icon} className="p-2 p-md-3" />
|
||||
<div className="flex-grow-1">
|
||||
<strong>{heading}</strong>
|
||||
<div>{subtext}</div>
|
||||
</div>
|
||||
<MaterialIcon type="keyboard_arrow_right" className="p-2 p-md-3" />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ChangePlanModal } from './change-plan/modals/change-plan-modal'
|
|||
import { ConfirmChangePlanModal } from './change-plan/modals/confirm-change-plan-modal'
|
||||
import { KeepCurrentPlanModal } from './change-plan/modals/keep-current-plan-modal'
|
||||
import { ChangeToGroupModal } from './change-plan/modals/change-to-group-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export function ActiveSubscription({
|
||||
subscription,
|
||||
|
@ -62,12 +63,13 @@ export function ActiveSubscription({
|
|||
subscription.recurly.account.has_past_due_invoice._ !== 'true' && (
|
||||
<>
|
||||
{' '}
|
||||
<button
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={() => setModalIdShown('change-plan')}
|
||||
>
|
||||
{t('change_plan')}
|
||||
</button>
|
||||
</OLButton>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
@ -103,7 +105,7 @@ export function ActiveSubscription({
|
|||
/>
|
||||
</p>
|
||||
<PriceExceptions subscription={subscription} />
|
||||
<p>
|
||||
<p className="d-inline-flex flex-wrap gap-1">
|
||||
<a
|
||||
href={subscription.recurly.billingDetailsLink}
|
||||
target="_blank"
|
||||
|
@ -121,7 +123,10 @@ export function ActiveSubscription({
|
|||
{t('view_your_invoices')}
|
||||
</a>
|
||||
{!recurlyLoadError && (
|
||||
<CancelSubscriptionButton className="btn btn-danger-ghost ms-1" />
|
||||
<>
|
||||
{' '}
|
||||
<CancelSubscriptionButton />
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
|
|
|
@ -9,41 +9,40 @@ import {
|
|||
redirectAfterCancelSubscriptionUrl,
|
||||
} from '../../../../../data/subscription-url'
|
||||
import showDowngradeOption from '../../../../../util/show-downgrade-option'
|
||||
import ActionButtonText from '../../../action-button-text'
|
||||
import GenericErrorAlert from '../../../generic-error-alert'
|
||||
import DowngradePlanButton from './downgrade-plan-button'
|
||||
import ExtendTrialButton from './extend-trial-button'
|
||||
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
const planCodeToDowngradeTo = 'paid-personal'
|
||||
|
||||
function ConfirmCancelSubscriptionButton({
|
||||
buttonClass,
|
||||
buttonText,
|
||||
handleCancelSubscription,
|
||||
isLoadingCancel,
|
||||
isSuccessCancel,
|
||||
isButtonDisabled,
|
||||
showNoThanks,
|
||||
onClick,
|
||||
disabled,
|
||||
isLoading,
|
||||
}: {
|
||||
buttonClass: string
|
||||
buttonText: string
|
||||
handleCancelSubscription: () => void
|
||||
isLoadingCancel: boolean
|
||||
isSuccessCancel: boolean
|
||||
isButtonDisabled: boolean
|
||||
showNoThanks: boolean
|
||||
onClick: () => void
|
||||
disabled: boolean
|
||||
isLoading: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const text = showNoThanks ? t('no_thanks_cancel_now') : t('cancel_my_account')
|
||||
return (
|
||||
<button
|
||||
className={`btn ${buttonClass}`}
|
||||
onClick={handleCancelSubscription}
|
||||
disabled={isButtonDisabled}
|
||||
<OLButton
|
||||
isLoading={isLoading}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={showNoThanks ? 'btn-inline-link' : undefined}
|
||||
bs3Props={{
|
||||
loading: isLoading ? t('processing_uppercase') + '…' : text,
|
||||
}}
|
||||
>
|
||||
<ActionButtonText
|
||||
inflight={isSuccessCancel || isLoadingCancel}
|
||||
buttonText={buttonText}
|
||||
/>
|
||||
</button>
|
||||
{text}
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -85,8 +84,7 @@ function NotCancelOption({
|
|||
<p>
|
||||
<ExtendTrialButton
|
||||
isButtonDisabled={isButtonDisabled}
|
||||
isLoadingSecondaryAction={isLoadingSecondaryAction}
|
||||
isSuccessSecondaryAction={isSuccessSecondaryAction}
|
||||
isLoading={isLoadingSecondaryAction || isSuccessSecondaryAction}
|
||||
runAsyncSecondaryAction={runAsyncSecondaryAction}
|
||||
/>
|
||||
</p>
|
||||
|
@ -114,8 +112,7 @@ function NotCancelOption({
|
|||
<p>
|
||||
<DowngradePlanButton
|
||||
isButtonDisabled={isButtonDisabled}
|
||||
isLoadingSecondaryAction={isLoadingSecondaryAction}
|
||||
isSuccessSecondaryAction={isSuccessSecondaryAction}
|
||||
isLoading={isLoadingSecondaryAction || isSuccessSecondaryAction}
|
||||
planToDowngradeTo={planToDowngradeTo}
|
||||
runAsyncSecondaryAction={runAsyncSecondaryAction}
|
||||
/>
|
||||
|
@ -130,12 +127,9 @@ function NotCancelOption({
|
|||
|
||||
return (
|
||||
<p>
|
||||
<button
|
||||
className="btn btn-secondary-info btn-secondary"
|
||||
onClick={handleKeepPlan}
|
||||
>
|
||||
<OLButton variant="secondary" onClick={handleKeepPlan}>
|
||||
{t('i_want_to_stay')}
|
||||
</button>
|
||||
</OLButton>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -188,13 +182,6 @@ export function CancelSubscription() {
|
|||
|
||||
const showExtendFreeTrial = userCanExtendTrial
|
||||
|
||||
let confirmCancelButtonText = t('cancel_my_account')
|
||||
let confirmCancelButtonClass = 'btn-primary'
|
||||
if (showExtendFreeTrial || showDowngrade) {
|
||||
confirmCancelButtonText = t('no_thanks_cancel_now')
|
||||
confirmCancelButtonClass = 'btn-inline-link'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<p>
|
||||
|
@ -214,12 +201,10 @@ export function CancelSubscription() {
|
|||
/>
|
||||
|
||||
<ConfirmCancelSubscriptionButton
|
||||
buttonClass={confirmCancelButtonClass}
|
||||
buttonText={confirmCancelButtonText}
|
||||
isButtonDisabled={isButtonDisabled}
|
||||
handleCancelSubscription={handleCancelSubscription}
|
||||
isSuccessCancel={isSuccessCancel}
|
||||
isLoadingCancel={isLoadingCancel}
|
||||
showNoThanks={showExtendFreeTrial || showDowngrade}
|
||||
onClick={handleCancelSubscription}
|
||||
disabled={isButtonDisabled}
|
||||
isLoading={isSuccessCancel || isLoadingCancel}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -2,20 +2,18 @@ import { useTranslation } from 'react-i18next'
|
|||
import { Plan } from '../../../../../../../../../types/subscription/plan'
|
||||
import { postJSON } from '../../../../../../../infrastructure/fetch-json'
|
||||
import { subscriptionUpdateUrl } from '../../../../../data/subscription-url'
|
||||
import ActionButtonText from '../../../action-button-text'
|
||||
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function DowngradePlanButton({
|
||||
isButtonDisabled,
|
||||
isLoadingSecondaryAction,
|
||||
isSuccessSecondaryAction,
|
||||
isLoading,
|
||||
planToDowngradeTo,
|
||||
runAsyncSecondaryAction,
|
||||
}: {
|
||||
isButtonDisabled: boolean
|
||||
isLoadingSecondaryAction: boolean
|
||||
isSuccessSecondaryAction: boolean
|
||||
isLoading: boolean
|
||||
planToDowngradeTo: Plan
|
||||
runAsyncSecondaryAction: (promise: Promise<unknown>) => Promise<unknown>
|
||||
}) {
|
||||
|
@ -37,17 +35,16 @@ export default function DowngradePlanButton({
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleDowngradePlan}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
<ActionButtonText
|
||||
inflight={isLoadingSecondaryAction || isSuccessSecondaryAction}
|
||||
buttonText={buttonText}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={handleDowngradePlan}
|
||||
disabled={isButtonDisabled}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? t('processing_uppercase') + '…' : buttonText,
|
||||
}}
|
||||
>
|
||||
{buttonText}
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { putJSON } from '../../../../../../../infrastructure/fetch-json'
|
||||
import { extendTrialUrl } from '../../../../../data/subscription-url'
|
||||
import ActionButtonText from '../../../action-button-text'
|
||||
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export default function ExtendTrialButton({
|
||||
isButtonDisabled,
|
||||
isLoadingSecondaryAction,
|
||||
isSuccessSecondaryAction,
|
||||
isLoading,
|
||||
runAsyncSecondaryAction,
|
||||
}: {
|
||||
isButtonDisabled: boolean
|
||||
isLoadingSecondaryAction: boolean
|
||||
isSuccessSecondaryAction: boolean
|
||||
isLoading: boolean
|
||||
runAsyncSecondaryAction: (promise: Promise<unknown>) => Promise<unknown>
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -30,17 +28,16 @@ export default function ExtendTrialButton({
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleExtendTrial}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
<ActionButtonText
|
||||
inflight={isLoadingSecondaryAction || isSuccessSecondaryAction}
|
||||
buttonText={buttonText}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={handleExtendTrial}
|
||||
disabled={isButtonDisabled}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? t('processing_uppercase') + '…' : buttonText,
|
||||
}}
|
||||
>
|
||||
{buttonText}
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export function CancelSubscriptionButton(
|
||||
props: React.ComponentProps<'button'>
|
||||
) {
|
||||
export function CancelSubscriptionButton() {
|
||||
const { t } = useTranslation()
|
||||
const { recurlyLoadError, setShowCancellation } =
|
||||
useSubscriptionDashboardContext()
|
||||
|
@ -17,8 +16,8 @@ export function CancelSubscriptionButton(
|
|||
if (recurlyLoadError) return null
|
||||
|
||||
return (
|
||||
<button onClick={handleCancelSubscriptionClick} {...props}>
|
||||
<OLButton variant="danger-ghost" onClick={handleCancelSubscriptionClick}>
|
||||
{t('cancel_your_subscription')}
|
||||
</button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export function ChangeToGroupPlan() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -10,13 +11,13 @@ export function ChangeToGroupPlan() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="card-gray text-center mt-3">
|
||||
<div className="card-gray text-center mt-3 p-3">
|
||||
<h2 style={{ marginTop: 0 }}>{t('looking_multiple_licenses')}</h2>
|
||||
<p style={{ margin: 0 }}>{t('reduce_costs_group_licenses')}</p>
|
||||
<br />
|
||||
<button className="btn btn-primary" onClick={handleClick}>
|
||||
<OLButton variant="primary" onClick={handleClick}>
|
||||
{t('change_to_group_plan')}
|
||||
</button>
|
||||
</OLButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { Plan } from '../../../../../../../../../types/subscription/plan'
|
||||
import Icon from '../../../../../../../shared/components/icon'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
function ChangeToPlanButton({ planCode }: { planCode: string }) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -12,9 +13,9 @@ function ChangeToPlanButton({ planCode }: { planCode: string }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<button className="btn btn-primary" onClick={handleClick}>
|
||||
<OLButton variant="primary" onClick={handleClick}>
|
||||
{t('change_to_this_plan')}
|
||||
</button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -27,9 +28,9 @@ function KeepCurrentPlanButton({ plan }: { plan: Plan }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<button className="btn btn-primary" onClick={handleClick}>
|
||||
<OLButton variant="primary" onClick={handleClick}>
|
||||
{t('keep_current_plan')}
|
||||
</button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -66,13 +67,13 @@ function PlansRow({ plan }: { plan: Plan }) {
|
|||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<td className="align-middle">
|
||||
<strong>{plan.name}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<td className="align-middle">
|
||||
{plan.displayPrice} / {plan.annual ? t('year') : t('month')}
|
||||
</td>
|
||||
<td>
|
||||
<td className="align-middle text-center">
|
||||
<ChangePlanButton plan={plan} />
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -97,9 +98,9 @@ export function IndividualPlansTable({ plans }: { plans: Array<Plan> }) {
|
|||
if (!plans || recurlyLoadError) return null
|
||||
|
||||
return (
|
||||
<table className="table table-vertically-centered-cells">
|
||||
<table className="table align-middle table-vertically-centered-cells m-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr className="d-none d-md-table-row">
|
||||
<th>{t('name')}</th>
|
||||
<th>{t('price')}</th>
|
||||
<th />
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SubscriptionDashModalIds } from '../../../../../../../../../../types/subscription/dashboard/modal-ids'
|
||||
import AccessibleModal from '../../../../../../../../shared/components/accessible-modal'
|
||||
import LoadingSpinner from '../../../../../../../../shared/components/loading-spinner'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import { ChangeToGroupPlan } from '../change-to-group-plan'
|
||||
import { IndividualPlansTable } from '../individual-plans-table'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
|
||||
function ChangePlanOptions() {
|
||||
const { plans, queryingIndividualPlansData, recurlyLoadError } =
|
||||
|
@ -22,7 +25,7 @@ function ChangePlanOptions() {
|
|||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="table-outlined-container">
|
||||
<div className="border rounded px-2 pt-1 table-outlined-container">
|
||||
<IndividualPlansTable plans={plans} />
|
||||
</div>
|
||||
<ChangeToGroupPlan />
|
||||
|
@ -39,14 +42,14 @@ export function ChangePlanModal() {
|
|||
if (modalIdShown !== modalId) return null
|
||||
|
||||
return (
|
||||
<AccessibleModal id={modalId} show animation onHide={handleCloseModal}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('change_plan')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModal id={modalId} show animation onHide={handleCloseModal} size="lg">
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('change_plan')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
<ChangePlanOptions />
|
||||
</Modal.Body>
|
||||
</AccessibleModal>
|
||||
</OLModalBody>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Subscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import { PriceForDisplayData } from '../../../../../../../../../../types/subscription/plan'
|
||||
import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../../../shared/components/accessible-modal'
|
||||
import getMeta from '../../../../../../../../utils/meta'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import GenericErrorAlert from '../../../../generic-error-alert'
|
||||
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
|
||||
import { getRecurlyGroupPlanCode } from '../../../../../../util/recurly-group-plan-code'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
const educationalPercentDiscount = 40
|
||||
const groupSizeForEducationalDiscount = 10
|
||||
|
@ -88,7 +101,7 @@ function GroupPrice({
|
|||
})}
|
||||
</span>
|
||||
|
||||
<br />
|
||||
<BootstrapVersionSwitcher bs3={<br />} />
|
||||
|
||||
<span className="circle-subtext">
|
||||
<span aria-hidden>
|
||||
|
@ -124,6 +137,8 @@ export function ChangeToGroupModal() {
|
|||
setGroupPlanToChangeToSize,
|
||||
setGroupPlanToChangeToUsage,
|
||||
} = useSubscriptionDashboardContext()
|
||||
const { modal: contactModal, showModal: showContactModal } =
|
||||
useContactUsModal({ autofillProjectUrl: false })
|
||||
const groupPlans = getMeta('ol-groupPlans')
|
||||
const personalSubscription = getMeta('ol-subscription') as Subscription
|
||||
const [error, setError] = useState(false)
|
||||
|
@ -157,11 +172,6 @@ export function ChangeToGroupModal() {
|
|||
}
|
||||
}, [personalSubscription, setGroupPlanToChangeToCode])
|
||||
|
||||
function handleGetInTouchButton() {
|
||||
handleCloseModal()
|
||||
$('[data-ol-contact-form-modal="contact-us"]').modal()
|
||||
}
|
||||
|
||||
if (
|
||||
modalIdShown !== modalId ||
|
||||
!groupPlans ||
|
||||
|
@ -172,197 +182,199 @@ export function ChangeToGroupModal() {
|
|||
return null
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<button className="close" onClick={handleCloseModal}>
|
||||
<span aria-hidden="true">×</span>
|
||||
<span className="sr-only">{t('close')}</span>
|
||||
</button>
|
||||
<div className="modal-title">
|
||||
<h2>{t('customize_your_group_subscription')}</h2>
|
||||
<h3>
|
||||
{t('save_x_percent_or_more', {
|
||||
percent: '30',
|
||||
})}
|
||||
</h3>
|
||||
</div>
|
||||
</Modal.Header>
|
||||
<>
|
||||
<UserProvider>{contactModal}</UserProvider>
|
||||
<OLModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle className="lh-sm">
|
||||
{t('customize_your_group_subscription')}
|
||||
<br />
|
||||
<span className="h5">
|
||||
{t('save_x_percent_or_more', {
|
||||
percent: '30',
|
||||
})}
|
||||
</span>
|
||||
</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<div className="container-fluid plans group-subscription-modal">
|
||||
{groupPlanToChangeToPriceError && <GenericErrorAlert />}
|
||||
<div className="row">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="circle circle-lg">
|
||||
<GroupPrice
|
||||
groupPlanToChangeToPrice={groupPlanToChangeToPrice}
|
||||
queryingGroupPlanToChangeToPrice={
|
||||
queryingGroupPlanToChangeToPrice
|
||||
}
|
||||
/>
|
||||
<OLModalBody>
|
||||
<div className="container-fluid plans group-subscription-modal">
|
||||
{groupPlanToChangeToPriceError && <GenericErrorAlert />}
|
||||
<div className="row">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="circle circle-lg mb-4 mx-auto">
|
||||
<GroupPrice
|
||||
groupPlanToChangeToPrice={groupPlanToChangeToPrice}
|
||||
queryingGroupPlanToChangeToPrice={
|
||||
queryingGroupPlanToChangeToPrice
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<p>{t('each_user_will_have_access_to')}:</p>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-3">
|
||||
<strong>
|
||||
<GroupPlanCollaboratorCount
|
||||
planCode={groupPlanToChangeToCode}
|
||||
/>
|
||||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
<strong>{t('all_premium_features')}</strong>
|
||||
</li>
|
||||
<li>{t('sync_dropbox_github')}</li>
|
||||
<li>{t('full_doc_history')}</li>
|
||||
<li>{t('track_changes')}</li>
|
||||
<li>
|
||||
<span aria-hidden>+ {t('more').toLowerCase()}</span>
|
||||
<span className="sr-only">{t('plus_more')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>{t('each_user_will_have_access_to')}:</p>
|
||||
<ul className="list-unstyled">
|
||||
<li className="list-item-with-margin-bottom">
|
||||
<strong>
|
||||
<GroupPlanCollaboratorCount
|
||||
planCode={groupPlanToChangeToCode}
|
||||
/>
|
||||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
<strong>{t('all_premium_features')}</strong>
|
||||
</li>
|
||||
<li>{t('sync_dropbox_github')}</li>
|
||||
<li>{t('full_doc_history')}</li>
|
||||
<li>{t('track_changes')}</li>
|
||||
<li>
|
||||
<span aria-hidden>+ {t('more').toLowerCase()}</span>
|
||||
<span className="sr-only">{t('plus_more')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<form className="form">
|
||||
<fieldset className="form-group">
|
||||
<legend className="legend-as-label">{t('plan')}</legend>
|
||||
{groupPlans.plans.map(option => (
|
||||
<label
|
||||
htmlFor={`plan-option-${option.code}`}
|
||||
key={option.code}
|
||||
className="group-plan-option"
|
||||
>
|
||||
<input
|
||||
<div className="col-md-6">
|
||||
<form className="form">
|
||||
<fieldset className="form-group">
|
||||
<legend className="legend-as-label">{t('plan')}</legend>
|
||||
{groupPlans.plans.map(option => (
|
||||
<OLFormCheckbox
|
||||
key={option.code}
|
||||
type="radio"
|
||||
name="plan-code"
|
||||
value={option.code}
|
||||
id={`plan-option-${option.code}`}
|
||||
onChange={e =>
|
||||
setGroupPlanToChangeToCode(e.target.value)
|
||||
}
|
||||
onChange={() => setGroupPlanToChangeToCode(option.code)}
|
||||
checked={option.code === groupPlanToChangeToCode}
|
||||
label={option.display}
|
||||
/>
|
||||
<span>{option.display}</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="size">{t('number_of_users')}</label>
|
||||
<select
|
||||
name="size"
|
||||
id="size"
|
||||
className="form-control"
|
||||
value={groupPlanToChangeToSize}
|
||||
onChange={e => setGroupPlanToChangeToSize(e.target.value)}
|
||||
>
|
||||
{groupPlans.sizes.map(size => (
|
||||
<option key={`size-option-${size}`}>{size}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div className="form-group">
|
||||
<strong>
|
||||
{t('percent_discount_for_groups', {
|
||||
percent: educationalPercentDiscount,
|
||||
size: groupSizeForEducationalDiscount,
|
||||
})}
|
||||
</strong>
|
||||
</div>
|
||||
<OLFormGroup controlId="size">
|
||||
<OLFormLabel>{t('number_of_users')}</OLFormLabel>
|
||||
<OLFormSelect
|
||||
name="size"
|
||||
value={groupPlanToChangeToSize}
|
||||
onChange={e => setGroupPlanToChangeToSize(e.target.value)}
|
||||
>
|
||||
{groupPlans.sizes.map(size => (
|
||||
<option key={`size-option-${size}`}>{size}</option>
|
||||
))}
|
||||
</OLFormSelect>
|
||||
</OLFormGroup>
|
||||
|
||||
<div className="form-group group-plan-option">
|
||||
<label htmlFor="usage">
|
||||
<input
|
||||
id="usage"
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
checked={groupPlanToChangeToUsage === 'educational'}
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setGroupPlanToChangeToUsage('educational')
|
||||
} else {
|
||||
setGroupPlanToChangeToUsage('enterprise')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{t('license_for_educational_purposes')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12 text-center">
|
||||
<div className="educational-discount-badge">
|
||||
{groupPlanToChangeToUsage === 'educational' && (
|
||||
<EducationDiscountAppliedOrNot
|
||||
groupSize={groupPlanToChangeToSize}
|
||||
<OLFormGroup>
|
||||
<strong>
|
||||
{t('percent_discount_for_groups', {
|
||||
percent: educationalPercentDiscount,
|
||||
size: groupSizeForEducationalDiscount,
|
||||
})}
|
||||
</strong>
|
||||
</OLFormGroup>
|
||||
|
||||
<OLFormCheckbox
|
||||
id="usage"
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
checked={groupPlanToChangeToUsage === 'educational'}
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setGroupPlanToChangeToUsage('educational')
|
||||
} else {
|
||||
setGroupPlanToChangeToUsage('enterprise')
|
||||
}
|
||||
}}
|
||||
label={t('license_for_educational_purposes')}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<div className="text-center">
|
||||
{groupPlanToChangeToPrice?.includesTax && (
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="total_with_subtotal_and_tax"
|
||||
values={{
|
||||
total: groupPlanToChangeToPrice.totalForDisplay,
|
||||
subtotal: groupPlanToChangeToPrice.subtotal,
|
||||
tax: groupPlanToChangeToPrice.tax,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
/* eslint-disable-next-line react/jsx-key */
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<strong>{t('new_subscription_will_be_billed_immediately')}</strong>
|
||||
</p>
|
||||
<hr className="thin" />
|
||||
{error && (
|
||||
<div className="alert alert-danger" aria-live="polite">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
<div className="educational-discount-badge pt-4 text-center">
|
||||
{groupPlanToChangeToUsage === 'educational' && (
|
||||
<EducationDiscountAppliedOrNot
|
||||
groupSize={groupPlanToChangeToSize}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-primary btn-lg"
|
||||
disabled={
|
||||
queryingGroupPlanToChangeToPrice ||
|
||||
!groupPlanToChangeToPrice ||
|
||||
inflight
|
||||
}
|
||||
onClick={upgrade}
|
||||
>
|
||||
{!inflight ? t('upgrade_now') : t('processing_uppercase') + '…'}
|
||||
</button>
|
||||
<hr className="thin" />
|
||||
<button className="btn-inline-link" onClick={handleGetInTouchButton}>
|
||||
{t('need_more_than_x_licenses', {
|
||||
x: 50,
|
||||
})}{' '}
|
||||
{t('please_get_in_touch')}
|
||||
</button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
</div>
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<div className="text-center">
|
||||
{groupPlanToChangeToPrice?.includesTax && (
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="total_with_subtotal_and_tax"
|
||||
values={{
|
||||
total: groupPlanToChangeToPrice.totalForDisplay,
|
||||
subtotal: groupPlanToChangeToPrice.subtotal,
|
||||
tax: groupPlanToChangeToPrice.tax,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
/* eslint-disable-next-line react/jsx-key */
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<strong>
|
||||
{t('new_subscription_will_be_billed_immediately')}
|
||||
</strong>
|
||||
</p>
|
||||
<hr className="thin my-3" />
|
||||
{error && (
|
||||
<OLNotification
|
||||
type="error"
|
||||
aria-live="polite"
|
||||
content={
|
||||
<>
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<OLButton
|
||||
variant="primary"
|
||||
size="large"
|
||||
disabled={
|
||||
queryingGroupPlanToChangeToPrice ||
|
||||
!groupPlanToChangeToPrice ||
|
||||
inflight
|
||||
}
|
||||
onClick={upgrade}
|
||||
isLoading={inflight}
|
||||
bs3Props={{
|
||||
loading: inflight
|
||||
? t('processing_uppercase') + '…'
|
||||
: t('upgrade_now'),
|
||||
}}
|
||||
>
|
||||
{t('upgrade_now')}
|
||||
</OLButton>
|
||||
<hr className="thin my-3" />
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link"
|
||||
onClick={showContactModal}
|
||||
>
|
||||
{t('need_more_than_x_licenses', {
|
||||
x: 50,
|
||||
})}{' '}
|
||||
{t('please_get_in_touch')}
|
||||
</OLButton>
|
||||
</div>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { SubscriptionDashModalIds } from '../../../../../../../../../../types/subscription/dashboard/modal-ids'
|
||||
import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../../../shared/components/accessible-modal'
|
||||
import getMeta from '../../../../../../../../utils/meta'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
export function ConfirmChangePlanModal() {
|
||||
const modalId: SubscriptionDashModalIds = 'change-to-plan'
|
||||
|
@ -46,23 +52,29 @@ export function ConfirmChangePlanModal() {
|
|||
planCodesChangingAtTermEnd.indexOf(planCodeToChangeTo) > -1
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('change_plan')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('change_plan')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
{error && (
|
||||
<div className="alert alert-danger" aria-live="polite">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
aria-live="polite"
|
||||
content={
|
||||
<>
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<p>
|
||||
<Trans
|
||||
|
@ -84,24 +96,30 @@ export function ConfirmChangePlanModal() {
|
|||
<p>{t('want_change_to_apply_before_plan_end')}</p>
|
||||
</>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</OLModalBody>
|
||||
|
||||
<Modal.Footer>
|
||||
<button
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
disabled={inflight}
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
disabled={inflight}
|
||||
className="btn btn-primary"
|
||||
isLoading={inflight}
|
||||
onClick={handleConfirmChange}
|
||||
bs3Props={{
|
||||
loading: inflight
|
||||
? t('processing_uppercase') + '…'
|
||||
: t('change_plan'),
|
||||
}}
|
||||
>
|
||||
{!inflight ? t('change_plan') : t('processing_uppercase') + '…'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('change_plan')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { SubscriptionDashModalIds } from '../../../../../../../../../../types/subscription/dashboard/modal-ids'
|
||||
import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../../../shared/components/accessible-modal'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import { cancelPendingSubscriptionChangeUrl } from '../../../../../../data/subscription-url'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
export function KeepCurrentPlanModal() {
|
||||
const modalId: SubscriptionDashModalIds = 'keep-current-plan'
|
||||
|
@ -33,23 +39,29 @@ export function KeepCurrentPlanModal() {
|
|||
if (modalIdShown !== modalId || !personalSubscription) return null
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
<OLModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('change_plan')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('change_plan')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<Modal.Body>
|
||||
<OLModalBody>
|
||||
{error && (
|
||||
<div className="alert alert-danger" aria-live="polite">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
aria-live="polite"
|
||||
content={
|
||||
<>
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<p>
|
||||
<Trans
|
||||
|
@ -65,26 +77,30 @@ export function KeepCurrentPlanModal() {
|
|||
]}
|
||||
/>
|
||||
</p>
|
||||
</Modal.Body>
|
||||
</OLModalBody>
|
||||
|
||||
<Modal.Footer>
|
||||
<button
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
disabled={inflight}
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
disabled={inflight}
|
||||
className="btn btn-primary"
|
||||
isLoading={inflight}
|
||||
onClick={confirmCancelPendingPlanChange}
|
||||
bs3Props={{
|
||||
loading: inflight
|
||||
? t('processing_uppercase') + '…'
|
||||
: t('revert_pending_plan_change'),
|
||||
}}
|
||||
>
|
||||
{!inflight
|
||||
? t('revert_pending_plan_change')
|
||||
: t('processing_uppercase') + '…'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
{t('revert_pending_plan_change')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ import ManagedInstitutions from './managed-institutions'
|
|||
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
import PremiumFeaturesLink from './premium-features-link'
|
||||
import OLCard from '@/features/ui/components/ol/ol-card'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
function SubscriptionDashboard() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -23,30 +27,35 @@ function SubscriptionDashboard() {
|
|||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-8 col-md-offset-2">
|
||||
<OLRow>
|
||||
<OLCol lg={{ span: 8, offset: 2 }}>
|
||||
{fromPlansPage && (
|
||||
<div className="alert alert-warning" aria-live="polite">
|
||||
{t('you_already_have_a_subscription')}
|
||||
</div>
|
||||
<OLNotification
|
||||
className="mb-4"
|
||||
aria-live="polite"
|
||||
content={t('you_already_have_a_subscription')}
|
||||
type="warning"
|
||||
/>
|
||||
)}
|
||||
<div className="card">
|
||||
<OLCard>
|
||||
<div className="page-header">
|
||||
<h1>{t('your_subscription')}</h1>
|
||||
</div>
|
||||
|
||||
<PersonalSubscription />
|
||||
<ManagedGroupSubscriptions />
|
||||
<ManagedInstitutions />
|
||||
<ManagedPublishers />
|
||||
<GroupSubscriptionMemberships />
|
||||
<InstitutionMemberships />
|
||||
{hasValidActiveSubscription && <PremiumFeaturesLink />}
|
||||
{!hasDisplayedSubscription &&
|
||||
(hasSubscription ? <ContactSupport /> : <FreePlan />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<PersonalSubscription />
|
||||
<ManagedGroupSubscriptions />
|
||||
<ManagedInstitutions />
|
||||
<ManagedPublishers />
|
||||
<GroupSubscriptionMemberships />
|
||||
<InstitutionMemberships />
|
||||
{hasValidActiveSubscription && <PremiumFeaturesLink />}
|
||||
{!hasDisplayedSubscription &&
|
||||
(hasSubscription ? <ContactSupport /> : <FreePlan />)}
|
||||
</div>
|
||||
</OLCard>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ export default function OLModal({ children, ...props }: OLModalProps) {
|
|||
backdrop: bs5Props.backdrop,
|
||||
animation: bs5Props.animation,
|
||||
id: bs5Props.id,
|
||||
className: bs5Props.className,
|
||||
backdropClassName: bs5Props.backdropClassName,
|
||||
...bs3Props,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import importOverleafModules from '../../../macros/import-overleaf-module.macro'
|
||||
import { JSXElementConstructor, useCallback, useState } from 'react'
|
||||
import {
|
||||
JSXElementConstructor,
|
||||
useCallback,
|
||||
useState,
|
||||
type UIEvent,
|
||||
} from 'react'
|
||||
|
||||
const [contactUsModalModules] = importOverleafModules('contactUsModal')
|
||||
const ContactUsModal: JSXElementConstructor<{
|
||||
|
@ -16,7 +21,7 @@ export const useContactUsModal = (options = { autofillProjectUrl: true }) => {
|
|||
setShow(false)
|
||||
}, [])
|
||||
|
||||
const showModal = useCallback((event?: Event) => {
|
||||
const showModal = useCallback((event?: Event | UIEvent) => {
|
||||
event?.preventDefault()
|
||||
setShow(true)
|
||||
}, [])
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
// Hack to make the contact modal appear above other modals
|
||||
.contact-modal {
|
||||
z-index: 1065;
|
||||
}
|
||||
|
||||
.contact-backdrop {
|
||||
z-index: 1060;
|
||||
}
|
||||
|
||||
.contact-us-modal {
|
||||
textarea {
|
||||
height: 120px;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
#change-to-group .modal-header h4 {
|
||||
line-height: 1.33rem;
|
||||
|
||||
.h5 {
|
||||
font-family: @font-family-sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.group-subscription-modal {
|
||||
.modal-header {
|
||||
text-align: center;
|
||||
|
|
|
@ -150,3 +150,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-gray {
|
||||
background-color: var(--neutral-10);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
// Hack to make the contact modal appear above other modals
|
||||
.contact-modal {
|
||||
z-index: 1065;
|
||||
}
|
||||
|
||||
.contact-backdrop {
|
||||
z-index: 1060;
|
||||
}
|
||||
|
||||
.contact-us-modal-textarea {
|
||||
height: 120px;
|
||||
}
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
@import 'editor/outline';
|
||||
@import 'editor/file-tree';
|
||||
@import 'editor/figure-modal';
|
||||
@import 'subscription';
|
||||
@import 'website-redesign';
|
||||
@import 'group-settings';
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* MAIN CONTENT
|
||||
*/
|
||||
|
||||
#subscription-dashboard-root {
|
||||
.hover-highlight {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--neutral-10);
|
||||
}
|
||||
}
|
||||
|
||||
li.row-link {
|
||||
display: flex;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-02) 0;
|
||||
text-decoration: none;
|
||||
color: var(--neutral-90);
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--neutral-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MODALS
|
||||
*/
|
||||
|
||||
.group-subscription-modal {
|
||||
.circle {
|
||||
font-size: var(--font-size-06);
|
||||
border-radius: 50%;
|
||||
background-color: var(--green-70);
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-02);
|
||||
font-weight: bold;
|
||||
|
||||
.small {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.circle-subtext {
|
||||
font-size: var(--font-size-03);
|
||||
}
|
||||
}
|
||||
|
||||
.legend-as-label {
|
||||
font-size: var(--font-size-02);
|
||||
font-weight: 600;
|
||||
color: var(--content-secondary);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.educational-discount-badge {
|
||||
height: 50px;
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
padding-left: var(--spacing-02);
|
||||
padding-right: var(--spacing-02);
|
||||
}
|
||||
|
||||
.applied {
|
||||
background-color: rgba($green-70, 0.1);
|
||||
color: var(--green-70);
|
||||
}
|
||||
|
||||
.ineligible {
|
||||
background-color: var(--neutral-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#change-plan {
|
||||
table {
|
||||
@include media-breakpoint-up(lg) {
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
width: 1%; // will expand to fit the content
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
display: block;
|
||||
|
||||
thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
tbody,
|
||||
td,
|
||||
tr {
|
||||
display: inline-block;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
padding-top: var(--spacing-07);
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
padding-top: var(--spacing-03);
|
||||
padding-bottom: var(--spacing-07);
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,8 @@ label {
|
|||
font-size: @font-size-base;
|
||||
color: @text-color;
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
line-height: @line-height-01;
|
||||
}
|
||||
|
||||
// Normalize form controls
|
||||
|
|
|
@ -48,7 +48,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-item-with-margin-bottom {
|
||||
margin-bottom: @line-height-computed;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export function renderWithSubscriptionDashContext(
|
|||
options?.metaTags?.forEach(tag =>
|
||||
window.metaAttributesCache.set(tag.name, tag.value)
|
||||
)
|
||||
window.metaAttributesCache.set('ol-user', {})
|
||||
|
||||
if (!options?.recurlyNotLoaded) {
|
||||
// @ts-ignore
|
||||
|
|
Loading…
Reference in a new issue