@servicetitan/notifications 28.5.0 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/__tests__/intercept.test.d.ts +2 -0
  2. package/dist/__tests__/intercept.test.d.ts.map +1 -0
  3. package/dist/__tests__/intercept.test.js +13 -0
  4. package/dist/__tests__/intercept.test.js.map +1 -0
  5. package/dist/__tests__/notifications-service.test.d.ts +2 -0
  6. package/dist/__tests__/notifications-service.test.d.ts.map +1 -0
  7. package/dist/__tests__/notifications-service.test.js +42 -0
  8. package/dist/__tests__/notifications-service.test.js.map +1 -0
  9. package/dist/api/notifications.api.d.ts +2 -2
  10. package/dist/api/notifications.api.d.ts.map +1 -1
  11. package/dist/common.d.ts +4 -22
  12. package/dist/common.d.ts.map +1 -1
  13. package/dist/common.js +4 -11
  14. package/dist/common.js.map +1 -1
  15. package/dist/components/__tests__/notifications.test.d.ts +2 -0
  16. package/dist/components/__tests__/notifications.test.d.ts.map +1 -0
  17. package/dist/components/__tests__/notifications.test.js +93 -0
  18. package/dist/components/__tests__/notifications.test.js.map +1 -0
  19. package/dist/components/notifications.d.ts +8 -1
  20. package/dist/components/notifications.d.ts.map +1 -1
  21. package/dist/components/notifications.js +43 -23
  22. package/dist/components/notifications.js.map +1 -1
  23. package/dist/demo/action-button-preview.d.ts.map +1 -1
  24. package/dist/demo/action-button-preview.js +2 -2
  25. package/dist/demo/action-button-preview.js.map +1 -1
  26. package/dist/demo/basic-preview.d.ts.map +1 -1
  27. package/dist/demo/basic-preview.js +1 -1
  28. package/dist/demo/basic-preview.js.map +1 -1
  29. package/dist/demo/container.d.ts +1 -3
  30. package/dist/demo/container.d.ts.map +1 -1
  31. package/dist/demo/container.js.map +1 -1
  32. package/dist/demo/duration-preview.d.ts.map +1 -1
  33. package/dist/demo/duration-preview.js +2 -2
  34. package/dist/demo/duration-preview.js.map +1 -1
  35. package/dist/demo/multiline-message-preview.d.ts.map +1 -1
  36. package/dist/demo/multiline-message-preview.js +2 -2
  37. package/dist/demo/multiline-message-preview.js.map +1 -1
  38. package/dist/demo/prevent-duplicates-preview.d.ts.map +1 -1
  39. package/dist/demo/prevent-duplicates-preview.js +2 -2
  40. package/dist/demo/prevent-duplicates-preview.js.map +1 -1
  41. package/dist/demo/progress-preview.d.ts.map +1 -1
  42. package/dist/demo/progress-preview.js +2 -2
  43. package/dist/demo/progress-preview.js.map +1 -1
  44. package/dist/demo/server-custom-preview.d.ts +1 -1
  45. package/dist/demo/server-custom-preview.d.ts.map +1 -1
  46. package/dist/demo/server-custom-preview.js +21 -7
  47. package/dist/demo/server-custom-preview.js.map +1 -1
  48. package/dist/demo/server-custom.d.ts +0 -1
  49. package/dist/demo/server-custom.d.ts.map +1 -1
  50. package/dist/demo/server-custom.js +25 -21
  51. package/dist/demo/server-custom.js.map +1 -1
  52. package/dist/demo/server-default.d.ts.map +1 -1
  53. package/dist/demo/server-default.js +1 -1
  54. package/dist/demo/server-default.js.map +1 -1
  55. package/dist/demo/status-variations-preview.d.ts.map +1 -1
  56. package/dist/demo/status-variations-preview.js +2 -2
  57. package/dist/demo/status-variations-preview.js.map +1 -1
  58. package/dist/demo/status-variations.d.ts.map +1 -1
  59. package/dist/demo/status-variations.js.map +1 -1
  60. package/dist/index.d.ts +3 -4
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -4
  63. package/dist/index.js.map +1 -1
  64. package/dist/intercept.d.ts +4 -0
  65. package/dist/intercept.d.ts.map +1 -0
  66. package/dist/intercept.js +5 -0
  67. package/dist/intercept.js.map +1 -0
  68. package/dist/notifications-channel.d.ts +2 -6
  69. package/dist/notifications-channel.d.ts.map +1 -1
  70. package/dist/notifications-channel.js.map +1 -1
  71. package/dist/notifications-service.d.ts +10 -8
  72. package/dist/notifications-service.d.ts.map +1 -1
  73. package/dist/notifications-service.js.map +1 -1
  74. package/dist/notifications.stories.d.ts +14 -0
  75. package/dist/notifications.stories.d.ts.map +1 -0
  76. package/dist/notifications.stories.js +20 -0
  77. package/dist/notifications.stories.js.map +1 -0
  78. package/dist/stores/__mocks__/mock-notifications-channel.d.ts +11 -0
  79. package/dist/stores/__mocks__/mock-notifications-channel.d.ts.map +1 -0
  80. package/dist/stores/__mocks__/mock-notifications-channel.js +36 -0
  81. package/dist/stores/__mocks__/mock-notifications-channel.js.map +1 -0
  82. package/dist/stores/__tests__/notifications.store.test.d.ts +2 -0
  83. package/dist/stores/__tests__/notifications.store.test.d.ts.map +1 -0
  84. package/dist/stores/__tests__/notifications.store.test.js +360 -0
  85. package/dist/stores/__tests__/notifications.store.test.js.map +1 -0
  86. package/dist/stores/notifications.store.d.ts +20 -8
  87. package/dist/stores/notifications.store.d.ts.map +1 -1
  88. package/dist/stores/notifications.store.js +156 -45
  89. package/dist/stores/notifications.store.js.map +1 -1
  90. package/dist/utils/__tests__/date-from-string.test.d.ts +2 -0
  91. package/dist/utils/__tests__/date-from-string.test.d.ts.map +1 -0
  92. package/dist/utils/__tests__/date-from-string.test.js +39 -0
  93. package/dist/utils/__tests__/date-from-string.test.js.map +1 -0
  94. package/dist/utils/__tests__/use-compatible-navigate.test.d.ts +2 -0
  95. package/dist/utils/__tests__/use-compatible-navigate.test.d.ts.map +1 -0
  96. package/dist/utils/__tests__/use-compatible-navigate.test.js +27 -0
  97. package/dist/utils/__tests__/use-compatible-navigate.test.js.map +1 -0
  98. package/dist/utils/date-from-string.d.ts.map +1 -0
  99. package/dist/utils/date-from-string.js +19 -0
  100. package/dist/utils/date-from-string.js.map +1 -0
  101. package/package.json +6 -6
  102. package/src/__tests__/intercept.test.ts +18 -0
  103. package/src/__tests__/notifications-service.test.ts +62 -0
  104. package/src/api/notifications.api.ts +2 -2
  105. package/src/common.ts +12 -27
  106. package/src/components/__tests__/notifications.test.tsx +107 -0
  107. package/src/components/notifications.tsx +59 -36
  108. package/src/demo/action-button-preview.tsx +4 -6
  109. package/src/demo/basic-preview.tsx +2 -4
  110. package/src/demo/container.tsx +1 -4
  111. package/src/demo/duration-preview.tsx +4 -6
  112. package/src/demo/multiline-message-preview.tsx +3 -9
  113. package/src/demo/prevent-duplicates-preview.tsx +3 -9
  114. package/src/demo/progress-preview.tsx +3 -9
  115. package/src/demo/server-custom-preview.tsx +17 -14
  116. package/src/demo/server-custom.tsx +30 -29
  117. package/src/demo/server-default.tsx +1 -3
  118. package/src/demo/status-variations-preview.tsx +4 -6
  119. package/src/demo/status-variations.tsx +0 -1
  120. package/src/index.ts +13 -4
  121. package/src/intercept.ts +14 -0
  122. package/src/notifications-channel.ts +2 -6
  123. package/src/notifications-service.ts +14 -42
  124. package/src/stores/__mocks__/mock-notifications-channel.ts +31 -0
  125. package/src/stores/__tests__/notifications.store.test.ts +458 -0
  126. package/src/stores/notifications.store.ts +178 -53
  127. package/src/utils/__tests__/date-from-string.test.ts +53 -0
  128. package/src/utils/__tests__/use-compatible-navigate.test.ts +43 -0
  129. package/src/utils/date-from-string.ts +22 -0
  130. package/dist/components/__tests__/container.test.d.ts +0 -2
  131. package/dist/components/__tests__/container.test.d.ts.map +0 -1
  132. package/dist/components/__tests__/container.test.js +0 -59
  133. package/dist/components/__tests__/container.test.js.map +0 -1
  134. package/dist/components/container.d.ts +0 -3
  135. package/dist/components/container.d.ts.map +0 -1
  136. package/dist/components/container.js +0 -26
  137. package/dist/components/container.js.map +0 -1
  138. package/dist/components/default-notification.d.ts +0 -7
  139. package/dist/components/default-notification.d.ts.map +0 -1
  140. package/dist/components/default-notification.js +0 -26
  141. package/dist/components/default-notification.js.map +0 -1
  142. package/dist/components/default-notification.module.css +0 -3
  143. package/dist/components/no-ssr.d.ts +0 -5
  144. package/dist/components/no-ssr.d.ts.map +0 -1
  145. package/dist/components/no-ssr.js +0 -9
  146. package/dist/components/no-ssr.js.map +0 -1
  147. package/dist/components/notifications-unwrapped.d.ts +0 -3
  148. package/dist/components/notifications-unwrapped.d.ts.map +0 -1
  149. package/dist/components/notifications-unwrapped.js +0 -27
  150. package/dist/components/notifications-unwrapped.js.map +0 -1
  151. package/dist/components/shadow-dom.d.ts +0 -3
  152. package/dist/components/shadow-dom.d.ts.map +0 -1
  153. package/dist/components/shadow-dom.js +0 -41
  154. package/dist/components/shadow-dom.js.map +0 -1
  155. package/dist/components/use-style-sheets.d.ts +0 -2
  156. package/dist/components/use-style-sheets.d.ts.map +0 -1
  157. package/dist/components/use-style-sheets.js +0 -15
  158. package/dist/components/use-style-sheets.js.map +0 -1
  159. package/dist/create-element.d.ts +0 -2
  160. package/dist/create-element.d.ts.map +0 -1
  161. package/dist/create-element.js +0 -8
  162. package/dist/create-element.js.map +0 -1
  163. package/dist/date-from-string.d.ts.map +0 -1
  164. package/dist/date-from-string.js +0 -27
  165. package/dist/date-from-string.js.map +0 -1
  166. package/dist/register.d.ts +0 -3
  167. package/dist/register.d.ts.map +0 -1
  168. package/dist/register.js +0 -5
  169. package/dist/register.js.map +0 -1
  170. package/src/components/__tests__/container.test.tsx +0 -71
  171. package/src/components/container.tsx +0 -35
  172. package/src/components/default-notification.module.css +0 -3
  173. package/src/components/default-notification.module.css.d.ts +0 -4
  174. package/src/components/default-notification.tsx +0 -59
  175. package/src/components/no-ssr.tsx +0 -11
  176. package/src/components/notifications-unwrapped.tsx +0 -51
  177. package/src/components/shadow-dom.tsx +0 -53
  178. package/src/components/use-style-sheets.ts +0 -19
  179. package/src/create-element.ts +0 -12
  180. package/src/date-from-string.ts +0 -30
  181. package/src/register.ts +0 -6
  182. /package/dist/{date-from-string.d.ts → utils/date-from-string.d.ts} +0 -0
