Merge pull request #26141 from overleaf/ii-managed-users-consent-screen

[web] Joining managed group from projects page

GitOrigin-RevId: 191203559fba94cad45f35de1af2427b2abb9326
This commit is contained in:
ilkin-overleaf
2025-06-11 08:05:09 +00:00
committed by Copybot
parent 6d202432ff
commit 69e2a57769
5 changed files with 98 additions and 34 deletions
@@ -33,4 +33,26 @@ export default {
res.sendStatus(200)
)
},
getNotification(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
const { notificationId } = req.params
NotificationsHandler.getUserNotifications(
userId,
function (err, unreadNotifications) {
if (err) {
return next(err)
}
const notification = unreadNotifications.find(
n => n._id === notificationId
)
if (!notification) {
return res.status(404).end()
}
res.json(notification)
}
)
},
}
+6
View File
@@ -915,6 +915,12 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
NotificationsController.markNotificationAsRead
)
webRouter.get(
'/user/notification/:notificationId',
AuthenticationController.requireLogin(),
NotificationsController.getNotification
)
// Deprecated in favour of /internal/project/:project_id but still used by versioning
privateApiRouter.get(
'/project/:project_id/details',
@@ -9,6 +9,7 @@ import type { NotificationGroupInvitation } from '../../../../../../../../../typ
import useAsync from '../../../../../../../shared/hooks/use-async'
import {
FetchError,
getJSON,
postJSON,
putJSON,
} from '../../../../../../../infrastructure/fetch-json'
@@ -43,17 +44,12 @@ type UseGroupInvitationNotificationReturnType = {
export function useGroupInvitationNotification(
notification: NotificationGroupInvitation
): UseGroupInvitationNotificationReturnType {
const {
_id: notificationId,
messageOpts: { token, managedUsersEnabled },
} = notification
const { _id: notificationId } = notification
const [groupInvitationStatus, setGroupInvitationStatus] =
useState<GroupInvitationStatus>(GroupInvitationStatus.Idle)
const { runAsync, isLoading: isAcceptingInvitation } = useAsync<
never,
FetchError
>()
const { runAsync, isLoading } = useAsync<void, FetchError>()
const { runAsync: runAsyncNotification, isLoading: isLoadingNotification } =
useAsync<NotificationGroupInvitation, FetchError>()
const location = useLocation()
const { handleDismiss } = useAsyncDismiss()
@@ -72,31 +68,41 @@ export function useGroupInvitationNotification(
}, [hasIndividualPaidSubscription])
const acceptGroupInvite = useCallback(() => {
if (managedUsersEnabled) {
location.assign(`/subscription/invites/${token}/`)
} else {
runAsync(
putJSON(`/subscription/invites/${token}/`, {
body: {
_csrf: getMeta('ol-csrfToken'),
},
})
)
.then(() => {
setGroupInvitationStatus(GroupInvitationStatus.SuccessfullyJoined)
})
.catch(err => {
debugConsole.error(err)
setGroupInvitationStatus(GroupInvitationStatus.Error)
})
.finally(() => {
// remove notification automatically in the browser
window.setTimeout(() => {
setGroupInvitationStatus(GroupInvitationStatus.NotificationIsHidden)
}, SUCCESSFUL_NOTIF_TIME_BEFORE_HIDDEN)
})
}
}, [runAsync, token, location, managedUsersEnabled])
// Fetch the latest notification data to ensure it's up-to-date
runAsyncNotification(getJSON(`/user/notification/${notificationId}`))
.then(notification => {
const {
messageOpts: { token, managedUsersEnabled },
} = notification
if (managedUsersEnabled) {
location.assign(`/subscription/invites/${token}/`)
} else {
runAsync(
putJSON(`/subscription/invites/${token}/`, {
body: {
_csrf: getMeta('ol-csrfToken'),
},
})
)
.then(() => {
setGroupInvitationStatus(GroupInvitationStatus.SuccessfullyJoined)
})
.catch(err => {
debugConsole.error(err)
setGroupInvitationStatus(GroupInvitationStatus.Error)
})
.finally(() => {
// remove notification automatically in the browser
window.setTimeout(() => {
setGroupInvitationStatus(
GroupInvitationStatus.NotificationIsHidden
)
}, SUCCESSFUL_NOTIF_TIME_BEFORE_HIDDEN)
})
}
})
.catch(debugConsole.error)
}, [runAsync, runAsyncNotification, notificationId, location])
const cancelPersonalSubscription = useCallback(() => {
setGroupInvitationStatus(GroupInvitationStatus.AskToJoin)
@@ -114,6 +120,8 @@ export function useGroupInvitationNotification(
setGroupInvitationStatus(GroupInvitationStatus.NotificationIsHidden)
}, [])
const isAcceptingInvitation = isLoadingNotification || isLoading
return {
isAcceptingInvitation,
groupInvitationStatus,
@@ -27,6 +27,10 @@ describe('<GroupInvitationNotification />', function () {
}
beforeEach(function () {
cy.intercept('GET', `/user/notification/${notification._id}`, {
statusCode: 200,
body: notification,
}).as('getNotification')
cy.intercept(
'PUT',
`/subscription/invites/${notification.messageOpts.token}`,
@@ -48,6 +52,7 @@ describe('<GroupInvitationNotification />', function () {
cy.findByRole('button', { name: 'Join now' }).click()
cy.wait('@getNotification')
cy.wait('@acceptInvite')
cy.findByText(
@@ -82,6 +87,7 @@ describe('<GroupInvitationNotification />', function () {
cy.findByRole('button', { name: 'Join now' }).click()
cy.wait('@getNotification')
cy.wait('@acceptInvite')
cy.findByText(
@@ -116,6 +122,7 @@ describe('<GroupInvitationNotification />', function () {
cy.findByRole('button', { name: 'Join now' }).click()
cy.wait('@getNotification')
cy.wait('@acceptInvite')
cy.findByText(
@@ -14,6 +14,9 @@ describe('NotificationsController', function () {
ctx.handler = {
getUserNotifications: sinon.stub().callsArgWith(1),
markAsRead: sinon.stub().callsArgWith(2),
promises: {
getUserNotifications: sinon.stub().callsArgWith(1),
},
}
ctx.req = {
params: {
@@ -77,4 +80,22 @@ describe('NotificationsController', function () {
})
})
})
it('should get a notification by notification id', function (ctx) {
return new Promise(resolve => {
const notification = { _id: notificationId, user_id: userId }
ctx.handler.getUserNotifications = sinon
.stub()
.callsArgWith(1, null, [notification])
ctx.controller.getNotification(ctx.req, {
json: body => {
body.should.deep.equal(notification)
resolve()
},
status: () => ({
end: () => {},
}),
})
})
})
})