use-project-notification-preferences.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { useCallback, useEffect, useState } from 'react'
  2. import { useProjectContext } from '@/shared/context/project-context'
  3. import { getJSON, postJSON } from '@/infrastructure/fetch-json'
  4. import { debugConsole } from '@/utils/debugging'
  5. import type {
  6. GlobalNotificationPreferencesSchema,
  7. NotificationPreferencesSchema,
  8. } from '../../../../../modules/notifications/app/src/types.js'
  9. import { sendMB } from '@/infrastructure/event-tracking'
  10. import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
  11. import { type PermissionsLevel } from '@/features/ide-react/types/permissions'
  12. export type SettableNotificationLevel = 'all' | 'replies' | 'off'
  13. export type NotificationLevel = SettableNotificationLevel | 'global-off'
  14. /**
  15. * Map UI notification level to backend preferences
  16. */
  17. function levelToPreferences(
  18. level: SettableNotificationLevel
  19. ): NotificationPreferencesSchema {
  20. switch (level) {
  21. case 'all':
  22. return {
  23. commentOnOwnProject: true,
  24. commentOnInvitedProject: true,
  25. repliesOnAuthoredThread: true,
  26. repliesOnParticipatingThread: true,
  27. commentResolvedOnAuthoredThread: true,
  28. commentResolvedOnParticipatingThread: true,
  29. commentReopenedOnAuthoredThread: true,
  30. commentReopenedOnParticipatingThread: true,
  31. trackedChangesOnOwnProject: true,
  32. trackedChangesOnInvitedProject: true,
  33. trackChangesAcceptedOnAuthoredChange: true,
  34. trackChangesRejectedOnAuthoredChange: true,
  35. }
  36. case 'replies':
  37. return {
  38. commentOnOwnProject: false,
  39. commentOnInvitedProject: false,
  40. repliesOnAuthoredThread: true,
  41. repliesOnParticipatingThread: true,
  42. commentResolvedOnAuthoredThread: true,
  43. commentResolvedOnParticipatingThread: true,
  44. commentReopenedOnAuthoredThread: true,
  45. commentReopenedOnParticipatingThread: true,
  46. trackedChangesOnOwnProject: false,
  47. trackedChangesOnInvitedProject: false,
  48. trackChangesAcceptedOnAuthoredChange: true,
  49. trackChangesRejectedOnAuthoredChange: true,
  50. }
  51. case 'off':
  52. return {
  53. commentOnOwnProject: false,
  54. commentOnInvitedProject: false,
  55. repliesOnAuthoredThread: false,
  56. repliesOnParticipatingThread: false,
  57. commentResolvedOnAuthoredThread: false,
  58. commentResolvedOnParticipatingThread: false,
  59. commentReopenedOnAuthoredThread: false,
  60. commentReopenedOnParticipatingThread: false,
  61. trackedChangesOnOwnProject: false,
  62. trackedChangesOnInvitedProject: false,
  63. trackChangesAcceptedOnAuthoredChange: false,
  64. trackChangesRejectedOnAuthoredChange: false,
  65. }
  66. }
  67. }
  68. /**
  69. * Map backend preferences to UI notification level, considering the user's
  70. * role so that only the relevant key variant is inspected.
  71. */
  72. function preferencesToLevel(
  73. preferences: GlobalNotificationPreferencesSchema,
  74. permissionsLevel: PermissionsLevel
  75. ): NotificationLevel {
  76. if (preferences.muteAllNotifications) {
  77. return 'global-off'
  78. }
  79. const isOwner = permissionsLevel === 'owner'
  80. const projectComments = isOwner
  81. ? preferences.commentOnOwnProject
  82. : preferences.commentOnInvitedProject
  83. const projectTrackedChanges = isOwner
  84. ? preferences.trackedChangesOnOwnProject
  85. : preferences.trackedChangesOnInvitedProject
  86. const anyProjectNotifications = projectComments || projectTrackedChanges
  87. const anyParticipantNotifications =
  88. preferences.repliesOnAuthoredThread ||
  89. preferences.repliesOnParticipatingThread ||
  90. preferences.trackChangesAcceptedOnAuthoredChange ||
  91. preferences.trackChangesRejectedOnAuthoredChange ||
  92. preferences.commentResolvedOnAuthoredThread ||
  93. preferences.commentResolvedOnParticipatingThread ||
  94. preferences.commentReopenedOnAuthoredThread ||
  95. preferences.commentReopenedOnParticipatingThread
  96. if (!anyProjectNotifications && !anyParticipantNotifications) {
  97. return 'off'
  98. }
  99. if (!anyProjectNotifications && anyParticipantNotifications) {
  100. return 'replies'
  101. }
  102. return 'all'
  103. }
  104. export function useProjectNotificationPreferences() {
  105. const { projectId } = useProjectContext()
  106. const { permissionsLevel } = useIdeReactContext()
  107. const [notificationLevel, setNotificationLevel] =
  108. useState<NotificationLevel>('all')
  109. const [isLoading, setIsLoading] = useState(true)
  110. // Load preferences on mount
  111. useEffect(() => {
  112. getJSON<GlobalNotificationPreferencesSchema>(
  113. `/notifications/preferences/project/${projectId}`
  114. )
  115. .then(prefs => {
  116. setNotificationLevel(preferencesToLevel(prefs, permissionsLevel))
  117. })
  118. .catch(debugConsole.error)
  119. .finally(() => setIsLoading(false))
  120. }, [projectId, permissionsLevel])
  121. const setLevel = useCallback(
  122. (level: SettableNotificationLevel) => {
  123. setNotificationLevel(level)
  124. const preferences = levelToPreferences(level)
  125. sendMB('setting-changed', {
  126. changedSetting: 'projectEmailNotifications',
  127. changedSettingVal: level,
  128. projectRole: permissionsLevel,
  129. })
  130. postJSON(`/notifications/preferences/project/${projectId}`, {
  131. body: preferences,
  132. }).catch(debugConsole.error)
  133. },
  134. [projectId, permissionsLevel]
  135. )
  136. return {
  137. notificationLevel,
  138. setNotificationLevel: setLevel,
  139. isLoading,
  140. }
  141. }