@@ -0,0 +1,107 @@
1
+ import { injectable, useOptionalDependencies } from '@servicetitan/react-ioc';
2
+ import { mockComponent } from '@servicetitan/testing-library';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { FC, Fragment } from 'react';
5
+
6
+ import { NotificationsApi } from '../../api/notifications.api';
7
+ import { NotificationsStore } from '../../stores/notifications.store';
8
+ import { NotificationsService } from '../../notifications-service';
9
+ import { useCompatibleNavigate } from '../../utils/use-compatible-navigate';
10
+
11
+ import { Notifications } from '../notifications';
12
+
13
+ jest.mock('@servicetitan/anvil2', () => mockComponent('Toaster', { renderProps: false }));
14
+ jest.mock('../../utils/use-compatible-navigate', () => ({
15
+ useCompatibleNavigate: jest.fn(),
16
+ }));
17
+
18
+ @injectable()
19
+ class MockNotificationsApi implements NotificationsApi {
20
+ getNotification = jest.fn();
21
+ getNotifications = jest.fn();
22
+ changeIsReadProperty = jest.fn();
23
+ }
24
+
25
+ describe(Notifications.name, () => {
26
+ let providedApi: NotificationsApi | undefined;
27
+ let providedService: NotificationsService | undefined;
28
+ let providedStore: NotificationsStore | undefined;
29
+ let props: Parameters<typeof Notifications>[0];
30
+
31
+ const Component: FC = () => {
32
+ [providedApi, providedService, providedStore] = useOptionalDependencies(
33
+ NotificationsApi,
34
+ NotificationsService,
35
+ NotificationsStore
36
+ );
37
+ return null;
38
+ };
39
+
40
+ beforeEach(() => (props = {}));
41
+
42
+ const subject = () =>
43
+ render(
44
+ <Notifications {...props}>
45
+ <Component />
46
+ </Notifications>
47
+ );
48
+
49
+ test('renders Toaster', () => {
50
+ subject();
51
+
52
+ expect(screen).toContainComponent('Toaster');
53
+ });
54
+
55
+ test('provides NotificationService', () => {
56
+ subject();
57
+
58
+ expect(providedService).toBeInstanceOf(NotificationsService);
59
+ });
60
+
61
+ test('provides NotificationStore', () => {
62
+ subject();
63
+
64
+ expect(providedStore).toBeInstanceOf(NotificationsStore);
65
+ });
66
+
67
+ test('sets store.navigate to useCompatibleNavigate result', () => {
68
+ const navigate = jest.fn();
69
+ jest.mocked(useCompatibleNavigate).mockReturnValue(navigate);
70
+
71
+ subject();
72
+
73
+ expect(providedStore!.navigate).toBe(navigate);
74
+ });
75
+
76
+ test('disposes store when component is unmounted', () => {
77
+ const { unmount } = subject();
78
+ const disposeSpy = jest.spyOn(providedStore!, 'dispose');
79
+
80
+ unmount();
81
+
82
+ expect(disposeSpy).toHaveBeenCalled();
83
+ });
84
+
85
+ test('renders one Toaster when there are are multiple notifications', () => {
86
+ render(
87
+ <Fragment>
88
+ <Notifications>
89
+ <Notifications />
90
+ </Notifications>
91
+ <Notifications />
92
+ </Fragment>
93
+ );
94
+
95
+ expect(screen.getAllByText('<Toaster />')).toHaveLength(1);
96
+ });
97
+
98
+ describe('when given an apiService', () => {
99
+ beforeEach(() => (props.apiService = MockNotificationsApi));
100
+
101
+ test('provides NotificationsApi', () => {
102
+ subject();
103
+
104
+ expect(providedApi).toBeInstanceOf(MockNotificationsApi);
105
+ });
106
+ });
107
+ });
@@ -1,25 +1,19 @@
1
- import { FC, PropsWithChildren } from 'react';
2
-
3
- import { Provider, interfaces } from '@servicetitan/react-ioc';
4
-
1
+ import { Toaster } from '@servicetitan/anvil2';
2
+ import { Provider, interfaces, useDependencies } from '@servicetitan/react-ioc';
5
3
  import {
6
- NotificationsApi,
7
- DefaultServerNotificationPayload,
8
- NotificationProcessStatus,
9
- } from '../api/notifications.api';
4
+ FC,
5
+ PropsWithChildren,
6
+ useCallback,
7
+ useEffect,
8
+ useRef,
9
+ useState,
10
+ useSyncExternalStore,
11
+ } from 'react';
12
+
13
+ import { NotificationsApi } from '../api/notifications.api';
10
14
  import { NotificationsStore } from '../stores/notifications.store';
