From 420c3881c24b76406a04cdf8616549bb1f79830d Mon Sep 17 00:00:00 2001 From: MoxAmber Date: Thu, 13 Feb 2025 12:50:02 +0000 Subject: [PATCH] Merge pull request #23503 from overleaf/as-compile-timeout-paywall-primary [web] Create UpgradePrompt paywall component GitOrigin-RevId: 591eaae0c179c7419f8ec93284cebb3ecfc52154 --- .../web/frontend/extracted-translations.json | 14 ++ .../js/shared/components/upgrade-prompt.tsx | 165 ++++++++++++++++++ .../stories/upgrade-prompt.stories.tsx | 24 +++ .../bootstrap-5/components/all.scss | 1 + .../bootstrap-5/components/badge.scss | 8 + .../components/upgrade-prompt.scss | 77 ++++++++ services/web/locales/en.json | 9 + 7 files changed, 298 insertions(+) create mode 100644 services/web/frontend/js/shared/components/upgrade-prompt.tsx create mode 100644 services/web/frontend/stories/upgrade-prompt.stories.tsx create mode 100644 services/web/frontend/stylesheets/bootstrap-5/components/upgrade-prompt.scss diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 22bb1cea0b..7d8fb8ae4f 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1,4 +1,6 @@ { + "10_editors_per_project": "", + "12x_more_compile_time": "", "1_2_width": "", "1_4_width": "", "3_4_width": "", @@ -162,6 +164,7 @@ "back_to_editor": "", "back_to_subscription": "", "back_to_your_projects": "", + "basic_compile_time": "", "become_an_advisor": "", "before_you_use_error_assistant": "", "beta_program_already_participating": "", @@ -271,6 +274,7 @@ "compact": "", "company_name": "", "compare": "", + "compare_all_plans": "", "comparing_from_x_to_y": "", "compile_error_entry_description": "", "compile_error_handling": "", @@ -308,6 +312,7 @@ "continue_github_merge": "", "continue_to": "", "continue_using_free_features": "", + "continue_with_free_plan": "", "copied": "", "copy": "", "copy_code": "", @@ -558,6 +563,7 @@ "for_universities": "", "format": "", "found_matching_deleted_users": "", + "free": "", "free_7_day_trial_billed_annually": "", "free_7_day_trial_billed_monthly": "", "free_plan_label": "", @@ -865,6 +871,7 @@ "lets_get_those_premium_features": "", "library": "", "license_for_educational_purposes_confirmation": "", + "limited_document_history": "", "limited_offer": "", "limited_to_n_editors": "", "limited_to_n_editors_per_project": "", @@ -1062,6 +1069,7 @@ "ok_join_project": "", "on": "", "on_free_plan_upgrade_to_access_features": "", + "one_editor_per_project": "", "one_step_away_from_professional_features": "", "only_group_admin_or_managers_can_delete_your_account_1": "", "only_group_admin_or_managers_can_delete_your_account_2": "", @@ -1142,6 +1150,7 @@ "pending_additional_licenses": "", "pending_addon_cancellation": "", "pending_invite": "", + "per_month": "", "per_user": "", "percent_is_the_percentage_of_the_line_width": "", "permanently_disables_the_preview": "", @@ -1266,6 +1275,7 @@ "reauthorize_github_account": "", "recaptcha_conditions": "", "recent_commits_in_github": "", + "recommended": "", "recompile": "", "recompile_from_scratch": "", "recompile_pdf": "", @@ -1555,6 +1565,7 @@ "sso_user_explanation_enabled_with_group_name": "", "sso_user_explanation_ready_with_admin_email": "", "sso_user_explanation_ready_with_group_name": "", + "standard": "", "start_a_free_trial": "", "start_by_adding_your_email": "", "start_by_fixing_the_first_error_in_your_doc": "", @@ -1570,6 +1581,7 @@ "stop_on_validation_error": "", "store_your_work": "", "stretch_width_to_text": "", + "student": "", "student_disclaimer": "", "subject": "", "subject_area": "", @@ -1813,6 +1825,7 @@ "unknown_group": "", "unlimited_collaborators_per_project": "", "unlimited_collabs": "", + "unlimited_document_history": "", "unlimited_projects": "", "unlink": "", "unlink_all_users": "", @@ -2019,6 +2032,7 @@ "your_affiliation_is_confirmed": "", "your_browser_does_not_support_this_feature": "", "your_compile_timed_out": "", + "your_current_plan": "", "your_current_plan_gives_you": "", "your_current_plan_supports_up_to_x_users": "", "your_current_project_will_revert_to_the_version_from_time": "", diff --git a/services/web/frontend/js/shared/components/upgrade-prompt.tsx b/services/web/frontend/js/shared/components/upgrade-prompt.tsx new file mode 100644 index 0000000000..9797f6a02a --- /dev/null +++ b/services/web/frontend/js/shared/components/upgrade-prompt.tsx @@ -0,0 +1,165 @@ +import OLBadge from '@/features/ui/components/ol/ol-badge' +import OLButton from '@/features/ui/components/ol/ol-button' +import OLCloseButton from '@/features/ui/components/ol/ol-close-button' +import OLCol from '@/features/ui/components/ol/ol-col' +import OLRow from '@/features/ui/components/ol/ol-row' +import MaterialIcon from '@/shared/components/material-icon' +import { PropsWithChildren } from 'react' +import { Container } from 'react-bootstrap-5' +import { Trans, useTranslation } from 'react-i18next' + +type IconListItemProps = PropsWithChildren<{ + icon: string +}> + +function IconListItem({ icon, children }: IconListItemProps) { + return ( +
  • +
    + +
    +
    {children}
    +
  • + ) +} + +type PlansLinkProps = { + itmCampaign: string +} +function PlansLink({ + children, + itmCampaign, +}: PropsWithChildren) { + return ( + + {children} + + + ) +} + +type UpgradePromptProps = { + title: string + summary: string + onClose: () => void + planPricing: { student: string; standard: string } + itmCampaign: string + isStudent?: boolean +} + +export function UpgradePrompt({ + title, + summary, + onClose, + planPricing, + itmCampaign, + isStudent = false, +}: UpgradePromptProps) { + const { t } = useTranslation() + const planPrice = isStudent ? planPricing.student : planPricing.standard + const planCode = isStudent + ? 'student_free_trial_7_days' + : 'collaborator_free_trial_7_days' + + return ( + + + onClose()} /> + + +

    {title}

    +

    {summary}

    +
    + + +
    + + +

    {isStudent ? t('student') : t('standard')}

    +
    + + + {t('recommended')} + + +
    + +

    + {planPrice}{' '} + {t('per_month')} +

    +
    + +
      + + {t('12x_more_compile_time')} + + + {t('10_editors_per_project')} + + + {t('unlimited_document_history')} + +
    +
    + + + {t('try_for_free')} + + +
    +
    + +
    + +

    {t('free')}

    +
    + +

    + {/* Invisible span here to hold the correct height to match a card with a price */} + + {t('your_current_plan')} +

    +
    + +
      + + {t('basic_compile_time')} + + + {t('one_editor_per_project')} + + + {t('limited_document_history')} + +
    +
    + + onClose()}> + {t('continue_with_free_plan')} + + +
    +
    +
    + +

    + {/* eslint-disable react/jsx-key */} + ]} + /> + {/* eslint-disable react/jsx-key */} +

    +
    +
    + ) +} diff --git a/services/web/frontend/stories/upgrade-prompt.stories.tsx b/services/web/frontend/stories/upgrade-prompt.stories.tsx new file mode 100644 index 0000000000..1d7d6cfe60 --- /dev/null +++ b/services/web/frontend/stories/upgrade-prompt.stories.tsx @@ -0,0 +1,24 @@ +import { UpgradePrompt } from '@/shared/components/upgrade-prompt' +import { StoryObj } from '@storybook/react/*' +import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher' + +type Story = StoryObj + +export const Generic: Story = { + args: { + title: 'Unlock more compile time', + summary: 'Your project took too long to compile and timed out.', + onClose: () => {}, + planPricing: { student: '$9', standard: '$21' }, + itmCampaign: 'storybook', + }, +} + +export default { + title: 'Shared / Components / Upgrade Prompt', + component: UpgradePrompt, + ...bsVersionDecorator, + args: { + bootstrapVersion: 5, + }, +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss index 00e838bc31..ae02138fb7 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss @@ -41,3 +41,4 @@ @import 'panel-heading'; @import 'menu-bar'; @import 'invite'; +@import 'upgrade-prompt'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss b/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss index d3b5b812d9..c6f09687f0 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss @@ -108,6 +108,14 @@ $max-width: 160px; font-weight: var(--badge-font-weight); } +.badge-premium-gradient { + background: var(--premium-gradient); + color: var(--content-primary-dark); + vertical-align: middle; + margin-left: 5px; + font-weight: var(--badge-font-weight); +} + .badge-link { display: inline-block; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/upgrade-prompt.scss b/services/web/frontend/stylesheets/bootstrap-5/components/upgrade-prompt.scss new file mode 100644 index 0000000000..ca261e7014 --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/components/upgrade-prompt.scss @@ -0,0 +1,77 @@ +.upgrade-prompt { + border-radius: var(--border-radius-base); + box-shadow: 0 4px 6px 0 #1e25301f; + padding: var(--spacing-06); + + .row { + margin-left: 0; + margin-right: 0; + } + + .upgrade-prompt-title { + font-weight: 600; + } + + .upgrade-prompt-summary { + margin-bottom: var(--spacing-10); + } + + .upgrade-prompt-card { + display: flex; + flex-direction: column; + border-radius: var(--border-radius-medium); + padding: var(--spacing-09); + height: 100%; + font-size: var(--font-size-02); + + h3 { + margin: 0; + font-weight: 600; + } + } + + .upgrade-prompt-card-premium { + border: 2px solid transparent; + background: + linear-gradient(white, white) padding-box, + var(--premium-gradient) border-box; + } + + .upgrade-prompt-card-free { + border: 2px solid var(--border-divider); + } + + .upgrade-prompt-price { + margin-bottom: var(--spacing-05); + } + + .upgrade-prompt-price-number { + @include heading-xl; + + font-weight: 600; + } + + .upgrade-prompt-list { + margin-bottom: var(--spacing-08); + + & .material-symbols { + vertical-align: bottom; + } + + & li:not(:last-of-type) { + margin-bottom: var(--spacing-05); + } + } + + .upgrade-prompt-all-plans { + @include body-xs; + + margin-top: var(--spacing-07); + margin-bottom: 0; + + & .material-symbols { + vertical-align: bottom; + font-size: var(--font-size-02); + } + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 63c23b1068..f3535e560b 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1,5 +1,7 @@ { + "10_editors_per_project": "10 editors per project", "12x_basic": "12x Basic", + "12x_more_compile_time": "12x more compile time on our fastest servers", "1_2_width": "½ width", "1_4_width": "¼ width", "3_4_width": "¾ width", @@ -206,6 +208,7 @@ "back_to_subscription": "Back to Subscription", "back_to_your_projects": "Back to your projects", "basic": "Basic", + "basic_compile_time": "Basic compile time", "basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers", "become_an_advisor": "Become an __appName__ advisor", "before_you_use_error_assistant": "Before you use Error Assist", @@ -352,6 +355,7 @@ "compact": "Compact", "company_name": "Company Name", "compare": "Compare", + "compare_all_plans": "Compare all plans on our <0>pricing page", "compare_features": "Compare features", "comparing_from_x_to_y": "Comparing from <0>__startTime__ to <0>__endTime__", "compile_error_entry_description": "An error which prevented this project from compiling", @@ -1149,6 +1153,7 @@ "library": "Library", "license": "License", "license_for_educational_purposes_confirmation": "<0>__percent__% educational discount<1/>I confirm this subscription is for educational purposes (applies to students or faculty using __appName__ for teaching)", + "limited_document_history": "Limited document history", "limited_to_n_editors": "Limited to __count__ editor", "limited_to_n_editors_per_project": "Limited to __count__ editor per project", "limited_to_n_editors_per_project_plural": "Limited to __count__ editors per project", @@ -1417,6 +1422,7 @@ "on": "On", "on_free_plan_upgrade_to_access_features": "You are on the __appName__ Free plan. Upgrade to access these <0>Premium features", "one_collaborator_per_project": "1 collaborator per project", + "one_editor_per_project": "1 editor per project", "one_free_collab": "One free collaborator", "one_per_project": "1 per project", "one_step_away_from_professional_features": "You are one step away from accessing <0>Overleaf Professional features!", @@ -1686,6 +1692,7 @@ "recaptcha_conditions": "The site is protected by reCAPTCHA and the Google <1>Privacy Policy and <2>Terms of Service apply.", "recent": "Recent", "recent_commits_in_github": "Recent commits in GitHub", + "recommended": "Recommended", "recompile": "Recompile", "recompile_from_scratch": "Recompile from scratch", "recompile_pdf": "Recompile the PDF", @@ -2346,6 +2353,7 @@ "unlimited": "Unlimited", "unlimited_collaborators_per_project": "Unlimited collaborators per project", "unlimited_collabs": "Unlimited collaborators", + "unlimited_document_history": "Unlimited document history", "unlimited_projects": "Unlimited projects", "unlink": "Unlink", "unlink_all_users": "Unlink all users", @@ -2580,6 +2588,7 @@ "your_affiliation_is_confirmed": "Your <0>__institutionName__ affiliation is confirmed.", "your_browser_does_not_support_this_feature": "Sorry, your browser doesn’t support this feature. Please update your browser to its latest version.", "your_compile_timed_out": "Your compile timed out", + "your_current_plan": "Your current plan", "your_current_plan_gives_you": "By pausing your subscription, you’ll be able to access your premium features faster when you need them again.", "your_current_plan_supports_up_to_x_users": "Your current plan supports up to __users__ users.", "your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",