InAppNotificationElm.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import React, {
  2. FC, useRef,
  3. } from 'react';
  4. import { HasObjectId } from '@growi/core';
  5. import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
  6. import { DropdownItem } from 'reactstrap';
  7. import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
  8. import { apiv3Post } from '~/client/util/apiv3-client';
  9. import { SupportedTargetModel } from '~/interfaces/activity';
  10. import { IInAppNotification, InAppNotificationStatuses } from '~/interfaces/in-app-notification';
  11. // Change the display for each targetmodel
  12. import PageModelNotification from './PageNotification/PageModelNotification';
  13. import UserModelNotification from './PageNotification/UserModelNotification';
  14. interface Props {
  15. notification: IInAppNotification & HasObjectId
  16. elemClassName?: string,
  17. type?: 'button' | 'dropdown-item',
  18. }
  19. const InAppNotificationElm: FC<Props> = (props: Props) => {
  20. const { notification } = props;
  21. const notificationRef = useRef<IInAppNotificationOpenable>(null);
  22. const clickHandler = async(notification: IInAppNotification & HasObjectId): Promise<void> => {
  23. if (notification.status === InAppNotificationStatuses.STATUS_UNOPENED) {
  24. // set notification status "OPEND"
  25. await apiv3Post('/in-app-notification/open', { id: notification._id });
  26. }
  27. const currentInstance = notificationRef.current;
  28. if (currentInstance != null) {
  29. currentInstance.open();
  30. }
  31. };
  32. const getActionUsers = () => {
  33. if (notification.targetModel === SupportedTargetModel.MODEL_USER) {
  34. return notification.target.username;
  35. }
  36. const latestActionUsers = notification.actionUsers.slice(0, 3);
  37. const latestUsers = latestActionUsers.map((user) => {
  38. return `@${user.name}`;
  39. });
  40. let actionedUsers = '';
  41. const latestUsersCount = latestUsers.length;
  42. if (latestUsersCount === 1) {
  43. actionedUsers = latestUsers[0];
  44. }
  45. else if (notification.actionUsers.length >= 4) {
  46. actionedUsers = `${latestUsers.slice(0, 2).join(', ')} and ${notification.actionUsers.length - 2} others`;
  47. }
  48. else {
  49. actionedUsers = latestUsers.join(', ');
  50. }
  51. return actionedUsers;
  52. };
  53. const renderActionUserPictures = (): JSX.Element => {
  54. const actionUsers = notification.actionUsers;
  55. if (actionUsers.length < 1) {
  56. return <></>;
  57. }
  58. if (actionUsers.length === 1) {
  59. return <UserPicture user={actionUsers[0]} size="md" noTooltip />;
  60. }
  61. return (
  62. <div className="position-relative">
  63. <UserPicture user={actionUsers[0]} size="md" noTooltip />
  64. <div className="position-absolute" style={{ top: 10, left: 10 }}>
  65. <UserPicture user={actionUsers[1]} size="md" noTooltip />
  66. </div>
  67. </div>
  68. );
  69. };
  70. const actionUsers = getActionUsers();
  71. const actionType: string = notification.action;
  72. let actionMsg: string;
  73. let actionIcon: string;
  74. switch (actionType) {
  75. case 'PAGE_LIKE':
  76. actionMsg = 'liked';
  77. actionIcon = 'icon-like';
  78. break;
  79. case 'PAGE_BOOKMARK':
  80. actionMsg = 'bookmarked on';
  81. actionIcon = 'icon-star';
  82. break;
  83. case 'PAGE_UPDATE':
  84. actionMsg = 'updated on';
  85. actionIcon = 'ti ti-agenda';
  86. break;
  87. case 'PAGE_RENAME':
  88. actionMsg = 'renamed';
  89. actionIcon = 'icon-action-redo';
  90. break;
  91. case 'PAGE_DUPLICATE':
  92. actionMsg = 'duplicated';
  93. actionIcon = 'icon-docs';
  94. break;
  95. case 'PAGE_DELETE':
  96. actionMsg = 'deleted';
  97. actionIcon = 'icon-trash';
  98. break;
  99. case 'PAGE_DELETE_COMPLETELY':
  100. actionMsg = 'completely deleted';
  101. actionIcon = 'icon-fire';
  102. break;
  103. case 'PAGE_REVERT':
  104. actionMsg = 'reverted';
  105. actionIcon = 'icon-action-undo';
  106. break;
  107. case 'PAGE_RECURSIVELY_RENAME':
  108. actionMsg = 'renamed under';
  109. actionIcon = 'icon-action-redo';
  110. break;
  111. case 'PAGE_RECURSIVELY_DELETE':
  112. actionMsg = 'deleted under';
  113. actionIcon = 'icon-trash';
  114. break;
  115. case 'PAGE_RECURSIVELY_DELETE_COMPLETELY':
  116. actionMsg = 'deleted completely under';
  117. actionIcon = 'icon-fire';
  118. break;
  119. case 'PAGE_RECURSIVELY_REVERT':
  120. actionMsg = 'reverted under';
  121. actionIcon = 'icon-action-undo';
  122. break;
  123. case 'COMMENT_CREATE':
  124. actionMsg = 'commented on';
  125. actionIcon = 'icon-bubble';
  126. break;
  127. case 'USER_REGISTRATION_APPROVAL_REQUEST':
  128. actionMsg = 'requested registration approval';
  129. actionIcon = 'icon-bubble';
  130. break;
  131. default:
  132. actionMsg = '';
  133. actionIcon = '';
  134. }
  135. const isDropdownItem = props.type === 'dropdown-item';
  136. // determine tag
  137. const TagElem = isDropdownItem
  138. ? DropdownItem
  139. // eslint-disable-next-line react/prop-types
  140. : props => <button type="button" {...props}>{props.children}</button>;
  141. return (
  142. <TagElem className={props.elemClassName} onClick={() => clickHandler(notification)}>
  143. <div className="d-flex align-items-center">
  144. <span
  145. className={`${notification.status === InAppNotificationStatuses.STATUS_UNOPENED
  146. ? 'grw-unopend-notification'
  147. : 'ml-2'
  148. } rounded-circle mr-3`}
  149. >
  150. </span>
  151. {renderActionUserPictures()}
  152. {notification.targetModel === SupportedTargetModel.MODEL_PAGE && (
  153. <PageModelNotification
  154. ref={notificationRef}
  155. notification={notification}
  156. actionMsg={actionMsg}
  157. actionIcon={actionIcon}
  158. actionUsers={actionUsers}
  159. />
  160. )}
  161. {notification.targetModel === SupportedTargetModel.MODEL_USER && (
  162. <UserModelNotification
  163. ref={notificationRef}
  164. notification={notification}
  165. actionMsg={actionMsg}
  166. actionIcon={actionIcon}
  167. actionUsers={actionUsers}
  168. />
  169. )}
  170. </div>
  171. </TagElem>
  172. );
  173. };
  174. export default InAppNotificationElm;