15
+ import { useCompatibleNavigate } from '../utils/use-compatible-navigate';
11
16
  import { NotificationsService } from '../notifications-service';
12
- import { register } from '../register';
13
-
14
- import {
15
- DEFAULT_CLIENT_NOTIFICATION_TYPE,
16
- DEFAULT_SERVER_NOTIFICATION_TYPE,
17
- DefaultNotificationOptions,
18
- Status,
19
- } from '../common';
20
-
21
- import { DefaultNotification } from './default-notification';
22
- import { NotificationsUnwrapped } from './notifications-unwrapped';
23
17
 
24
18
  export interface NotificationsProps extends PropsWithChildren<{}> {
25
19
  apiService?: interfaces.Newable<NotificationsApi>;
@@ -38,26 +32,55 @@ export const Notifications: FC<NotificationsProps> = ({ apiService, children })
38
32
  </Provider>
39
33
  );
40
34
 
41
- register(DEFAULT_CLIENT_NOTIFICATION_TYPE, ({ notification, onClose }) => {
42
- const payload = notification.payload as DefaultNotificationOptions;
35
+ declare global {
36
+ /**
37
+ * This variable ensures there is one and only one \<Toaster />.
38
+ * It contains the unique id of the instance that owns the Toaster.
39
+ */
40
+ // eslint-disable-next-line no-var
41
+ var STNotificationsActiveId: string | undefined;
42
+ }
43
+
44
+ const NotificationsUnwrapped: FC = () => {
45
+ // Configure and clean up store
43
46
 
44
- return <DefaultNotification {...payload} onClose={onClose} />;
45
- });
47
+ const [store] = useDependencies(NotificationsStore);
48
+ const navigate = useCompatibleNavigate();
49
+ useEffect(() => {
50
+ store.navigate = navigate;
51
+ return () => {
52
+ store.dispose();
53
+ };
54
+ }, [store, navigate]);
46
55
 
47
- register(DEFAULT_SERVER_NOTIFICATION_TYPE, ({ notification, onClose }) => {
48
- const payload = notification.payload as DefaultServerNotificationPayload;
56
+ // Use useSyncExternalStore to ensure we re-render when the active id changes
49
57
 
50
- switch (notification.status) {
51
- case NotificationProcessStatus.Info:
52
- return <DefaultNotification {...payload} onClose={onClose} />;
58
+ const [thisId] = useState(() => makeId());
59
+ const syncExternalStoreCallback = useRef<Function>();
60
+ const subscribe = useCallback((callback: Function) => {
61
+ syncExternalStoreCallback.current = callback;
62
+ return () => (syncExternalStoreCallback.current = undefined);
63
+ }, []);
64
+ const activeId = useSyncExternalStore(subscribe, () => globalThis.STNotificationsActiveId);
53
65
 
54
- case NotificationProcessStatus.InProgress:
55
- return <DefaultNotification {...payload} duration={0} onClose={onClose} />;
66
+ // Set and clean up active id
56
67
 
57
- case NotificationProcessStatus.Success:
58
- return <DefaultNotification {...payload} status={Status.Success} onClose={onClose} />;
68
+ useEffect(() => {
69
+ if (globalThis.STNotificationsActiveId === undefined) {
70
+ globalThis.STNotificationsActiveId = thisId;
71
+ syncExternalStoreCallback.current?.();
72
+ }
73
+ return () => {
74
+ if (globalThis.STNotificationsActiveId === thisId) {
75
+ globalThis.STNotificationsActiveId = undefined;
76
+ syncExternalStoreCallback.current?.();
77
+ }
78
+ };
79
+ }, [syncExternalStoreCallback, thisId]);
59
80
 
60
- case NotificationProcessStatus.Failure:
61
- return <DefaultNotification {...payload} status={Status.Error} onClose={onClose} />;
62
- }
63
- });
81
+ return activeId === thisId ? <Toaster id={'st-notifications-' + thisId} /> : null;
82
+ };
83
+
84
+ function makeId() {
85
+ return Math.round(1e8 * Math.random()).toString(36);
86
+ }
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button, Flex } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -38,10 +36,10 @@ export const ActionButtonPreview: FC = () => {
38
36
  });
