diff --git a/services/web/app/views/subscriptions/dashboard-react.pug b/services/web/app/views/subscriptions/dashboard-react.pug index 19444222c3..068f52de0a 100644 --- a/services/web/app/views/subscriptions/dashboard-react.pug +++ b/services/web/app/views/subscriptions/dashboard-react.pug @@ -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) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/action-button-text.tsx b/services/web/frontend/js/features/subscription/components/dashboard/action-button-text.tsx deleted file mode 100644 index 805306f84e..0000000000 --- a/services/web/frontend/js/features/subscription/components/dashboard/action-button-text.tsx +++ /dev/null @@ -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') + '…'} -} diff --git a/services/web/frontend/js/features/subscription/components/dashboard/generic-error-alert.tsx b/services/web/frontend/js/features/subscription/components/dashboard/generic-error-alert.tsx index 57921c67c1..e6c6f24d42 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/generic-error-alert.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/generic-error-alert.tsx @@ -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 ( -
- {t('generic_something_went_wrong')}. {t('try_again')}.{' '} - {t('generic_if_problem_continues_contact_us')}. -
+ + {t('generic_something_went_wrong')}. {t('try_again')}.{' '} + {t('generic_if_problem_continues_contact_us')}. + + } + /> ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx b/services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx index c38cfa9bf2..4650acb1f1 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx @@ -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({ ) : ( - + )}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/institution-memberships.tsx b/services/web/frontend/js/features/subscription/components/dashboard/institution-memberships.tsx index 970a62e7f3..4a3c51abce 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/institution-memberships.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/institution-memberships.tsx @@ -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 ( -
-

- Sorry, something went wrong. Subscription information related to - institutional affiliations may not be displayed. Please try again - later. -

-
+ + Sorry, something went wrong. Subscription information related to + institutional affiliations may not be displayed. Please try again + later. +

+ } + /> ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/leave-group-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/leave-group-modal.tsx index 542181a2f7..6214f3b46f 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/leave-group-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/leave-group-modal.tsx @@ -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 ( - - - {t('leave_group')} - + + {t('leave_group')} + - +

{t('sure_you_want_to_leave_group')}

-
+ - - - - -
+ {t('processing_uppercase')} + + + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx b/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx index 35eae6c726..2645684ee9 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx @@ -100,27 +100,29 @@ export default function ManagedGroupSubscriptions() {

- - - {groupSettingsEnabledFor?.includes(subscription._id) && ( - - )} - +
    + + + {groupSettingsEnabledFor?.includes(subscription._id) && ( + + )} + +

) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx b/services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx index 97d63cdf09..a63b872919 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx @@ -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 } }} />

- - - +
    + + + +

Monthly metrics emails: {subscriptionChanging ? ( ) : ( - + )}

