react-notify-sdk 1.0.62 → 1.0.64

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,75 @@ 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 () => {
91
- 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");
98
+ const origin = window.location.origin;
99
+ // 🔹 1. Fetch ALL active messages once per project
100
+ const fetchMessages = async () => {
101
+ const response = await fetch("https://dhgnstjrkeuqnsapwcec.functions.supabase.co/get-feature-message", {
102
+ method: "POST",
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ },
106
+ body: JSON.stringify({
107
+ projectKey,
108
+ pathname: pathname,
109
+ origin: origin,
110
+ }),
111
+ });
112
+ if (!response.ok)
113
+ return null;
114
+ const data = await response.json();
115
+ if (data) {
116
+ setMessages(data);
112
117
  }
113
118
  };
114
- fetchMessage();
119
+ fetchMessages();
120
+ // 🔹 2. Subscribe ONCE per project
115
121
  const subscription = supabaseClient
116
- .channel('feature_messages_channel')
117
- .on('postgres_changes', {
118
- event: '*',
119
- schema: 'public',
120
- table: 'feature_messages',
121
- }, (payload) => {
122
+ .channel("feature_messages_channel")
123
+ .on("postgres_changes", { event: "*", schema: "public", table: "feature_messages" }, (payload) => {
122
124
  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
- }
125
+ if (msg.project_key !== projectKey)
126
+ return;
127
+ setMessages((prev) => {
128
+ const filtered = prev.filter((m) => m.id !== msg.id);
129
+ if (msg.is_active) {
130
+ return [msg, ...filtered];
131
+ }
132
+ return filtered;
133
+ });
135
134
  })
136
135
  .subscribe();
137
136
  return () => {
137
+ if (fadeOutTimer)
138
+ clearTimeout(fadeOutTimer);
138
139
  supabaseClient.removeChannel(subscription);
139
140
  };
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) }) }));
141
+ }, [projectKey]);
142
+ useEffect(() => {
143
+ if (!pathname || !messages.length)
144
+ return;
145
+ let fadeOutTimer = null;
146
+ const origin = typeof window !== "undefined" ? window.location.origin : "";
147
+ const match = messages.find((msg) => routeMatches(msg.route, pathname) &&
148
+ baseUrlMatches(msg.base_url) &&
149
+ !isDismissed(msg));
150
+ if (match) {
151
+ if (fadeOutTimer)
152
+ clearTimeout(fadeOutTimer);
153
+ setMessage(match);
154
+ setVisible(true);
155
+ }
156
+ else {
157
+ setVisible(false);
158
+ fadeOutTimer = setTimeout(() => setMessage(null), 300);
159
+ }
160
+ return () => {
161
+ if (fadeOutTimer)
162
+ clearTimeout(fadeOutTimer);
163
+ };
164
+ }, [pathname, messages]);
165
+ return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message, onDismiss: () => {
166
+ if (message)
167
+ dismissMessage(message);
168
+ } }) }));
144
169
  };
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.64",
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",