39
37
 
40
38
  return (
41
- <ButtonGroup>
39
+ <Flex direction="row" gap="2">
42
40
  <Button onClick={handleWithLinkClick}>With Link</Button>
43
41
  <Button onClick={handleWithExternalLinkClick}>With Link (new tab)</Button>
44
42
  <Button onClick={handleWithHandlerClick}>With Handler</Button>
45
- </ButtonGroup>
43
+ </Flex>
46
44
  );
47
45
  };
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -3,12 +3,9 @@ import { useEffect, useMemo, FC } from 'react';
3
3
  import { Provider, useDependencies } from '@servicetitan/react-ioc';
4
4
 
5
5
  import { Notification } from '../api/notifications.api';
6
-
7
6
  import { NOTIFICATIONS_CHANNEL_TOKEN, Notifications, NotificationsService } from '..';
8
7
 
9
- interface EventCallback {
10
- (notification: Notification): void;
11
- }
8
+ type EventCallback = (notification: Notification) => void;
12
9
 
13
10
  class Publisher {
14
11
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button, Flex } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -22,9 +20,9 @@ export const DurationPreview: FC = () => {
22
20
  });
23
21
 
24
22
  return (
25
- <ButtonGroup>
23
+ <Flex direction="row" gap="2">
26
24
  <Button onClick={handleOneSecondClick}>1 second</Button>
27
25
  <Button onClick={handleUnlimitedClick}>Unlimited</Button>
28
- </ButtonGroup>
26
+ </Flex>
29
27
  );
