react-notify-sdk 1.0.34 → 1.0.35
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.
- package/dist/FadeWrapper.d.ts +2 -2
- package/dist/FeatureMessage.d.ts +6 -0
- package/dist/FeatureMessage.js +47 -0
- package/dist/FeatureMessageProvider.d.ts +2 -0
- package/dist/FeatureMessageProvider.js +82 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/supabase/supabaseClient.d.ts +7 -27
- package/dist/supabase/supabaseClient.js +17 -59
- package/dist/types.d.ts +3 -11
- package/package.json +6 -5
- package/dist/NotifyMessage.d.ts +0 -6
- package/dist/NotifyMessage.js +0 -49
- package/dist/NotifyProvider.d.ts +0 -12
- package/dist/NotifyProvider.js +0 -76
- package/dist/useFeatureMessage.d.ts +0 -17
- package/dist/useFeatureMessage.js +0 -148
- /package/dist/{UseDeviceDetection.d.ts → useDeviceDetection.d.ts} +0 -0
- /package/dist/{UseDeviceDetection.js → useDeviceDetection.js} +0 -0
package/dist/FadeWrapper.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { FeatureMessages } from './types';
|
|
3
3
|
interface FadeWrapperProps {
|
|
4
4
|
visible: boolean;
|
|
5
|
-
message:
|
|
5
|
+
message: FeatureMessages;
|
|
6
6
|
device: string;
|
|
7
7
|
}
|
|
8
8
|
export declare const FadeWrapper: import("goober").StyledVNode<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & import("goober").DefaultTheme & FadeWrapperProps, never>>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/////////////---Icon imports---////////////////////
|
|
3
|
+
import { AlertTriangle, CircleX, Info } from "lucide-react";
|
|
4
|
+
const FeatureMessage = ({ message }) => {
|
|
5
|
+
function hexToRgba(hex, alpha) {
|
|
6
|
+
// Remove leading #
|
|
7
|
+
hex = hex.replace(/^#/, "");
|
|
8
|
+
// Expand shorthand form (#03F → #0033FF)
|
|
9
|
+
if (hex.length === 3) {
|
|
10
|
+
hex = hex.split("").map(x => x + x).join("");
|
|
11
|
+
}
|
|
12
|
+
// Parse into r, g, b
|
|
13
|
+
const bigint = parseInt(hex, 16);
|
|
14
|
+
const r = (bigint >> 16) & 255;
|
|
15
|
+
const g = (bigint >> 8) & 255;
|
|
16
|
+
const b = bigint & 255;
|
|
17
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
18
|
+
}
|
|
19
|
+
return (_jsxs("div", { style: {
|
|
20
|
+
width: '100%',
|
|
21
|
+
paddingTop: '4px', // p-4 = 1rem
|
|
22
|
+
paddingBottom: '4px',
|
|
23
|
+
paddingLeft: '12px',
|
|
24
|
+
paddingRight: '12px',
|
|
25
|
+
backgroundColor: message?.backgroundColor,
|
|
26
|
+
border: `1px solid ${message?.textColor}`, // Completed border style
|
|
27
|
+
borderRadius: "8px",
|
|
28
|
+
borderWidth: "1px",
|
|
29
|
+
borderColor: `${hexToRgba(message.borderColor, 0.3)}`,
|
|
30
|
+
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 })] }));
|
|
46
|
+
};
|
|
47
|
+
export default FeatureMessage;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// src/FeatureMessageProvider.tsx
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import FeatureMessage from './FeatureMessage';
|
|
5
|
+
import { getSupabaseClient } from './supabase/supabaseClient';
|
|
6
|
+
import useDeviceDetection from './useDeviceDetection';
|
|
7
|
+
import FadeWrapper from './FadeWrapper';
|
|
8
|
+
// Wildcard route matching utility
|
|
9
|
+
const routeMatches = (pattern, path) => {
|
|
10
|
+
if (pattern === path)
|
|
11
|
+
return true;
|
|
12
|
+
const escapeRegex = (str) => str.replace(/([.+^=!:${}()|[\]/\\])/g, "\\$1");
|
|
13
|
+
const regexPattern = "^" + pattern.split("*").map(escapeRegex).join(".*") + "$";
|
|
14
|
+
try {
|
|
15
|
+
return new RegExp(regexPattern).test(path);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const FeatureMessageProvider = ({ projectKey,
|
|
22
|
+
//className,
|
|
23
|
+
//style,
|
|
24
|
+
//disableDefaultStyles = true,
|
|
25
|
+
}) => {
|
|
26
|
+
const [message, setMessage] = useState(null);
|
|
27
|
+
const [visible, setVisible] = useState(false);
|
|
28
|
+
const device = useDeviceDetection();
|
|
29
|
+
// Create supabase client with project key header
|
|
30
|
+
const supabaseClient = getSupabaseClient();
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (typeof window === 'undefined')
|
|
33
|
+
return; // Skip on server
|
|
34
|
+
const fetchMessage = async () => {
|
|
35
|
+
const { data } = await supabaseClient
|
|
36
|
+
.from('feature_messages')
|
|
37
|
+
.select('*')
|
|
38
|
+
.eq('project_key', projectKey)
|
|
39
|
+
.eq('route', window.location.pathname)
|
|
40
|
+
.eq('is_active', true)
|
|
41
|
+
.order('created_at', { ascending: false });
|
|
42
|
+
//.limit(1)
|
|
43
|
+
//.single();
|
|
44
|
+
const match = data?.find((msg) => routeMatches(msg.route, window.location.pathname));
|
|
45
|
+
if (match) {
|
|
46
|
+
setMessage(match);
|
|
47
|
+
setVisible(true); // Fade in
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Trigger fade out before removing message
|
|
51
|
+
setVisible(false);
|
|
52
|
+
setTimeout(() => setMessage(null), 300); // match fadeOut duration
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
fetchMessage();
|
|
56
|
+
const subscription = supabaseClient
|
|
57
|
+
.channel('feature_messages_channel')
|
|
58
|
+
.on('postgres_changes', {
|
|
59
|
+
event: '*',
|
|
60
|
+
schema: 'public',
|
|
61
|
+
table: 'feature_messages',
|
|
62
|
+
}, (payload) => {
|
|
63
|
+
const msg = payload.new;
|
|
64
|
+
if (msg.project_key === projectKey && msg.is_active && routeMatches(msg.route, window.location.pathname)) {
|
|
65
|
+
setMessage(msg);
|
|
66
|
+
setVisible(true);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
setVisible(false);
|
|
70
|
+
setTimeout(() => setMessage(null), 300);
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.subscribe();
|
|
74
|
+
return () => {
|
|
75
|
+
supabaseClient.removeChannel(subscription);
|
|
76
|
+
};
|
|
77
|
+
}, [window.location.pathname, projectKey]);
|
|
78
|
+
if (!message)
|
|
79
|
+
return null;
|
|
80
|
+
//const defaultClasses = `absolute ${message?.position}-6 left-1/2 transform -translate-x-1/2 z-20 w-full md:w-[${message?.width}%] px-3 py-1 bg-[${message?.backgroundColor}] border border-[${message?.borderColor}] text-[${message?.textColor}] rounded-lg shadow`;
|
|
81
|
+
return (_jsx(FadeWrapper, { visible: visible, device: device, message: message, children: _jsx(FeatureMessage, { message: message }) }));
|
|
82
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { FeatureMessageProvider } from './FeatureMessageProvider';
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
export interface FeatureMessagesConfig {
|
|
3
|
-
/**
|
|
4
|
-
* Your SaaS API key (provided to customers)
|
|
5
|
-
* This identifies the customer and their project
|
|
6
|
-
*/
|
|
7
|
-
projectKey: string;
|
|
8
|
-
/**
|
|
9
|
-
* Optional: Custom endpoint if you're using a different domain
|
|
10
|
-
* Defaults to your SaaS service endpoint
|
|
11
|
-
*/
|
|
12
|
-
endpoint?: string;
|
|
13
|
-
/**
|
|
14
|
-
* Optional: Enable debug logging
|
|
15
|
-
*/
|
|
16
|
-
debug?: boolean;
|
|
17
|
-
}
|
|
18
|
-
export declare const getSupabaseClient: (config: FeatureMessagesConfig) => SupabaseClient;
|
|
19
2
|
/**
|
|
20
|
-
* Get the
|
|
21
|
-
*
|
|
3
|
+
* Get the Supabase client for the SDK
|
|
4
|
+
* This connects to YOUR centralized notification platform database
|
|
5
|
+
* No configuration needed from developers - it's plug and play!
|
|
22
6
|
*/
|
|
23
|
-
export declare
|
|
7
|
+
export declare function getSupabaseClient(): SupabaseClient;
|
|
24
8
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
9
|
+
* Optional: Allow overriding the default configuration for testing/development
|
|
10
|
+
* This is mainly for your internal use during SDK development
|
|
27
11
|
*/
|
|
28
|
-
export declare
|
|
29
|
-
/**
|
|
30
|
-
* Reset the client (useful for testing or switching configurations)
|
|
31
|
-
*/
|
|
32
|
-
export declare const resetFeatureMessagesClient: () => void;
|
|
12
|
+
export declare function overrideSupabaseConfig(url: string, anonKey: string): void;
|
|
@@ -1,65 +1,23 @@
|
|
|
1
1
|
import { createClient } from '@supabase/supabase-js';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let featureMessagesClient = null;
|
|
7
|
-
let customerConfig = null;
|
|
8
|
-
export const getSupabaseClient = (config) => {
|
|
9
|
-
if (!config.projectKey) {
|
|
10
|
-
throw new Error('Feature Messages project key is required. Get yours at https://your-saas-dashboard.com');
|
|
11
|
-
}
|
|
12
|
-
// Validate that the service is properly configured
|
|
13
|
-
if (process.env.REACT_APP_SUPABASE_URL === 'https://dhgnstjrkeuqnsapwcec.supabase.co' ||
|
|
14
|
-
process.env.REACT_APP_SUPABASE_KEY === 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRoZ25zdGpya2V1cW5zYXB3Y2VjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA2MDA0NTEsImV4cCI6MjA2NjE3NjQ1MX0.TLBAPIWjPYvzNUUPHyakIypRydAeWM8Y1OrUzVvIgLQ') {
|
|
15
|
-
throw new Error('Feature Messages service is not properly configured. ' +
|
|
16
|
-
'Please contact support@your-saas.com for assistance.');
|
|
17
|
-
}
|
|
18
|
-
customerConfig = config;
|
|
19
|
-
if (!featureMessagesClient) {
|
|
20
|
-
featureMessagesClient = createClient(supabaseUrl, supabaseKey, {
|
|
21
|
-
global: {
|
|
22
|
-
headers: {
|
|
23
|
-
// Pass the project key in headers for filtering
|
|
24
|
-
'x-feature-messages-project-key': config.projectKey,
|
|
25
|
-
// Add package version for analytics/debugging
|
|
26
|
-
'x-feature-messages-version': process.env.npm_package_version || '1.0.0',
|
|
27
|
-
// Identify this as package usage (not dashboard)
|
|
28
|
-
'x-feature-messages-source': 'npm-package',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
realtime: {
|
|
32
|
-
headers: {
|
|
33
|
-
'x-feature-messages-project-key': config.projectKey,
|
|
34
|
-
'x-feature-messages-source': 'npm-package',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return featureMessagesClient;
|
|
40
|
-
};
|
|
2
|
+
// Your centralized Supabase configuration (hardcoded in the SDK)
|
|
3
|
+
const SDK_SUPABASE_URL = 'https://dhgnstjrkeuqnsapwcec.supabase.co';
|
|
4
|
+
const SDK_SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRoZ25zdGpya2V1cW5zYXB3Y2VjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA2MDA0NTEsImV4cCI6MjA2NjE3NjQ1MX0.TLBAPIWjPYvzNUUPHyakIypRydAeWM8Y1OrUzVvIgLQ';
|
|
5
|
+
let supabaseClient = null;
|
|
41
6
|
/**
|
|
42
|
-
* Get the
|
|
43
|
-
*
|
|
7
|
+
* Get the Supabase client for the SDK
|
|
8
|
+
* This connects to YOUR centralized notification platform database
|
|
9
|
+
* No configuration needed from developers - it's plug and play!
|
|
44
10
|
*/
|
|
45
|
-
export
|
|
46
|
-
if (!
|
|
47
|
-
|
|
48
|
-
'Get your project key at https://your-saas-dashboard.com');
|
|
11
|
+
export function getSupabaseClient() {
|
|
12
|
+
if (!supabaseClient) {
|
|
13
|
+
supabaseClient = createClient(SDK_SUPABASE_URL, SDK_SUPABASE_ANON_KEY);
|
|
49
14
|
}
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Get the current customer configuration
|
|
54
|
-
* Used internally by the package
|
|
55
|
-
*/
|
|
56
|
-
export const getCustomerConfig = () => {
|
|
57
|
-
return customerConfig;
|
|
58
|
-
};
|
|
15
|
+
return supabaseClient;
|
|
16
|
+
}
|
|
59
17
|
/**
|
|
60
|
-
*
|
|
18
|
+
* Optional: Allow overriding the default configuration for testing/development
|
|
19
|
+
* This is mainly for your internal use during SDK development
|
|
61
20
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
21
|
+
export function overrideSupabaseConfig(url, anonKey) {
|
|
22
|
+
supabaseClient = createClient(url, anonKey);
|
|
23
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { FeatureMessagesConfig } from "./supabase/supabaseClient";
|
|
3
|
-
export type FeatureMessage = {
|
|
1
|
+
export type FeatureMessages = {
|
|
4
2
|
id: string;
|
|
5
3
|
project_key: string;
|
|
6
4
|
route: string;
|
|
@@ -17,14 +15,8 @@ export type FeatureMessage = {
|
|
|
17
15
|
created_at: string;
|
|
18
16
|
type?: 'Information' | 'Warning' | 'Error';
|
|
19
17
|
};
|
|
20
|
-
export interface FeatureMessageFilter {
|
|
21
|
-
projectKey: string;
|
|
22
|
-
route: string;
|
|
23
|
-
deviceType?: 'desktop' | 'tablet' | 'mobile';
|
|
24
|
-
}
|
|
25
18
|
export type FeatureMessageProviderProps = {
|
|
26
|
-
config: FeatureMessagesConfig | null;
|
|
27
19
|
projectKey: string;
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
className?: string;
|
|
21
|
+
disableDefaultStyles?: boolean;
|
|
30
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-notify-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
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",
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
"ua-parser-js": "^2.0.4"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/react": "^19.1.
|
|
25
|
+
"@types/react": "^19.1.13",
|
|
26
|
+
"@types/react-dom": "^19.1.9",
|
|
26
27
|
"@types/react-router-dom": "^5.3.3",
|
|
27
|
-
"typescript": "^5.
|
|
28
|
+
"typescript": "^5.9.2"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"react": "^19.1.
|
|
31
|
-
"react-dom": "^19.
|
|
31
|
+
"react": "^19.1.1",
|
|
32
|
+
"react-dom": "^19.1.1",
|
|
32
33
|
"react-router-dom": "^7.6.2"
|
|
33
34
|
}
|
|
34
35
|
}
|
package/dist/NotifyMessage.d.ts
DELETED
package/dist/NotifyMessage.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import FadeWrapper from './FadeWrapper';
|
|
3
|
-
/////////////---Icon imports---////////////////////
|
|
4
|
-
import { AlertTriangle, CircleX, Info } from "lucide-react";
|
|
5
|
-
import useDeviceDetection from './UseDeviceDetection';
|
|
6
|
-
const NotificationMessage = ({ message }) => {
|
|
7
|
-
const device = useDeviceDetection();
|
|
8
|
-
function hexToRgba(hex, alpha) {
|
|
9
|
-
// Remove leading #
|
|
10
|
-
hex = hex.replace(/^#/, "");
|
|
11
|
-
// Expand shorthand form (#03F → #0033FF)
|
|
12
|
-
if (hex.length === 3) {
|
|
13
|
-
hex = hex.split("").map(x => x + x).join("");
|
|
14
|
-
}
|
|
15
|
-
// Parse into r, g, b
|
|
16
|
-
const bigint = parseInt(hex, 16);
|
|
17
|
-
const r = (bigint >> 16) & 255;
|
|
18
|
-
const g = (bigint >> 8) & 255;
|
|
19
|
-
const b = bigint & 255;
|
|
20
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
21
|
-
}
|
|
22
|
-
return (_jsx(FadeWrapper, { visible: message?.is_active, device: device, message: message, children: _jsxs("div", { style: {
|
|
23
|
-
width: '100%',
|
|
24
|
-
paddingTop: '4px', // p-4 = 1rem
|
|
25
|
-
paddingBottom: '4px',
|
|
26
|
-
paddingLeft: '12px',
|
|
27
|
-
paddingRight: '12px',
|
|
28
|
-
backgroundColor: message?.backgroundColor,
|
|
29
|
-
borderRadius: "8px",
|
|
30
|
-
borderWidth: "1px",
|
|
31
|
-
borderColor: `${hexToRgba(message.borderColor, 0.3)}`,
|
|
32
|
-
boxShadow: `0 10px 15px -3px ${hexToRgba(message.borderColor, 0.4)}, 0 4px 6px -4px ${hexToRgba(message.borderColor, 0.4)}`
|
|
33
|
-
}, children: [_jsxs("div", { className: 'w-full flex flex-row items-center', style: {
|
|
34
|
-
width: '100%',
|
|
35
|
-
display: 'flex',
|
|
36
|
-
flexDirection: 'row',
|
|
37
|
-
alignItems: 'center'
|
|
38
|
-
}, 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: {
|
|
39
|
-
fontSize: '1.125rem', // text-lg = 18px = 1.125rem
|
|
40
|
-
fontWeight: 600, // font-semibold = 600
|
|
41
|
-
color: `${message?.textColor}`,
|
|
42
|
-
marginLeft: '12px'
|
|
43
|
-
}, children: message.title })] }), _jsx("p", { className: `text-sm text-[${message?.textColor}]`, style: {
|
|
44
|
-
marginTop: '8px',
|
|
45
|
-
fontSize: '0.875rem', // text-sm = 14px = 0.875rem
|
|
46
|
-
color: `${message?.textColor}`,
|
|
47
|
-
}, children: message.content })] }) }));
|
|
48
|
-
};
|
|
49
|
-
export default NotificationMessage;
|
package/dist/NotifyProvider.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { FeatureMessageProviderProps } from './types';
|
|
2
|
-
import { FeatureMessagesConfig } from './supabase/supabaseClient';
|
|
3
|
-
interface FeatureMessagesContextType {
|
|
4
|
-
config: FeatureMessagesConfig | null;
|
|
5
|
-
projectKey: string | null;
|
|
6
|
-
}
|
|
7
|
-
export declare const NotifyProvider: ({ children, projectKey, endpoint, }: FeatureMessageProviderProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
8
|
-
/**
|
|
9
|
-
* Hook to get the feature messages context (for advanced use cases)
|
|
10
|
-
*/
|
|
11
|
-
export declare const useFeatureMessagesContext: () => FeatureMessagesContextType;
|
|
12
|
-
export {};
|
package/dist/NotifyProvider.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// src/FeatureMessage.tsx
|
|
3
|
-
import { useEffect, useState, createContext, useContext } from 'react';
|
|
4
|
-
import NotificationMessage from './NotifyMessage';
|
|
5
|
-
import { getSupabaseClient } from './supabase/supabaseClient';
|
|
6
|
-
import { useFeatureMessages } from './useFeatureMessage';
|
|
7
|
-
import useDeviceDetection from './UseDeviceDetection';
|
|
8
|
-
const FeatureMessagesContext = createContext({
|
|
9
|
-
config: null,
|
|
10
|
-
projectKey: null,
|
|
11
|
-
});
|
|
12
|
-
export const NotifyProvider = ({ children, projectKey, endpoint, }) => {
|
|
13
|
-
const config = { projectKey, endpoint };
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
getSupabaseClient(config);
|
|
16
|
-
}, [projectKey, endpoint]);
|
|
17
|
-
const [message, setMessage] = useState(null);
|
|
18
|
-
const [visible, setVisible] = useState(false);
|
|
19
|
-
const device = useDeviceDetection();
|
|
20
|
-
if (!message)
|
|
21
|
-
return null;
|
|
22
|
-
return (_jsxs(FeatureMessagesContext.Provider, { value: { config, projectKey }, children: [children, _jsx(AutomaticFeatureMessages, { projectKey: projectKey })] }));
|
|
23
|
-
/*
|
|
24
|
-
return message ? (
|
|
25
|
-
<FadeWrapper visible={visible} device={device} message={message}>
|
|
26
|
-
<NotificationMessage message={message} />
|
|
27
|
-
</FadeWrapper>
|
|
28
|
-
)
|
|
29
|
-
:
|
|
30
|
-
null;
|
|
31
|
-
*/
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Internal component that automatically displays messages based on current route
|
|
35
|
-
*/
|
|
36
|
-
const AutomaticFeatureMessages = ({ projectKey }) => {
|
|
37
|
-
// Get current route - works with any router or vanilla JavaScript
|
|
38
|
-
const [currentRoute, setCurrentRoute] = useState(window.location.pathname);
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
// Listen for route changes (works with React Router, Next.js Router, etc.)
|
|
41
|
-
const handleRouteChange = () => {
|
|
42
|
-
setCurrentRoute(window.location.pathname);
|
|
43
|
-
};
|
|
44
|
-
// Listen for browser navigation
|
|
45
|
-
window.addEventListener('popstate', handleRouteChange);
|
|
46
|
-
// Listen for programmatic navigation (works with most routers)
|
|
47
|
-
const originalPushState = history.pushState;
|
|
48
|
-
const originalReplaceState = history.replaceState;
|
|
49
|
-
history.pushState = function () {
|
|
50
|
-
originalPushState.apply(history, arguments);
|
|
51
|
-
setTimeout(handleRouteChange, 0);
|
|
52
|
-
};
|
|
53
|
-
history.replaceState = function () {
|
|
54
|
-
originalReplaceState.apply(history, arguments);
|
|
55
|
-
setTimeout(handleRouteChange, 0);
|
|
56
|
-
};
|
|
57
|
-
return () => {
|
|
58
|
-
window.removeEventListener('popstate', handleRouteChange);
|
|
59
|
-
history.pushState = originalPushState;
|
|
60
|
-
history.replaceState = originalReplaceState;
|
|
61
|
-
};
|
|
62
|
-
}, []);
|
|
63
|
-
// Use the internal hook to get messages for current route
|
|
64
|
-
const { message, dismissMessage } = useFeatureMessages({
|
|
65
|
-
projectKey,
|
|
66
|
-
route: currentRoute,
|
|
67
|
-
});
|
|
68
|
-
// Automatically render the message if one exists
|
|
69
|
-
return message ? (_jsx(NotificationMessage, { message: message })) : null;
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* Hook to get the feature messages context (for advanced use cases)
|
|
73
|
-
*/
|
|
74
|
-
export const useFeatureMessagesContext = () => {
|
|
75
|
-
return useContext(FeatureMessagesContext);
|
|
76
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { FeatureMessage, FeatureMessageFilter } from './types';
|
|
2
|
-
export interface UseFeatureMessagesReturn {
|
|
3
|
-
message: FeatureMessage | null;
|
|
4
|
-
messages: FeatureMessage[];
|
|
5
|
-
loading: boolean;
|
|
6
|
-
error: string | null;
|
|
7
|
-
dismissMessage: (messageId: string) => void;
|
|
8
|
-
refetch: () => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
export declare const useFeatureMessages: (filter: FeatureMessageFilter) => {
|
|
11
|
-
message: FeatureMessage | null;
|
|
12
|
-
messages: FeatureMessage[];
|
|
13
|
-
loading: boolean;
|
|
14
|
-
error: string | null;
|
|
15
|
-
dismissMessage: (messageId: string) => void;
|
|
16
|
-
refetch: () => Promise<void>;
|
|
17
|
-
};
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import { getFeatureMessagesClient } from './supabase/supabaseClient';
|
|
3
|
-
import { getCustomerConfig } from './supabase/supabaseClient';
|
|
4
|
-
import useDeviceDetection from './UseDeviceDetection';
|
|
5
|
-
const routeMatches = (pattern, path) => {
|
|
6
|
-
if (pattern === path)
|
|
7
|
-
return true;
|
|
8
|
-
const escapeRegex = (str) => str.replace(/([.+^=!:${}()|[\]/\\])/g, "\\$1");
|
|
9
|
-
const regexPattern = "^" + pattern.split("*").map(escapeRegex).join(".*") + "$";
|
|
10
|
-
try {
|
|
11
|
-
return new RegExp(regexPattern).test(path);
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
export const useFeatureMessages = (filter) => {
|
|
18
|
-
const [messages, setMessages] = useState([]);
|
|
19
|
-
const [loading, setLoading] = useState(true);
|
|
20
|
-
const [error, setError] = useState(null);
|
|
21
|
-
const [dismissedMessages, setDismissedMessages] = useState(new Set());
|
|
22
|
-
const channelRef = useRef(null);
|
|
23
|
-
const deviceInfo = useDeviceDetection();
|
|
24
|
-
const customerConfig = getCustomerConfig();
|
|
25
|
-
const { projectKey, route, deviceType } = filter;
|
|
26
|
-
const currentDeviceType = deviceType || deviceInfo;
|
|
27
|
-
// Filter messages based on route and active status
|
|
28
|
-
const filterMessages = useCallback((allMessages) => {
|
|
29
|
-
const now = new Date();
|
|
30
|
-
return allMessages.filter(message => {
|
|
31
|
-
// Check if message is active
|
|
32
|
-
if (!message.is_active)
|
|
33
|
-
return false;
|
|
34
|
-
// Check if message has ended
|
|
35
|
-
//if (message.ended_at && new Date(message.ended_at) < now) return false;
|
|
36
|
-
// Check project key (this should match what the customer set up in the dashboard)
|
|
37
|
-
if (message.project_key !== projectKey)
|
|
38
|
-
return false;
|
|
39
|
-
// Check route matching (including wildcards)
|
|
40
|
-
if (!routeMatches(route, message.route))
|
|
41
|
-
return false;
|
|
42
|
-
// Check if message has been dismissed
|
|
43
|
-
if (dismissedMessages.has(message.id))
|
|
44
|
-
return false;
|
|
45
|
-
return true;
|
|
46
|
-
});
|
|
47
|
-
}, [projectKey, route, dismissedMessages]);
|
|
48
|
-
// Get the most recent active message (since trigger enforces single active message)
|
|
49
|
-
const getActiveMessage = useCallback((filteredMessages) => {
|
|
50
|
-
if (filteredMessages.length === 0)
|
|
51
|
-
return null;
|
|
52
|
-
// Sort by creation date (newest first) since only one can be active
|
|
53
|
-
const sorted = [...filteredMessages].sort((a, b) => {
|
|
54
|
-
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
55
|
-
});
|
|
56
|
-
return sorted[0];
|
|
57
|
-
}, []);
|
|
58
|
-
// Fetch messages from your SaaS backend
|
|
59
|
-
const fetchMessages = useCallback(async () => {
|
|
60
|
-
try {
|
|
61
|
-
setError(null);
|
|
62
|
-
const client = getFeatureMessagesClient();
|
|
63
|
-
// Query messages for this project key
|
|
64
|
-
const { data, error: fetchError } = await client
|
|
65
|
-
.from('feature_messages')
|
|
66
|
-
.select('*')
|
|
67
|
-
.eq('project_key', projectKey)
|
|
68
|
-
.eq('is_active', true);
|
|
69
|
-
if (fetchError) {
|
|
70
|
-
throw fetchError;
|
|
71
|
-
}
|
|
72
|
-
setMessages(data || []);
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
console.error('Error fetching feature messages:', err);
|
|
76
|
-
setError(err instanceof Error ? err.message : 'Failed to fetch messages');
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
setLoading(false);
|
|
80
|
-
}
|
|
81
|
-
}, [projectKey]);
|
|
82
|
-
// Set up realtime subscription
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
try {
|
|
85
|
-
const client = getFeatureMessagesClient();
|
|
86
|
-
// Clean up existing subscription
|
|
87
|
-
if (channelRef.current) {
|
|
88
|
-
client.removeChannel(channelRef.current);
|
|
89
|
-
}
|
|
90
|
-
// Create new subscription for this project
|
|
91
|
-
const channel = client
|
|
92
|
-
.channel(`feature_messages_${projectKey}`)
|
|
93
|
-
.on('postgres_changes', {
|
|
94
|
-
event: '*',
|
|
95
|
-
schema: 'public',
|
|
96
|
-
table: 'feature_messages',
|
|
97
|
-
filter: `project_key=eq.${projectKey}`,
|
|
98
|
-
}, (payload) => {
|
|
99
|
-
if (customerConfig?.debug) {
|
|
100
|
-
console.log('Feature message change received:', payload);
|
|
101
|
-
}
|
|
102
|
-
if (payload.eventType === 'INSERT' || payload.eventType === 'UPDATE') {
|
|
103
|
-
const newMessage = payload.new;
|
|
104
|
-
setMessages(prev => {
|
|
105
|
-
const existing = prev.find(m => m.id === newMessage.id);
|
|
106
|
-
if (existing) {
|
|
107
|
-
return prev.map(m => m.id === newMessage.id ? newMessage : m);
|
|
108
|
-
}
|
|
109
|
-
return [...prev, newMessage];
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
else if (payload.eventType === 'DELETE') {
|
|
113
|
-
const deletedMessage = payload.old;
|
|
114
|
-
setMessages(prev => prev.filter(m => m.id !== deletedMessage.id));
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
.subscribe();
|
|
118
|
-
channelRef.current = channel;
|
|
119
|
-
// Initial fetch
|
|
120
|
-
fetchMessages();
|
|
121
|
-
return () => {
|
|
122
|
-
if (channelRef.current) {
|
|
123
|
-
client.removeChannel(channelRef.current);
|
|
124
|
-
channelRef.current = null;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
setError(err instanceof Error ? err.message : 'Failed to initialize feature messages');
|
|
130
|
-
setLoading(false);
|
|
131
|
-
}
|
|
132
|
-
}, [projectKey, fetchMessages, customerConfig?.debug]);
|
|
133
|
-
// Dismiss a message
|
|
134
|
-
const dismissMessage = useCallback((messageId) => {
|
|
135
|
-
setDismissedMessages(prev => new Set([...prev, messageId]));
|
|
136
|
-
}, []);
|
|
137
|
-
// Get filtered messages and active message
|
|
138
|
-
const filteredMessages = filterMessages(messages);
|
|
139
|
-
const activeMessage = getActiveMessage(filteredMessages);
|
|
140
|
-
return {
|
|
141
|
-
message: activeMessage,
|
|
142
|
-
messages: filteredMessages,
|
|
143
|
-
loading,
|
|
144
|
-
error,
|
|
145
|
-
dismissMessage,
|
|
146
|
-
refetch: fetchMessages,
|
|
147
|
-
};
|
|
148
|
-
};
|
|
File without changes
|
|
File without changes
|