diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
index a10db10cbc..075eb1d784 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
@@ -18,6 +18,7 @@ import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import Errors from '../Errors/Errors.js'
import AuthenticationController from '../Authentication/AuthenticationController.mjs'
import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
+import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
// This rate limiter allows a different number of requests depending on the
// number of callaborators a user is allowed. This is implemented by providing
@@ -328,14 +329,26 @@ async function viewInvite(req, res) {
// cleanup if set for register page
delete req.session.sharedProjectData
+ const { variant: sharingUpdates } =
+ await SplitTestHandler.promises.getAssignment(req, res, 'sharing-updates')
+
// finally render the invite
- res.render('project/invite/show', {
- invite,
- token,
- project,
- owner,
- title: 'Project Invite',
- })
+ if (sharingUpdates === 'enabled') {
+ res.render('project/invite/show', {
+ token,
+ projectName: project.name,
+ projectId: invite.projectId,
+ title: 'Project Invite',
+ })
+ } else {
+ res.render('project/invite/show-legacy', {
+ invite,
+ token,
+ project,
+ owner,
+ title: 'Project Invite',
+ })
+ }
}
async function acceptInvite(req, res) {
diff --git a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
index 416b6b4eb6..01551937e9 100644
--- a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
+++ b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
@@ -20,6 +20,7 @@ import UrlHelper from '../Helpers/UrlHelper.mjs'
import UserGetter from '../User/UserGetter.mjs'
import Settings from '@overleaf/settings'
import LimitationsManager from '../Subscription/LimitationsManager.mjs'
+import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
const { getSafeAdminDomainRedirect } = UrlHelper
const { canRedirectToAdminDomain } = AdminAuthorizationHelper
@@ -113,7 +114,15 @@ async function tokenAccessPage(req, res, next) {
}
}
- res.render('project/token/access-react', {
+ const { variant: sharingUpdates } =
+ await SplitTestHandler.promises.getAssignment(req, res, 'sharing-updates')
+
+ const viewPath =
+ sharingUpdates === 'enabled'
+ ? 'project/token/access-react'
+ : 'project/token/access-react-legacy'
+
+ res.render(viewPath, {
postUrl: makePostUrl(token),
})
} catch (err) {
diff --git a/services/web/app/views/project/invite/show-legacy.pug b/services/web/app/views/project/invite/show-legacy.pug
new file mode 100644
index 0000000000..503ec78796
--- /dev/null
+++ b/services/web/app/views/project/invite/show-legacy.pug
@@ -0,0 +1,35 @@
+extends ../../layout-marketing
+
+block content
+ main#main-content.content.content-alt
+ .container
+ .row
+ .col-12.col-md-8.col-md-offset-2.offset-md-2
+ .card.project-invite-accept
+ .card-body
+ .page-header.text-center
+ h1 #{translate("user_wants_you_to_see_project", {username:owner.first_name, projectname:""})}
+ br
+ em #{project.name}
+ .row.text-center
+ .col-12.col-md-12
+ p
+ | #{translate("accepting_invite_as")}
+ em #{user.email}
+ .row
+ .col-12.col-md-12
+ form.form(
+ data-ol-regular-form
+ method='POST'
+ action='/project/' + invite.projectId + '/invite/token/' + token + '/accept'
+ )
+ input(name='_csrf' type='hidden' value=csrfToken)
+ input(name='token' type='hidden' value=token)
+ .form-group.text-center
+ button.btn.btn-lg.btn-primary(
+ type='submit'
+ data-ol-disabled-inflight
+ )
+ span(data-ol-inflight='idle') #{translate("join_project")}
+ span(hidden data-ol-inflight='pending') #{translate("joining")}…
+ .form-group.text-center
diff --git a/services/web/app/views/project/invite/show.pug b/services/web/app/views/project/invite/show.pug
index 503ec78796..e1b4d608f1 100644
--- a/services/web/app/views/project/invite/show.pug
+++ b/services/web/app/views/project/invite/show.pug
@@ -1,35 +1,17 @@
-extends ../../layout-marketing
+extends ../../layout-website-redesign
+
+block vars
+ - isWebsiteRedesign = true
+
+block entrypointVar
+ - entrypoint = 'pages/project-invite'
+
+block append meta
+ meta(name='ol-user' data-type='json' content=user)
+ meta(name='ol-inviteToken' data-type='string' content=token)
+ meta(name='ol-projectName' data-type='string' content=projectName)
+ meta(name='ol-project_id' data-type='string' content=projectId)
block content
main#main-content.content.content-alt
- .container
- .row
- .col-12.col-md-8.col-md-offset-2.offset-md-2
- .card.project-invite-accept
- .card-body
- .page-header.text-center
- h1 #{translate("user_wants_you_to_see_project", {username:owner.first_name, projectname:""})}
- br
- em #{project.name}
- .row.text-center
- .col-12.col-md-12
- p
- | #{translate("accepting_invite_as")}
- em #{user.email}
- .row
- .col-12.col-md-12
- form.form(
- data-ol-regular-form
- method='POST'
- action='/project/' + invite.projectId + '/invite/token/' + token + '/accept'
- )
- input(name='_csrf' type='hidden' value=csrfToken)
- input(name='token' type='hidden' value=token)
- .form-group.text-center
- button.btn.btn-lg.btn-primary(
- type='submit'
- data-ol-disabled-inflight
- )
- span(data-ol-inflight='idle') #{translate("join_project")}
- span(hidden data-ol-inflight='pending') #{translate("joining")}…
- .form-group.text-center
+ #project-invite-page
diff --git a/services/web/app/views/project/token/access-react-legacy.pug b/services/web/app/views/project/token/access-react-legacy.pug
new file mode 100644
index 0000000000..90d5f9ea4b
--- /dev/null
+++ b/services/web/app/views/project/token/access-react-legacy.pug
@@ -0,0 +1,16 @@
+extends ../../layout-react
+
+block entrypointVar
+ - entrypoint = 'pages/token-access'
+
+block vars
+ - var suppressFooter = true
+ - var suppressPugCookieBanner = true
+ - var suppressSkipToContent = true
+
+block append meta
+ meta(name='ol-postUrl' data-type='string' content=postUrl)
+ meta(name='ol-user' data-type='json' content=user)
+
+block content
+ #token-access-page
diff --git a/services/web/app/views/project/token/access-react.pug b/services/web/app/views/project/token/access-react.pug
index 90d5f9ea4b..3ed897a5c4 100644
--- a/services/web/app/views/project/token/access-react.pug
+++ b/services/web/app/views/project/token/access-react.pug
@@ -1,16 +1,15 @@
-extends ../../layout-react
+extends ../../layout-website-redesign
block entrypointVar
- entrypoint = 'pages/token-access'
-block vars
- - var suppressFooter = true
- - var suppressPugCookieBanner = true
- - var suppressSkipToContent = true
-
block append meta
meta(name='ol-postUrl' data-type='string' content=postUrl)
meta(name='ol-user' data-type='json' content=user)
+block vars
+ - isWebsiteRedesign = true
+
block content
- #token-access-page
+ main#main-content.content.content-alt
+ #token-access-page
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index b969e9c481..df1d98a93b 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -956,6 +956,7 @@
"join_now": "",
"join_overleaf_labs": "",
"join_project": "",
+ "join_project_lowercase": "",
"join_team_explanation": "",
"join_x_enterprise_group": "",
"join_x_managed_enterprise_group": "",
@@ -2337,6 +2338,7 @@
"your_git_access_info_bullet_5": "",
"your_git_access_tokens": "",
"your_message_to_collaborators": "",
+ "your_name_and_email_address_will_be_visible_to_project_editors": "",
"your_name_and_email_address_will_be_visible_to_the_project_owner_and_other_editors": "",
"your_new_plan": "",
"your_password_was_detected": "",
@@ -2362,6 +2364,7 @@
"youre_already_setup_for_sso": "",
"youre_creating_account_for_x_it_will_be_managed_by_y": "",
"youre_joining": "",
+ "youre_joining_x_as_y": "",
"youre_not_eligible_for_a_free_trial": "",
"youre_on_free_trial_which_ends_on": "",
"youre_signed_in_as_logout": "",
diff --git a/services/web/frontend/js/features/share-project/invite-root.tsx b/services/web/frontend/js/features/share-project/invite-root.tsx
new file mode 100644
index 0000000000..d39257211c
--- /dev/null
+++ b/services/web/frontend/js/features/share-project/invite-root.tsx
@@ -0,0 +1,36 @@
+import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
+import getMeta from '@/utils/meta'
+import Invite from '@/features/share-project/invite'
+import useAsync from '@/shared/hooks/use-async'
+import { postJSON } from '@/infrastructure/fetch-json'
+import { useLocation } from '@/shared/hooks/use-location'
+import { debugConsole } from '@/utils/debugging'
+
+export default function InviteRoot() {
+ const user = getMeta('ol-user')
+ const projectName = getMeta('ol-projectName')
+ const projectId = getMeta('ol-project_id')
+ const token = getMeta('ol-inviteToken')
+ const location = useLocation()
+ const { isLoading, runAsync } = useAsync()
+ const { isReady } = useWaitForI18n()
+
+ const handleSubmit = () => {
+ runAsync(postJSON(`/project/${projectId}/invite/token/${token}/accept`))
+ .then(() => location.assign(`/project/${projectId}`))
+ .catch(debugConsole.error)
+ }
+
+ if (!isReady) {
+ return null
+ }
+
+ return (
+