@pushwoosh/smart-components-kit 1.3.16 → 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.
Files changed (32) hide show
  1. package/ProductUpdates/ProductUpdateCard.d.ts +2 -0
  2. package/ProductUpdates/ProductUpdateCard.js +79 -0
  3. package/ProductUpdates/ProductUpdates.d.ts +2 -0
  4. package/ProductUpdates/ProductUpdates.js +63 -0
  5. package/ProductUpdates/ProductUpdatesModal.d.ts +2 -0
  6. package/ProductUpdates/ProductUpdatesModal.js +59 -0
  7. package/ProductUpdates/assets/fallback.svg +1 -0
  8. package/ProductUpdates/constants.d.ts +7 -0
  9. package/ProductUpdates/constants.js +6 -0
  10. package/ProductUpdates/helpers.d.ts +4 -0
  11. package/ProductUpdates/helpers.js +39 -0
  12. package/ProductUpdates/hooks/useIsAllowProductUpdates.d.ts +1 -0
  13. package/ProductUpdates/hooks/useIsAllowProductUpdates.js +9 -0
  14. package/ProductUpdates/hooks/useProductUpdates.d.ts +13 -0
  15. package/ProductUpdates/hooks/useProductUpdates.js +64 -0
  16. package/ProductUpdates/index.d.ts +5 -0
  17. package/ProductUpdates/index.js +4 -0
  18. package/ProductUpdates/polyglot/index.d.ts +2 -0
  19. package/ProductUpdates/polyglot/index.js +10 -0
  20. package/ProductUpdates/polyglot/messages_en.d.ts +3 -0
  21. package/ProductUpdates/polyglot/messages_en.js +6 -0
  22. package/ProductUpdates/polyglot/messages_ru.d.ts +3 -0
  23. package/ProductUpdates/polyglot/messages_ru.js +6 -0
  24. package/ProductUpdates/polyglot/types.d.ts +9 -0
  25. package/ProductUpdates/polyglot/types.js +1 -0
  26. package/ProductUpdates/styles.d.ts +1 -0
  27. package/ProductUpdates/styles.js +8 -0
  28. package/ProductUpdates/types.d.ts +16 -0
  29. package/ProductUpdates/types.js +1 -0
  30. package/index.d.ts +1 -0
  31. package/index.js +1 -0
  32. package/package.json +4 -4
