react-notify-sdk 1.0.41 → 1.0.43

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.
@@ -1,6 +1,7 @@
1
1
  import { FeatureMessages } from './types';
2
2
  interface FeatureMessageProps {
3
3
  message: FeatureMessages;
4
+ onDismiss?: (id: string) => void;
4
5
  }
5
- declare const FeatureMessage: ({ message }: FeatureMessageProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const FeatureMessage: ({ message, onDismiss }: FeatureMessageProps) => import("react/jsx-runtime").JSX.Element;
6
7
  export default FeatureMessage;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /////////////---Icon imports---////////////////////
3
3
  import { AlertTriangle, CircleX, Info } from "lucide-react";
4
- const FeatureMessage = ({ message }) => {
4
+ const FeatureMessage = ({ message, onDismiss }) => {
5
5
  function hexToRgba(hex, alpha) {
6
6
  // Remove leading #
7
7
  hex = hex.replace(/^#/, "");
@@ -16,32 +16,110 @@ const FeatureMessage = ({ message }) => {
16
16
  const b = bigint & 255;
17
17
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
18
18
  }
19
+ // Handle click with tracking
20
+ const handleClick = async (e) => {
21
+ // Don't trigger if clicking close button
22
+ if (e.target.closest('[data-dismiss-button]')) {
23
+ return;
24
+ }
25
+ const action = message.click_action || 'none';
26
+ // Perform the action
27
+ switch (action) {
28
+ case 'navigate':
29
+ if (message.click_url) {
30
+ window.location.href = message.click_url;
31
+ }
32
+ break;
33
+ case 'external':
34
+ if (message.click_url) {
35
+ window.open(message.click_url, '_blank', 'noopener,noreferrer');
36
+ }
37
+ break;
38
+ case 'dismiss':
39
+ handleDismiss();
40
+ break;
41
+ case 'none':
42
+ default:
43
+ break;
44
+ }
45
+ };
46
+ const handleDismiss = () => {
47
+ if (onDismiss) {
48
+ onDismiss(message.id);
49
+ }
50
+ };
51
+ const filterButton = (type) => {
52
+ if (type?.click_action === "external") {
53
+ return 'Open';
54
+ }
55
+ else if (type?.click_action === "navigate") {
56
+ return 'Go to';
57
+ }
58
+ else if (type?.click_action === "dismiss") {
59
+ return 'Close';
60
+ }
61
+ else {
62
+ return '';
63
+ }
64
+ };
19
65
  return (_jsxs("div", { style: {
20
- width: '100%',
21
- paddingTop: '4px', // p-4 = 1rem
22
- paddingBottom: '4px',
23
- paddingLeft: '12px',
24
- paddingRight: '12px',
66
+ width: "95%", // w-[95%]
67
+ height: "100%", // h-full
68
+ marginLeft: "auto", // mx-auto
69
+ marginRight: "auto",
70
+ display: "flex", // flex flex-row
71
+ flexDirection: "row",
72
+ paddingTop: "0.75rem", // py-3
73
+ paddingBottom: "0.75rem",
74
+ paddingLeft: "0.75rem", // px-3
75
+ paddingRight: "0.75rem",
76
+ borderRadius: "0.5rem", // rounded-lg
25
77
  backgroundColor: message?.backgroundColor,
26
- border: `1px solid ${message?.textColor}`, // Completed border style
27
- borderRadius: "8px",
28
78
  borderWidth: "1px",
29
- borderColor: `${hexToRgba(message.borderColor, 0.3)}`,
79
+ borderColor: hexToRgba(message?.borderColor, 0.3),
30
80
  boxShadow: `0 10px 15px -3px ${hexToRgba(message.borderColor, 0.4)}, 0 4px 6px -4px ${hexToRgba(message.borderColor, 0.4)}`
31
- }, children: [_jsxs("div", { className: 'w-full flex flex-row items-center', style: {
32
- width: '100%',
33
- display: 'flex',
34
- flexDirection: 'row',
35
- alignItems: 'center'
36
- }, children: [message?.type === 'Information' && _jsx(Info, { size: 16, style: { color: 'rgb(59 130 24)' } }), message?.type === 'Warning' && _jsx(AlertTriangle, { size: 16, style: { color: 'rgb(245 158 1)' } }), message?.type === 'Error' && _jsx(CircleX, { size: 16, style: { color: 'rgb(239 68 68)' } }), _jsx("p", { className: `text-lg font-semibold text-[${message?.textColor}] ml-3`, style: {
37
- fontSize: '1.125rem', // text-lg = 18px = 1.125rem
38
- fontWeight: 600, // font-semibold = 600
39
- color: `${message?.textColor}`,
40
- marginLeft: '12px'
41
- }, children: message.title })] }), _jsx("p", { className: `text-sm text-[${message?.textColor}]`, style: {
42
- marginTop: '8px',
43
- fontSize: '0.875rem', // text-sm = 14px = 0.875rem
44
- color: `${message?.textColor}`,
45
- }, children: message.content })] }));
81
+ }, children: [_jsxs("div", { style: {
82
+ width: message?.click_action === "view only" ? "100%" : "80%", // w-4/5
83
+ height: "100%", // h-full
84
+ display: "flex", // flex flex-col
85
+ flexDirection: "column",
86
+ borderRightWidth: "1px", // border-r
87
+ borderRightColor: "white"
88
+ }, children: [_jsxs("div", { style: {
89
+ position: "relative", // relative
90
+ width: "100%", // w-full
91
+ display: "flex", // flex flex-row
92
+ flexDirection: "row",
93
+ alignItems: "center" // items-center
94
+ }, children: [message?.type === "Information" && (_jsx(Info, { size: 16, style: { color: "#3b82f6" } }) // text-blue-500
95
+ ), message?.type === "Warning" && (_jsx(AlertTriangle, { size: 16, style: { color: "#f59e0b" } }) // text-amber-500
96
+ ), message?.type === "Error" && (_jsx(CircleX, { size: 16, style: { color: "#ef4444" } }) // text-red-500
97
+ ), _jsx("p", { style: {
98
+ color: message?.textColor,
99
+ fontSize: "1.125rem", // text-lg
100
+ fontWeight: 600, // font-semibold
101
+ marginLeft: "0.75rem" // ml-3
102
+ }, children: message?.title })] }), _jsx("p", { style: {
103
+ color: message?.textColor,
104
+ fontSize: "0.75rem", // text-xs
105
+ marginTop: "0.25rem" // mt-1
106
+ }, children: message?.content })] }), message?.click_action && message?.click_action === "view only" ?
107
+ null
108
+ :
109
+ _jsx("button", {
110
+ //data-dismiss-button
111
+ style: {
112
+ width: "20%", // w-1/5
113
+ height: "1rem", // h-4 (16px)
114
+ marginTop: "auto", // my-auto
115
+ marginBottom: "auto",
116
+ display: "flex", // flex
117
+ justifyContent: "center", // justify-center
118
+ alignItems: "center" // items-center
119
+ }, children: _jsx("p", { style: {
120
+ fontSize: "1rem", // text-base
121
+ color: "white",
122
+ cursor: "pointer"
123
+ }, onClick: message?.click_action ? handleClick : undefined, onMouseEnter: (e) => (e.currentTarget.style.color = `${message?.borderColor}`), onMouseLeave: (e) => (e.currentTarget.style.color = "white"), children: `${filterButton(message)}` }) })] }));
46
124
  };
