pager-widget 1.0.11-alpha.9 → 1.0.13
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/index.css +1 -1
- package/dist/index.html +40 -0
- package/dist/index.js +8 -8
- package/dist/react-parcel.9f367d21.css +2970 -0
- package/dist/react-parcel.9f367d21.css.map +1 -0
- package/dist/react-parcel.js +231527 -0
- package/dist/react-parcel.js.map +1 -0
- package/dist/server.html +33 -39
- package/out/AgentFeedbackButtonWrapper.js +15 -0
- package/out/App.js +28 -0
- package/out/BotMain.js +91 -0
- package/out/ButtonsWrapper.js +29 -0
- package/out/ChatPage.js +9 -0
- package/out/ChatPageContent.js +131 -0
- package/out/ChatPageHeader.js +267 -0
- package/out/ChatPageInput.js +29 -0
- package/out/ChatWidget.js +17 -0
- package/out/Consent.js +66 -0
- package/out/CountryDropdown.js +31 -0
- package/out/Form.js +238 -0
- package/out/HomePage.js +11 -0
- package/out/HomePageContent.js +32 -0
- package/out/HomePageFooter.js +87 -0
- package/out/HomePageHeader.js +66 -0
- package/out/InputField.js +80 -0
- package/out/Markdown.js +126 -0
- package/out/Message.js +197 -0
- package/out/MessagesT.js +2 -0
- package/out/NewRecentMessagePage.js +525 -0
- package/out/NewRecentMessageTab.js +23 -0
- package/out/ReceivedMessage.js +211 -0
- package/out/RecentMessageDetail.js +178 -0
- package/out/RecentMessageList.js +112 -0
- package/out/RecentMessageListCard.js +20 -0
- package/out/RecentMessageListHeader.js +156 -0
- package/out/RecentMessagePage.js +183 -0
- package/out/RecentMessageTab.js +35 -0
- package/out/WidgetMessage.js +14 -0
- package/out/WidgetToggleButton.js +191 -0
- package/out/WidgetType.js +2 -0
- package/out/assets/icons/pikaicons-react.js +17475 -0
- package/out/components/ConsentForm.js +7 -0
- package/out/components/CustomTooltip.js +34 -0
- package/out/hooks/useChatHistoryExists.js +32 -0
- package/out/hooks/useChatHistoryListExist.js +81 -0
- package/out/hooks/useChatScroll.js +14 -0
- package/out/hooks/useCurrentConversationExists.js +88 -0
- package/out/hooks/useInitialMessage.js +33 -0
- package/out/hooks/useMessage.js +230 -0
- package/out/hooks/useMessageReceiver.js +183 -0
- package/out/hooks/usePopupAnimation.js +20 -0
- package/out/hooks/useSocket.js +19 -0
- package/out/hooks/useSocketContext.js +81 -0
- package/out/hooks/useWidgetDimension.js +41 -0
- package/out/hooks/useWidgetToggle.js +22 -0
- package/out/index.js +31 -0
- package/out/markdown/CodeCopy.js +22 -0
- package/out/markdown/CodeHighlight.js +58 -0
- package/out/markdown/PagerMarkdown.js +36 -0
- package/out/markdown/pager_md.js +152 -0
- package/out/onscreenNotificationPopup.js +23 -0
- package/out/socket.js +11 -0
- package/out/utils/checkConversationExit.js +24 -0
- package/out/utils/convertTime.js +43 -0
- package/out/utils/sendInitialMessage.js +82 -0
- package/out/widget.js +21 -0
- package/out/widgetConfigStore.js +205 -0
- package/out/widgetConfigStore2.js +569 -0
- package/out/widgetControlstore.js +23 -0
- package/out/widgetStateHandler.js +106 -0
- package/out/widgetStore.js +171 -0
- package/out/widgetUserStore.js +47 -0
- package/package.json +3 -3
- package/server.html +33 -39
- package/.husky/pre-commit +0 -4
- package/docs/issues/001-use-widget-resize-hook.md +0 -51
- package/docs/issues/002-embed-snippet-container-resize.md +0 -81
- package/docs/prd/0001-iframe-resize-scroll-through.md +0 -333
- package/test-iframe-resize.html +0 -74
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setWidgetLocalState = void 0;
|
|
4
|
+
// const LOCAL_KEY = 'DUMMY'
|
|
5
|
+
const setWidgetLocalState = ({ widget_id,
|
|
6
|
+
// conversation_id,
|
|
7
|
+
user_state, events, }) => {
|
|
8
|
+
const conversation_id = localStorage.getItem('CURRENT_CONVERSATION') ?? null;
|
|
9
|
+
console.log('conversation id from current', conversation_id);
|
|
10
|
+
appendEvent({ widget_id, conversation_id, user_state, events });
|
|
11
|
+
};
|
|
12
|
+
exports.setWidgetLocalState = setWidgetLocalState;
|
|
13
|
+
const appendEvent = ({ widget_id, conversation_id, user_state, events, }) => {
|
|
14
|
+
console.log('DEBUGG :: append event called', events);
|
|
15
|
+
const existing = localStorage.getItem(`CONVERSATION::${widget_id}`);
|
|
16
|
+
const _events = Array.isArray(events) ? events : [events];
|
|
17
|
+
const currentWidgetState = {
|
|
18
|
+
widget_id,
|
|
19
|
+
conversation_id,
|
|
20
|
+
user_state,
|
|
21
|
+
events: _events,
|
|
22
|
+
};
|
|
23
|
+
if (!existing) {
|
|
24
|
+
localStorage.setItem(`CONVERSATION::${widget_id}`, JSON.stringify(currentWidgetState));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(existing);
|
|
29
|
+
const isSameConversation = parsed.conversation_id === conversation_id;
|
|
30
|
+
const previousEvents = isSameConversation ? parsed.events : [];
|
|
31
|
+
// 🧹 Remove FORM_INPUT_NEEDED if form is now submitted
|
|
32
|
+
// if (user_state === 'FORM_SUBMITTED') {
|
|
33
|
+
// previousEvents = previousEvents.filter(
|
|
34
|
+
// (e) => e.type !== 'FORM_INPUT_NEEDED'
|
|
35
|
+
// )
|
|
36
|
+
// }
|
|
37
|
+
// ✅ Deduplicate events by `event_id` (if present), else fallback to serialized JSON
|
|
38
|
+
const allEvents = [...previousEvents, ..._events];
|
|
39
|
+
const deduplicated = [
|
|
40
|
+
...new Map(allEvents.map((e) => {
|
|
41
|
+
const key = e.type === 'received' && 'message' in e
|
|
42
|
+
? e.message?.event_id
|
|
43
|
+
: JSON.stringify(e);
|
|
44
|
+
return [key, e];
|
|
45
|
+
})).values(),
|
|
46
|
+
];
|
|
47
|
+
const updatedState = {
|
|
48
|
+
widget_id,
|
|
49
|
+
conversation_id,
|
|
50
|
+
user_state,
|
|
51
|
+
events: deduplicated,
|
|
52
|
+
};
|
|
53
|
+
localStorage.setItem(`CONVERSATION::${widget_id}`, JSON.stringify(updatedState));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('Failed to append event to widget state:', error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
// const appendEvent = ({
|
|
60
|
+
// widget_id,
|
|
61
|
+
// conversation_id,
|
|
62
|
+
// user_state,
|
|
63
|
+
// events,
|
|
64
|
+
// }: appendEventType) => {
|
|
65
|
+
// console.log('DEBUGG :: append event called', events)
|
|
66
|
+
// const existing = localStorage.getItem(LOCAL_KEY)
|
|
67
|
+
// const _events = Array.isArray(events) ? events : [events]
|
|
68
|
+
// const currentWidgetState: LocalEventStore = {
|
|
69
|
+
// widget_id,
|
|
70
|
+
// conversation_id,
|
|
71
|
+
// user_state,
|
|
72
|
+
// events: _events,
|
|
73
|
+
// }
|
|
74
|
+
// if (!existing) {
|
|
75
|
+
// localStorage.setItem(LOCAL_KEY, JSON.stringify(currentWidgetState))
|
|
76
|
+
// return
|
|
77
|
+
// }
|
|
78
|
+
// try {
|
|
79
|
+
// const parsed: LocalEventStore = JSON.parse(existing)
|
|
80
|
+
// const isSameConversation = parsed.conversation_id === conversation_id
|
|
81
|
+
// // const deduplicated = [
|
|
82
|
+
// // ...new Map(
|
|
83
|
+
// // [...parsed.events, ..._events].map((e) => [
|
|
84
|
+
// // e?.message?.event_id ?? JSON.stringify(e),
|
|
85
|
+
// // e,
|
|
86
|
+
// // ])
|
|
87
|
+
// // ).values(),
|
|
88
|
+
// // ]
|
|
89
|
+
// let previousEvents = isSameConversation ? parsed.events : []
|
|
90
|
+
// // 👉 Remove FORM_INPUT_NEEDED if current state is FORM_SUBMITTED
|
|
91
|
+
// if (user_state === 'FORM_SUBMITTED') {
|
|
92
|
+
// previousEvents = previousEvents.filter(
|
|
93
|
+
// (e) => e.type !== 'FORM_INPUT_NEEDED'
|
|
94
|
+
// )
|
|
95
|
+
// }
|
|
96
|
+
// const updatedState: LocalEventStore = {
|
|
97
|
+
// widget_id,
|
|
98
|
+
// conversation_id,
|
|
99
|
+
// user_state,
|
|
100
|
+
// events: [...previousEvents, ..._events],
|
|
101
|
+
// }
|
|
102
|
+
// localStorage.setItem(LOCAL_KEY, JSON.stringify(updatedState))
|
|
103
|
+
// } catch (error) {
|
|
104
|
+
// console.error('Failed to append event to widget state:', error)
|
|
105
|
+
// }
|
|
106
|
+
// }
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useGetCurrentConversationId = exports.useChatExit = exports.useUserDetails = exports.useIsWidgetExpanded = exports.useSelectTab = exports.useSelectedConversationData = exports.useInspectConversation = exports.widgetStore = exports.WidgetExpandType = exports.TabType = void 0;
|
|
4
|
+
const store_1 = require("@xstate/store");
|
|
5
|
+
const react_1 = require("@xstate/store/react");
|
|
6
|
+
const react_2 = require("react");
|
|
7
|
+
// Tab types
|
|
8
|
+
var TabType;
|
|
9
|
+
(function (TabType) {
|
|
10
|
+
TabType["HOME"] = "home";
|
|
11
|
+
TabType["CHAT"] = "chat";
|
|
12
|
+
TabType["RECENT_MESSAGE"] = "recentMessage";
|
|
13
|
+
TabType["RECENT_MESSAGE_LIST"] = "recentMessageList";
|
|
14
|
+
TabType["RECENT_MESSAGE_DETAIL"] = "recentMessageDetail";
|
|
15
|
+
TabType["NEW_RECENT_MESSAGE"] = "newRecentMessage";
|
|
16
|
+
})(TabType || (exports.TabType = TabType = {}));
|
|
17
|
+
var WidgetExpandType;
|
|
18
|
+
(function (WidgetExpandType) {
|
|
19
|
+
WidgetExpandType["DEFAULT"] = "default";
|
|
20
|
+
WidgetExpandType["EXPANDED"] = "expanded";
|
|
21
|
+
WidgetExpandType["FULLSCREEN"] = "fullscreen";
|
|
22
|
+
})(WidgetExpandType || (exports.WidgetExpandType = WidgetExpandType = {}));
|
|
23
|
+
// ---- DEFAULT CONTEXT ----
|
|
24
|
+
const defaultContext = {
|
|
25
|
+
current_conversation_id: null,
|
|
26
|
+
selected_conversation_id: '',
|
|
27
|
+
switchTab: TabType.HOME,
|
|
28
|
+
isExpanded: WidgetExpandType.DEFAULT,
|
|
29
|
+
user_details: {
|
|
30
|
+
SYS_FirstName: '',
|
|
31
|
+
SYS_LastName: '',
|
|
32
|
+
SYS_Location: '',
|
|
33
|
+
SYS_Timezone: '',
|
|
34
|
+
SYS_UserEmail: '',
|
|
35
|
+
},
|
|
36
|
+
isChatExit: false,
|
|
37
|
+
};
|
|
38
|
+
const storedData = localStorage.getItem('widgetContext');
|
|
39
|
+
const initialContext = storedData
|
|
40
|
+
? { ...defaultContext, ...JSON.parse(storedData) }
|
|
41
|
+
: defaultContext;
|
|
42
|
+
// ---- STORE ----
|
|
43
|
+
exports.widgetStore = (0, store_1.createStore)({
|
|
44
|
+
context: initialContext,
|
|
45
|
+
on: {
|
|
46
|
+
setCurrentConversation: (context, event) => {
|
|
47
|
+
context.current_conversation_id = event.conversation_id;
|
|
48
|
+
},
|
|
49
|
+
selectConversation: (context, event) => {
|
|
50
|
+
context.selected_conversation_id = event.conversation_id;
|
|
51
|
+
persistContext();
|
|
52
|
+
},
|
|
53
|
+
selectTab: (context, event) => {
|
|
54
|
+
context.switchTab = event.tab;
|
|
55
|
+
persistContext();
|
|
56
|
+
},
|
|
57
|
+
expandWidget: (context, event) => {
|
|
58
|
+
context.isExpanded = event.expanded_size;
|
|
59
|
+
persistContext();
|
|
60
|
+
},
|
|
61
|
+
updateUserDetails: (context, event) => {
|
|
62
|
+
context.user_details = {
|
|
63
|
+
...context.user_details,
|
|
64
|
+
...event.user_details,
|
|
65
|
+
};
|
|
66
|
+
persistContext();
|
|
67
|
+
},
|
|
68
|
+
handleExitChatState: (context, event) => {
|
|
69
|
+
console.log('exit chat called in store');
|
|
70
|
+
context.isChatExit = event.value;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
// ---- PERSIST ----
|
|
75
|
+
const persistContext = () => {
|
|
76
|
+
const currentContext = exports.widgetStore.getSnapshot().context;
|
|
77
|
+
localStorage.setItem('widgetContext', JSON.stringify(currentContext));
|
|
78
|
+
};
|
|
79
|
+
// ---- DEBUGGING ----
|
|
80
|
+
const useInspectConversation = (debug = false) => {
|
|
81
|
+
(0, react_2.useEffect)(() => {
|
|
82
|
+
if (debug) {
|
|
83
|
+
exports.widgetStore.inspect((inspectionEvent) => {
|
|
84
|
+
console.log('CONVERSATION INSPECTION::', inspectionEvent);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}, []);
|
|
88
|
+
};
|
|
89
|
+
exports.useInspectConversation = useInspectConversation;
|
|
90
|
+
// ---- SELECTORS ----
|
|
91
|
+
const useSelectedConversationData = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.selected_conversation_id);
|
|
92
|
+
exports.useSelectedConversationData = useSelectedConversationData;
|
|
93
|
+
const useSelectTab = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.switchTab);
|
|
94
|
+
exports.useSelectTab = useSelectTab;
|
|
95
|
+
const useIsWidgetExpanded = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.isExpanded);
|
|
96
|
+
exports.useIsWidgetExpanded = useIsWidgetExpanded;
|
|
97
|
+
const useUserDetails = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.user_details);
|
|
98
|
+
exports.useUserDetails = useUserDetails;
|
|
99
|
+
const useChatExit = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.isChatExit);
|
|
100
|
+
exports.useChatExit = useChatExit;
|
|
101
|
+
const useGetCurrentConversationId = () => (0, react_1.useSelector)(exports.widgetStore, (state) => state.context.current_conversation_id);
|
|
102
|
+
exports.useGetCurrentConversationId = useGetCurrentConversationId;
|
|
103
|
+
// import { createStore } from '@xstate/store'
|
|
104
|
+
// import { useSelector } from '@xstate/store/react'
|
|
105
|
+
// import { useEffect } from 'react'
|
|
106
|
+
// export enum TabType {
|
|
107
|
+
// HOME = 'home',
|
|
108
|
+
// CHAT = 'chat',
|
|
109
|
+
// RECENT_MESSAGE = 'recentMessage',
|
|
110
|
+
// RECENT_MESSAGE_LIST = 'recentMessageList',
|
|
111
|
+
// RECENT_MESSAGE_DETAIL = 'recentMessageDetail',
|
|
112
|
+
// // etc.
|
|
113
|
+
// }
|
|
114
|
+
// type conversationPayload = {
|
|
115
|
+
// conversation_id: string
|
|
116
|
+
// }
|
|
117
|
+
// type selectTabPayloadT = {
|
|
118
|
+
// tab: TabType
|
|
119
|
+
// }
|
|
120
|
+
// const storedData = localStorage.getItem('widgetContext')
|
|
121
|
+
// const initialContext = storedData
|
|
122
|
+
// ? JSON.parse(storedData)
|
|
123
|
+
// : {
|
|
124
|
+
// selected_conversation_id: '',
|
|
125
|
+
// switchTab: TabType.HOME,
|
|
126
|
+
// isExpanded: false,
|
|
127
|
+
// }
|
|
128
|
+
// export const widgetStore = createStore({
|
|
129
|
+
// context: initialContext,
|
|
130
|
+
// on: {
|
|
131
|
+
// selectConversation: (context, event: conversationPayload) => {
|
|
132
|
+
// context.selected_conversation_id = event.conversation_id
|
|
133
|
+
// persistContext()
|
|
134
|
+
// },
|
|
135
|
+
// selectTab: (context, event: selectTabPayloadT) => {
|
|
136
|
+
// context.switchTab = event.tab
|
|
137
|
+
// persistContext()
|
|
138
|
+
// },
|
|
139
|
+
// expandWidget: (context) => {
|
|
140
|
+
// context.isExpanded = !context.isExpanded
|
|
141
|
+
// persistContext()
|
|
142
|
+
// },
|
|
143
|
+
// },
|
|
144
|
+
// })
|
|
145
|
+
// export const useInspectConversation = (debug = false) => {
|
|
146
|
+
// useEffect(() => {
|
|
147
|
+
// if (debug) {
|
|
148
|
+
// widgetStore.inspect((inspectionEvent) => {
|
|
149
|
+
// console.log('CONVERSATION INSPECTION::', inspectionEvent)
|
|
150
|
+
// })
|
|
151
|
+
// }
|
|
152
|
+
// }, [])
|
|
153
|
+
// }
|
|
154
|
+
// const persistContext = () => {
|
|
155
|
+
// const currentContext = widgetStore.getSnapshot().context
|
|
156
|
+
// localStorage.setItem('widgetContext', JSON.stringify(currentContext))
|
|
157
|
+
// }
|
|
158
|
+
// export const useSelectedConversationData = () =>
|
|
159
|
+
// useSelector(widgetStore, (state) => {
|
|
160
|
+
// return state.context.selected_conversation_id
|
|
161
|
+
// })
|
|
162
|
+
// export const useSelectTab = () => {
|
|
163
|
+
// return useSelector(widgetStore, (state) => {
|
|
164
|
+
// return state.context.switchTab
|
|
165
|
+
// })
|
|
166
|
+
// }
|
|
167
|
+
// export const useIsWidgetExpanded = () => {
|
|
168
|
+
// return useSelector(widgetStore, (state) => {
|
|
169
|
+
// return state.context.isExpanded
|
|
170
|
+
// })
|
|
171
|
+
// }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useUserData = exports.userDetailInitializer = exports.widgetUserStore = void 0;
|
|
4
|
+
const store_1 = require("@xstate/store");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_2 = require("@xstate/store/react");
|
|
7
|
+
exports.widgetUserStore = (0, store_1.createStore)({
|
|
8
|
+
context: {
|
|
9
|
+
user_details: {
|
|
10
|
+
SYS_FirstName: 'sample',
|
|
11
|
+
SYS_LastName: 'B',
|
|
12
|
+
SYS_UserEmail: 'example@gmail.com',
|
|
13
|
+
SYS_Location: 'India',
|
|
14
|
+
SYS_Timezone: 'Asia/Kolkata',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
on: {
|
|
18
|
+
setUserDetails: (_, event) => {
|
|
19
|
+
return {
|
|
20
|
+
user_details: {
|
|
21
|
+
SYS_FirstName: event.userDetails?.SYS_FirstName ?? 'sample',
|
|
22
|
+
SYS_LastName: event.userDetails?.SYS_LastName ?? 'B',
|
|
23
|
+
SYS_UserEmail: event.userDetails?.SYS_UserEmail ?? 'example@gmail.com',
|
|
24
|
+
SYS_Location: event.userDetails?.SYS_Location ?? 'India',
|
|
25
|
+
SYS_Timezone: event.userDetails?.SYS_Timezone ?? 'Asia/Kolkata',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const userDetailInitializer = (userDetails, debug = false) => {
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
exports.widgetUserStore.trigger.setUserDetails({ ...userDetails });
|
|
34
|
+
if (debug) {
|
|
35
|
+
exports.widgetUserStore.inspect((inspectionEvent) => {
|
|
36
|
+
console.log('USER INSPECTION::', inspectionEvent);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
};
|
|
41
|
+
exports.userDetailInitializer = userDetailInitializer;
|
|
42
|
+
const useUserData = () => {
|
|
43
|
+
(0, react_2.useSelector)(exports.widgetUserStore, (state) => {
|
|
44
|
+
return state.context.user_details;
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
exports.useUserData = useUserData;
|
package/package.json
CHANGED
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
"lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx}\"",
|
|
9
9
|
"prepublishOnly": "npm run build:lib",
|
|
10
10
|
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,md}\" --config ./.prettierrc.json",
|
|
11
|
-
"prepare": "husky install",
|
|
12
11
|
"parcel:build": "PARCEL_WORKER_BACKEND=process TAILWIND_MODE=build DEGUG=* parcel build src/index.tsx --no-source-maps",
|
|
13
12
|
"postcss:build": "postcss src/global.css -o dist/global.css",
|
|
14
|
-
"release": "
|
|
13
|
+
"release": "bumpp"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|
|
17
16
|
"@eslint/js": "^9.25.1",
|
|
@@ -24,6 +23,7 @@
|
|
|
24
23
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
25
24
|
"@types/socket.io-client": "^1.4.36",
|
|
26
25
|
"autoprefixer": "^10.4.21",
|
|
26
|
+
"bumpp": "^11.1.0",
|
|
27
27
|
"eslint": "^9.25.1",
|
|
28
28
|
"eslint-config-prettier": "^10.1.2",
|
|
29
29
|
"eslint-plugin-prettier": "^5.2.6",
|
|
@@ -89,6 +89,6 @@
|
|
|
89
89
|
],
|
|
90
90
|
"mode": "all"
|
|
91
91
|
},
|
|
92
|
-
"version": "1.0.
|
|
92
|
+
"version": "1.0.13",
|
|
93
93
|
"name": "pager-widget"
|
|
94
94
|
}
|
package/server.html
CHANGED
|
@@ -3,45 +3,39 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
<title>React with Tailwind</title>
|
|
8
|
+
<!-- <script type="module" src="./index.tsx"></script> -->
|
|
9
|
+
<script type="module" src="./index.js"></script>
|
|
10
|
+
<link rel="stylesheet" href="./index.css">
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
SYS_Timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
41
|
-
}
|
|
42
|
-
}, 'https://dev.pagergpt.ai');
|
|
43
|
-
});
|
|
44
|
-
})();
|
|
45
|
-
</script>
|
|
46
|
-
</body>
|
|
13
|
+
<div id="pager-widget-f08cbac9-472f-40ca-a1ef-4cdbf88cddae"></div>
|
|
14
|
+
<!-- This is where React will mount -->
|
|
15
|
+
<script>
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
window.WAWidget.init({
|
|
18
|
+
id: 'pager-widget-f08cbac9-472f-40ca-a1ef-4cdbf88cddae',
|
|
19
|
+
endpoint: 'https://dev.pagergpt.ai/wgt-hook',
|
|
20
|
+
// widget_id: 'b6a8b7a6-10e8-41fa-ab89-b4c9413c20a8',
|
|
21
|
+
widget_id: 'f08cbac9-472f-40ca-a1ef-4cdbf88cddae',
|
|
22
|
+
openByDefault: true,
|
|
23
|
+
widget_size: 'small',
|
|
24
|
+
userDetails: {
|
|
25
|
+
SYS_FirstName: 'sample',
|
|
26
|
+
SYS_LastName: 'user',
|
|
27
|
+
SYS_UserEmail: 'email',
|
|
28
|
+
SYS_Location: 'india',
|
|
29
|
+
SYS_Timezone: 'Asia/Kolkata',
|
|
30
|
+
},
|
|
31
|
+
style: {
|
|
32
|
+
logo_shape: 'circle',
|
|
33
|
+
logo_size: {
|
|
34
|
+
size: 1,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
}, 1000)
|
|
39
|
+
</script>
|
|
40
|
+
</body>
|
|
47
41
|
</html>
|
package/.husky/pre-commit
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
## What to build
|
|
2
|
-
|
|
3
|
-
Create a `useWidgetResize` hook that sends the widget's rendered dimensions to the parent page via `postMessage` whenever the widget's DOM size changes.
|
|
4
|
-
|
|
5
|
-
The hook uses **ResizeObserver** (not state-based triggers) to detect ALL size changes:
|
|
6
|
-
- Toggle open/close
|
|
7
|
-
- Expand (DEFAULT → EXPANDED → FULLSCREEN)
|
|
8
|
-
- Window resize
|
|
9
|
-
- Content growth / layout shifts
|
|
10
|
-
|
|
11
|
-
The hook measures the rendered `.pager-widget-root` element and sends the actual pixel dimensions to `window.parent` via `postMessage`.
|
|
12
|
-
|
|
13
|
-
### Message shape
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"type": "widgetResize",
|
|
18
|
-
"width": 448,
|
|
19
|
-
"height": 637
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Note: no `state` field — dimensions are the source of truth. The parent just resizes to match.
|
|
24
|
-
|
|
25
|
-
### Edge cases
|
|
26
|
-
|
|
27
|
-
- **Not in iframe**: skip sending if `window.parent === window`
|
|
28
|
-
- **Zero dimensions**: skip if measured width or height is 0 (DOM not yet ready)
|
|
29
|
-
- **Initial render**: fire once on mount after first requestAnimationFrame
|
|
30
|
-
- **Flood protection**: throttle via `requestAnimationFrame` guard (one message per frame)
|
|
31
|
-
|
|
32
|
-
### Integration point
|
|
33
|
-
|
|
34
|
-
The hook is called in `widget.tsx` where the toggle state and rendering live.
|
|
35
|
-
|
|
36
|
-
## Acceptance criteria
|
|
37
|
-
|
|
38
|
-
- [x] `src/hooks/useWidgetResize.ts` exists
|
|
39
|
-
- [x] Hook measures the rendered widget dimensions via `getBoundingClientRect()`
|
|
40
|
-
- [x] Hook sends `postMessage({ type: 'widgetResize', width, height }, '*')` on size change
|
|
41
|
-
- [x] Hook skips when not in an iframe
|
|
42
|
-
- [x] Hook skips when dimensions are zero
|
|
43
|
-
- [x] Hook is integrated into `widget.tsx`
|
|
44
|
-
- [ ] Hook uses **ResizeObserver** instead of state-based `setTimeout(50ms)` approach
|
|
45
|
-
- [ ] Hook fires on ALL size changes (toggle, expand, window resize — not just toggle state transitions)
|
|
46
|
-
- [ ] Hook throttles messages with `requestAnimationFrame`
|
|
47
|
-
- [ ] Unit tests pass for all edge cases (not in iframe, zero dimensions, observer cleanup)
|
|
48
|
-
|
|
49
|
-
## Blocked by
|
|
50
|
-
|
|
51
|
-
None — can start immediately.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
## What to build
|
|
2
|
-
|
|
3
|
-
Update `server.html` (the backend container page served at `/embed/{widget_id}`) to use HubSpot's container pattern instead of the current full-viewport iframe.
|
|
4
|
-
|
|
5
|
-
### Current (broken)
|
|
6
|
-
|
|
7
|
-
The current `server.html` has a full-viewport iframe that blocks all pointer events on the host page:
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<iframe style="border:none;position:fixed;bottom:0;right:0;width:100%;height:100%;z-index:9999;">
|
|
11
|
-
</iframe>
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
### Target (container pattern)
|
|
15
|
-
|
|
16
|
-
The updated page has a container div that starts at launcher size and is dynamically resized:
|
|
17
|
-
|
|
18
|
-
```html
|
|
19
|
-
<div id="pager-widget-container"
|
|
20
|
-
style="position:fixed;bottom:0;right:0;z-index:9999;min-height:60px;min-width:60px">
|
|
21
|
-
<iframe src="https://dev.pagergpt.ai/wgt-hook/embed/{widget_id}?v={version}"
|
|
22
|
-
style="position:absolute;width:100%!important;height:100%!important;border:none!important;bottom:0!important;right:0!important;background:transparent!important"
|
|
23
|
-
title="Pager Widget">
|
|
24
|
-
</iframe>
|
|
25
|
-
</div>
|
|
26
|
-
<script>
|
|
27
|
-
(function() {
|
|
28
|
-
var container = document.getElementById('pager-widget-container');
|
|
29
|
-
|
|
30
|
-
window.addEventListener('message', function(event) {
|
|
31
|
-
if (event.data && event.data.type === 'widgetResize') {
|
|
32
|
-
container.style.width = event.data.width + 'px';
|
|
33
|
-
container.style.height = event.data.height + 'px';
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
})();
|
|
37
|
-
</script>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Behavior
|
|
41
|
-
|
|
42
|
-
| Widget state | Container size | Page interaction |
|
|
43
|
-
|---|---|---|
|
|
44
|
-
| Collapsed (launcher only) | ~60×60px | Fully interactive — scroll, click all page elements |
|
|
45
|
-
| Open (chat panel, DEFAULT) | ~400×58-70vh (dynamic) | Only the chat panel area captures events |
|
|
46
|
-
| Open (chat panel, EXPANDED) | ~50vw × 58-70vh | Wider panel, still only panel area blocks events |
|
|
47
|
-
| Open (chat panel, FULLSCREEN) | ~95vw × 58-70vh | Nearly full-width, still only panel area blocks events |
|
|
48
|
-
|
|
49
|
-
### CSS breakdown
|
|
50
|
-
|
|
51
|
-
| Property | Value | Purpose |
|
|
52
|
-
|---|---|---|
|
|
53
|
-
| `position: fixed` | bottom; right | Anchors widget to viewport corner |
|
|
54
|
-
| `z-index: 9999` | high value | Stays above page content |
|
|
55
|
-
| `min-height: 60px` | launcher button height | Prevents collapse to zero before first message |
|
|
56
|
-
| `min-width: 60px` | launcher button width | Prevents collapse to zero before first message |
|
|
57
|
-
| `width` / `height` | set dynamically | Updated by message listener |
|
|
58
|
-
|
|
59
|
-
The inner iframe uses `position:absolute;width:100%;height:100%` to fill the container.
|
|
60
|
-
|
|
61
|
-
### Security
|
|
62
|
-
|
|
63
|
-
The `postMessage` listener does not check `event.origin` since customers have different domains. The widget side uses `targetOrigin: '*'`. Origin validation can be added later via a known-allowlist of customer origins.
|
|
64
|
-
|
|
65
|
-
### File to update
|
|
66
|
-
|
|
67
|
-
- `server.html` — the backend template served at `/embed/{widget_id}`
|
|
68
|
-
|
|
69
|
-
## Acceptance criteria
|
|
70
|
-
|
|
71
|
-
- [ ] `server.html` container div starts at `min-height:60px; min-width:60px` before first resize message
|
|
72
|
-
- [ ] Page scrolling works when widget is collapsed (no full-viewport iframe)
|
|
73
|
-
- [ ] Page scrolling works outside the widget's chat panel when open
|
|
74
|
-
- [ ] Container resizes correctly when widget opens and collapses
|
|
75
|
-
- [ ] Container resizes correctly when widget expands (DEFAULT → EXPANDED → FULLSCREEN)
|
|
76
|
-
- [ ] UserDetails postMessage to inner iframe still works on load
|
|
77
|
-
- [ ] Failed resize (JS error) leaves container at 60×60px (safe failure)
|
|
78
|
-
|
|
79
|
-
## Blocked by
|
|
80
|
-
|
|
81
|
-
- [ ] Slice 1: `useWidgetResize` hook must be updated to ResizeObserver (the container page has nothing to listen to without the hook sending messages)
|