Răsfoiți Sursa

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

[web] Joining managed group from projects page

GitOrigin-RevId: 191203559fba94cad45f35de1af2427b2abb9326
ilkin-overleaf 1 an în urmă
părinte
comite
69e2a57769

+ 22 - 0
services/web/app/src/Features/Notifications/NotificationsController.mjs

@@ -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 - 0
services/web/app/src/router.mjs

@@ -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',

+ 42 - 34
services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx

@@ -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,

+ 7 - 0
services/web/test/frontend/components/project-list/notifications/group-invitation.spec.tsx

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

+ 21 - 0
services/web/test/unit/src/Notifications/NotificationsController.test.mjs

@@ -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: () => {},
+        }),
+      })
+    })
+  })
 })