47
125
  export default FeatureMessage;
@@ -23,6 +23,36 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
23
23
  const [visible, setVisible] = useState(false);
24
24
  const [pathname, setPathname] = useState(() => typeof window !== 'undefined' ? window.location.pathname : '');
25
25
  const device = useDeviceDetection();
26
+ //Dismiss message caching
27
+ //Dismissed message cached retrieval
28
+ const DISMISSED_KEY = "__feature_messages_dismissed__";
29
+ const getDismissedMessages = () => {
30
+ if (typeof window === "undefined")
31
+ return {};
32
+ try {
33
+ return JSON.parse(localStorage.getItem(DISMISSED_KEY) || "{}");
34
+ }
35
+ catch {
36
+ return {};
37
+ }
38
+ };
39
+ //Dismissed message cache storing
40
+ const dismissMessage = (message) => {
41
+ const dismissed = getDismissedMessages();
42
+ dismissed[message.id] = {
43
+ messageId: message.id,
44
+ dismissedAt: new Date().toISOString(),
45
+ messageUpdatedAt: message.updated_at,
46
+ };
47
+ localStorage.setItem(DISMISSED_KEY, JSON.stringify(dismissed));
48
+ };
49
+ const isDismissed = (message) => {
50
+ const dismissed = getDismissedMessages()[message.id];
51
+ if (!dismissed)
52
+ return false;
53
+ // Re-show if message changed
54
+ return dismissed.messageUpdatedAt === message.updated_at;
55
+ };
26
56
  // Create supabase client with project key header
