Просмотр исходного кода

Merge branch 'feat/notification' into feat/create-page-snapshot

Shun Miyazawa 4 лет назад
Родитель
Сommit
8f5f1ecdb1

+ 30 - 29
packages/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -1,15 +1,19 @@
-import React, { useState, useEffect, FC } from 'react';
+import React, {
+  useState, useEffect, FC, useCallback,
+} from 'react';
 import {
 import {
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
 } from 'reactstrap';
 } from 'reactstrap';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
+import { apiv3Post } from '~/client/util/apiv3-client';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import InAppNotificationList from './InAppNotificationList';
 import InAppNotificationList from './InAppNotificationList';
 import SocketIoContainer from '~/client/services/SocketIoContainer';
 import SocketIoContainer from '~/client/services/SocketIoContainer';
-import { useSWRxInAppNotifications } from '~/stores/in-app-notification';
+import { useSWRxInAppNotifications, useSWRxInAppNotificationStatus } from '~/stores/in-app-notification';
+
+import { toastError } from '~/client/util/apiNotification';
 
 
 const logger = loggerFactory('growi:InAppNotificationDropdown');
 const logger = loggerFactory('growi:InAppNotificationDropdown');
 
 
@@ -20,57 +24,54 @@ type Props = {
 const InAppNotificationDropdown: FC<Props> = (props: Props) => {
 const InAppNotificationDropdown: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const [count, setCount] = useState(0);
   const [isOpen, setIsOpen] = useState(false);
   const [isOpen, setIsOpen] = useState(false);
   const limit = 6;
   const limit = 6;
-  const { data: inAppNotificationData, mutate } = useSWRxInAppNotifications(limit);
+  const { data: inAppNotificationData, mutate: mutateInAppNotificationData } = useSWRxInAppNotifications(limit);
+  const { data: inAppNotificationUnreadStatusCount, mutate: mutateInAppNotificationUnreadStatusCount } = useSWRxInAppNotificationStatus();
 
 
-  useEffect(() => {
-    initializeSocket(props);
-    fetchNotificationStatus();
-  }, []);
 
 
-  const initializeSocket = (props) => {
+  const initializeSocket = useCallback((props) => {
     const socket = props.socketIoContainer.getSocket();
     const socket = props.socketIoContainer.getSocket();
-    socket.on('notificationUpdated', (data: { userId: string, count: number }) => {
-      setCount(data.count);
+    socket.on('notificationUpdated', () => {
+      mutateInAppNotificationUnreadStatusCount();
     });
     });
-  };
+  }, [mutateInAppNotificationUnreadStatusCount]);
 
 
   const updateNotificationStatus = async() => {
   const updateNotificationStatus = async() => {
     try {
     try {
       await apiv3Post('/in-app-notification/read');
       await apiv3Post('/in-app-notification/read');
-      setCount(0);
     }
     }
     catch (err) {
     catch (err) {
+      toastError(err);
       logger.error(err);
       logger.error(err);
     }
     }
   };
   };
 
 
-  const fetchNotificationStatus = async() => {
-    try {
-      const res = await apiv3Get('/in-app-notification/status');
-      const { count } = res.data;
-      setCount(count);
-    }
-    catch (err) {
-      logger.error(err);
-    }
-  };
+  useEffect(() => {
+    initializeSocket(props);
+  }, [initializeSocket, props]);
+
 
 
-  const toggleDropdownHandler = () => {
-    if (!isOpen && count > 0) {
-      updateNotificationStatus();
+  const toggleDropdownHandler = async() => {
+    if (!isOpen && inAppNotificationUnreadStatusCount != null && inAppNotificationUnreadStatusCount > 0) {
+      await updateNotificationStatus();
+      mutateInAppNotificationUnreadStatusCount();
     }
     }
 
 
     const newIsOpenState = !isOpen;
     const newIsOpenState = !isOpen;
     if (newIsOpenState) {
     if (newIsOpenState) {
-      mutate();
+      mutateInAppNotificationData();
     }
     }
     setIsOpen(newIsOpenState);
     setIsOpen(newIsOpenState);
   };
   };
 
 
-  const badge = count > 0 ? <span className="badge badge-pill badge-danger grw-notification-badge">{count}</span> : '';
+  let badge;
+  if (inAppNotificationUnreadStatusCount != null && inAppNotificationUnreadStatusCount > 0) {
+    badge = <span className="badge badge-pill badge-danger grw-notification-badge">{inAppNotificationUnreadStatusCount}</span>;
+  }
+  else {
+    badge = '';
+  }
 
 
   return (
   return (
     <Dropdown className="notification-wrapper" isOpen={isOpen} toggle={toggleDropdownHandler}>
     <Dropdown className="notification-wrapper" isOpen={isOpen} toggle={toggleDropdownHandler}>

+ 30 - 5
packages/app/src/components/InAppNotification/InAppNotificationPage.tsx

@@ -1,15 +1,21 @@
-import React, { FC, useState } from 'react';
+import React, {
+  FC, useState, useEffect, useCallback,
+} from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import InAppNotificationList from './InAppNotificationList';
 import InAppNotificationList from './InAppNotificationList';
-import { useSWRxInAppNotifications } from '../../stores/in-app-notification';
+import { useSWRxInAppNotifications, useSWRxInAppNotificationStatus } from '../../stores/in-app-notification';
 import PaginationWrapper from '../PaginationWrapper';
 import PaginationWrapper from '../PaginationWrapper';
 import CustomNavAndContents from '../CustomNavigation/CustomNavAndContents';
 import CustomNavAndContents from '../CustomNavigation/CustomNavAndContents';
 import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
-import { apiv3Put } from '~/client/util/apiv3-client';
+import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
+
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:InAppNotificationPage');
 
 
 
 
 type Props = {
 type Props = {
@@ -20,6 +26,21 @@ const InAppNotificationPageBody: FC<Props> = (props) => {
   const { appContainer } = props;
   const { appContainer } = props;
   const limit = appContainer.config.pageLimitationXL;
   const limit = appContainer.config.pageLimitationXL;
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { mutate } = useSWRxInAppNotificationStatus();
+
+  const updateNotificationStatus = useCallback(async() => {
+    try {
+      await apiv3Post('/in-app-notification/read');
+      mutate();
+    }
+    catch (err) {
+      logger.error(err);
+    }
+  }, [mutate]);
+
+  useEffect(() => {
+    updateNotificationStatus();
+  }, [updateNotificationStatus]);
 
 
   const InAppNotificationCategoryByStatus = (status?: InAppNotificationStatuses) => {
   const InAppNotificationCategoryByStatus = (status?: InAppNotificationStatuses) => {
     const [activePage, setActivePage] = useState(1);
     const [activePage, setActivePage] = useState(1);
@@ -34,7 +55,8 @@ const InAppNotificationPageBody: FC<Props> = (props) => {
       default:
       default:
     }
     }
 
 
-    const { data: notificationData, mutate } = useSWRxInAppNotifications(limit, offset, categoryStatus);
+    const { data: notificationData, mutate: mutateNotificationData } = useSWRxInAppNotifications(limit, offset, categoryStatus);
+    const { mutate: mutateAllNotificationData } = useSWRxInAppNotifications(limit, offset, undefined);
 
 
     const setAllNotificationPageNumber = (selectedPageNumber): void => {
     const setAllNotificationPageNumber = (selectedPageNumber): void => {
       setActivePage(selectedPageNumber);
       setActivePage(selectedPageNumber);
@@ -53,7 +75,10 @@ const InAppNotificationPageBody: FC<Props> = (props) => {
 
 
     const updateUnopendNotificationStatusesToOpened = async() => {
     const updateUnopendNotificationStatusesToOpened = async() => {
       await apiv3Put('/in-app-notification/all-statuses-open');
       await apiv3Put('/in-app-notification/all-statuses-open');
-      mutate();
+      // mutate notification statuses in 'UNREAD' Category
+      mutateNotificationData();
+      // mutate notification statuses in 'ALL' Category
+      mutateAllNotificationData();
     };
     };
 
 
 
 

+ 1 - 3
packages/app/src/server/routes/apiv3/in-app-notification.ts

@@ -1,4 +1,3 @@
-import { InAppNotification } from '../../models/in-app-notification';
 import { IInAppNotification } from '../../../interfaces/in-app-notification';
 import { IInAppNotification } from '../../../interfaces/in-app-notification';
 
 
 const express = require('express');
 const express = require('express');
@@ -69,8 +68,7 @@ module.exports = (crowi) => {
     const userId = req.user._id;
     const userId = req.user._id;
     try {
     try {
       const count = await inAppNotificationService.getUnreadCountByUser(userId);
       const count = await inAppNotificationService.getUnreadCountByUser(userId);
-      const result = { count };
-      return res.apiv3(result);
+      return res.apiv3({ count });
     }
     }
     catch (err) {
     catch (err) {
       return res.apiv3Err(err);
       return res.apiv3Err(err);

+ 1 - 2
packages/app/src/server/service/in-app-notification.ts

@@ -42,12 +42,11 @@ export default class InAppNotificationService {
   emitSocketIo = async(targetUsers) => {
   emitSocketIo = async(targetUsers) => {
     if (this.socketIoService.isInitialized) {
     if (this.socketIoService.isInitialized) {
       targetUsers.forEach(async(userId) => {
       targetUsers.forEach(async(userId) => {
-        const count = await this.getUnreadCountByUser(userId);
 
 
         // emit to the room for each user
         // emit to the room for each user
         await this.socketIoService.getDefaultSocket()
         await this.socketIoService.getDefaultSocket()
           .in(getRoomNameWithId(RoomPrefix.USER, userId))
           .in(getRoomNameWithId(RoomPrefix.USER, userId))
-          .emit('notificationUpdated', { userId, count });
+          .emit('notificationUpdated');
       });
       });
     }
     }
   }
   }

+ 9 - 0
packages/app/src/stores/in-app-notification.ts

@@ -13,3 +13,12 @@ export const useSWRxInAppNotifications = <Data, Error>(
     endpoint => apiv3Get(endpoint, { limit, offset, status }).then(response => response.data),
     endpoint => apiv3Get(endpoint, { limit, offset, status }).then(response => response.data),
   );
   );
 };
 };
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const useSWRxInAppNotificationStatus = <Data, Error>(
+): SWRResponse<number, Error> => {
+  return useSWR(
+    ['/in-app-notification/status'],
+    endpoint => apiv3Get(endpoint).then(response => response.data.count),
+  );
+};