30
28
  };
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -15,9 +13,5 @@ export const MultilineMessagePreview: FC = () => {
15
13
  message: '1. First\r\n2. Second\r\n3. Third',
16
14
  });
17
15
 
18
- return (
19
- <ButtonGroup>
20
- <Button onClick={handleClick}>Show</Button>
21
- </ButtonGroup>
22
- );
16
+ return <Button onClick={handleClick}>Show</Button>;
23
17
  };
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -17,9 +15,5 @@ export const PreventDuplicatesPreview: FC = () => {
17
15
  true
18
16
  );
19
17
 
20
- return (
21
- <ButtonGroup>
22
- <Button onClick={handleClick}>Show</Button>
23
- </ButtonGroup>
24
- );
18
+ return <Button onClick={handleClick}>Show</Button>;
25
19
  };
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -15,9 +13,5 @@ export const ProgressPreview: FC = () => {
15
13
  progress: 25,
16
14
  });
17
15
 
18
- return (
19
- <ButtonGroup>
20
- <Button onClick={handleClick}>Show</Button>
21
- </ButtonGroup>
22
- );
16
+ return <Button onClick={handleClick}>Show</Button>;
23
17
  };
@@ -1,16 +1,19 @@
1
- import { Toast, Avatar, Stack } from '@servicetitan/design-system';
1
+ import { intercept } from '../intercept';
2
2
 