27
57
  const supabaseClient = getSupabaseClient(projectKey);
28
58
  // Effect to track pathname changes
@@ -55,6 +85,7 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
55
85
  useEffect(() => {
56
86
  if (typeof window === 'undefined')
57
87
  return; // Skip on server
88
+ let fadeOutTimer = null; // Track the timer
58
89
  const fetchMessage = async () => {
59
90
  const { data } = await supabaseClient
60
91
  .from('feature_messages')
@@ -64,14 +95,16 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
64
95
  .eq('is_active', true)
65
96
  .order('created_at', { ascending: false });
66
97
  const match = data?.find((msg) => routeMatches(msg.route, pathname));
67
- if (match) {
98
+ if (match && !isDismissed(match)) {
99
+ if (fadeOutTimer)
100
+ clearTimeout(fadeOutTimer); // Clear any pending timeout
68
101
  setMessage(match);
69
102
  setVisible(true); // Fade in
70
103
  }
71
104
  else {
72
105
  // Trigger fade out before removing message
106
+ fadeOutTimer = setTimeout(() => setMessage(null), 300);
73
107
  setVisible(false);
74
- setTimeout(() => setMessage(null), 300); // match fadeOut duration
75
108
  }
76
109
  };
77
110
  fetchMessage();
@@ -84,12 +117,14 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
84
117
  }, (payload) => {
85
118
  const msg = payload.new;
86
119
  if (msg.project_key === projectKey && msg.is_active && routeMatches(msg.route, pathname)) {
120
+ if (fadeOutTimer)
121
+ clearTimeout(fadeOutTimer); // Clear any pending timeout
87
122
  setMessage(msg);
88
123
  setVisible(true);
89
124
  }
90
125
  else {
91
126
  setVisible(false);
92
- setTimeout(() => setMessage(null), 300);
127
+ fadeOutTimer = setTimeout(() => setMessage(null), 300);
93
128
  }
94
129
  })
95
130
  .subscribe();
@@ -99,5 +134,5 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
99
134
  }, [pathname, projectKey]);
100
135
  if (!message)
101
136
  return null;
102
- return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message }) }));
137
+ return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message, onDismiss: () => dismissMessage(message) }) }));
103
138
  };
@@ -0,0 +1 @@
1
+ export declare const trackClick: (messageId: string, projectKey: string, clickAction: string, clickUrl?: string) => Promise<void>;
@@ -0,0 +1,20 @@
1
+ // src/lib/trackEvent.ts
2
+ import { getSupabaseClient } from "../supabase/supabaseClient";
3
+ export const trackClick = async (messageId, projectKey, clickAction, clickUrl) => {
4
+ try {
5
+ const client = getSupabaseClient(projectKey);
6
+ await client.from('message_analytics').insert({
7
+ project_key: projectKey,
8
+ message_id: messageId,
9
+ event_type: 'click',
10
+ click_action: clickAction,
11
+ click_url: clickUrl,
12
+ user_agent: navigator.userAgent,
13
+ created_at: new Date().toISOString(),
14
+ });
15
+ }
16
+ catch (error) {
17
+ // Fail silently - don't break the user experience
18
+ console.error('Failed to track click:', error);
19
+ }
20
+ };
package/dist/types.d.ts CHANGED
@@ -13,10 +13,19 @@ export type FeatureMessages = {
13
13
  borderWidth: number;
14
14
  is_active: boolean;
15
15
  created_at: string;
16
+ updated_at?: string;
17
+ ended_at?: string;
16
18
  type?: 'Information' | 'Warning' | 'Error';
19
+ click_action?: 'view only' | 'navigate' | 'external' | 'dismiss';
20
+ click_url?: string;
17
21
  };
18
22
  export type FeatureMessageProviderProps = {
19
23
  projectKey: string;
20
24
  className?: string;
21
25
  disableDefaultStyles?: boolean;
22
26
  };
27
+ export type DismissedMessage = {
28
+ messageId: string;
29
+ dismissedAt: string;
30
+ messageUpdatedAt: any;
31
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-notify-sdk",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "SDK for displaying real-time route-specific messages in React apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",