@@ -0,0 +1,2 @@
1
+ import type { ProductUpdateCardProps } from './types';
2
+ export declare function ProductUpdateCard({ update, onNavigate }: ProductUpdateCardProps): import("react").JSX.Element;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Badge, Button } from '@pushwoosh/dumb-components';
4
+ import { Color } from '@pushwoosh/kit-constants';
5
+ import { Cluster, Columns, Horizontal, Vertical } from '@pushwoosh/kit-helpers';
6
+ import { Paragraph, Text } from '@pushwoosh/kit-typography';
7
+ import { useSPAContext } from '@pushwoosh/spa-context';
8
+ import FallbackImage from './assets/fallback.svg?url';
9
+ import { ALERT_STATUS_UNREAD } from './constants';
10
+ import { formatUpdateDate, openActionLink } from './helpers';
11
+ import { useLang } from './polyglot';
12
+ import { CardImage } from './styles';
13
+ export function ProductUpdateCard({
14
+ update,
15
+ onNavigate
16
+ }) {
17
+ const lang = useLang();
18
+ const {
19
+ history,
20
+ language
21
+ } = useSPAContext();
22
+ const [imageSrc, setImageSrc] = useState(update.imageUrl || FallbackImage);
23
+ const handleAction = link => () => {
24
+ if (openActionLink(link, history)) {
25
+ onNavigate();
26
+ }
27
+ };
28
+ return _jsxs(Columns, {
29
+ "$sizes": "256px 1fr",
30
+ "$gap": 32,
31
+ "$alignItems": "start",
32
+ children: [_jsx(CardImage, {
33
+ src: imageSrc,
34
+ alt: "",
35
+ loading: "lazy",
36
+ onError: () => setImageSrc(FallbackImage)
37
+ }), _jsxs(Vertical, {
38
+ "$gap": 16,
39
+ children: [_jsxs(Vertical, {
40
+ "$gap": 4,
41
+ children: [_jsxs(Horizontal, {
42
+ "$gap": 8,
43
+ "$alignItems": "center",
44
+ "$justifyContent": "start",
45
+ children: [_jsx(Text, {
46
+ "$variant": "footnote",
47
+ "$color": Color.LOCKED,
48
+ "$uppercase": true,
49
+ children: formatUpdateDate(update.created, language)
50
+ }), update.status === ALERT_STATUS_UNREAD && _jsx(Badge, {
51
+ color: "sky",
52
+ children: lang({
53
+ message: 'SMART.PRODUCT_UPDATES.BADGE.NEW'
54
+ })
55
+ })]
56
+ }), _jsxs(Vertical, {
57
+ children: [_jsx(Text, {
58
+ "$variant": "h4",
59
+ children: update.title
60
+ }), update.content && _jsx(Paragraph, {
61
+ children: update.content
62
+ })]
63
+ })]
64
+ }), (update.actionLink || update.secondaryActionLink) && _jsxs(Cluster, {
65
+ "$gap": 8,
66
+ "$flexWrap": "wrap",
67
+ children: [update.actionLink && _jsx(Button, {
68
+ color: "primary",
69
+ onClick: handleAction(update.actionLink),
70
+ children: update.actionLinkLabel || update.actionLink
71
+ }), update.secondaryActionLink && _jsx(Button, {
72
+ color: "secondary",
73
+ onClick: handleAction(update.secondaryActionLink),
74
+ children: update.secondaryActionLinkLabel || update.secondaryActionLink
75
+ })]
76
+ })]
77
+ })]
78
+ });
79
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProductUpdatesProps } from './types';
2
+ export declare function ProductUpdates({ autoOpenOnUnread }: ProductUpdatesProps): import("react").JSX.Element | null;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Button } from '@pushwoosh/dumb-components';
4
+ import { MegaphoneIcon } from '@pushwoosh/kit-icons';
5
+ import { useIsAllowProductUpdates } from './hooks/useIsAllowProductUpdates';
6
+ import { useProductUpdates } from './hooks/useProductUpdates';
7
+ import { useLang } from './polyglot';
8
+ import { ProductUpdatesModal } from './ProductUpdatesModal';
9
+ export function ProductUpdates({
10
+ autoOpenOnUnread = false
11
+ }) {
12
+ const lang = useLang();
13
+ const [isOpen, setIsOpen] = useState(false);
14
+ const hasAutoOpened = useRef(false);
15
+ const isAllowed = useIsAllowProductUpdates();
16
+ const {
17
+ updates,
18
+ unreadCount,
19
+ isLoading,
20
+ hasUpdates,
21
+ markAllRead,
22
+ clearNewBadges
23
+ } = useProductUpdates();
24
+ useEffect(() => {
25
+ if (!isAllowed || !autoOpenOnUnread || hasAutoOpened.current || unreadCount === 0) {
26
+ return;
27
+ }
28
+ hasAutoOpened.current = true;
29
+ setIsOpen(true);
30
+ markAllRead();
31
+ }, [isAllowed, autoOpenOnUnread, unreadCount, markAllRead]);
32
+ const handleOpen = () => {
33
+ setIsOpen(true);
34
+ markAllRead();
35
+ };
36
+ const handleClose = () => {
37
+ setIsOpen(false);
38
+ clearNewBadges();
39
+ };
40
+ if (!isAllowed || isLoading || !hasUpdates) {
41
+ return null;
42
+ }
43
+ return _jsxs(_Fragment, {
44
+ children: [_jsx(Button, {
45
+ color: "secondary",
46
+ view: "ghost",
47
+ size: "field",
48
+ icon: _jsx(MegaphoneIcon, {
49
+ size: "medium",
50
+ view: "lined"
51
+ }),
52
+ onClick: handleOpen,
53
+ children: lang({
54
+ message: 'SMART.PRODUCT_UPDATES.BUTTON'
55
+ })
56
+ }), _jsx(ProductUpdatesModal, {
57
+ isOpen: isOpen,
58
+ onClose: handleClose,
59
+ updates: updates,
60
+ unreadCount: unreadCount
61
+ })]
62
+ });
63
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProductUpdatesModalProps } from './types';
2
+ export declare function ProductUpdatesModal(props: ProductUpdatesModalProps): import("react").JSX.Element;
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from 'react';
3
+ import { Divider, Modal, ModalBody, ModalHeader, ModalTitle } from '@pushwoosh/dumb-components';
4
+ import { Color } from '@pushwoosh/kit-constants';
5
+ import { Horizontal, Vertical } from '@pushwoosh/kit-helpers';
6
+ import { Text } from '@pushwoosh/kit-typography';
7
+ import { useLang } from './polyglot';
8
+ import { ProductUpdateCard } from './ProductUpdateCard';
9
+ export function ProductUpdatesModal(props) {
10
+ const {
11
+ isOpen,
12
+ onClose,
13
+ updates,
14
+ unreadCount
15
+ } = props;
16
+ const lang = useLang();
17
+ // A colon only reads right when a count follows it.
18
+ const base = lang({
19
+ message: 'SMART.PRODUCT_UPDATES.TITLE'
20
+ });
21
+ const title = unreadCount > 0 ? `${base}:` : base;
22
+ return _jsxs(Modal, {
23
+ size: "large",
24
+ isOpen: isOpen,
25
+ onClose: onClose,
26
+ children: [_jsx(ModalHeader, {
27
+ children: _jsx(ModalTitle, {
28
+ onClose: onClose,
29
+ children: _jsxs(Horizontal, {
30
+ "$gap": 8,
31
+ "$alignItems": "baseline",
32
+ "$justifyContent": "start",
33
+ children: [_jsx("span", {
34
+ children: title
35
+ }), unreadCount > 0 && _jsx(Text, {
36
+ "$color": Color.LOCKED,
37
+ children: lang({
38
+ message: 'SMART.PRODUCT_UPDATES.UNREAD',
39
+ count: unreadCount
40
+ })
41
+ })]
42
+ })
43
+ })
44
+ }), _jsx(ModalBody, {
45
+ children: _jsx(Vertical, {
46
+ "$gap": 16,
47
+ "$padding": {
48
+ top: 8
49
+ },
50
+ children: updates.map((update, index) => _jsxs(Fragment, {
51
+ children: [index > 0 && _jsx(Divider, {}), _jsx(ProductUpdateCard, {
52
+ update: update,
53
+ onNavigate: onClose
54
+ })]
55
+ }, update.uuid))
56
+ })
57
+ })]
58
+ });
59
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="256" height="160" fill="none" xmlns:v="https://vecta.io/nano"><style><![CDATA[.B{fill:#f8f1f2}.C{color-interpolation-filters:sRGB}.D{flood-opacity:0}.E{fill:#fef6dc}]]></style><g clip-path="url(#M)"><path d="M0 0H256V160.121H0V0Z" fill="url(#E)"/><path d="M0 0H256V160.121H0V0Z" fill="url(#F)" fill-opacity=".8"/><path d="M100.375 15.723l.131.025c.54.382 2.935 5.782 3.5 6.798l-3.945 2.133-.04.007c-.5-.212-3.101-5.928-3.545-6.884l3.899-2.078z" class="B"/><path d="M173.172 32.816l3.98 2.787-3.917 6.102c-.944-.386-3.281-2.062-4.045-2.756l3.982-6.134z" class="E"/><g class="B"><path d="M162.016 68.984l.287-.014c.658.298 3.35 2.004 3.793 2.563-.171.605-2.636 4.86-3.127 5.338-1.258-.62-3.105-1.969-4.434-2.784l3.481-5.103zm-68.752 1.277c.667-.121.974.565 1.269 1.122.647 1.221 1.876 2.681 2.145 3.991.061.296-3.135 2.159-3.556 2.407l-.279-.093c-.46-.544-3.02-4.612-3.209-5.236.441-.377 3.035-1.859 3.63-2.192zm64.949-52.46l3.815 2.169-3.244 5.859c-.418.27-3.313-1.602-3.829-1.942l3.258-6.086z"/></g><path d="M90.04 43.218c.615.156 2.971 4.202 3.335 4.96-1.008.862-2.332 1.814-3.403 2.603-.884-1.169-2.505-3.868-3.364-5.209l3.432-2.354z" class="E"/><g class="B"><path d="M179.019 58.206c.341.114 2.681 2.363 3.115 2.768l-3.772 4.684-3.275-2.662c.997-1.395 2.794-3.512 3.932-4.79zm-100.934-.105c.382.275 3.239 4.144 3.66 4.753-.775.817-2.096 1.685-3.127 2.738-1.333-1.337-2.462-3.167-3.821-4.624l3.288-2.867z"/></g><path d="M78.363 32.098c.321.177 2.145 2.956 2.62 3.519-1.031.669-1.633 1.324-2.498 2.187-.474-.187-2.316-2.766-2.784-3.363l2.662-2.343z" class="E"/><g filter="url(#A)"><path d="M128.951 99.645l-.079 34.866-34.607-16.706V92.623l34.686 7.021zm32.554 18.482l-32.632 16.384.078-34.865 33.107-6.295-.553 24.776z" fill="url(#G)"/></g><g filter="url(#B)"><path d="M128.968 99.645l33.817-6.295-.566 24.776-33.331 16.384-35.349-16.706V92.623l35.429 7.021z" fill="url(#H)"/></g><path d="M128.888 134.51l.08-34.865 33.816-6.295-.565 24.776-33.331 16.384z" fill="url(#I)"/><path d="M128.888 134.51l.081-34.865-35.43-7.021v25.18l35.349 16.707z" fill="url(#H)"/><path d="M163.228 83.342l-34.26 16.303-36.156-16.383 34.946-10.169 35.47 10.25z" fill="url(#J)"/><g filter="url(#C)"><path d="M153.436 58.335h.585l-.146.517.146.171h-.194l-.388 1.383h-12.31v33.469l-12.08 5.77-12.906-5.836V60.405h-12.494l-.387-1.383h-.195l.146-.172-.145-.516h.584l24.893-29.274 24.891 29.275z" fill="url(#K)"/></g><path opacity=".8" d="M154.02 58.335l-.581 2.07h-49.79l-.581-2.07h50.952z" fill="#fff3d5"/><g filter="url(#D)"><path d="M154.021 59.023h-12.892v34.851l-12.08 5.77-12.906-5.836V59.023h-13.076l25.478-29.962 25.476 29.962z" fill="url(#L)"/></g><g class="B"><use href="#N"/><path d="M111.374 63.892l.524 1.414 1.413.523-1.413.523-.524 1.414-.523-1.414-1.413-.523 1.413-.523.523-1.414zm-4.68 5.165l.523 1.414 1.414.523-1.414.523-.523 1.414-.523-1.414-1.414-.523 1.414-.523.523-1.414zm-12.591-9.039l.48 1.296 1.296.48-1.296.48-.48 1.296-.48-1.296-1.296-.48 1.296-.48.48-1.296zm-6.295-4.842l.349.942.942.349-.942.349-.349.943-.349-.943-.942-.349.942-.349.349-.942zm84.257-5.488l.524 1.414 1.413.523-1.413.523-.524 1.414-.523-1.414-1.414-.523 1.414-.523.523-1.414z"/></g><path d="M146.724 101.259l-17.756-1.615 34.26-16.303 15.455 1.856-31.959 16.061z" fill="#fefcf9"/><path d="M111.213 101.258l17.755-1.614-36.156-16.383-14.608 2.018 33.009 15.98z" fill="#fefdfa"/></g><defs><filter id="A" x="93.619" y="92.623" width="69.084" height="43.178" filterUnits="userSpaceOnUse" class="C"><feFlood result="A" class="D"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="B"/><feOffset dy="0.645649"/><feGaussianBlur stdDeviation=".323"/><feComposite in2="B" operator="out"/><feColorMatrix values="0 0 0 0 0.363468 0 0 0 0 0.269585 0 0 0 0 0.457351 0 0 0 0.2 0"/><feBlend in2="A"/><feBlend in="SourceGraphic"/></filter><filter id="B" x="91.602" y="91.332" width="74.411" height="47.052" filterUnits="userSpaceOnUse" class="C"><feFlood result="A" class="D"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="B"/><feOffset dx="0.645649" dy="1.2913"/><feGaussianBlur stdDeviation="1.291"/><feComposite in2="B" operator="out"/><feColorMatrix values="0 0 0 0 0.363468 0 0 0 0 0.269585 0 0 0 0 0.457351 0 0 0 0.1 0"/><feBlend in2="A"/><feBlend in="SourceGraphic"/></filter><filter id="C" x="98.548" y="26.478" width="59.992" height="79.623" filterUnits="userSpaceOnUse" class="C"><feFlood result="A" class="D"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="B"/><feOffset dy="1.93695"/><feGaussianBlur stdDeviation="2.26"/><feComposite in2="B" operator="out"/><feColorMatrix values="0 0 0 0 1 0 0 0 0 0.904522 0 0 0 0 0.713567 0 0 0 1 0"/><feBlend in2="A"/><feBlend in="SourceGraphic"/></filter><filter id="D" x="103.067" y="29.061" width="50.953" height="72.521" filterUnits="userSpaceOnUse" class="C"><feFlood class="D"/><feBlend in="SourceGraphic" result="B"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="C"/><feOffset dy="1.93695"/><feGaussianBlur stdDeviation="2.905"/><feComposite in2="C" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/><feBlend in2="B"/></filter><radialGradient id="E" cx="0" cy="0" r="1" gradientTransform="translate(128 101.061) rotate(90) scale(138.718 138.718)" href="#O"><stop stop-color="#fef6df"/><stop offset=".067" stop-color="#fef5df"/><stop offset=".133" stop-color="#fcf4df"/><stop offset=".2" stop-color="#faf1e0"/><stop offset=".267" stop-color="#f6ede0"/><stop offset=".333" stop-color="#f2e8e1"/><stop offset=".4" stop-color="#ede2e2"/><stop offset=".467" stop-color="#e7dbe3"/><stop offset=".533" stop-color="#e2d4e4"/><stop offset=".6" stop-color="#dccde5"/><stop offset=".667" stop-color="#d7c7e6"/><stop offset=".733" stop-color="#d2c2e7"/><stop offset=".8" stop-color="#cfbee8"/><stop offset=".867" stop-color="#cdbbe8"/><stop offset=".933" stop-color="#cbb9e9"/></radialGradient><radialGradient id="F" cx="0" cy="0" r="1" gradientTransform="translate(128 125.061) rotate(-90) scale(29 108.326)" href="#O"><stop stop-color="#b68fcb"/><stop offset="1" stop-color="#d0b9f5" stop-opacity="0"/></radialGradient><radialGradient id="G" cx="0" cy="0" r="1" gradientTransform="matrix(-34.7657 16.4641 -13.6341 -30.037 128.951 101.985)" href="#O"><stop stop-color="#fef1e7"/><stop offset="1" stop-color="#f7e8f5"/></radialGradient><radialGradient id="H" cx="0" cy="0" r="1" gradientTransform="translate(128.969 101.985) rotate(155.126) scale(39.1417 33.1083)" href="#O"><stop stop-color="#fef1e7"/><stop offset="1" stop-color="#f7e8f5"/></radialGradient><radialGradient id="I" cx="0" cy="0" r="1" gradientTransform="translate(128.807 100.371) rotate(36.3738) scale(37.2877 30.7075)" href="#O"><stop stop-color="#fadaca"/><stop offset="1" stop-color="#cfbede"/></radialGradient><radialGradient id="J" cx="0" cy="0" r="1" gradientTransform="translate(128 95.5281) rotate(-90) scale(24.2926 35.1879)" href="#O"><stop stop-color="#ffe5b3"/><stop offset="1" stop-color="#fffefc"/></radialGradient><linearGradient id="K" x1="128.544" y1="21.763" x2="128.544" y2="99.645" href="#O"><stop stop-color="#fff4d8"/><stop offset=".586" stop-color="#fffbf1"/><stop offset="1" stop-color="#fff3d3"/></linearGradient><linearGradient id="L" x1="128.544" y1="21.763" x2="128.544" y2="99.645" href="#O"><stop stop-color="#ffedbf"/><stop offset=".717" stop-color="#fffbf1"/><stop offset="1" stop-color="#fff3d3"/></linearGradient><clipPath id="M"><path fill="#fff" d="M0 0h256v160H0z"/></clipPath><path id="N" d="M149.79 64.053l.524 1.414 1.413.523-1.413.523-.524 1.414-.523-1.414-1.413-.523 1.413-.523.523-1.414z"/><linearGradient id="O" gradientUnits="userSpaceOnUse"/></defs></svg>
@@ -0,0 +1,7 @@
1
+ import type { AlertStatus, AlertType, OrderBy, OrderDirection } from '@pushwoosh/rpc-v2-http-api-data/pushwoosh_accounts_alerts_v1';
2
+ export declare const UPDATES_PER_PAGE = 20;
3
+ export declare const ALERT_TYPE_PRODUCT_UPDATE: AlertType;
4
+ export declare const ALERT_STATUS_READ: AlertStatus;
5
+ export declare const ALERT_STATUS_UNREAD: AlertStatus;
6
+ export declare const ORDER_BY_CREATED: OrderBy;
7
+ export declare const ORDER_DIRECTION_DESC: OrderDirection;
@@ -0,0 +1,6 @@
1
+ export const UPDATES_PER_PAGE = 20;
2
+ export const ALERT_TYPE_PRODUCT_UPDATE = 'ALERT_TYPE_PRODUCT_UPDATE';
3
+ export const ALERT_STATUS_READ = 'ALERT_STATUS_READ';
4
+ export const ALERT_STATUS_UNREAD = 'ALERT_STATUS_UNREAD';
5
+ export const ORDER_BY_CREATED = 'ORDER_BY_CREATED';
6
+ export const ORDER_DIRECTION_DESC = 'ORDER_DIRECTION_DESC';
@@ -0,0 +1,4 @@
1
+ export declare function openActionLink(link: string, history: {
2
+ push: (path: string) => void;
3
+ }): boolean;
4
+ export declare function formatUpdateDate(value: string | Date | undefined, locale: string): string;
@@ -0,0 +1,39 @@
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;
4
+ export function openActionLink(link, history) {
5
+ let url;
6
+ try {
7
+ url = new URL(link, window.location.origin);
8
+ } catch {
9
+ return false;
10
+ }
11
+ if (url.origin !== window.location.origin) {
12
+ window.open(url.href, '_blank', 'noopener,noreferrer');
13
+ return false;
14
+ }
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);
21
+ return true;
22
+ }
23
+ // UTC on purpose: an announcement is dated once for everyone, and a local-time
24
+ // render turns a midnight-UTC send into the previous day west of Greenwich.
25
+ export function formatUpdateDate(value, locale) {
26
+ if (!value) {
27
+ return '';
28
+ }
29
+ const date = value instanceof Date ? value : new Date(value);
30
+ if (Number.isNaN(date.getTime())) {
31
+ return '';
32
+ }
33
+ return date.toLocaleDateString(locale, {
34
+ month: 'short',
35
+ day: 'numeric',
36
+ year: 'numeric',
37
+ timeZone: 'UTC'
38
+ });
39
+ }
@@ -0,0 +1 @@
1
+ export declare function useIsAllowProductUpdates(): boolean;
@@ -0,0 +1,9 @@
1
+ import { useSPAContext } from '@pushwoosh/spa-context';
2
+ // Product updates are Pushwoosh announcements; a whitelabel tenant must not see them.
3
+ // Same gate DocsButton uses, and it is deliberately on display, not on the broadcast.
4
+ export function useIsAllowProductUpdates() {
5
+ const {
6
+ environment
7
+ } = useSPAContext();
8
+ return !environment.isOMO;
9
+ }
@@ -0,0 +1,13 @@
1
+ import type { ProductUpdate } from '../types';
2
+ type UseProductUpdates = {
3
+ readonly updates: ProductUpdate[];
4
+ readonly unreadCount: number;
5
+ readonly isLoading: boolean;
6
+ readonly hasUpdates: boolean;
7
+ /** Sends the read marks. Call on open: showing the modal is the delivery. */
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;
11
+ };
12
+ export declare function useProductUpdates(): UseProductUpdates;
13
+ export {};
@@ -0,0 +1,64 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ import { useAsync } from '@pushwoosh/kit-helpers';
3
+ import { useSPAContext } from '@pushwoosh/spa-context';
4
+ import { ALERT_STATUS_READ, ALERT_STATUS_UNREAD, ALERT_TYPE_PRODUCT_UPDATE, ORDER_BY_CREATED, ORDER_DIRECTION_DESC, UPDATES_PER_PAGE } from '../constants';
5
+ import { useIsAllowProductUpdates } from './useIsAllowProductUpdates';
6
+ const EMPTY = [];
7
+ export function useProductUpdates() {
8
+ const {
9
+ rpcGateway
10
+ } = useSPAContext();
11
+ const isAllowed = useIsAllowProductUpdates();
12
+ const [areBadgesCleared, setAreBadgesCleared] = useState(false);
13
+ const hasSentRef = useRef(false);
14
+ const state = useAsync(async () => {
15
+ if (!isAllowed) {
16
+ return EMPTY;
17
+ }
18
+ const response = await rpcGateway.internalAlerts.List({
19
+ type: ALERT_TYPE_PRODUCT_UPDATE,
20
+ page: 0,
21
+ perPage: UPDATES_PER_PAGE,
22
+ orderBy: ORDER_BY_CREATED,
23
+ orderDirection: ORDER_DIRECTION_DESC
24
+ });
25
+ return response.alerts;
26
+ }, [rpcGateway, isAllowed]);
27
+ const fetched = state.value ?? EMPTY;
28
+ const updates = areBadgesCleared ? fetched.map(update => ({
29
+ ...update,
30
+ status: ALERT_STATUS_READ
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.
34
+ const markAllRead = useCallback(async () => {
35
+ if (hasSentRef.current) {
36
+ return;
37
+ }
38
+ const unread = fetched.filter(update => update.status === ALERT_STATUS_UNREAD);
39
+ if (unread.length === 0) {
40
+ return;
41
+ }
42
+ hasSentRef.current = true;
43
+ try {
44
+ await rpcGateway.internalAlerts.UpdateStatus({
45
+ alerts: unread.map(update => ({
46
+ uuid: update.uuid,
47
+ status: ALERT_STATUS_READ
48
+ }))
49
+ });
50
+ } catch (error) {
51
+ console.error('Failed to mark product updates as read:', error);
52
+ hasSentRef.current = false;
53
+ }
54
+ }, [rpcGateway, fetched]);
55
+ const clearNewBadges = useCallback(() => setAreBadgesCleared(true), []);
56
+ return {
57
+ updates,
58
+ unreadCount: updates.filter(update => update.status === ALERT_STATUS_UNREAD).length,
59
+ isLoading: state.loading,
60
+ hasUpdates: updates.length > 0,
61
+ markAllRead,
62
+ clearNewBadges
63
+ };
64
+ }
@@ -0,0 +1,5 @@
1
+ export { ProductUpdates } from './ProductUpdates';
2
+ export { ProductUpdatesModal } from './ProductUpdatesModal';
3
+ export { useProductUpdates } from './hooks/useProductUpdates';
4
+ export { useIsAllowProductUpdates } from './hooks/useIsAllowProductUpdates';
5
+ export type { ProductUpdate, ProductUpdateCardProps, ProductUpdatesModalProps, ProductUpdatesProps, } from './types';
@@ -0,0 +1,4 @@
1
+ export { ProductUpdates } from './ProductUpdates';
2
+ export { ProductUpdatesModal } from './ProductUpdatesModal';
3
+ export { useProductUpdates } from './hooks/useProductUpdates';
4
+ export { useIsAllowProductUpdates } from './hooks/useIsAllowProductUpdates';
@@ -0,0 +1,2 @@
1
+ import { type ProductUpdatesMessages } from './types';
2
+ export declare const Lang: import("react").FC<import("@pushwoosh/dumb-components/polyglot").LangProps<ProductUpdatesMessages>>, useLang: () => (props: import("@pushwoosh/dumb-components/polyglot").LangProps<ProductUpdatesMessages>) => string;
@@ -0,0 +1,10 @@
1
+ import { createLang } from '@pushwoosh/dumb-components/polyglot';
2
+ import { messages as en } from './messages_en';
3
+ import { messages as ru } from './messages_ru';
4
+ export const {
5
+ Lang,
6
+ useLang
7
+ } = createLang({
8
+ en,
9
+ ru
10
+ });
@@ -0,0 +1,3 @@
1
+ import type { LangMessages } from '@pushwoosh/dumb-components/polyglot';
2
+ import { type ProductUpdatesMessages } from './types';
3
+ export declare const messages: LangMessages<ProductUpdatesMessages>;
@@ -0,0 +1,6 @@
1
+ export const messages = {
2
+ 'SMART.PRODUCT_UPDATES.BUTTON': 'Product updates',
3
+ 'SMART.PRODUCT_UPDATES.TITLE': 'Product updates',
4
+ 'SMART.PRODUCT_UPDATES.UNREAD': '{{count}} unread',
5
+ 'SMART.PRODUCT_UPDATES.BADGE.NEW': 'NEW'
6
+ };
@@ -0,0 +1,3 @@
1
+ import type { LangMessages } from '@pushwoosh/dumb-components/polyglot';
2
+ import { type ProductUpdatesMessages } from './types';
3
+ export declare const messages: LangMessages<ProductUpdatesMessages>;
@@ -0,0 +1,6 @@
1
+ export const messages = {
2
+ 'SMART.PRODUCT_UPDATES.BUTTON': 'Обновления продукта',
3
+ 'SMART.PRODUCT_UPDATES.TITLE': 'Обновления продукта',
4
+ 'SMART.PRODUCT_UPDATES.UNREAD': 'непрочитанных: {{count}}',
5
+ 'SMART.PRODUCT_UPDATES.BADGE.NEW': 'NEW'
6
+ };
@@ -0,0 +1,9 @@
1
+ import type { NoParams } from '@pushwoosh/dumb-components/polyglot';
2
+ export type ProductUpdatesMessages = {
3
+ 'SMART.PRODUCT_UPDATES.BUTTON': NoParams;
4
+ 'SMART.PRODUCT_UPDATES.TITLE': NoParams;
5
+ 'SMART.PRODUCT_UPDATES.UNREAD': {
6
+ count: number;
7
+ };
8
+ 'SMART.PRODUCT_UPDATES.BADGE.NEW': NoParams;
9
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const CardImage: import("styled-components").StyledComponent<"img", any, {}, never>;
@@ -0,0 +1,8 @@
1
+ import styled from 'styled-components';
2
+ import { ShapeRadius } from '@pushwoosh/kit-constants';
3
+ // The only rule kit-helpers cannot express: object-fit has no prop there, and it is
4
+ // what keeps a wrongly proportioned upload from stretching the card.
5
+ export const CardImage = styled.img.withConfig({
6
+ displayName: "CardImage",
7
+ componentId: "sc-11vonu6-0"
8
+ })(["width:256px;height:160px;object-fit:cover;border-radius:", ";display:block;"], ShapeRadius.SECTION);
@@ -0,0 +1,16 @@
1
+ import type { Alert } from '@pushwoosh/rpc-v2-http-api-data/internal_alerts';
2
+ export type ProductUpdate = Alert;
3
+ export type ProductUpdatesProps = {
4
+ /** Opens the modal by itself once, when the user has never-read updates. */
5
+ readonly autoOpenOnUnread?: boolean;
6
+ };
7
+ export type ProductUpdatesModalProps = {
8
+ readonly isOpen: boolean;
9
+ readonly onClose: () => void;
10
+ readonly updates: ProductUpdate[];
11
+ readonly unreadCount: number;
12
+ };
13
+ export type ProductUpdateCardProps = {
14
+ readonly update: ProductUpdate;
15
+ readonly onNavigate: () => void;
16
+ };
@@ -0,0 +1 @@
1
+ export {};
package/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './DynamicContentPickerForm';
6
6
  export * from './EmailTemplatePreview';
7
7
  export * from './ExportCSV';
8
8
  export * from './PlatformsConfigurationDrawer';
9
+ export * from './ProductUpdates';
9
10
  export * from './LanguagesSelector';
10
11
  export * from './MediaStorage';
11
12
  export * from './mmsAttachments';
package/index.js CHANGED
@@ -6,6 +6,7 @@ export * from './DynamicContentPickerForm';
6
6
  export * from './EmailTemplatePreview';
7
7
  export * from './ExportCSV';
8
8
  export * from './PlatformsConfigurationDrawer';
9
+ export * from './ProductUpdates';
9
10
  export * from './LanguagesSelector';
10
11
  export * from './MediaStorage';
11
12
  export * from './mmsAttachments';
package/package.json CHANGED
@@ -24,12 +24,12 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@floating-ui/react-dom": "^2.1.2",
27
- "@pushwoosh/dumb-components": "^1.1.202",
27
+ "@pushwoosh/dumb-components": "^1.1.209",
28
28
  "@pushwoosh/kit-constants": "^1.11.1",
29
29
  "@pushwoosh/kit-helpers": "^4.12.0",
30
- "@pushwoosh/kit-icons": "^2.1.22",
30
+ "@pushwoosh/kit-icons": "^2.2.3",
31
31
  "@pushwoosh/kit-typography": "^1.7.6",
32
- "@pushwoosh/rpc-v2-http-api-data": "^0.2.114",
32
+ "@pushwoosh/rpc-v2-http-api-data": "^0.2.151",
33
33
  "@pushwoosh/spa-context": "^1.7.1",
34
34
  "idb-keyval": "^6.2.2",
35
35
  "react-hook-form": "^7.68.0",
@@ -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.16"
55
+ "version": "1.3.18"
56
56
  }