diff --git a/services/web/frontend/js/features/subscription/components/dashboard/managed-publisher.tsx b/services/web/frontend/js/features/subscription/components/dashboard/managed-publisher.tsx index 9e79d9ece1..b3c147acef 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/managed-publisher.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/managed-publisher.tsx @@ -22,18 +22,20 @@ export default function ManagedPublisher({ publisher }: ManagedPublisherProps) { tOptions={{ interpolation: { escapeValue: true } }} />

- - +
    + + +

) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx index e97449ffdd..6fc965a9be 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx @@ -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 ( <>
- + {isSuccess ? ( - {t('recurly_email_updated')} + ) : ( <>

@@ -40,17 +45,21 @@ function PersonalSubscriptionRecurlySyncEmail() { />

- + {t('update')} +
)} -
+

diff --git a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx index 237c56b3aa..2116a1c25f 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx @@ -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 ( - <> -
- {t('account_has_past_due_invoice_change_plan_warning')}{' '} - - {t('view_your_invoices')} - -
- + + {t('account_has_past_due_invoice_change_plan_warning')}{' '} + + {t('view_your_invoices')} + + + } + /> ) } @@ -75,9 +79,10 @@ function PersonalSubscription() { subscription={personalSubscription as RecurlySubscription} /> {recurlyLoadError && ( -
- {t('payment_provider_unreachable_error')} -
+ {t('payment_provider_unreachable_error')}} + /> )}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx index 9ec0a1f9cc..1c9dfcca0d 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx @@ -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 ( - + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/row-link.tsx b/services/web/frontend/js/features/subscription/components/dashboard/row-link.tsx index fd6d4d9bc2..b022e325b6 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/row-link.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/row-link.tsx @@ -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() ? : +} + +function BS3RowLink({ href, heading, subtext, icon }: RowLinkProps) { return (
@@ -23,3 +28,18 @@ export function RowLink({ href, heading, subtext, icon }: RowLinkProps) { ) } + +function BS5RowLink({ href, heading, subtext, icon }: RowLinkProps) { + return ( +
  • + + +
    + {heading} +
    {subtext}
    +
    + +
    +
  • + ) +} diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx index ce35dedd13..b24a71113e 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx @@ -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' && ( <> {' '} - + )}

    @@ -103,7 +105,7 @@ export function ActiveSubscription({ />

    -

    +

    {!recurlyLoadError && ( - + <> + {' '} + + )}

    diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx index 83e3fe5f54..8bfac39904 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx @@ -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 ( - + {text} + ) } @@ -85,8 +84,7 @@ function NotCancelOption({

    @@ -114,8 +112,7 @@ function NotCancelOption({

    @@ -130,12 +127,9 @@ function NotCancelOption({ return (

    - +

    ) } @@ -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 (

    @@ -214,12 +201,10 @@ export function CancelSubscription() { />

    ) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/downgrade-plan-button.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/downgrade-plan-button.tsx index 4fcac209d7..cf8f3a9f7a 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/downgrade-plan-button.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/downgrade-plan-button.tsx @@ -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) => Promise }) { @@ -37,17 +35,16 @@ export default function DowngradePlanButton({ } return ( - <> - - + + {buttonText} + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/extend-trial-button.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/extend-trial-button.tsx index 116436610d..107ec00a1c 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/extend-trial-button.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/extend-trial-button.tsx @@ -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) => Promise }) { const { t } = useTranslation() @@ -30,17 +28,16 @@ export default function ExtendTrialButton({ } return ( - <> - - + + {buttonText} + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx index 962b81a694..eae299cd88 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx @@ -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 ( - + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx index 8c977efc39..ed7e929916 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx @@ -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 ( -
    +

    {t('looking_multiple_licenses')}

    {t('reduce_costs_group_licenses')}


    - +
    ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx index 98d8c2aa36..019c3f81d0 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx @@ -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 ( - + ) } @@ -27,9 +28,9 @@ function KeepCurrentPlanButton({ plan }: { plan: Plan }) { } return ( - + ) } @@ -66,13 +67,13 @@ function PlansRow({ plan }: { plan: Plan }) { return ( - + {plan.name} - + {plan.displayPrice} / {plan.annual ? t('year') : t('month')} - + @@ -97,9 +98,9 @@ export function IndividualPlansTable({ plans }: { plans: Array }) { if (!plans || recurlyLoadError) return null return ( - +
    - +
    {t('name')} {t('price')} diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-plan-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-plan-modal.tsx index 7c365525f2..519da10665 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-plan-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-plan-modal.tsx @@ -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 ( <> -
    +
    @@ -39,14 +42,14 @@ export function ChangePlanModal() { if (modalIdShown !== modalId) return null return ( - - - {t('change_plan')} - + + + {t('change_plan')} + - + - - + + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx index be079e377f..7888deaadf 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx @@ -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({ })} -
    + } /> @@ -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 ( - - - -
    -

    {t('customize_your_group_subscription')}

    -

    - {t('save_x_percent_or_more', { - percent: '30', - })} -

    -
    -
    + <> + {contactModal} + + + + {t('customize_your_group_subscription')} +
    + + {t('save_x_percent_or_more', { + percent: '30', + })} + +
    +
    - -
    - {groupPlanToChangeToPriceError && } -
    -
    -
    - + +
    + {groupPlanToChangeToPriceError && } +
    +
    +
    + +
    +

    {t('each_user_will_have_access_to')}:

    +
      +
    • + + + +
    • +
    • + {t('all_premium_features')} +
    • +
    • {t('sync_dropbox_github')}
    • +
    • {t('full_doc_history')}
    • +
    • {t('track_changes')}
    • +
    • + + {t('more').toLowerCase()} + {t('plus_more')} +
    • +
    -

    {t('each_user_will_have_access_to')}:

    -
      -
    • - - - -
    • -
    • - {t('all_premium_features')} -
    • -
    • {t('sync_dropbox_github')}
    • -
    • {t('full_doc_history')}
    • -
    • {t('track_changes')}
    • -
    • - + {t('more').toLowerCase()} - {t('plus_more')} -
    • -
    -
    -
    -
    -
    - {t('plan')} - {groupPlans.plans.map(option => ( -
    -
    - - {t('percent_discount_for_groups', { - percent: educationalPercentDiscount, - size: groupSizeForEducationalDiscount, - })} - -
    + + {t('number_of_users')} + setGroupPlanToChangeToSize(e.target.value)} + > + {groupPlans.sizes.map(size => ( + + ))} + + -
    - -
    -
    -
    -
    -
    -
    -
    - {groupPlanToChangeToUsage === 'educational' && ( - + + {t('percent_discount_for_groups', { + percent: educationalPercentDiscount, + size: groupSizeForEducationalDiscount, + })} + + + + { + if (e.target.checked) { + setGroupPlanToChangeToUsage('educational') + } else { + setGroupPlanToChangeToUsage('enterprise') + } + }} + label={t('license_for_educational_purposes')} /> - )} +
    -
    -
    - - - -
    - {groupPlanToChangeToPrice?.includesTax && ( -

    - , - ]} - /> -

    - )} -

    - {t('new_subscription_will_be_billed_immediately')} -

    -
    - {error && ( -
    - {t('generic_something_went_wrong')}. {t('try_again')}.{' '} - {t('generic_if_problem_continues_contact_us')}. +
    + {groupPlanToChangeToUsage === 'educational' && ( + + )}
    - )} - -
    - -
    - - +
    + + + +
    + {groupPlanToChangeToPrice?.includesTax && ( +

    + , + ]} + /> +

    + )} +

    + + {t('new_subscription_will_be_billed_immediately')} + +

    +
    + {error && ( + + {t('generic_something_went_wrong')}. {t('try_again')}.{' '} + {t('generic_if_problem_continues_contact_us')}. + + } + /> + )} + + {t('upgrade_now')} + +
    + + {t('need_more_than_x_licenses', { + x: 50, + })}{' '} + {t('please_get_in_touch')} + +
    +
    + + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/confirm-change-plan-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/confirm-change-plan-modal.tsx index a534c5bcc4..75b5b56d62 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/confirm-change-plan-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/confirm-change-plan-modal.tsx @@ -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 ( - - - {t('change_plan')} - + + {t('change_plan')} + - + {error && ( -
    - {t('generic_something_went_wrong')}. {t('try_again')}.{' '} - {t('generic_if_problem_continues_contact_us')}. -
    + + {t('generic_something_went_wrong')}. {t('try_again')}.{' '} + {t('generic_if_problem_continues_contact_us')}. + + } + /> )}

    {t('want_change_to_apply_before_plan_end')}

    )} -
    + - - - - -
    + {t('change_plan')} + + + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/keep-current-plan-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/keep-current-plan-modal.tsx index 788539b90b..963e6bed67 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/keep-current-plan-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/keep-current-plan-modal.tsx @@ -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 ( - - - {t('change_plan')} - + + {t('change_plan')} + - + {error && ( -
    - {t('generic_something_went_wrong')}. {t('try_again')}.{' '} - {t('generic_if_problem_continues_contact_us')}. -
    + + {t('generic_something_went_wrong')}. {t('try_again')}.{' '} + {t('generic_if_problem_continues_contact_us')}. + + } + /> )}

    -
    + - - - - -
    + {t('revert_pending_plan_change')} + + + ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx b/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx index 74463929c9..afa5d0d7ea 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx @@ -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 (
    -
    -
    + + {fromPlansPage && ( -
    - {t('you_already_have_a_subscription')} -
    + )} -
    +

    {t('your_subscription')}

    - - - - - - - {hasValidActiveSubscription && } - {!hasDisplayedSubscription && - (hasSubscription ? : )} -
    -
    -
    +
    + + + + + + + {hasValidActiveSubscription && } + {!hasDisplayedSubscription && + (hasSubscription ? : )} +
    + + +
    ) } diff --git a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx index 579e20cbfe..fb25cc5628 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx @@ -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, } diff --git a/services/web/frontend/js/shared/hooks/use-contact-us-modal.tsx b/services/web/frontend/js/shared/hooks/use-contact-us-modal.tsx index 6225830c33..19810f627a 100644 --- a/services/web/frontend/js/shared/hooks/use-contact-us-modal.tsx +++ b/services/web/frontend/js/shared/hooks/use-contact-us-modal.tsx @@ -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) }, []) diff --git a/services/web/frontend/stylesheets/app/contact-us.less b/services/web/frontend/stylesheets/app/contact-us.less index 468cbd874b..48400dee10 100644 --- a/services/web/frontend/stylesheets/app/contact-us.less +++ b/services/web/frontend/stylesheets/app/contact-us.less @@ -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; diff --git a/services/web/frontend/stylesheets/app/group-subscription-modal.less b/services/web/frontend/stylesheets/app/group-subscription-modal.less index 78c2fccff3..20ddc90617 100644 --- a/services/web/frontend/stylesheets/app/group-subscription-modal.less +++ b/services/web/frontend/stylesheets/app/group-subscription-modal.less @@ -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; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/card.scss b/services/web/frontend/stylesheets/bootstrap-5/components/card.scss index 46fa38e207..af16932f7c 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/card.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/card.scss @@ -150,3 +150,7 @@ } } } + +.card-gray { + background-color: var(--neutral-10); +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/modals/contact-us-modal.scss b/services/web/frontend/stylesheets/bootstrap-5/modals/contact-us-modal.scss index 57711a799d..7ac5e62cbc 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/modals/contact-us-modal.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/modals/contact-us-modal.scss @@ -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; } diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index 8734714ced..ea5e4c10c2 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -11,5 +11,6 @@ @import 'editor/outline'; @import 'editor/file-tree'; @import 'editor/figure-modal'; +@import 'subscription'; @import 'website-redesign'; @import 'group-settings'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss new file mode 100644 index 0000000000..d7a46ea6d3 --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss @@ -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; + } + } + } +} diff --git a/services/web/frontend/stylesheets/components/forms.less b/services/web/frontend/stylesheets/components/forms.less index 830bf92f3c..d0b7087a1a 100755 --- a/services/web/frontend/stylesheets/components/forms.less +++ b/services/web/frontend/stylesheets/components/forms.less @@ -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 diff --git a/services/web/frontend/stylesheets/components/lists.less b/services/web/frontend/stylesheets/components/lists.less index 9ed8c726bc..755941664c 100644 --- a/services/web/frontend/stylesheets/components/lists.less +++ b/services/web/frontend/stylesheets/components/lists.less @@ -48,7 +48,3 @@ } } } - -.list-item-with-margin-bottom { - margin-bottom: @line-height-computed; -} diff --git a/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx b/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx index c3eb45dd2a..f3603d484a 100644 --- a/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx +++ b/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx @@ -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