3
- import { register } from '..';
3
+ export function setupIntercept() {
4
+ intercept<{ username: string }>('CustomServerNotification', notification => {
5
+ const { username, ...payload } = notification.payload;
4
6
 
5
- register('CustomServerNotification', ({ notification, onClose }) => {
6
- const { username, message } = notification.payload;
7
-
8
- return (
9
- <Toast title="You have a new message" portal={false} onClose={onClose}>
10
- <Stack alignItems="center">
11
- <Avatar name={username} className="m-r-1" />
12
- <Stack.Item fill>{message}</Stack.Item>
13
- </Stack>
14
- </Toast>
15
- );
16
- });
7
+ return {
8
+ ...notification,
9
+ payload: {
10
+ ...payload,
11
+ title: `Hi ${username}!`,
12
+ action: {
13
+ label: 'Thanks!',
14
+ onClick: () => window.alert("You're welcome!"),
15
+ },
16
+ },
17
+ };
18
+ });
19
+ }
@@ -1,36 +1,37 @@
1
- import { FC } from 'react';
2
-
3
- import { Button } from '@servicetitan/design-system';
1
+ import { Button } from '@servicetitan/anvil2';
2
+ import { FC, useEffect } from 'react';
4
3
 
5
4
  import { NotificationProcessStatus } from '..';
6
-
7
5
  import { Container } from './container';
6
+ import { setupIntercept } from './server-custom-preview';
8
7
 
