@pushwoosh/smart-components-kit 1.3.17 → 1.3.18

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.
@@ -18,7 +18,8 @@ export function ProductUpdates({
18
18
  unreadCount,
19
19
  isLoading,
20
20
  hasUpdates,
21
- markAllRead
21
+ markAllRead,
22
+ clearNewBadges
22
23
  } = useProductUpdates();
23
24
  useEffect(() => {
24
25
  if (!isAllowed || !autoOpenOnUnread || hasAutoOpened.current || unreadCount === 0) {
@@ -26,10 +27,15 @@ export function ProductUpdates({
26
27
  }
27
28
  hasAutoOpened.current = true;
28
29
  setIsOpen(true);
29
- }, [isAllowed, autoOpenOnUnread, unreadCount]);
30
+ markAllRead();
31
+ }, [isAllowed, autoOpenOnUnread, unreadCount, markAllRead]);
32
+ const handleOpen = () => {
33
+ setIsOpen(true);
34
+ markAllRead();
35
+ };
30
36
  const handleClose = () => {
31
37
  setIsOpen(false);
32
- markAllRead();
38
+ clearNewBadges();
33
39
  };
34
40
  if (!isAllowed || isLoading || !hasUpdates) {
35
41
  return null;
@@ -43,7 +49,7 @@ export function ProductUpdates({
43
49
  size: "medium",
44
50
  view: "lined"
45
51
  }),
46
- onClick: () => setIsOpen(true),
52
+ onClick: handleOpen,
47
53
  children: lang({
48
54
  message: 'SMART.PRODUCT_UPDATES.BUTTON'
49
55
  })
@@ -1,5 +1,6 @@
1
- // Same-origin links stay inside the SPA; the root config resolves the /applications/app-code
2
- // placeholder on the routing event, so a deeplink needs no application code here.
1
+ // The root config resolves the app-code placeholder only on a real page load; the same
2
+ // push inside the SPA answers 404 for the literal "app-code" and lands on /projects.
3
+ const APP_CODE_PLACEHOLDER = /^\/(applications|journeys)\/app-code(?=\/|$)/i;
3
4
  export function openActionLink(link, history) {
4
5
  let url;
5
6
  try {
@@ -11,7 +12,12 @@ export function openActionLink(link, history) {
11
12
  window.open(url.href, '_blank', 'noopener,noreferrer');
12
13
  return false;
13
14
  }
14
- history.push(`${url.pathname}${url.search}${url.hash}`);
15
+ const path = `${url.pathname}${url.search}${url.hash}`;
16
+ if (APP_CODE_PLACEHOLDER.test(url.pathname)) {
17
+ window.location.assign(path);
18
+ return false;
19
+ }
20
+ history.push(path);
15
21
  return true;
16
22
  }
17
23
  // UTC on purpose: an announcement is dated once for everyone, and a local-time
@@ -4,7 +4,10 @@ type UseProductUpdates = {
4
4
  readonly unreadCount: number;
5
5
  readonly isLoading: boolean;
6
6
  readonly hasUpdates: boolean;
7
+ /** Sends the read marks. Call on open: showing the modal is the delivery. */
7
8
  readonly markAllRead: () => Promise<void>;
9
+ /** Drops the NEW badges and the unread counter. Call on close, so they last while reading. */
10
+ readonly clearNewBadges: () => void;
8
11
  };
9
12
  export declare function useProductUpdates(): UseProductUpdates;
10
13
  export {};
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useRef, useState } from 'react';
2
2
  import { useAsync } from '@pushwoosh/kit-helpers';
3
3
  import { useSPAContext } from '@pushwoosh/spa-context';
4
4
  import { ALERT_STATUS_READ, ALERT_STATUS_UNREAD, ALERT_TYPE_PRODUCT_UPDATE, ORDER_BY_CREATED, ORDER_DIRECTION_DESC, UPDATES_PER_PAGE } from '../constants';
@@ -9,7 +9,8 @@ export function useProductUpdates() {
9
9
  rpcGateway
10
10
  } = useSPAContext();
11
11
  const isAllowed = useIsAllowProductUpdates();
12
- const [isMarkedRead, setIsMarkedRead] = useState(false);
12
+ const [areBadgesCleared, setAreBadgesCleared] = useState(false);
13
+ const hasSentRef = useRef(false);
13
14
  const state = useAsync(async () => {
14
15
  if (!isAllowed) {
15
16
  return EMPTY;
@@ -24,17 +25,21 @@ export function useProductUpdates() {
24
25
  return response.alerts;
25
26
  }, [rpcGateway, isAllowed]);
26
27
  const fetched = state.value ?? EMPTY;
27
- // Read is all-or-nothing: closing the modal marks everything it showed,
28
- // so one flag is enough to reflect it without refetching.
29
- const updates = isMarkedRead ? fetched.map(update => ({
28
+ const updates = areBadgesCleared ? fetched.map(update => ({
30
29
  ...update,
31
30
  status: ALERT_STATUS_READ
32
31
  })) : fetched;
32
+ // The mark goes out on open, not on close: a reload with the modal still open is not
33
+ // a close, and the same updates used to pop again.
33
34
  const markAllRead = useCallback(async () => {
35
+ if (hasSentRef.current) {
36
+ return;
37
+ }
34
38
  const unread = fetched.filter(update => update.status === ALERT_STATUS_UNREAD);
35
39
  if (unread.length === 0) {
36
40
  return;
37
41
  }
42
+ hasSentRef.current = true;
38
43
  try {
39
44
  await rpcGateway.internalAlerts.UpdateStatus({
40
45
  alerts: unread.map(update => ({
@@ -42,16 +47,18 @@ export function useProductUpdates() {
42
47
  status: ALERT_STATUS_READ
43
48
  }))
44
49
  });
45
- setIsMarkedRead(true);
46
50
  } catch (error) {
47
51
  console.error('Failed to mark product updates as read:', error);
52
+ hasSentRef.current = false;
48
53
  }
49
54
  }, [rpcGateway, fetched]);
55
+ const clearNewBadges = useCallback(() => setAreBadgesCleared(true), []);
50
56
  return {
51
57
  updates,
52
58
  unreadCount: updates.filter(update => update.status === ALERT_STATUS_UNREAD).length,
53
59
  isLoading: state.loading,
54
60
  hasUpdates: updates.length > 0,
55
- markAllRead
61
+ markAllRead,
62
+ clearNewBadges
56
63
  };
57
64
  }
package/package.json CHANGED
@@ -52,5 +52,5 @@
52
52
  "react": "^16.14.0 || ^18.0.0",
53
53
  "react-dom": "^16.14.0 || ^18.0.0"
54
54
  },
55
- "version": "1.3.17"
55
+ "version": "1.3.18"
56
56
  }