* feat(web): add pro-value-framing split test copy
Serve quantified, benefit-led plan differentiators on the plans and
interstitial pages to the `enabled` variant of the `pro-value-framing`
split test, in place of the vague "increased/max AI allowance" and
"10/unlimited collaborators" framing.
The variant changes the bullet set and order as well as the strings, so
it swaps in its own short feature list rather than remapping keys: Pro
leads with "Everything in Standard, plus:", Free gains Error Assist and
"Compile simple projects". The plans page comparison table mirrors the
card wording in its AI allowance row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(web): assert plan copy numbers against plan features
Check the quantified claims in the plan copy against the SaaS plan
features: the collaborator limits, and the "24x" compile timeout claims
as the ratio of the paid to the free compileTimeout. The "2x more AI
usage" claim is checked against the allowance tooltips, since the
allowances themselves come from AI_*_QUOTA_GRANT and are not available
in the unit test environment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Remove overly contrasted color in card
* Remove redundant `makeUISettings2026`
* Simplify valueFramingOverrides
* Fix types
* Revert changes to the table, replace `2x_more_ai_usage` to `2x_the_ai_allowance`
* Remove oververbose PlansValueFraming.test.mjs
* Revert "Remove overly contrasted color in card"
This reverts commit 8e83a0c523c4ebfe8a40a04023f1801080d0e412.
* Apply suggestions from code review
GitOrigin-RevId: 25b0736af47a3a32f4e1dd108ab0eb14c1bc13e3
The paused branch of getFormattedRenewalDate formatted the reactivation
date in the viewer's local timezone with a hand-rolled format literal,
while the surrounding dates are UTC. Compute the reactivation date in
UTC and format it with formatPaymentDate.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
GitOrigin-RevId: 1f1679c1988c696c16c78bd11152d6a39a20c4e1
* [web] render address form above payment element on Stripe checkout
Changing country across Stripe regions (US/CA vs rest) remounts the Elements
tree and clears any entered card details. Rendering the address form (which
contains the country selector) first lets users settle the country before
entering payment details.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* [web] gate checkout address-first order behind checkout-address-order split test
Per PR review: the reorder needs testing, so it is now behind the
checkout-address-order split test (default keeps the current payment-first
order). With the enabled variant the heading becomes "Payment details", since
"Select a payment method" no longer sits above the payment element.
Part of #36429
* Rename `checkout-address-order` to `checkout-address-first`
GitOrigin-RevId: 9c861b1013a284b10757fc416bcdc5610790d582
* [web] Remove SubscriptionFormatters, format subscription dates in the frontend
Replace the server-formatted nextPaymentDueAt/nextPaymentDueDate/
trialEndsAtFormatted fields with the raw periodEnd date and format in
the React dashboard (same strings as before, English + UTC). Also
consolidate hand-rolled trial checks onto isInFreeTrial /
SubscriptionHelper.isInTrial.
* [web] Remove the now-unused dateformat dependency
* [web] Address review: payment-dates unit test, UTC dates in acceptance test
Add a dedicated unit test asserting the exact formatted strings from
formatPaymentDate/formatPaymentDateTime, and use explicit UTC timestamps
in the SubscriptionDashboard acceptance fixtures instead of
local-timezone Date constructors.
GitOrigin-RevId: 16ac4f1098b14ef9f1478d22cf39c1357ead7dea
* [web] Add cancel-loss-messaging split test
Populate the cancel confirmation step with plan-specific losses when the
cancel-loss-messaging split test is enabled: "Are you sure you want to
cancel?" heading, the subscription end date, and a card listing what the
user loses moving to the Free plan (compile timeout, AI, collaborators,
history), with the lost benefit struck through next to its Free-plan
replacement.
Shown only to non-trial Student/Standard/Pro individual subscribers with
no other retention offer (trial extension or downgrade upsell) — those
keep the existing confirmation.
Copy matches the mockups in the ticket. No new analytics events; the
existing subscription-page-cancel-button-click / subscription-cancelled
funnel is used.
Part of #35765
* [web] Consolidate ActiveSubscription stories, add cancel confirmation story
Replace the 13 fixture-per-story exports with a single Dashboard story
driven by controls (subscription fixture select, flexible licensing,
cancel-loss-messaging variant), using the CSF factories pattern. Add a
CancelConfirmation story that renders the cancel confirmation step
directly so the cancel-loss-messaging variant can be previewed without
clicking through, with a Professional fixture available to see the Pro
loss rows.
* [web] Use withSplitTests for the ActiveSubscription stories
Register cancel-loss-messaging in defaultSplitTestsArgTypes and drive the
stories' variant from the shared withSplitTests decorator (radio control,
SplitTestContext provided from args) instead of hand-rolled meta tags and
SplitTestProvider. Generalize withSplitTests' typing so it composes with
CSF factories (preview.meta) as well as legacy meta objects.
* [web] Fix Storybook indexing of the ActiveSubscription stories
Storybook's static CSF indexer requires preview.meta() to receive an
object literal, so spread withSplitTests' argTypes/decorators into the
meta literal instead of wrapping the whole config in the helper call.
* [web] Compose ActiveSubscription stories from orthogonal controls
- Wrap the stories in #subscription-dashboard-root so the dashboard's
scoped styles (e.g. the cancel-loss strikethrough) apply in Storybook
- Replace the single fixture dropdown with independent controls (group,
plan, period, currency, trial, status, pending plan change, add-ons,
coupons, pending license change), building the subscription from the
fixtures' base + patches
- Guard impossible mixes with argTypes `if` conditions: individual-only
controls hide when group is on, and pendingLicenseChange only shows
for groups
* [web] Add student plan to the ActiveSubscription story controls
The cancel-loss-messaging variant has student-specific rows (AI
Assistant / 10 collaborators), so the plan control needs a student
option. Groups have no student plans, so for group subscriptions the
student choice maps to the Standard group fixture.
* [web] Provide ol-plans in the ActiveSubscription stories
The cancel confirmation's downgrade branch (monthly Standard is
isEligibleForDowngradeUpsell) looks up paid-personal in ol-plans and
spins forever when the meta tag is missing. Provide ol-plans and
ol-groupPlans from the fixtures, matching the test render helper.
GitOrigin-RevId: e8491c8d39ed059e42ea3442e8d70c3ef304b009
* Fix negative AI quota countdown after reset period elapses
Backend (`FeatureUsageRateLimiter`, `TokenUsageRateLimiter`): once the
24h period had lapsed, `remainingUsage`/`remainingTokens` reset to the
full allowance but `resetDate` kept pointing at the old `periodStart +
24h` (i.e. in the past). Advance to `now + 24h` in that case.
Frontend (`NewPlanPaywall`, `ErrorGetPremium`): the "wait X hours and Y
minutes" copy computed `restoreTime - now` without clamping, so a past
`restoreTime` produced negative hours/minutes (users saw "wait -118
hours and -35 minutes"). Clamp to 0.
* Address PR review on quota reset fix
- Keep `resetDate` tied to the stored `periodStart + PERIOD` rather
than jumping to `now + PERIOD` when the period has lapsed. The
moving-target variant unnecessarily reinterpreted a stable field
(existing consumers such as `use-ai-access-gate` check whether the
reset date is in the past).
- Clamp `remainingUsage` / `remainingTokens` at 0 so a concurrent
over-usage never leaks a negative count.
- Align the boundary check with `_checkRateLimit` / `checkUsage`
(inclusive `<=`).
- Cover the period boundary and the clamp in unit tests for both
`FeatureUsageRateLimiter` and `WorkbenchRateLimiter`.
* Truncate test fixtures to whole seconds
`resetDate` is serialized with `Date.toString()`, which has second
resolution, so fixtures with a millisecond component failed the
boundary assertions.
* Revert code changes; keep tests that document the bug
The fixes masked the symptom (negative countdown) without addressing
the underlying issue, and it is not fully understood yet. Revert the
rate limiters and the writefull paywall components to their original
behaviour, and instead pin the current behaviour in tests:
- `it.fails` tests document that `remainingUsage`/`remainingTokens`
go negative when usage exceeds the allowance — these flip red when
the bug is fixed, at which point they should become plain `it`.
- The lapsed-period tests assert (with explanatory comments) that
`resetDate` keeps pointing at the end of the lapsed period, which is
the server-side ingredient of the negative-countdown support ticket.
* Reapply quota fixes
Restores the post-review state of the rate limiters (clamp remaining
counts at 0, inclusive period boundary, resetDate tied to periodStart)
and the writefull paywall components (clamp countdown at 0).
Tests intentionally untouched in this commit; the two `it.fails` tests
now need flipping to plain `it` in a follow-up.
* Flip bug-documenting tests to plain assertions
The clamp fixes are reapplied, so the `it.fails` tests now assert the
fixed behaviour directly. Trim the explanatory comments.
* Set resetDate to `Date.now() + PERIOD_IN_MILLISECONDS` instead of the expired/stale date.
Co-authored-by: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
* Update lapsed-period tests for the fresh resetDate
`resetDate` is now `Date.now() + PERIOD` once the period has lapsed, so
assert an approximately-fresh date instead of the stale period end.
---------
Co-authored-by: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
GitOrigin-RevId: ad39627ab9e5ceccb2a61443b2b7d39c68600594
[web] Add Stripe wallet payments (Apple Pay / Google Pay) in analytics and remove conversion to "card"
GitOrigin-RevId: f6d404d35eb108d7ddaf8ca1b6c2164cb5100c9c
* [web] Add snapshot test for all annualDividedByTwelve values
Captures the 85 stored values into a JSON fixture and asserts the
effective value (stored field, or PlansHelper.roundedTwelfth(annual)
once the field is derived at runtime) matches. Verification harness for
the annualDividedByTwelve refactor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* [web] Compute annualDividedByTwelve at runtime instead of hardcoding it
* [web] Update annualDividedByTwelve snapshot for the rounding rule
The runtime derivation reproduces 84 of the 85 historical values exactly.
The one difference is PEN assistant, which was historically stored
unrounded (350 / 12 = 29.1667) and now follows the round-up-to-0.05 rule
(29.20). The assistant add-on is unavailable, so this is not user-visible.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Remove `localizedAddOnsPricing` from test
* Add more sanity tests
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
GitOrigin-RevId: e60e8b3e90360817d4be3732243d242dc0cbb69e
* [web] Remove redundant monthlyTimesTwelve from pricing tables
monthlyTimesTwelve was always exactly monthly * 12 in every pricing row,
and the value was only ever used to render the "billed monthly for a year"
strikethrough price in the compile-timeout paywall.
- Drop the stored field from the pricing tables
- Send raw monthly/annual numbers in ol-standardPlanPricing instead of
pre-formatted strings
- Format the prices (and derive the strikethrough as monthly * 12) in the
paywall modal via the frontend currency helper
No user-facing change: the displayed prices are identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove `monthlyTimesTwelve` from script
* Remove comments
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: b637208540b3b96180b1a3cc4417b09944c5d158
* [web] Widen auth-aux container to fit page headers on one line
Bump .auth-aux-container max-width from 320px to 400px, matching
.login-register-container. Fixes the "Keep your account safe" header
wrapping to two lines on primaryEmailCheck, and keeps the auth pages
(reconfirm, passwordReset, setPassword) consistent with login/register.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add x-padding to `.auth-aux-container`
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: 3256447bd7f6a1b13fbe09d8d359f037c0e50ac2
* Remove `plans-2026-phase-1` from subscription module
* Remove code related to `plans-2026-phase-1`
* Remove `SplitTestUserGetter.getProjection`
* Update tests
* Remove last mentions of `plans-2026-phase-1`
* In test, wait for `_updateCustomerIoSubscriptionProperties` to be done
* Remove `purchaseAddon` test
* Remove AI-addon tests
* Fix `_quotaTierToAllowance` throwing on zero-grant tiers
`!quota` treated `0` as invalid, so `basic` (grant 0) threw. Check `typeof quota !== 'number'` alone. Unmasked once branch removed the split-test branch that skipped this path.
* Extract `waitForCustomerIoSync` helper in FeaturesUpdater tests
* https://github.com/overleaf/internal/pull/34771#discussion_r3552882875
GitOrigin-RevId: 0fe0e41b7a4105a3c4739214e2fffb77e91dbc42
* [web] Add compile-timeout-interstitial split test
Show targeted interstitial copy to users arriving from the compile
timeout paywall (paywall-type=compile-timeout). Mirrors the existing
ai-limit-upgrade-copy pattern.
When enabled:
- H1 becomes "Upgrade for 24x more compile time"
- Standard/Pro card descriptions become compile-time targeted
- "24x Basic compile timeout" feature is bolded and hoisted to the top
of the includes list (all plan types)
Part of #34982
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Don't exclude trial users from the compile-timeout copy
* Add missing dot to translation
* Add "AI assistant" to student card
* Refactor how feature list is defined and ordered in pug file
* Replace `aiLimitUpgradeCopy` and `compileTimeoutInterstitial` boolean by `upgradeCopy` string
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: 6c7c7e0457f93284d6798be229da5513f92ca77c
Adding position:relative prevents the visually hidden elements from getting absolutely positioned outside of the scroll area
GitOrigin-RevId: abe9e0ff91f769be39ed39223ebbbe17420af111
* Remove `plans2026` from pug files in subscriptions module
* Remove `plans2026` param from `makeGroupPlans`
* Remove `plans2026` param from `makeUISettings` in interstitial-payment.mjs
* Remove `plans2026` param from `makeInterstitialPaymentConfig` in interstitial-payment.mjs
* Remove `plans2026` param from `makeIndividualPlans`
* Remove `plans2026` param from `makeStudentPlans`
* Remove `plans2026` from try-premium page
* Remove `plans2026` from controllers
* Remove unused files
* Update translations
* Update tests
* Update tests
* Remove unused translations
* Rename the collaborator upgrade-mode test to read explicitly as the
Standard-plan replacement for the removed redirect test, and drop the
stale "when plans 2026 is enabled" framing now the controller no longer
branches on the flag.
* Surface controller errors in InterstitialPayment tests
Move assertions out of the res.render callback into render/redirect
helpers that resolve with the captured args and reject on the wrong
path or a controller rejection, so failures show the real error
instead of timing out. Restore the ai-limit-upgrade-copy split-test
assignment for the AI copy tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* format
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: f0b055b4d92b494b531766a13b057336d9c2c9af
* [web] Add ai-limit-upgrade-copy split test on the interstitial
* Add french translations
* Exclude trials from ai-limit-copy
* Add `workbench` and `assistant` to the paywall-types having the updated copy
GitOrigin-RevId: 8da856c17718280c74919a38ec78345d0bd5f781
* Remove double spaces
* Enforce curly apostrophes
* Enforce French typographic spaces in fr.json
Add vitest test enforcing French spacing rules per
https://typographisme.net/post/Les-espaces-typographiques-et-le-web:
- NNBSP (U+202F) before ? ! ;
- NBSP (U+00A0) before :
- NBSP (U+00A0) around guillemets « »
Fix 38 fr.json values that were using NBSP (or no space) before ? ! ;.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Extend French typography test to numbers and percent signs
Add rules per https://typographisme.net/post/Les-espaces-typographiques-et-le-web:
- NNBSP (U+202F) between digit groups (thousands separator)
- NBSP (U+00A0) between a number and a following word/unit
- NBSP (U+00A0) before %
Fix 9 fr.json values where a regular space was used instead of NBSP between
a number and a following word (e.g. "1 résultat", "10 jetons", "7 jours").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Use point médian for inclusive writing in fr.json
Replace parenthetical gender/plural markers like "sûr(e)", "abonné(e)",
"licence(s)" with the point médian "·" (e.g. "sûr·e", "abonné·e",
"licence·s") per modern French écriture inclusive.
Add a vitest rule rejecting "word(suffix)" patterns where suffix is 1-3
letters and there is no space before the opening parenthesis.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Simplify inclusive-writing check to "(e)" substring match
The previous regex (matchAll without the /g flag) would have thrown at
runtime. Drop matchAll entirely and use includes('(e)').
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add point médian to participles in fr.json
Apply inclusive writing (·e) to past participles agreeing with the
addressed user ("affilié·e", "invité·e", "connecté·e", "abonné·e", etc.).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Use inclusive forms in fr.json
* Change "de Overleaf" to "d’Overleaf"
* Fix tests
* Check for consecutive spaces, not just double spaces
* Update comment about guillemets
* Add ESLint rules for locale JSON conventions
Add three rules to @overleaf/eslint-plugin that run against locale JSON
files via @eslint/json:
- no-consecutive-spaces-in-locales
- no-straight-apostrophes-in-locales
- french-typography-in-locales
Wire them up in services/web/eslint.config.mjs against `locales/*.json`
(and `locales/fr.json` for the French-typography rule).
* Add `@eslint/json` so JSON files can be parsed
* Scope JS/TS-specific config to JS/TS files only so that built-in rules aren't applied to JSON files
* Disable `no-irregular-whitespace` in french-typography-in-locales.js
* Make the rules fixable
* Improve regexes: user `\s` instead of custom matchers
* Add sorted-keys-in-locales ESLint rule
Mirrors scripts/translations/sort.js as a fixable ESLint rule:
require top-level keys in locale JSON files to be sorted alphabetically.
The autofix re-serializes the file with sorted keys.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add no-unused-locale-keys ESLint rule
Mirrors scripts/translations/cleanupUnusedLocales.js as a fixable ESLint
rule applied only to en.json. Shells out to `find` + `cat` once per
process (cached) to grep all candidate source files for key references,
then reports any en.json key that isn't found. The autofix re-serializes
en.json without the unused keys.
Hardcoded ALWAYS_USED set covers keys built at runtime (month names,
third-party-datastore notifications) that the regex scan can't see.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add locale-variables-match-en ESLint rule
Mirrors scripts/translations/checkVariables.js as an ESLint rule applied
to non-en locale JSON files. Reads en.json (cached) and reports any
member whose `__name__` placeholders differ from the base.
GLOBALS (e.g. __appName__) are allowed as extras; IGNORE_NESTING_FOR
preserves the script's per-key exceptions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Replace some double linebreak (@overleaf/no-consecutive-spaces-in-locales)
* Add no-orphan-locale-keys ESLint rule
Detect translation keys in non-en locale files that aren't present in
en.json. Mirrors the `--sync-non-en` flag of cleanupUnusedLocales.js.
The autofix re-serializes the locale file without the orphan keys,
sorted alphabetically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add tests for the locale JSON ESLint rules
Cover the four single-file rules:
- no-consecutive-spaces-in-locales
- no-straight-apostrophes-in-locales
- sorted-keys-in-locales
- french-typography-in-locales (including the inclusive-writing
suggestion for "(e)")
The three cross-file rules (no-unused-locale-keys, no-orphan-locale-keys,
locale-variables-match-en) read from disk and aren't covered here; they
need integration tests via fixtures.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop bin/lint_locales — checks are now covered by ESLint rules
The four checks in bin/lint_locales are duplicated by the new
@overleaf/eslint-plugin rules:
- sort.js --check → sorted-keys-in-locales
- cleanupUnusedLocales.js --check → no-unused-locale-keys
+ no-orphan-locale-keys
- checkVariables.js → locale-variables-match-en
- single-quote grep → no-straight-apostrophes-in-locales
`yarn lint` runs the ESLint rules against locales/*.json, so the
lint_locales script and Makefile target are redundant.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Remove Locales.test.mjs
* Delete scripts replaced by @overleaf/eslint-plugin rules
Remove the three locale scripts now superseded by ESLint rules:
- scripts/translations/sort.js → sorted-keys-in-locales
- scripts/translations/cleanupUnusedLocales.js → no-unused-locale-keys
+ no-orphan-locale-keys
- scripts/translations/checkVariables.js → locale-variables-match-en
Also drop the matching Makefile targets (sort_locales,
cleanup_unused_locales), update AGENTS.md / copilot-instructions.md to
point at `yarn lint --fix` instead, and prune the deleted entries from
the esm-check-migration allowlist.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Delete redundant scripts/translations/sort-locale-file.js
Single-file sort utility — superseded by `yarn lint --fix path/to/file.json`
via the sorted-keys-in-locales ESLint rule. No callers in repo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Update comments and messages
* Add test file pattern to ESLint configuration
* Reorder "Backend specific rules" so `no-extraneous-dependencies` doesn't fail on test files
* Make french-typography fixes idempotent and catch edge cases
Apply Copilot's review feedback on PR #34221:
- autoFix() now uses `\\s*`/`\\s+` instead of `\\s?`/`\\s` so it
collapses any run of mixed whitespace into the correct NBSP/NNBSP,
making the fix idempotent and order-independent.
- The `:` lint regex now captures optional whitespace and reports
when it isn't NBSP (catches `"Liste :"` with a regular space).
- The `%` lint regex now matches the missing-space case (`"10%"`)
via `\\s*` instead of requiring one char between digit and `%`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Remove sort-locale-file.js from pre-commit
* Revert no-unused-locale-keys ESLint rule; Restore lint_locales scripts
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
GitOrigin-RevId: 8351fab074e6e7f77920b70237df1cd1c781e053
* Replace token-link email with 6-digit code on SSO registration
Unverified SSO emails previously received a long-lived token link
(90-day TTL) via UserEmailsConfirmationHandler. This replaces that
flow with the same 6-digit code verification used for password
registration, redirecting through /registration/confirm-email.
- SSOManager.registerSSO now always confirms email (caller must
verify first); removes sendConfirmationEmail / _finishRegistration
- SSOController._signUp sends confirmation code and stores
pendingSSORegistration in session when IdP email_verified is false
- New SSOConfirmEmailHandler completes registration after code check
via completeSSOEmailConfirmation module hook
- OnboardingController confirm-email handlers accept
pendingSSORegistration alongside pendingUserRegistration
confirmEmailFromToken (POST /user/emails/confirm) removal is deferred
to a follow-up PR to avoid breaking in-flight 90-day tokens.
Closes#28607
* Fix unverified-email edge cases; Add ORCID e2e tests;
* Rename `confirmEmail` parameter to `emailVerifiedByIdP` in _signUp function
* Remove `sendConfirmationEmail`
* Mock getUserByAnyEmail in tests
* Extract _finishSSORegistration helper to deduplicate the register →
set session flags → allocate referral → finishSaasLogin → finishLogin
sequence shared by both the direct and deferred (code-confirmed) paths.
* Stop duplicating session data in pendingSSORegistration
analyticsId, splitTests, and referal_* are already in the session at
confirmation time — no need to copy them into pendingSSORegistration.
Re-fetch splitTests fresh on completion instead.
* Simplify the code
* Remove dead confirmEmail template
No callers remain after sendConfirmationEmail was deleted. The token-link
flow (confirmEmailFromToken) only validates tokens, never sends email.
* Remove dead reconfirmEmail template
* Address comments from Copilot
* Clear stale pending registration when starting a new flow
* Add unit tests for completeSSOEmailConfirmation
* Add `verificationMethod` param
* Fix camelcase issues
* Extract _createSSOUser and _registerAndFinish helpers to deduplicate registration logic
* Remove obscure "registration_error"
* Prevent FormTextIcon from shrinking
* Enable "email_already_registered_sso" error
* Misc. improvements to confirm-email-form.tsx
* Remove `UserEmailsConfirmationHandler` mock
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
* Add info on sso_email.pug page
---------
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
GitOrigin-RevId: d0196ebc6d81ff61bcd27726d0b899b743d08d64
* [web] Order plans in Change Plan modal consistently
Reorder the plans returned by `buildPlansListForSubscriptionDash` so the
Subscription page "Change plan" modal lists them top-to-bottom as:
1. Student annual
2. Student monthly
3. Standard monthly
4. Standard annual
5. Pro monthly
6. Pro annual
Previously `buildPlansList` produced three per-period buckets which the
dash function concatenated, giving an order that flipped per family.
Replace that with an explicit `CHANGE_PLAN_MODAL_PLAN_CODES` list so the
order matches the Design QA spec at a glance. The now-unused
`studentAccounts`, `individualMonthlyPlans`, `individualAnnualPlans`,
`groupMonthlyPlans`, and `groupAnnualPlans` buckets are dropped from
`buildPlansList` (no other callers).
Closes#34024
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Update personal-plan acceptance test for new buildPlansList shape
The previous test asserted `buildPlansList().individualMonthlyPlans`,
which no longer exists after the change-plan modal reorder dropped the
per-period buckets. Move the assertion to
`buildPlansListForSubscriptionDash()`, which is where the personal-plan
exclusion is now enforced (via `CHANGE_PLAN_MODAL_PLAN_CODES`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Drop now-dead client-side plan filter
`IndividualPlansTable` used to filter out `paid-personal`,
`paid-personal-annual` and `institutional_commons` defensively because
the old `buildPlansListForSubscriptionDash` returned every non-group
plan that wasn't `hideFromUsers`. The previous commit pins the modal to
an explicit six-plan list (`CHANGE_PLAN_MODAL_PLAN_CODES`), so none of
those plan codes ever reach the frontend and the filter is dead. Remove
it and the now-unused `useMemo` import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "[web] Drop now-dead client-side plan filter"
This reverts commit 83e8448f2cfa2c68e44b749d5a2bc350a7443c6d.
We'll do that in a later cleanup
* Swap "Student monthly" and "Student annual" for consistency
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 046a235e14e7ad6622288f5a5a723f5a4f7f14da
* [web] Redirect missing AI add-on purchase to subscription dashboard
The two error paths in `previewAddonPurchase` redirected to
`/user/subscription/plans#ai-assist`, but the `#ai-assist` anchor was
removed when the AI Assist add-on was retired, so users land at the top
of the plans page with no context. Align both with the other error
branches in the same function and the `plans-2026-phase-1` enabled
branch, which already redirect to
`/user/subscription?redirect-reason=ai-assist-unavailable` — the
subscription dashboard shows the matching warning alert
(`redirect-alerts.tsx`).
Update the acceptance test to match the new redirect target.
Closes#34074
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Update ai-assist-unavailable warning to reflect bundled AI features
The previous copy said "AI Assist isn't available to you due to your
current subscription type", which read as a hard block. Now that the AI
Assist add-on has been retired and AI features are included with every
paid plan, the warning should point users to the pricing page instead of
implying their plan can't access AI at all.
Keep the existing translation key for now — a follow-up can rename it
once #33624 (AI page CTA destination) is resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Link the ai-assist-unavailable warning to the pricing page
* [web] Rename key `ai_assist_unavailable_due_to_subscription_type` -> `ai_assist_unavailable`
* [web] Update french and german translations
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: ae1319fa5b857d8f292de77c82ef0bda1c7ad144
* [web] Fix footer For Students link to activate student toggle
The footer link only set itm_referrer plus a #student-annual hash. The
plans page reads the active plan/period from `plan` and `period` query
params (PlansHelper.getPlansPageViewOptions), so the student tab never
activated from the footer link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* syncStudentModeFromPlanType after in handleDeprecatedHash
* Change URL update to use replaceState in the pricing page
* Revert "Change URL update to use replaceState in the pricing page"
This reverts commit eac71f193029e3f1c75e0c97261d8a5982c0d35c.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 69d689d0fe89fc68cefab9233739fc61da8f2ced
Insert a new "Do you offer discounts for nonprofits?" accordion item
under the educational group discount question in the "Overleaf
multi-license plans" FAQ tab. Routes the "contact sales" link through
the existing `faqContactLink` mixin so click tracking stays consistent
with the other FAQ contact links.
Closes#33494
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 582517f1d1f1f7934610253c252cf0f8af2b68a2
* [web] Stop bolding AI features unconditionally on the interstitial
The four `strong: true` flags on the AI features in `sectionMain2026`
caused those rows to render bold on every interstitial visit, regardless
of paywall context. The original intent (per Design QA #34022) was for
boldness to highlight the features relevant to the specific paywall the
user came from (e.g. AI paywall -> AI features bolded) — that
conditional logic was never wired up, and currently no `purchaseReferrer`
or paywall reason is plumbed through to the feature config.
Remove the unconditional `strong: true` so the cards render consistently
with the pricing page. Reintroduce conditional bolding in a follow-up
once the paywall→features mapping is scoped by design.
Closes#34022
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Remove `card-include-strong` and related code
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 2112214217f3b53d34518efbca546082ce559e26
* [web] Render "Try for free instead" CTA as link, not button
Design QA wants the "Try for free instead" CTAs on the pricing and
interstitial pages styled as marketing links (`link-monospace link-lg`)
rather than the current `btn-ghost` button. Add a `link` button type to
the `plans-cta` mixin that drops the `btn` class and applies the link
classes, and set `buttonType: 'link'` on the six `try_for_free_instead`
CTAs (plans-individual, plans-student, interstitial-payment).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Make link smaller
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f911698a9bfa19f8180e58edb3cebcea90468cbd
* Add tests on plurals
* Update `collabs_per_proj` and its pluralisations
* Update `n_user` and its pluralisations
* Update `showing_x_results` and its pluralisations
* Update `show_x_more_projects` and its pluralisations
* `bin/run web npm run extract-translations`
* Populate `_plural` keys in non-en locales
For 2-form languages (da, de, es, fi, fr, it, nl, no, pt, sv, tr), copy
the existing bare-key value into the new `_plural` sibling to prevent
i18next from falling back to English for count!=1.
Also remove orphan singular keys (`collabs_per_proj_single`,
`showing_1_result*`) left over from the previous commits.
Bare-key values remain in their original plural form pending translator
review — count=1 will still render the plural form in non-en until
translators flip those to singular. Multi-form (cs, pl, ru) and
single-form (ja, ko, zh-CN, zh-TW) locales are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Flip non-en bare-key values to singular form
Per review, the i18next v3 plural convention uses the bare key for count=1
(singular) and `_plural` for count!=1. The non-en bare-key values were
left as the original plural form by the previous commit so the `_plural`
siblings could be copied from them; this commit flips the bare values to
the singular form per language.
Languages where singular and plural noun forms coincide (Finnish, Swedish,
Turkish) are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Apply suggestions from code review
Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com>
GitOrigin-RevId: 628513ca792c2dcce023247e52b7320e2741cc54
* [web] Fix preview next-invoice date for cadence-change upgrades
When upgrading from a monthly plan to an annual plan (or vice versa) the
user pays for a full new-cadence term today, so the next payment is one
new-term-length from now — not the current cycle's period end. Previously
we always echoed subscription.periodEnd in the preview, which surfaced
the stale current-cycle date and misled the user into thinking they'd
be charged again ~25 days later.
makeChangePreview now compares the current and next plans' annual flag:
on a cadence flip it returns now + 1 year or now + 1 month; otherwise it
keeps the existing behaviour.
Closes#33283.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Format
* Fix next invoice date using priceincents
* Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 05b660ecb518c04b60e88f2ddc7531733245bdde
- pricing table: integration icons gap uses --spacing-06 (horizontal),
integrations content gap uses --spacing-04 (vertical)
- Student card no longer renders with the green stroke highlight
- Interstitial H1 wrapped in .main-heading-section so its spacing
matches the pricing page
Part of #33619.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 12ddd223f68c776c06a3d5dc5faa841819baae90
The "continue with free plan" skip link sat directly under the
disclaimer with no separation. Add spacing-08 margin-top to match
the disclaimer's own padding-top above the cards.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: ba7334785757a39ca0bdff309ded224e6cb8e3bf
* [web] Add Papers/ReadCube icon to plans page integrations
Closes#33493
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Delete old 200kB zotero logo
* Allow png use in logos
* Allow wrap
* [web] Share third-party integration icon list across plans and onboarding pages
Extract the icons array to services/web/app/src/util/third-party-icons.mjs
so the plans-2026 feature table and the try_premium onboarding page render
from a single source. The try_premium page now also includes Papers and
follows the plans page ordering.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Allow ciam try-premium logo row to wrap
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f5a52418cbe01d9e343092b552183dffa3ae78bd
Plan cards inherited neutral-60 (=neutral-60) for description and
include-list text, which can fail WCAG contrast on the light card
background. Switch to content-secondary (=neutral-70) per Vee's
short-term recommendation; affects the free plan description, card
include items (e.g. "Basic AI allowance") and the in-card group
picker labels.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 42aff473a779b4b4f36b6c648d86097a79f820c8
* Update hrefs
* Revert link on expired subscriptions
* Revert hrefs of other buttons
* Use `plans-2026-phase-1` feature flag
GitOrigin-RevId: 3fe489c6ec192adc2fb836b07429dc2a11f9a57f
* fix(web): show correct plan in future payments preview when upgrading over a pending downgrade
When a user had a scheduled plan downgrade and then immediately upgraded
to a higher plan, makeChangePreview() always used the pending (stale)
plan code/name/price for the future payments display rather than the
newly selected plan.
Check whether the current change is a plan change (premium-subscription
or group-plan-upgrade type) and if so use subscriptionChange's plan
details instead of pendingChange's, since the immediate upgrade overrides
the scheduled downgrade.
Closes#33299
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(web): add unit tests for makeChangePreview pending-change plan override
Covers the four cases: premium-subscription and group-plan-upgrade types
use subscriptionChange plan (not pendingChange), add-on-purchase type
defers to pendingChange plan, and no-pending-change falls back to
subscriptionChange as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: cc2f9c88e5dfdfb89370798e857ea98caf8fcf85
* Update .plans-cta-plain-link styles so the clickable area doesn't overflow
* Update .plans-educational-discount-label styles so the clickable area doesn't overflow
* Fix lint
GitOrigin-RevId: cedbaa78a079fd4f7cefe2be9b39252d30ba6355
* Remove stale "You already have a subscription" notification after cancel/plan change
The notification was derived from a server-rendered meta tag set at page load,
so it persisted through cancel and plan-change flows. Now derived directly from
the URL param on the client; the param is stripped on cancel button click
(replaceState) and before plan-change reloads (location.replace via
reloadWithoutHasSubscription helper).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix format
* Update services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix change-plan tests after location.reload → location.replace migration
reloadWithoutHasSubscription calls location.replace() not location.reload(),
so update assertions accordingly. Also stub toString() to return the jsdom
origin so FlashMessage's replaceState call doesn't throw a SecurityError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Guard reloadWithoutHasSubscription against empty URL
When called after component unmount, useLocation's toString() returns '',
causing new URL('') to throw. No-op early to avoid the exception.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Guard against empty URL in history state replacement for subscription cancellation
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 8408ee971adf038e2d819eae5df060ace62a7e14
Users in the plans-2026-phase-1=enabled split test can no longer
purchase the AI Assist add-on via crafted HTTP requests. The preview
and purchase endpoints return 404/redirect for these users.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 2c75eb622cf44dc91019a692290ac646b51fd72c
* [web] Update paywall copy for plans-2026 AI tiers
Replace AI-related strings in upgrade/paywall UI when the
plans-2026-phase-1 flag is enabled:
- Add `higher_ai_allowance` ("Higher AI allowance") for paywall bullet
lists and body text; keep `higher_ai_limits` ("Higher AI limits") for
the subscriptions dashboard, which uses different wording per spec
- Update three locale strings to use "allowance" instead of "limits":
`access_all_premium_features_ai`, `plus_additional_collaborators_and_more`,
`upgrade_to_add_more_collaborators_and_more`
- Fix casing: `upgrade_to_review` "Upgrade to Review" → "Upgrade to review"
- Switch upgrade-benefits and onboarding prompt from `higher_ai_limits`
to `higher_ai_allowance`
- Fix subscriptions free-plan to show `higher_ai_limits` instead of
`get_unlimited_ai` under the plans2026 flag
- Fix compile-timeout button to show "Start free trial" under plans2026
instead of "Start a free trial"
- Update two Cypress assertions for the new track-changes modal title
* Update extracted translations for higher AI allowance
* Remove "start a free trial" with exclamation
* Remove "get unlimited ai" translation entries
GitOrigin-RevId: 12300d94dc81c5407a21d4682d5714d7284c31b0
* plans-2026: wrap cards to 2-col at md, 4-col at lg
Replaces horizontal scroll at md/lg with a wrapping CSS grid so cards
stack in rows instead of requiring horizontal scrolling. The 4-column
layout now kicks in at lg (~992px) rather than xl (~1200px).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* plans-2026: replace mobile horizontal scroll with 1-col stack
Removes the overflow-x scroll behaviour at xs/sm (and the
scrollbar-overlap margin/padding hack that came with it). Cards now
stack in a single column on small screens, consistent with the
2-col/4-col grid above.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix lint
* plans-2026: fix student mode 2-col at lg, remove mobile min-width
Student-mode 2-col override was only kicking in at xl; move it to lg so
the two visible cards (free + student) fill the row correctly. Also drop
the 280px min-width on .plans-card-col — now that mobile uses a 1-col
grid instead of horizontal scroll, it causes unnecessary overflow on
narrow viewports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update breakpoint for plans cards layout to lg for improved responsiveness
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 5ef923dd3795353bd946c1857e78f4b5f7063ab0
* Rename _faq_new.pug to _faq.pug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add 2026 plans page FAQ templates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Wire up quotes and FAQ section in plans_2026.pug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update styles, quote component, and translations for 2026 plans page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix missing accordion icon wrapper in last FAQ item
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fixup: rename `.card-pattern-sides` back to `.card-pattern`
* Create SASS placeholder and mixin
Suggested by Tim
* Fixup quote style
* Update spacings to match Figma
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 2f4e284038d79157d2b029a967918077bb206032
* Add AI allowance tooltip copy and wire into plans feature comparison
Adds per-tier tooltip strings for basic, increased, and max AI
allowance. Splits the ai_allowance feature entry in the individual and
student plan configs into separate per-tier objects so each can carry
its own featureExplanation key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix plans page styling: section padding, badge line-height, checkbox accent-color
- Replace padding-block shorthand with explicit start/end on
.plans-and-pricing-section to avoid overriding the bottom spacing
- Add missing line-height to .price-plan-card-badge
- Add accent-color to .plans-educational-discount-checkbox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Only render geo-banner-container when there is content
Previously, .geo-banner-container was always rendered even when no
banners or errors were present, which introduced an unnecessary empty
wrapper in the DOM. Also moves the payment error conditions to be
top-level siblings (removing the `if paymentError` indirection) to
make each case independently explicit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Migrate plans page to .plans-2026 gap-based layout
Replaces .plans-new-design's per-section padding rules with a single
flex column + gap on .plans-2026, letting the layout engine handle
spacing uniformly. Removes the now-redundant .plans-and-pricing-section
padding block, .plans-cards-content padding, and
.plans-new-design-content-spacing class from the heading row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 3a112c46759419ef0bc994dfb88498fc19b18c10
* Update tests to check that refreshed features are used correctly
* Fix feature refresh handling in ProjectController
GitOrigin-RevId: 14c0cedf72f6cfe6657aa35fab16e319a37231e7
* Add tooltip next to "Overleaf AI" row
* Add AI to features list
* Fix tests
* Revert changes which will be overwritten by https://github.com/overleaf/internal/pull/31993
* Add missing import (conflict with main)
GitOrigin-RevId: ce236670bd63701a3976a3eb84dc695ee0e928b1
* Add Overleaf AI row in the Plans pages
* Show "Overleaf AI" row only with plans-2026-phase-1
* Hide AI-Assist add-on with plans-2026-phase-1
* Update tests
GitOrigin-RevId: 905a8eede1fc460edd6da76a0988d5e8e57880fb
* Compute student discount from prices
* Add presentational discount in the checkout page
* Put student discount row behind feature flag
* Update code and tests to clarify that `currency` is always defined
* Introduce `usePlanPriceItems` to normalize the list
* Simplify `usePlanPriceItems`
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
* Remove student discount percent
* Update Standard Monthly/Annual names in the checkout page
* Simplify `getRecommendedCurrency` mock
* Fix testid: price-summary-plan
* Add test on stripe-price-summary
* Add `Math.abs` on accessibility discounted info (!)
---------
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
GitOrigin-RevId: f297eab4b6abd6a84842054667a3734cb33866fe
* Remove legacy notification cookies
* Remove CIAM notification message from login page
* Remove e2e test "it can dismiss the login notification"
* Clarify comment about removing legacy notification dismiss cookies
GitOrigin-RevId: 33fdc02a397c0f4be491f030ab58fd8954276048
* Extract currency conversion and rate limiting functions to helpers
* Add script to create prices from CSV for Stripe integration
* Add tests for create prices from CSV script
* Add usage documentation for create_prices_from_csv script
* Add `planCode` metadata
* Temp: Plans CSV example
* Revert "Temp: Plans CSV example"
This reverts commit 810b1ed67052f7a1a0deb20b70f14507a282fcf1.
* Various improvements to price and product creation script
---------
Co-authored-by: Tim Down <158919+timdown@users.noreply.github.com>
GitOrigin-RevId: c015c6dd904db3143781581db4210cef282a4070
* Reapply "[web] Make CIAM login notification dismissible (with cookies) (#30251)"
This reverts commit 7bafafe54b24245c4da88d1c81540a3b1c98231b.
* Add a test `should redirect to /register with a notification`
* Fix destructuring of options in notification mixins
* Remove `data-ol-dismiss-cookie-paths` default, enforce it being set
* Handle the case of standard notifications without the dismiss setup
Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
---------
Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: fbf441c1efe0aa5d80899a31ec3ad51c1dba6d24
* [web] Promisify ProjectLocator (#30319)
* Promisify ProjectLocator
* Update ProjectLocator unit tests
* Update unit test:
Add a subfolder to reproduce issue where `endOfBranch` is called before the search has ended.
* Add missing `else` to prevent additional call to `endOfBranch`
* Simplify conditions by moving `if (element != null)` higher
* Replace `endOfBranch` by check at end of `startSearch`
GitOrigin-RevId: 95dfb47bb549698a406315db1a4b58bce5de791e
* Extend notifications so they can be dismissed
The dismissal is stored in a cookie for a year
* Extend CIAM notifications so they can be dismissed
* Make the CIAM notification dismissable
* Style the close button so it is in the corner of the notification
* Add e2e test
* [e2e] Replace 'not.exist' by 'not.be.visible'
* Set cookie with the same pattern as the "cookie-banner cookie"
* Hide notification in the frontend
* Revert "Hide notification in the frontend"
This reverts commit b5d205f3e3a4e2555be038eb3b7561761a2cde59.
* Add `data-ol-dismiss-cookie-paths` to the notification
* Add `data-ol-dismiss-cookie-paths` to the notification (CIAM variant)
* Shorten cookie name
* Revert: remove the .corner class
* Apply design from Figma for CIAM notifications
GitOrigin-RevId: 4070715c6a63d0497b7a41c343c3f943ced4bfef
* Replace request by fetch
* Promisify NotificationsHandler.js
* Replace to `fetchNothing` when we don't consume the body.
I also checked that these endpoints don't return anything: https://github.com/overleaf/internal/blob/de2d0ed8cab15c4347ddecf227073a6d4e06120f/services/notifications/app.js
* Make URLs safer
* Ignore (but log) failing `fetchNothing`s on DELETE endpoints
* Defensively add `userId.toString()` to prevent bugs (!)
* Update NotificationsHandler unit tests
* Throw fetch errors only if we did not get a response
GitOrigin-RevId: 03700ad29658c0e3af4e476b25a964030e9c97f1
* Remove focused style from the stepper
* Reduce spacing between password input and password policy
* Make input validation icon larger
* Make animated-tick.svg faster
* Move notification to below h1
* Move notification to below h1
* Fix: Warning: Invalid DOM property `autocomplete`. Did you mean `autoComplete`?
* Wrap form-text in a span so it's displayed correctly and not altered by flex
* Remove "Please request a new password reset email and follow the link." text after the email is entered
* Remove (unnecessary?) flex property on .ciam-form-text-icon
* Rename `messageTextNode` to `messageTextSpan`
* Revert changes to input-validator.ts
GitOrigin-RevId: ad83c92a59b9dbc872cf4e49362b0baec9fb5b93
* Remove double dots
* Make skip button Tertiary instead of Link
* Bold email
* New zotero logo
GitOrigin-RevId: 569fa4c18fc85abf55b492f7033472445c63ed6b
* no grey hyphen when the input is empty
* show email in bold
* Fix tests
* Add test to verify email display in confirmation code input
* Format
GitOrigin-RevId: ade4a670b9dc80b99f64cc3273407a98428521b8
* Add collapsible info in ODC
* Remove transition effects on caret
* Update summary focus-visible style
Make it inline-block so the clickable part doesn't go out of bounds.
Update outline style.
* Make the caret orientation consistent with Figma and Dashboard
* Remove extra padding
* Make `summary` styled like ds btn-tertiary
See https://www.figma.com/design/qHqSob0u4b4h1i2ModSTLn/CIAM---Phase-01?node-id=2285-20806&t=AlQIeoQEd2kG2h2M-0
GitOrigin-RevId: 9a49efe68088ea4969a803ffaa1c981c5a7ed7bb
* Make CIAM copies of Pug files
passwordResetCiam.pug
setPasswordCiam.pug
* Update controller with split test assignment
* Use CIAM layout in passwordResetCiam.pug
* Style passwordResetCiam according to designs
* Use CIAM layout in setPasswordCiam.pug
* Style setPasswordCiam according to designs
* Use settings value in registration screen for must_be_at_least_n_characters
* Retrieve email input with a script
* Replace mb-4 by --ds-spacing-800
* Add eye icon to toggle password visibility
* Avoid double dots after some translated strings
* Use `ciamCustomFormDangerMessage`
* Use `ciamErrorNotification`
* Use `ciamButtonContentLoading`
* Replace remaining "mb" classes
* Move new password errors to the top of the form
* Fix CIAM mixins path after rebase
* Use `ciamCustomFormDangerMessage`
* Add `data-ol-spinner-inflight` to buttons
* Replace classname ciam-notification by notification-ds
Remove borders from CIAM notifications
Fix font size
* Revert "Use settings value in registration screen for must_be_at_least_n_characters"
This reverts commit a0af95c11e171097750ad7ee871f6baf89d5c0cb.
(It's Friday afternoon so I don't want to update unrelated stuff :D)
* Update: check_your_inbox
* Remove `.ciam-card` min-height.
Unnecessary thanks to `.confirm-email-success-form`'s min-height: 400px;
* Use phosphor icons
* Style `formMessagesNewStyle` with DS notifications within CIAM pages
Alternatively, we could extend/duplicate `showMessagesNewStyle` with a CIAM variant
* Revert "Style `formMessagesNewStyle` with DS notifications within CIAM pages"
This reverts commit ed382dc1e8cdf5b916c1527f4da0a825167e9675.
* Fix styling of dynamically-created DS notifications
* Set password length info to secondary color
* Move `ciamSamlErrorNotLoggedIn` to saas-authentication module
Prevents errors in CE:
Error: ENOENT: no such file or directory, open '/overleaf/services/web/modules/saas-authentication/app/views/_mixins.pug'
at /overleaf/services/web/app/views/_mixins/ciam_mixins.pug line 3
---------
Co-authored-by: Tim Down <158919+timdown@users.noreply.github.com>
GitOrigin-RevId: afe58f18ecee92460ab628a285b6edb48a5c678d
* Add CIAM explanatory banner to login page
* Center .ciam-card-footer on all sizes
* Move bottom padding to .ciam-layout
So it exists even when there is no footer
* Make every CIAM text input large
* Lint
GitOrigin-RevId: 3c5af9ffefdb064780e7818bf2fbe77c14562cd6
* Create DS version for Bootstrap Input form elements
* Move DS Button Storybook to DS component folder
* Use phosphor icons
* Add ds-focus-outline mixin
* Use math.div
GitOrigin-RevId: e50934212ec5949f0f7abc7880eb73933fce2a9b
* Remove the nocdn fallback
Co-authored-by: Tim Down <tim.down@overleaf.com>
* Remove comment about nocdn query string in ExpressLocals.mjs
Co-authored-by: Tim Down <tim.down@overleaf.com>
* Remove `Frontend` global
---------
Co-authored-by: Tim Down <tim.down@overleaf.com>
GitOrigin-RevId: 74160ed4cf8ba17f8bd9b6793c8baa80883d3361
* Allow passing auditLog extra info in addEmailAddress
* Create script re_add_deleted_emails.mjs
* Check email validity when parsing the CSV
* Allow no IP and initiator for the log events
* Add an acceptance test on re_add_deleted_emails
* Fix remove_unconfirmed_emails.mjs
We're not planning to run this again, but this could help if some code from here ever gets copy-pasted
* Fix remove_unconfirmed_emails.mjs
Rename `emailsToRemove` to `emailsToRemoveCandidates`
* Remove console.logs of event logs:
That's unnecessary complexity and we already have the data from the previous script
* Copy `addEmailAddress` into the script
* Handle primary emails correctly
* Ignore a TS error
* Fix async function in createUsers (tests)
* Add assertion for already in use emails in ReAddDeletedEmailsTests
* Add information in admin-panel for add-email audit logs
GitOrigin-RevId: f8480426d7b323f88a268874488d9ae641bc6045
* [web] Add script to check for wrongly removed confirmed emails
* List emails that were added again
* Use `trackProgress`
* Prettier logs
* Add `READ_PREFERENCE_SECONDARY`
* Check `change-primary-email`
* Add check with `confirm-email` should be always empty, but let's check
* Update services/web/scripts/check_removed_emails.mjs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 4f83d69cffd0aec2c5d1201f1f6130eacdbb2e39
* Move `main#main-content.content.content-alt` into a React component (`ContentLayout`)
* Create the CIAM variant of `RegistrationConfirmEmailForm`
* `bin/run web npm run extract-translations`
* Use `CiamLayout` in the Storybook demo
* Add `SplitTestProvider` in tests
* Fix Storybook: Wrap `RegistrationConfirmEmailForm` Story in `onboarding-confirm-email`
* Refactor SCSS files:
- only imports in all.scss
- split .storybook-layout and .storybook-enabled
- extract ciam-spacing.scss
GitOrigin-RevId: f4a214a0978423a1621dd8f60bf459af7b8f877e
* Add font Inter
Files downloaded from https://rsms.me/inter/
Kept only the woff2 files and CSS definitions for Inter-Regular and Inter-SemiBold
* Delete inter folder
* Add inter to build-fonts.mjs and run it
* Add inter.css
* Update latofonts archive URL
GitOrigin-RevId: 24cd8a48f4566d8a55443eb2cd938ee129dff5f8
* Conditionally render a CIAM copy of login.pug depending on `ciam` split-test
* Conditionally render a CIAM copy of register.pug depending on `ciam` split-test
* Conditionally render a CIAM copy of institutional_login.pug depending on `ciam` split-test
* Rename the test to `uniaccessphase1`
* Fixup ciam paths (underscores, not hyphens)
* Escape translations in `log_in_with_existing_institution_email`
See https://github.com/overleaf/internal/pull/29390#discussion_r2479318609
GitOrigin-RevId: fff0e154d1e1faad4842a2c49fa5889f9cc189e6
* Add a Storybook Layout page compiling all the "small pages" layouts
* Add a CIAM page layout to Storybook and create an initial Layout
* Use rem in font mixins
* Add a `--ciam-` prefix to the new CSS variables
* Fix linting
GitOrigin-RevId: 7a89fd1531c87597a918a9170d174cce556d77c4
* Temporary: forcibly reproduce the loading state and test failure with a `setTimeout`
* Wait for the loading to be finished in the test
* Revert "Temporary: forcibly reproduce the loading state and test failure with a `setTimeout`"
This reverts commit fb0270cc0cacd49fcff74e186b50b55f822f7729.
* Let the test pass if the loading text is not found
* Replace `getByTest` to `findByText` so it awaits
GitOrigin-RevId: 6cee6e3a86b6a5f24d95c7e3e1fcef4c4efcc094
* Replace request by fetch
* Promisify `dropboxDuplicateProjectNames`
* Promisify `dropboxUnlinkedDueToLapsedReconfirmation`
* Promisify `featuresUpgradedByAffiliation`
* Promisify `redundantPersonalSubscription`
* Promisify `projectInvite`
* Promisify `ipMatcherAffiliation`
* Promisify `tpdsFileLimit`
* Promisify `groupInvitation`
* Promisify `personalAndGroupSubscriptions`
* Sanitize URL
* Add default fetch timeout
* Update tests
* Update tests with fetch-utils
* Update external usage
* Import `ObjectId` from `mongodb-legacy`
Co-authored-by: Alf Eaton <alf.eaton@overleaf.com>
* Add `toString()` to userId in `ipMatcherAffiliation` calls
* [v1] Return "200 null" instead of no content in `v2/api/v2/users/:userId/ip_matcher` when there is no data, so it can be read as JSON in the frontend
Can't be 204 because of lint: "Do not specify body content for a response with a non-content status code"
* Temporarily remove `enrolment_ad_html` test to test notifications
* Revert "Temporarily remove `enrolment_ad_html` test to test notifications"
This reverts commit fb8b0c26a3adbbc64053cd3f5f2570ccc97328fb.
* Use `return await`
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
---------
Co-authored-by: Alf Eaton <alf.eaton@overleaf.com>
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 17ee861852aa4ac15d3b46b1b28c763fad5333d2
* Add `delete-project` and `modify-admin` admin capabilities
* Add `isSuperAdmin` check
* Replace frontend checks
* Add requireAdminPermission middleware on disable2FA
* Remove `isSuperAdmin` tag
* Fix tests on admin capabilities
* Replace isSuperAdmin tests by tests in admin-roles
* Replace superadmin tests by admin-capabilities
* Update tests: server-pro admins are superadmins
* Remove "In Server Pro, all Admin users are also Super Admins"
* Update tests after revert "server-pro admins are superadmins"
* Create capability "delete-2fa"
* Add special privileges for admins in Server Pro to preserve the existing behaviour
* Reorder definitions to avoid future eslint error
* Add super_admins admin-role
* Add some sensible capabilityDependencies
* Fix inconsistency: super_admins -> super_admin
* In tests, await controller methods instead of using manual promises
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
---------
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: ca1fff3ead5b2001cb5d5d25de15970d55fb2c1b
* Promisify UserMembershipController
* Remove nested promisification from UserMembershipHandler
* Move export to the bottom of the file
* Remove manual promises
GitOrigin-RevId: a72b9ee4973da7a04b1ffeb588bcae3db696602f