9
- import './server-custom-preview';
8
+ export const ServerCustomExample: FC = () => {
9
+ useEffect(setupIntercept, []);
10
10
 
11
- export const ServerCustomExample: FC = () => (
12
- <Container>
13
- {publisher => {
14
- const handleClick = () => {
15
- const notification = {
16
- id: Date.now(),
17
- userId: 0,
18
- type: 'CustomServerNotification',
19
- status: NotificationProcessStatus.Info,
20
- isRead: false,
21
- createdOn: new Date(),
22
- modifiedOn: new Date(),
23
- version: NotificationProcessStatus.Info,
24
- payload: JSON.stringify({
25
- username: 'Jane',
26
- message: 'Hello, your order completed.',
27
- }),
28
- };
11
+ return (
12
+ <Container>
13
+ {publisher => {
14
+ const handleClick = () => {
15
+ const notification = {
16
+ id: Date.now(),
17
+ userId: 0,
18
+ type: 'CustomServerNotification',
19
+ status: NotificationProcessStatus.Info,
20
+ isRead: false,
21
+ createdOn: new Date(),
22
+ modifiedOn: new Date(),
23
+ version: NotificationProcessStatus.Info,
24
+ payload: JSON.stringify({
25
+ username: 'Jane',
26
+ message: 'Your order is completed.',
27
+ }),
28
+ };
29
29
 
30
- publisher.fire(notification);
31
- };
30
+ publisher.fire(notification);
31
+ };
32
32
 
33
- return <Button onClick={handleClick}>Show</Button>;
34
- }}
35
- </Container>
36
- );
33
+ return <Button onClick={handleClick}>Show</Button>;
34
+ }}
35
+ </Container>
36
+ );
37
+ };
@@ -1,9 +1,7 @@
1
+ import { Button } from '@servicetitan/anvil2';
1
2
  import { FC } from 'react';
2
3
 
3
- import { Button } from '@servicetitan/design-system';
4
-
5
4
  import { NotificationProcessStatus } from '..';
6
-
7
5
  import { Container } from './container';
8
6
 
9
7
  export const ServerDefaultExample: FC = () => (
@@ -1,8 +1,6 @@
1
- import { FC } from 'react';
2
-
1
+ import { Button, Flex } from '@servicetitan/anvil2';
3
2
  import { useDependencies } from '@servicetitan/react-ioc';
4
-
5
- import { ButtonGroup, Button } from '@servicetitan/design-system';
3
+ import { FC } from 'react';
6
4
 
7
5
  import { NotificationsService } from '..';
8
6
 
@@ -30,11 +28,11 @@ export const StatusVariationsPreview: FC = () => {
30
28
  });
31
29
 
32
30
  return (
33
- <ButtonGroup>
31
+ <Flex direction="row" gap="2">
34
32
  <Button onClick={handleInfoClick}>Info</Button>
35
33
  <Button onClick={handleSuccessClick}>Success</Button>
36
34
  <Button onClick={handleWarningClick}>Warning</Button>
37
35
  <Button onClick={handleErrorClick}>Error</Button>
38
- </ButtonGroup>
36
+ </Flex>
39
37
  );
40
38
  };
@@ -1,7 +1,6 @@
1
1
  import { FC } from 'react';
2
2
 
3
3
  import { Container } from './container';
4
-
5
4
  import { StatusVariationsPreview } from './status-variations-preview';
6
5
 
