react-notify-sdk 1.0.62 → 1.0.63

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.
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { FeatureMessages } from './types';
3
3
  interface FadeWrapperProps {
4
4
  visible: boolean;
5
- message: FeatureMessages;
5
+ message: FeatureMessages | null;
6
6
  device: string | null;
7
7
  children: React.ReactNode;
8
8
  }
@@ -1,6 +1,6 @@
1
1
  import { FeatureMessages } from './types';
2
2
  interface FeatureMessageProps {
3
- message: FeatureMessages;
3
+ message: FeatureMessages | null;
4
4
  onDismiss?: (id: string) => void;
5
5
  }
6
6
  declare const FeatureMessage: ({ message, onDismiss }: FeatureMessageProps) => import("react/jsx-runtime").JSX.Element;
@@ -31,16 +31,16 @@ const FeatureMessage = ({ message, onDismiss }) => {
31
31
  if (e.target.closest('[data-dismiss-button]')) {
32
32
  return;
33
33
  }
34
- const action = message.click_action || 'none';
34
+ const action = message?.click_action || 'none';
35
35
  // Perform the action
36
36
  switch (action) {
37
37
  case 'navigate':
38
- if (message.click_url) {
39
- spaNavigate(message.click_url);
38
+ if (message?.click_url) {
39
+ spaNavigate(message?.click_url);
40
40
  }
41
41
  break;
42
42
  case 'external':
43
- if (message.click_url) {
43
+ if (message?.click_url) {
44
44
  window.open(message.click_url, '_blank', 'noopener,noreferrer');
45
45
  }
46
46
  break;
@@ -54,7 +54,7 @@ const FeatureMessage = ({ message, onDismiss }) => {
54
54
  };
55
55
  const handleDismiss = () => {
56
56
  if (onDismiss) {
57
- onDismiss(message.id);
57
+ onDismiss(message?.id ? message.id : '');
58
58
  }
59
59
  };
60
60
  const filterButton = (type) => {
@@ -85,8 +85,8 @@ const FeatureMessage = ({ message, onDismiss }) => {
85
85
  borderRadius: "0.5rem", // rounded-lg
86
86
  backgroundColor: message?.backgroundColor,
87
87
  borderWidth: "1px",
88
- borderColor: hexToRgba(message?.borderColor, 0.3),
89
- boxShadow: `0 10px 15px -3px ${hexToRgba(message.borderColor, 0.4)}, 0 4px 6px -4px ${hexToRgba(message.borderColor, 0.4)}`
88
+ borderColor: hexToRgba(message?.borderColor ? message?.borderColor : '', 0.3),
89
+ boxShadow: `0 10px 15px -3px ${hexToRgba(message?.borderColor ? message?.borderColor : '', 0.4)}, 0 4px 6px -4px ${hexToRgba(message?.borderColor ? message?.borderColor : '', 0.4)}`
90
90
  }, children: [_jsxs("div", { style: {
91
91
  width: message?.click_action === "view only" ? "100%" : "80%", // w-4/5
92
92
  height: "100%", // h-full
@@ -1,2 +1,2 @@
1
1
  import { FeatureMessageProviderProps } from './types';
2
- export declare const FeatureMessageProvider: ({ projectKey, }: FeatureMessageProviderProps) => import("react/jsx-runtime").JSX.Element | null;
2
+ export declare const FeatureMessageProvider: ({ projectKey, }: FeatureMessageProviderProps) => import("react/jsx-runtime").JSX.Element;
@@ -6,8 +6,8 @@ import { getSupabaseClient } from './supabase/supabaseClient';
6
6
  import useDeviceDetection from './useDeviceDetection';
7
7
  import FadeWrapper from './FadeWrapper';
8
8
  // Wildcard route matching utility
9
- const routeMatches = (pattern, path, baseUrl) => {
10
- if (pattern === path && baseUrl === window.location.origin)
9
+ const routeMatches = (pattern, path) => {
10
+ if (pattern === path)
11
11
  return true;
12
12
  const escapeRegex = (str) => str.replace(/([.+^=!:${}()|[\]/\\])/g, "\\$1");
13
13
  const regexPattern = "^" + pattern.split("*").map(escapeRegex).join(".*") + "$";
@@ -18,8 +18,16 @@ const routeMatches = (pattern, path, baseUrl) => {
18
18
  return false;
19
19
  }
20
20
  };
21
+ const baseUrlMatches = (msgBaseUrl) => {
22
+ if (!msgBaseUrl)
23
+ return true; // backward compatibility
24
+ if (typeof window === "undefined")
25
+ return false;
26
+ return msgBaseUrl === window.location.origin;
27
+ };
21
28
  export const FeatureMessageProvider = ({ projectKey, }) => {
22
29
  const [message, setMessage] = useState(null);
30
+ const [messages, setMessages] = useState([]);
23
31
  const [visible, setVisible] = useState(false);
24
32
  const [pathname, setPathname] = useState(() => typeof window !== 'undefined' ? window.location.pathname : '');
25
33
  const device = useDeviceDetection();
@@ -48,11 +56,11 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
48
56
  setVisible(false);
49
57
  };
50
58
  const isDismissed = (message) => {
51
- const dismissed = getDismissedMessages()[message.id];
59
+ const dismissed = getDismissedMessages()[message?.id];
52
60
  if (!dismissed)
53
61
  return false;
54
62
  // Re-show if message changed
55
- return dismissed.messageUpdatedAt === message.updated_at;
63
+ return dismissed.messageUpdatedAt === message?.updated_at;
56
64
  };
57
65
  // Create supabase client with project key header
58
66
  const supabaseClient = getSupabaseClient(projectKey);
@@ -87,58 +95,67 @@ export const FeatureMessageProvider = ({ projectKey, }) => {
87
95
  if (typeof window === 'undefined')
88
96
  return; // Skip on server
89
97
  let fadeOutTimer = null; // Track the timer
90
- const fetchMessage = async () => {
98
+ const origin = window.location.origin;
99
+ // 🔹 1. Fetch ALL active messages once per project
100
+ const fetchMessages = async () => {
91
101
  const { data } = await supabaseClient
92
- .from('feature_messages')
93
- .select('*')
94
- .eq('project_key', projectKey)
95
- .eq('route', pathname)
96
- .eq('base_url', window.location.origin)
97
- .eq('is_active', true)
98
- .order('created_at', { ascending: false });
99
- const match = data?.find((msg) => routeMatches(msg.route, pathname, msg.base_url));
100
- if (match && !isDismissed(match)) {
101
- if (fadeOutTimer)
102
- clearTimeout(fadeOutTimer); // Clear any pending timeout
103
- setMessage(match);
104
- setVisible(true); // Fade in
105
- console.log("match set");
106
- }
107
- else {
108
- // Trigger fade out before removing message
109
- fadeOutTimer = setTimeout(() => setMessage(null), 300);
110
- setVisible(false);
111
- console.log("match not set");
102
+ .from("feature_messages")
103
+ .select("*")
104
+ .eq("project_key", projectKey)
105
+ .eq("is_active", true)
106
+ .order("created_at", { ascending: false });
107
+ if (data) {
108
+ setMessages(data);
112
109
  }
113
110
  };
114
- fetchMessage();
111
+ fetchMessages();
112
+ // 🔹 2. Subscribe ONCE per project
115
113
  const subscription = supabaseClient
116
- .channel('feature_messages_channel')
117
- .on('postgres_changes', {
118
- event: '*',
119
- schema: 'public',
120
- table: 'feature_messages',
121
- }, (payload) => {
114
+ .channel("feature_messages_channel")
115
+ .on("postgres_changes", { event: "*", schema: "public", table: "feature_messages" }, (payload) => {
122
116
  const msg = payload.new;
123
- if (msg.project_key === projectKey && msg.is_active && routeMatches(msg.route, pathname, msg.base_url)) {
124
- if (fadeOutTimer)
125
- clearTimeout(fadeOutTimer); // Clear any pending timeout
126
- setMessage(msg);
127
- setVisible(true);
128
- console.log("renders message");
129
- }
130
- else {
131
- setVisible(false);
132
- console.log("does not render message");
133
- fadeOutTimer = setTimeout(() => setMessage(null), 300);
134
- }
117
+ if (msg.project_key !== projectKey)
118
+ return;
119
+ setMessages((prev) => {
120
+ const filtered = prev.filter((m) => m.id !== msg.id);
121
+ if (msg.is_active) {
122
+ return [msg, ...filtered];
123
+ }
124
+ return filtered;
125
+ });
135
126
  })
136
127
  .subscribe();
137
128
  return () => {
129
+ if (fadeOutTimer)
130
+ clearTimeout(fadeOutTimer);
138
131
  supabaseClient.removeChannel(subscription);
139
132
  };
140
- }, [pathname, projectKey]);
141
- if (!message)
142
- return null;
143
- return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message, onDismiss: () => dismissMessage(message) }) }));
133
+ }, [projectKey]);
134
+ useEffect(() => {
135
+ if (!pathname || !messages.length)
136
+ return;
137
+ let fadeOutTimer = null;
138
+ const origin = typeof window !== "undefined" ? window.location.origin : "";
139
+ const match = messages.find((msg) => routeMatches(msg.route, pathname) &&
140
+ baseUrlMatches(msg.base_url) &&
141
+ !isDismissed(msg));
142
+ if (match) {
143
+ if (fadeOutTimer)
144
+ clearTimeout(fadeOutTimer);
145
+ setMessage(match);
146
+ setVisible(true);
147
+ }
148
+ else {
149
+ setVisible(false);
150
+ fadeOutTimer = setTimeout(() => setMessage(null), 300);
151
+ }
152
+ return () => {
153
+ if (fadeOutTimer)
154
+ clearTimeout(fadeOutTimer);
155
+ };
156
+ }, [pathname, messages]);
157
+ return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message, onDismiss: () => {
158
+ if (message)
159
+ dismissMessage(message);
160
+ } }) }));
144
161
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-notify-sdk",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
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",