InAppNotificationDropdown.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import React, { useState, useEffect, useRef } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import { useRipple } from 'react-use-ripple';
  4. import {
  5. Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
  6. } from 'reactstrap';
  7. import { apiv3Post } from '~/client/util/apiv3-client';
  8. import { toastError } from '~/client/util/toastr';
  9. import { useSWRxInAppNotifications, useSWRxInAppNotificationStatus } from '~/stores/in-app-notification';
  10. import { useDefaultSocket } from '~/stores/socket-io';
  11. import loggerFactory from '~/utils/logger';
  12. import InAppNotificationList from './InAppNotificationList';
  13. const logger = loggerFactory('growi:InAppNotificationDropdown');
  14. export const InAppNotificationDropdown = (): JSX.Element => {
  15. const { t } = useTranslation('commons');
  16. const [isOpen, setIsOpen] = useState(false);
  17. const limit = 6;
  18. const { data: socket } = useDefaultSocket();
  19. const { data: inAppNotificationData, mutate: mutateInAppNotificationData } = useSWRxInAppNotifications(
  20. limit, undefined, undefined,
  21. { revalidateOnFocus: isOpen },
  22. );
  23. const { data: inAppNotificationUnreadStatusCount, mutate: mutateInAppNotificationUnreadStatusCount } = useSWRxInAppNotificationStatus();
  24. // ripple
  25. const buttonRef = useRef(null);
  26. useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  27. const updateNotificationStatus = async() => {
  28. try {
  29. await apiv3Post('/in-app-notification/read');
  30. }
  31. catch (err) {
  32. toastError(err);
  33. logger.error(err);
  34. }
  35. };
  36. useEffect(() => {
  37. if (socket != null) {
  38. socket.on('notificationUpdated', () => {
  39. mutateInAppNotificationUnreadStatusCount();
  40. });
  41. // clean up
  42. return () => {
  43. socket.off('notificationUpdated');
  44. };
  45. }
  46. }, [mutateInAppNotificationUnreadStatusCount, socket]);
  47. const toggleDropdownHandler = async() => {
  48. if (!isOpen && inAppNotificationUnreadStatusCount != null && inAppNotificationUnreadStatusCount > 0) {
  49. await updateNotificationStatus();
  50. mutateInAppNotificationUnreadStatusCount();
  51. }
  52. const newIsOpenState = !isOpen;
  53. if (newIsOpenState) {
  54. mutateInAppNotificationData();
  55. }
  56. setIsOpen(newIsOpenState);
  57. };
  58. let badge;
  59. if (inAppNotificationUnreadStatusCount != null && inAppNotificationUnreadStatusCount > 0) {
  60. badge = <span className="badge rounded-pill bg-danger grw-notification-badge">{inAppNotificationUnreadStatusCount}</span>;
  61. }
  62. else {
  63. badge = '';
  64. }
  65. return (
  66. <Dropdown className="notification-wrapper grw-notification-dropdown" isOpen={isOpen} toggle={toggleDropdownHandler} direction="end">
  67. <DropdownToggle className="px-3" color="primary" innerRef={buttonRef}>
  68. <span className="material-symbols-outlined">notifications</span> {badge}
  69. </DropdownToggle>
  70. <DropdownMenu end>
  71. { inAppNotificationData != null && inAppNotificationData.docs.length === 0
  72. // no items
  73. ? <DropdownItem disabled>{t('in_app_notification.mark_all_as_read')}</DropdownItem>
  74. // render DropdownItem
  75. : <InAppNotificationList inAppNotificationData={inAppNotificationData} />
  76. }
  77. <DropdownItem divider />
  78. <DropdownItem tag="a" href="/me/all-in-app-notifications">
  79. { t('in_app_notification.see_all') }
  80. </DropdownItem>
  81. </DropdownMenu>
  82. </Dropdown>
  83. );
  84. };