7
6
  export const StatusVariationsExample: FC = () => (
package/src/index.ts CHANGED
@@ -1,7 +1,16 @@
1
- export { NotificationProcessStatus } from './api/notifications.api';
2
- export { DefaultNotification, DefaultNotificationProps } from './components/default-notification';
1
+ export {
2
+ NotificationsApi,
3
+ NotificationProcessStatus,
4
+ DefaultServerNotificationPayload,
5
+ } from './api/notifications.api';
3
6
  export { Notifications, NotificationsProps } from './components/notifications';
4
- export { DefaultNotificationOptions, Status } from './common';
7
+ export {
8
+ DefaultNotificationOptions,
9
+ LinkAction,
10
+ FunctionAction,
11
+ Notification,
12
+ Status,
13
+ } from './common';
14
+ export { intercept, NotificationInterceptor } from './intercept';
5
15
  export { NotificationsChannel, NOTIFICATIONS_CHANNEL_TOKEN } from './notifications-channel';
6
16
  export { INotificationsService, NotificationsService } from './notifications-service';
7
- export { register } from './register';
@@ -0,0 +1,14 @@
1
+ import { DefaultNotificationOptions, Notification } from './common';
2
+ import { NotificationsStore } from './stores/notifications.store';
3
+
4
+ export type NotificationInterceptor<T extends Record<keyof any, any> = {}> = (
5
+ notification: Notification<T>,
6
+ dismiss: () => void
7
+ ) => Notification<DefaultNotificationOptions> | undefined;
8
+
9
+ export function intercept<T extends Record<keyof any, any> = {}>(
10
+ type: string,
11
+ fn?: NotificationInterceptor<T>
12
+ ) {
13
+ NotificationsStore.intercept(type, fn);
14
+ }
@@ -1,8 +1,6 @@
1
1
  import { symbolToken } from '@servicetitan/react-ioc';
2
2
 
3
- interface EventCallback {
4
- (context: any, data: any): void;
5
- }
3
+ type EventCallback = (data: any, context: any) => void;
6
4
 
7
5
  interface EventsDispatcher {
8
6
  bind(eventName: string, callback: EventCallback, context?: any): this;
@@ -14,9 +12,7 @@ interface Publisher extends EventsDispatcher {
14
12
  global_emitter: EventsDispatcher;
15
13
  }
16
14
 
17
- export interface NotificationsChannel {
18
- (): Publisher;
19
- }
15
+ export type NotificationsChannel = () => Publisher;
20
16
 
21
17
  export const NOTIFICATIONS_CHANNEL_TOKEN = symbolToken<NotificationsChannel>(
22
18
  'NOTIFICATIONS_CHANNEL_TOKEN'
@@ -4,13 +4,15 @@ import { DefaultNotificationOptions, Status } from './common';
4
4
 
5
5
  import { NotificationsStore } from './stores/notifications.store';
6
6
 
7
+ type OptionsWithoutStatus = Omit<DefaultNotificationOptions, 'status'>;
8
+
7
9
  export interface INotificationsService {
8
10
  initialize(userId?: number): Promise<void>;
9
11
  show(options: DefaultNotificationOptions, preventDuplicates?: boolean): void;
10
- info(options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean): void;
11
- success(options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean): void;
12
- warning(options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean): void;
13
- error(options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean): void;
12
+ info(options: OptionsWithoutStatus, preventDuplicates?: boolean): void;
13
+ success(options: OptionsWithoutStatus, preventDuplicates?: boolean): void;
14
+ warning(options: OptionsWithoutStatus, preventDuplicates?: boolean): void;
15
+ error(options: OptionsWithoutStatus, preventDuplicates?: boolean): void;
14
16
  }
15
17
 
16
18
  @injectable()
@@ -25,49 +27,19 @@ export class NotificationsService implements INotificationsService {
25
27
  this.store.add(options, preventDuplicates);
26
28
  };
27
29
 
28
- info = (options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean) => {
29
- this.store.add(
30
- {
31
- ...options,
32
- status: Status.Info,
33
- },
34
- preventDuplicates
35
- );
30
+ info = (options: OptionsWithoutStatus, preventDuplicates?: boolean) => {
31
+ this.store.add({ ...options, status: Status.Info }, preventDuplicates);
36
32
  };
37
33
 
38
- success = (
39
- options: Omit<DefaultNotificationOptions, 'status'>,
40
- preventDuplicates?: boolean
41
- ) => {
42
- this.store.add(
43
- {
44
- ...options,
45
- status: Status.Success,
46
- },
47
- preventDuplicates
48
- );
34
+ success = (options: OptionsWithoutStatus, preventDuplicates?: boolean) => {
35
+ this.store.add({ ...options, status: Status.Success }, preventDuplicates);
49
36
  };
50
37
 
51
- warning = (
52
- options: Omit<DefaultNotificationOptions, 'status'>,
53
- preventDuplicates?: boolean
54
- ) => {
55
- this.store.add(
56
- {
57
- ...options,
58
- status: Status.Warning,
59
- },
60
- preventDuplicates
61
- );
38
+ warning = (options: OptionsWithoutStatus, preventDuplicates?: boolean) => {
39
+ this.store.add({ ...options, status: Status.Warning }, preventDuplicates);
62
40
  };
63
41
 
64
- error = (options: Omit<DefaultNotificationOptions, 'status'>, preventDuplicates?: boolean) => {
65
- this.store.add(
66
- {
67
- ...options,
68
- status: Status.Error,
69
- },
70
- preventDuplicates
71
- );
42
+ error = (options: OptionsWithoutStatus, preventDuplicates?: boolean) => {
43
+ this.store.add({ ...options, status: Status.Error }, preventDuplicates);
72
44
  };
73
45
  }