@xcelsior/ui-chat 2.0.3 → 2.0.5
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/.storybook/preview.tsx +2 -1
- package/dist/index.d.mts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +1073 -473
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1060 -463
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/components/BookingCancelledCard.tsx +103 -0
- package/src/components/BookingCards.stories.tsx +102 -0
- package/src/components/BookingConfirmationCard.tsx +170 -0
- package/src/components/BookingSlotPicker.stories.tsx +87 -0
- package/src/components/BookingSlotPicker.tsx +253 -0
- package/src/components/BrandIcons.stories.tsx +32 -1
- package/src/components/BrandIcons.tsx +21 -17
- package/src/components/Chat.tsx +78 -83
- package/src/components/ChatWidget.tsx +30 -3
- package/src/components/MessageItem.tsx +83 -72
- package/src/components/MessageList.tsx +4 -0
- package/src/hooks/useDraggablePosition.ts +147 -42
- package/src/hooks/useMessages.ts +119 -45
- package/src/hooks/useWebSocket.ts +17 -4
- package/src/index.tsx +11 -0
- package/src/types.ts +39 -2
- package/src/utils/api.ts +1 -0
- package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
- package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
- package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
- package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
- package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
- package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
- package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
- package/storybook-static/assets/{Color-YHDXOIA2-BMnd3YrF.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-CFRXHY34-i_W8iCu9.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
- package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
- package/storybook-static/assets/MessageItem.stories-Cs5Vtkle.js +422 -0
- package/storybook-static/assets/{entry-preview-oDnntGcx.js → entry-preview-vcpiajAT.js} +1 -1
- package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
- package/storybook-static/assets/{iframe-CGBtu2Se.js → iframe-Cx1n-SeE.js} +2 -2
- package/storybook-static/assets/preview-B8y-wc-n.css +1 -0
- package/storybook-static/assets/preview-CC4t7T7W.js +1 -0
- package/storybook-static/assets/{preview-BRpahs9B.js → preview-Do3b3dZv.js} +2 -2
- package/storybook-static/iframe.html +1 -1
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/tsconfig.json +4 -0
- package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
- package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
- package/storybook-static/assets/Chat.stories-J_Yp51wU.js +0 -803
- package/storybook-static/assets/MessageItem-DAaKZ9s9.js +0 -14
- package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +0 -255
- package/storybook-static/assets/ToastContext-Bty1K7ya.js +0 -1
- package/storybook-static/assets/preview-DUOvJmsz.js +0 -1
- package/storybook-static/assets/preview-DcGwT3kv.css +0 -1
package/src/hooks/useMessages.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import type { IMessage, IChatConfig, UseMessagesReturn } from '../types';
|
|
3
3
|
import type { UseWebSocketReturn } from './useWebSocket';
|
|
4
4
|
import { fetchMessages } from '../utils/api';
|
|
5
5
|
|
|
6
|
+
/** Max time (ms) to show the bot thinking indicator before auto-clearing */
|
|
7
|
+
const BOT_THINKING_TIMEOUT = 15_000;
|
|
8
|
+
|
|
9
|
+
/** Conversation statuses where the bot will NOT respond */
|
|
10
|
+
const AGENT_STATUSES = new Set(['agent_active', 'pending_agent']);
|
|
11
|
+
|
|
6
12
|
export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig): UseMessagesReturn {
|
|
7
13
|
const [messages, setMessages] = useState<IMessage[]>([]);
|
|
8
14
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -11,8 +17,10 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
11
17
|
const [hasMore, setHasMore] = useState(true);
|
|
12
18
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
13
19
|
const [isBotThinking, setIsBotThinking] = useState(false);
|
|
20
|
+
const botThinkingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
21
|
+
/** True when an agent has taken over — set from conversation status OR message history */
|
|
22
|
+
const agentActiveRef = useRef(false);
|
|
14
23
|
|
|
15
|
-
// Extract stable references from config
|
|
16
24
|
const { httpApiUrl, conversationId, headers, onError, toast } = config;
|
|
17
25
|
|
|
18
26
|
const headersWithApiKey = useMemo(
|
|
@@ -23,14 +31,44 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
23
31
|
[headers, config.apiKey]
|
|
24
32
|
);
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
/** Clear bot thinking timer and state */
|
|
35
|
+
const clearBotThinking = useCallback(() => {
|
|
36
|
+
setIsBotThinking(false);
|
|
37
|
+
if (botThinkingTimerRef.current) {
|
|
38
|
+
clearTimeout(botThinkingTimerRef.current);
|
|
39
|
+
botThinkingTimerRef.current = null;
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
// ── Fetch conversation status to seed agentActiveRef ──────────────
|
|
44
|
+
// This is the PRIMARY mechanism — not relying on message history pagination.
|
|
27
45
|
useEffect(() => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
if (!httpApiUrl || !conversationId) return;
|
|
47
|
+
|
|
48
|
+
const fetchStatus = async () => {
|
|
49
|
+
try {
|
|
50
|
+
const url = `${httpApiUrl}/conversation?conversationId=${encodeURIComponent(conversationId)}`;
|
|
51
|
+
const res = await fetch(url, { headers: headersWithApiKey });
|
|
52
|
+
if (!res.ok) return;
|
|
53
|
+
const json = await res.json();
|
|
54
|
+
// Backend returns { data: { conversation: { status, ... } } } or { data: { status, ... } }
|
|
55
|
+
const conv = json.data?.conversation ?? json.data;
|
|
56
|
+
if (conv?.status && AGENT_STATUSES.has(conv.status)) {
|
|
57
|
+
agentActiveRef.current = true;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Non-critical — fallback to message history check
|
|
32
61
|
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
fetchStatus();
|
|
65
|
+
}, [httpApiUrl, conversationId, headersWithApiKey]);
|
|
33
66
|
|
|
67
|
+
// ── Fetch existing messages ──────────────────────────────────────
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!httpApiUrl || !conversationId) return;
|
|
70
|
+
|
|
71
|
+
const loadMessages = async () => {
|
|
34
72
|
setIsLoading(true);
|
|
35
73
|
setError(null);
|
|
36
74
|
|
|
@@ -44,6 +82,10 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
44
82
|
setMessages(result.data);
|
|
45
83
|
setNextPageToken(result.nextPageToken);
|
|
46
84
|
setHasMore(!!result.nextPageToken);
|
|
85
|
+
// Secondary check: agent messages in the visible page
|
|
86
|
+
if (result.data.some((m: IMessage) => m.senderType === 'agent')) {
|
|
87
|
+
agentActiveRef.current = true;
|
|
88
|
+
}
|
|
47
89
|
} catch (err) {
|
|
48
90
|
const error = err instanceof Error ? err : new Error('Failed to load messages');
|
|
49
91
|
setError(error);
|
|
@@ -55,50 +97,89 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
55
97
|
};
|
|
56
98
|
|
|
57
99
|
loadMessages();
|
|
58
|
-
// Only re-run when conversationId or httpApiUrl changes
|
|
59
100
|
}, [conversationId, httpApiUrl, headersWithApiKey, onError, toast]);
|
|
60
101
|
|
|
61
|
-
// Extract onMessageReceived callback
|
|
62
102
|
const { onMessageReceived } = config;
|
|
63
103
|
|
|
64
|
-
// Listen for incoming messages from WebSocket
|
|
104
|
+
// ── Listen for incoming messages from WebSocket ───────────────────
|
|
65
105
|
useEffect(() => {
|
|
66
106
|
if (websocket.lastMessage?.type === 'message' && websocket.lastMessage.data) {
|
|
67
107
|
const newMessage: IMessage = websocket.lastMessage.data;
|
|
68
108
|
|
|
69
|
-
if (conversationId && newMessage.conversationId !== conversationId)
|
|
70
|
-
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
109
|
+
if (conversationId && newMessage.conversationId !== conversationId) return;
|
|
110
|
+
|
|
73
111
|
setMessages((prev) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
if (prev.some((msg) => msg.id === newMessage.id)) return prev;
|
|
113
|
+
const newMsgTime = new Date(newMessage.createdAt).getTime();
|
|
114
|
+
const tempIdx = prev.findIndex(
|
|
115
|
+
(msg) =>
|
|
116
|
+
msg.id.startsWith('temp-') &&
|
|
117
|
+
msg.content === newMessage.content &&
|
|
118
|
+
msg.senderId === newMessage.senderId &&
|
|
119
|
+
Math.abs(new Date(msg.createdAt).getTime() - newMsgTime) < 10_000
|
|
120
|
+
);
|
|
121
|
+
if (tempIdx >= 0) {
|
|
122
|
+
const next = [...prev];
|
|
123
|
+
next[tempIdx] = newMessage;
|
|
124
|
+
return next;
|
|
77
125
|
}
|
|
78
126
|
return [...prev, newMessage];
|
|
79
127
|
});
|
|
80
128
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
if (newMessage.senderType === 'agent') {
|
|
130
|
+
agentActiveRef.current = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clear bot thinking when a non-customer message arrives
|
|
134
|
+
if (newMessage.senderType !== 'customer') {
|
|
135
|
+
clearBotThinking();
|
|
84
136
|
}
|
|
85
137
|
|
|
86
|
-
// Notify parent component about new message
|
|
87
138
|
onMessageReceived?.(newMessage);
|
|
88
139
|
}
|
|
89
|
-
}, [websocket.lastMessage, onMessageReceived, conversationId]);
|
|
140
|
+
}, [websocket.lastMessage, onMessageReceived, conversationId, clearBotThinking]);
|
|
90
141
|
|
|
142
|
+
// ── Handle bot_not_responding + conversation_updated events ───────
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (!websocket.lastMessage) return;
|
|
145
|
+
const { type, data } = websocket.lastMessage;
|
|
146
|
+
|
|
147
|
+
if (type === 'bot_not_responding') {
|
|
148
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
149
|
+
clearBotThinking();
|
|
150
|
+
agentActiveRef.current = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (type === 'conversation_updated') {
|
|
154
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
155
|
+
if (data?.status && AGENT_STATUSES.has(data.status)) {
|
|
156
|
+
agentActiveRef.current = true;
|
|
157
|
+
clearBotThinking();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, [websocket.lastMessage, conversationId, clearBotThinking]);
|
|
161
|
+
|
|
162
|
+
// ── addMessage (used for optimistic customer messages) ────────────
|
|
91
163
|
const addMessage = useCallback((message: IMessage) => {
|
|
164
|
+
// Read agentActiveRef BEFORE the async setMessages
|
|
165
|
+
const isAgentActive = agentActiveRef.current;
|
|
166
|
+
|
|
92
167
|
setMessages((prev) => {
|
|
93
|
-
|
|
94
|
-
if (prev.some((msg) => msg.id === message.id)) {
|
|
95
|
-
return prev;
|
|
96
|
-
}
|
|
168
|
+
if (prev.some((msg) => msg.id === message.id)) return prev;
|
|
97
169
|
return [...prev, message];
|
|
98
170
|
});
|
|
99
|
-
|
|
100
|
-
if (message.senderType === '
|
|
171
|
+
|
|
172
|
+
if (message.senderType === 'agent') {
|
|
173
|
+
agentActiveRef.current = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Only show bot thinking for customer messages when NO agent is active
|
|
177
|
+
if (message.senderType === 'customer' && !isAgentActive) {
|
|
101
178
|
setIsBotThinking(true);
|
|
179
|
+
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
180
|
+
botThinkingTimerRef.current = setTimeout(() => {
|
|
181
|
+
setIsBotThinking(false);
|
|
182
|
+
}, BOT_THINKING_TIMEOUT);
|
|
102
183
|
}
|
|
103
184
|
}, []);
|
|
104
185
|
|
|
@@ -108,12 +189,11 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
108
189
|
|
|
109
190
|
const clearMessages = useCallback(() => {
|
|
110
191
|
setMessages([]);
|
|
192
|
+
agentActiveRef.current = false;
|
|
111
193
|
}, []);
|
|
112
194
|
|
|
113
195
|
const loadMore = useCallback(async () => {
|
|
114
|
-
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken)
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
196
|
+
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
|
|
117
197
|
|
|
118
198
|
setIsLoadingMore(true);
|
|
119
199
|
setError(null);
|
|
@@ -121,11 +201,7 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
121
201
|
try {
|
|
122
202
|
const result = await fetchMessages(
|
|
123
203
|
httpApiUrl,
|
|
124
|
-
{
|
|
125
|
-
conversationId,
|
|
126
|
-
limit: 20,
|
|
127
|
-
pageToken: nextPageToken,
|
|
128
|
-
},
|
|
204
|
+
{ conversationId, limit: 20, pageToken: nextPageToken },
|
|
129
205
|
headersWithApiKey
|
|
130
206
|
);
|
|
131
207
|
|
|
@@ -139,15 +215,13 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
|
|
|
139
215
|
} finally {
|
|
140
216
|
setIsLoadingMore(false);
|
|
141
217
|
}
|
|
142
|
-
}, [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
onError,
|
|
150
|
-
]);
|
|
218
|
+
}, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
return () => {
|
|
222
|
+
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
223
|
+
};
|
|
224
|
+
}, []);
|
|
151
225
|
|
|
152
226
|
return {
|
|
153
227
|
messages,
|
|
@@ -28,6 +28,13 @@ export function useWebSocket(
|
|
|
28
28
|
const reconnectAttemptsRef = useRef(0);
|
|
29
29
|
const messageHandlerRef = useRef<((event: MessageEvent) => void) | null>(null);
|
|
30
30
|
const abortedRef = useRef(false);
|
|
31
|
+
/**
|
|
32
|
+
* Monotonically increasing counter incremented each time connect() is called.
|
|
33
|
+
* Each WebSocket closure captures the generation at open time; if the generation
|
|
34
|
+
* has advanced by the time onclose fires, the close event belongs to a stale
|
|
35
|
+
* connection and must not trigger reconnection.
|
|
36
|
+
*/
|
|
37
|
+
const connectionGenerationRef = useRef(0);
|
|
31
38
|
const maxReconnectAttempts = 5;
|
|
32
39
|
const reconnectDelay = 3000;
|
|
33
40
|
|
|
@@ -47,10 +54,8 @@ export function useWebSocket(
|
|
|
47
54
|
const message: IWebSocketMessage = JSON.parse(event.data);
|
|
48
55
|
setLastMessage(message);
|
|
49
56
|
|
|
50
|
-
// Handle
|
|
51
|
-
if (message.type === '
|
|
52
|
-
config.onMessageReceived?.(message.data);
|
|
53
|
-
} else if (message.type === 'error') {
|
|
57
|
+
// Handle error messages — regular messages are handled by useMessages via lastMessage
|
|
58
|
+
if (message.type === 'error') {
|
|
54
59
|
const err = new Error(message.data?.message || 'WebSocket error');
|
|
55
60
|
setError(err);
|
|
56
61
|
config.onError?.(err);
|
|
@@ -78,6 +83,10 @@ export function useWebSocket(
|
|
|
78
83
|
// Skip if the effect was already cleaned up (React Strict Mode)
|
|
79
84
|
if (abortedRef.current) return;
|
|
80
85
|
|
|
86
|
+
// Capture this connection's generation so that stale onclose/onopen
|
|
87
|
+
// callbacks from a previous WebSocket instance are ignored.
|
|
88
|
+
const generation = ++connectionGenerationRef.current;
|
|
89
|
+
|
|
81
90
|
console.log('connecting to WebSocket...', config.currentUser, config.conversationId);
|
|
82
91
|
try {
|
|
83
92
|
// Clean up existing connection
|
|
@@ -103,6 +112,7 @@ export function useWebSocket(
|
|
|
103
112
|
const ws = new WebSocket(url.toString());
|
|
104
113
|
|
|
105
114
|
ws.onopen = () => {
|
|
115
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
106
116
|
if (abortedRef.current) {
|
|
107
117
|
ws.close(1000, 'Effect cleaned up');
|
|
108
118
|
return;
|
|
@@ -115,6 +125,7 @@ export function useWebSocket(
|
|
|
115
125
|
};
|
|
116
126
|
|
|
117
127
|
ws.onerror = (event) => {
|
|
128
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
118
129
|
if (abortedRef.current) return;
|
|
119
130
|
console.error('WebSocket error:', event);
|
|
120
131
|
const err = new Error('WebSocket connection error');
|
|
@@ -123,6 +134,8 @@ export function useWebSocket(
|
|
|
123
134
|
};
|
|
124
135
|
|
|
125
136
|
ws.onclose = (event) => {
|
|
137
|
+
// Ignore close events from superseded connections (reconnect or re-mount).
|
|
138
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
126
139
|
if (abortedRef.current) return;
|
|
127
140
|
console.log('WebSocket closed:', event.code, event.reason);
|
|
128
141
|
setIsConnected(false);
|
package/src/index.tsx
CHANGED
|
@@ -4,6 +4,9 @@ export type { ChatWidgetProps } from './components/ChatWidget';
|
|
|
4
4
|
export { Chat } from './components/Chat';
|
|
5
5
|
|
|
6
6
|
// Individual components (for custom implementations)
|
|
7
|
+
export { BookingCancelledCard } from './components/BookingCancelledCard';
|
|
8
|
+
export { BookingConfirmationCard } from './components/BookingConfirmationCard';
|
|
9
|
+
export { BookingSlotPicker } from './components/BookingSlotPicker';
|
|
7
10
|
export { ChatHeader } from './components/ChatHeader';
|
|
8
11
|
export { ChatInput } from './components/ChatInput';
|
|
9
12
|
export { MarkdownMessage } from './components/MarkdownMessage';
|
|
@@ -48,6 +51,10 @@ import type {
|
|
|
48
51
|
ConversationPriority,
|
|
49
52
|
ConversationChannel,
|
|
50
53
|
UseMessagesReturn,
|
|
54
|
+
IBookingSlot,
|
|
55
|
+
IBookingData,
|
|
56
|
+
IBookingConfirmationData,
|
|
57
|
+
IBookingCancelledData,
|
|
51
58
|
} from './types';
|
|
52
59
|
|
|
53
60
|
export type {
|
|
@@ -68,4 +75,8 @@ export type {
|
|
|
68
75
|
ConversationPriority,
|
|
69
76
|
ConversationChannel,
|
|
70
77
|
UseMessagesReturn,
|
|
78
|
+
IBookingSlot,
|
|
79
|
+
IBookingData,
|
|
80
|
+
IBookingConfirmationData,
|
|
81
|
+
IBookingCancelledData,
|
|
71
82
|
};
|
package/src/types.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface IUser {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// Message types
|
|
11
|
-
export type MessageType = 'text' | 'image' | 'file' | 'system';
|
|
11
|
+
export type MessageType = 'text' | 'image' | 'file' | 'system' | 'booking_slots' | 'booking_confirmation' | 'booking_cancelled';
|
|
12
12
|
export type MessageStatus = 'sent' | 'delivered' | 'read';
|
|
13
13
|
|
|
14
14
|
export interface IMessage {
|
|
@@ -49,9 +49,46 @@ export interface IConversation {
|
|
|
49
49
|
metadata?: Record<string, unknown>;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// Booking data interfaces
|
|
53
|
+
export interface IBookingSlot {
|
|
54
|
+
date: string; // ISO date: "2026-03-25"
|
|
55
|
+
dayLabel: string; // "Tue 25"
|
|
56
|
+
dayName: string; // "Tuesday"
|
|
57
|
+
slots: string[]; // ["09:00", "09:30", "10:00", ...]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface IBookingData {
|
|
61
|
+
availableDates: IBookingSlot[];
|
|
62
|
+
timezone: string;
|
|
63
|
+
meetingDuration: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface IBookingConfirmationData {
|
|
67
|
+
bookingId: string;
|
|
68
|
+
visitorName: string;
|
|
69
|
+
meetingDate: string;
|
|
70
|
+
meetingTime: string;
|
|
71
|
+
meetingDuration: number;
|
|
72
|
+
meetingPurpose: string;
|
|
73
|
+
meetLink?: string;
|
|
74
|
+
calendarLink?: string;
|
|
75
|
+
timezone: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IBookingCancelledData {
|
|
79
|
+
bookingId: string;
|
|
80
|
+
meetingDate: string;
|
|
81
|
+
meetingTime: string;
|
|
82
|
+
meetingPurpose: string;
|
|
83
|
+
cancelledBy: 'visitor' | 'admin';
|
|
84
|
+
}
|
|
85
|
+
|
|
52
86
|
// WebSocket message types
|
|
53
87
|
export interface IWebSocketMessage {
|
|
54
|
-
type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system'
|
|
88
|
+
type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system'
|
|
89
|
+
| 'escalation_requested' | 'bot_response' | 'conversation_updated'
|
|
90
|
+
| 'bot_not_responding'
|
|
91
|
+
| 'booking_slots' | 'booking_confirmation' | 'booking_cancelled';
|
|
55
92
|
data: any;
|
|
56
93
|
}
|
|
57
94
|
|
package/src/utils/api.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";import{c as l,C as j,G as S}from"./globe-BtMvkLMD.js";/**
|
|
2
|
+
* @license lucide-react v0.477.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const v=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 13V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8",key:"3spt84"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M16 19h6",key:"xwg31i"}],["path",{d:"M19 16v6",key:"tddt3s"}]],_=l("CalendarPlus",v);/**
|
|
7
|
+
* @license lucide-react v0.477.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const z=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]],k=l("Calendar",z);/**
|
|
12
|
+
* @license lucide-react v0.477.0 - ISC
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the ISC license.
|
|
15
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
+
*/const B=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],I=l("CircleCheck",B);/**
|
|
17
|
+
* @license lucide-react v0.477.0 - ISC
|
|
18
|
+
*
|
|
19
|
+
* This source code is licensed under the ISC license.
|
|
20
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
21
|
+
*/const M=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],T=l("CircleX",M);/**
|
|
22
|
+
* @license lucide-react v0.477.0 - ISC
|
|
23
|
+
*
|
|
24
|
+
* This source code is licensed under the ISC license.
|
|
25
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
26
|
+
*/const w=[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]],m=l("MessageSquare",w);/**
|
|
27
|
+
* @license lucide-react v0.477.0 - ISC
|
|
28
|
+
*
|
|
29
|
+
* This source code is licensed under the ISC license.
|
|
30
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
31
|
+
*/const L=[["path",{d:"m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",key:"ftymec"}],["rect",{x:"2",y:"6",width:"14",height:"12",rx:"2",key:"158x01"}]],N=l("Video",L);function h({data:n,theme:r}){const d=(r==null?void 0:r.background)||"#00001a",i=(()=>{if(!d.startsWith("#"))return!1;const c=d.replace("#",""),u=parseInt(c.substring(0,2),16),f=parseInt(c.substring(2,4),16),C=parseInt(c.substring(4,6),16);return(.299*u+.587*f+.114*C)/255>.5})(),t=(r==null?void 0:r.primary)||"#337eff",p=(r==null?void 0:r.statusPositive)||"#22c55e",o=(r==null?void 0:r.text)||(i?"#1a1a2e":"#f7f7f8"),x=(r==null?void 0:r.textMuted)||(i?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),g=i?{backgroundColor:"rgba(0,0,0,0.03)",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.08)",borderRadius:"14px",padding:"16px",overflow:"hidden"}:{backgroundColor:"rgba(255,255,255,0.04)",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",borderRadius:"14px",padding:"16px",overflow:"hidden"},a={display:"flex",alignItems:"flex-start",gap:"8px",marginBottom:"8px"},y={height:"1px",backgroundColor:i?"rgba(0,0,0,0.06)":"rgba(255,255,255,0.06)",margin:"12px 0"},s={display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",flex:1,padding:"9px 12px",borderRadius:"9px",fontSize:"12px",fontWeight:"600",letterSpacing:"0.01em",cursor:"pointer",textDecoration:"none",border:"none",transition:"opacity 0.15s ease"};return e.jsxs("div",{style:g,children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px",marginBottom:"14px"},children:[e.jsx(I,{size:22,color:p,"aria-hidden":"true"}),e.jsxs("div",{children:[e.jsx("p",{style:{fontSize:"14px",fontWeight:"700",color:o,letterSpacing:"0.006em",lineHeight:1.3},children:"Meeting Confirmed"}),e.jsx("p",{style:{fontSize:"11px",color:p,letterSpacing:"0.01em",marginTop:"1px"},children:"You'll receive a calendar invite shortly"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{style:a,children:[e.jsx(k,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:[n.meetingDate," at ",n.meetingTime]})]}),e.jsxs("div",{style:a,children:[e.jsx(j,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:[n.meetingDuration," minutes"]})]}),e.jsxs("div",{style:{...a,marginBottom:"0"},children:[e.jsx(m,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsx("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:n.meetingPurpose})]})]}),n.timezone&&e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px",marginTop:"8px"},children:[e.jsx(S,{size:11,color:x,"aria-hidden":"true"}),e.jsx("span",{style:{fontSize:"11px",color:x,letterSpacing:"0.01em"},children:n.timezone})]}),(n.meetLink||n.calendarLink)&&e.jsxs(e.Fragment,{children:[e.jsx("div",{style:y}),e.jsxs("div",{style:{display:"flex",gap:"8px"},children:[n.meetLink&&e.jsxs("a",{href:n.meetLink,target:"_blank",rel:"noopener noreferrer",style:{...s,background:`linear-gradient(135deg, ${t}, ${(r==null?void 0:r.primaryStrong)||"#005eff"})`,color:"#ffffff",boxShadow:`0 2px 8px -2px ${t}50`},children:[e.jsx(N,{size:13,"aria-hidden":"true"}),"Join Meet"]}),n.calendarLink&&e.jsxs("a",{href:n.calendarLink,target:"_blank",rel:"noopener noreferrer",style:{...s,backgroundColor:i?"rgba(0,0,0,0.05)":"rgba(255,255,255,0.07)",color:o,boxShadow:i?"inset 0 0 0 1px rgba(0,0,0,0.1)":"inset 0 0 0 0.5px rgba(255,255,255,0.12)"},children:[e.jsx(_,{size:13,"aria-hidden":"true"}),"Add to Calendar"]})]})]})]})}try{h.displayName="BookingConfirmationCard",h.__docgenInfo={description:"",displayName:"BookingConfirmationCard",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingConfirmationData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}}}}}catch{}function b({data:n,theme:r}){const d=(r==null?void 0:r.background)||"#00001a",i=(()=>{if(!d.startsWith("#"))return!1;const s=d.replace("#",""),c=parseInt(s.substring(0,2),16),u=parseInt(s.substring(2,4),16),f=parseInt(s.substring(4,6),16);return(.299*c+.587*u+.114*f)/255>.5})(),t=(r==null?void 0:r.statusNegative)||"#ef4444",p=(r==null?void 0:r.text)||(i?"#1a1a2e":"#f7f7f8"),o=(r==null?void 0:r.textMuted)||(i?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),x=i?{backgroundColor:"rgba(239,68,68,0.04)",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.12)",borderRadius:"14px",padding:"16px"}:{backgroundColor:"rgba(239,68,68,0.06)",boxShadow:"inset 0 0 0 0.5px rgba(239,68,68,0.18)",borderRadius:"14px",padding:"16px"},g={fontSize:"13px",color:o,lineHeight:1.4,textDecoration:"line-through",textDecorationColor:i?"rgba(0,0,0,0.25)":"rgba(255,255,255,0.2)"},a={display:"flex",alignItems:"flex-start",gap:"8px",marginBottom:"8px"},y=n.cancelledBy==="visitor"?"Cancelled by you":"Cancelled by support";return e.jsxs("div",{style:x,children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px",marginBottom:"14px"},children:[e.jsx(T,{size:22,color:t,"aria-hidden":"true"}),e.jsxs("div",{children:[e.jsx("p",{style:{fontSize:"14px",fontWeight:"700",color:p,letterSpacing:"0.006em",lineHeight:1.3},children:"Meeting Cancelled"}),e.jsx("p",{style:{fontSize:"11px",color:t,letterSpacing:"0.01em",marginTop:"1px",opacity:.8},children:y})]})]}),e.jsxs("div",{children:[e.jsxs("div",{style:a,children:[e.jsx(k,{size:14,color:o,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:g,children:[n.meetingDate," at ",n.meetingTime]})]}),e.jsxs("div",{style:{...a,marginBottom:"0"},children:[e.jsx(m,{size:14,color:o,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsx("span",{style:g,children:n.meetingPurpose})]})]})]})}try{b.displayName="BookingCancelledCard",b.__docgenInfo={description:"",displayName:"BookingCancelledCard",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingCancelledData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}}}}}catch{}export{h as B,b as a};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";import{B,a as j}from"./BookingCancelledCard-XHuB-Ebp.js";import"./index-yBjzXJbu.js";import"./globe-BtMvkLMD.js";import"./index--qcDGAq6.js";const T={title:"Components/BookingConfirmationCard",component:B,parameters:{layout:"padded"},tags:["autodocs"],decorators:[a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(a,{})})]},d={bookingId:"bk_abc123",visitorName:"John Doe",meetingDate:"2026-03-26",meetingTime:"10:00",meetingDuration:30,meetingPurpose:"Discuss mobile app development project",meetLink:"https://meet.google.com/abc-defg-hij",calendarLink:"https://calendar.google.com/event?eid=abc123",timezone:"Australia/Sydney"},n={args:{data:d}},r={args:{data:{...d,meetLink:void 0}}},o={args:{data:d,theme:{primary:"#337eff",background:"#ffffff",text:"#1a1a2e",textMuted:"rgba(0,0,0,0.35)"}},decorators:[a=>e.jsx("div",{style:{maxWidth:360,background:"#ffffff",padding:16,borderRadius:12},children:e.jsx(a,{})})]},t={render:a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(j,{...a})}),args:{data:{bookingId:"bk_abc123",meetingDate:"2026-03-26",meetingTime:"10:00",meetingPurpose:"Discuss mobile app development project",cancelledBy:"visitor"}}},i={render:a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(j,{...a})}),args:{data:{bookingId:"bk_abc123",meetingDate:"2026-03-26",meetingTime:"10:00",meetingPurpose:"Discuss mobile app development project",cancelledBy:"admin"}}};var s,m,c;n.parameters={...n.parameters,docs:{...(s=n.parameters)==null?void 0:s.docs,source:{originalSource:`{
|
|
2
|
+
args: {
|
|
3
|
+
data: mockConfirmation
|
|
4
|
+
}
|
|
5
|
+
}`,...(c=(m=n.parameters)==null?void 0:m.docs)==null?void 0:c.source}}};var g,p,l;r.parameters={...r.parameters,docs:{...(g=r.parameters)==null?void 0:g.docs,source:{originalSource:`{
|
|
6
|
+
args: {
|
|
7
|
+
data: {
|
|
8
|
+
...mockConfirmation,
|
|
9
|
+
meetLink: undefined
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}`,...(l=(p=r.parameters)==null?void 0:p.docs)==null?void 0:l.source}}};var u,f,b;o.parameters={...o.parameters,docs:{...(u=o.parameters)==null?void 0:u.docs,source:{originalSource:`{
|
|
13
|
+
args: {
|
|
14
|
+
data: mockConfirmation,
|
|
15
|
+
theme: {
|
|
16
|
+
primary: '#337eff',
|
|
17
|
+
background: '#ffffff',
|
|
18
|
+
text: '#1a1a2e',
|
|
19
|
+
textMuted: 'rgba(0,0,0,0.35)'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
decorators: [Story => <div style={{
|
|
23
|
+
maxWidth: 360,
|
|
24
|
+
background: '#ffffff',
|
|
25
|
+
padding: 16,
|
|
26
|
+
borderRadius: 12
|
|
27
|
+
}}>
|
|
28
|
+
<Story />
|
|
29
|
+
</div>]
|
|
30
|
+
}`,...(b=(f=o.parameters)==null?void 0:f.docs)==null?void 0:b.source}}};var k,C,x;t.parameters={...t.parameters,docs:{...(k=t.parameters)==null?void 0:k.docs,source:{originalSource:`{
|
|
31
|
+
render: args => <div style={{
|
|
32
|
+
maxWidth: 360,
|
|
33
|
+
background: '#00001a',
|
|
34
|
+
padding: 16,
|
|
35
|
+
borderRadius: 12
|
|
36
|
+
}}>
|
|
37
|
+
<BookingCancelledCard {...args} />
|
|
38
|
+
</div>,
|
|
39
|
+
args: {
|
|
40
|
+
data: {
|
|
41
|
+
bookingId: 'bk_abc123',
|
|
42
|
+
meetingDate: '2026-03-26',
|
|
43
|
+
meetingTime: '10:00',
|
|
44
|
+
meetingPurpose: 'Discuss mobile app development project',
|
|
45
|
+
cancelledBy: 'visitor'
|
|
46
|
+
} as IBookingCancelledData
|
|
47
|
+
}
|
|
48
|
+
}`,...(x=(C=t.parameters)==null?void 0:C.docs)==null?void 0:x.source}}};var y,h,v;i.parameters={...i.parameters,docs:{...(y=i.parameters)==null?void 0:y.docs,source:{originalSource:`{
|
|
49
|
+
render: args => <div style={{
|
|
50
|
+
maxWidth: 360,
|
|
51
|
+
background: '#00001a',
|
|
52
|
+
padding: 16,
|
|
53
|
+
borderRadius: 12
|
|
54
|
+
}}>
|
|
55
|
+
<BookingCancelledCard {...args} />
|
|
56
|
+
</div>,
|
|
57
|
+
args: {
|
|
58
|
+
data: {
|
|
59
|
+
bookingId: 'bk_abc123',
|
|
60
|
+
meetingDate: '2026-03-26',
|
|
61
|
+
meetingTime: '10:00',
|
|
62
|
+
meetingPurpose: 'Discuss mobile app development project',
|
|
63
|
+
cancelledBy: 'admin'
|
|
64
|
+
} as IBookingCancelledData
|
|
65
|
+
}
|
|
66
|
+
}`,...(v=(h=i.parameters)==null?void 0:h.docs)==null?void 0:v.source}}};const W=["Confirmed","ConfirmedNoMeetLink","ConfirmedLightTheme","CancelledByVisitor","CancelledByAdmin"];export{i as CancelledByAdmin,t as CancelledByVisitor,n as Confirmed,o as ConfirmedLightTheme,r as ConfirmedNoMeetLink,W as __namedExportsOrder,T as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as t}from"./jsx-runtime-Cf8x2fCZ.js";import{r as y}from"./index--qcDGAq6.js";import{G as B,C as D}from"./globe-BtMvkLMD.js";function S({data:i,theme:r,onSlotSelected:m,disabled:o=!1}){var b;const[p,h]=y.useState(0),[a,k]=y.useState(null),x=(r==null?void 0:r.background)||"#00001a",d=(()=>{if(!x.startsWith("#"))return!1;const e=x.replace("#",""),n=parseInt(e.substring(0,2),16),c=parseInt(e.substring(2,4),16),I=parseInt(e.substring(4,6),16);return(.299*n+.587*c+.114*I)/255>.5})(),j=(r==null?void 0:r.primary)||"#337eff",g=(r==null?void 0:r.text)||(d?"#1a1a2e":"#f7f7f8"),s=(r==null?void 0:r.textMuted)||(d?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),C=d?{backgroundColor:"rgba(0,0,0,0.03)",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.08)",borderRadius:"14px",padding:"14px"}:{backgroundColor:"rgba(255,255,255,0.04)",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",borderRadius:"14px",padding:"14px"},u={backgroundColor:j,color:"#ffffff",borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"600",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",flexShrink:0},f=d?{backgroundColor:"rgba(0,0,0,0.04)",color:g,borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"500",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.1)",flexShrink:0}:{backgroundColor:"rgba(255,255,255,0.05)",color:g,borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"500",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.1)",flexShrink:0},v=(a==null?void 0:a.date)===((b=i.availableDates[p])==null?void 0:b.date)?a.time:null,z=(e,n)=>{o||(k({date:e,time:n}),m(e,n))},l=i.availableDates[p];return t.jsxs("div",{style:C,children:[t.jsx("p",{style:{fontSize:"11px",fontWeight:"600",letterSpacing:"0.06em",textTransform:"uppercase",color:s,marginBottom:"10px"},children:"Select a date"}),t.jsx("div",{style:{display:"flex",gap:"6px",overflowX:"auto",paddingBottom:"2px",scrollbarWidth:"none",msOverflowStyle:"none",marginBottom:"14px"},children:i.availableDates.map((e,n)=>t.jsx("button",{type:"button",onClick:()=>!o&&h(n),style:n===p?u:f,title:e.dayName,"aria-pressed":n===p,disabled:o,children:e.dayLabel},e.date))}),l&&t.jsxs(t.Fragment,{children:[t.jsx("p",{style:{fontSize:"11px",fontWeight:"600",letterSpacing:"0.06em",textTransform:"uppercase",color:s,marginBottom:"8px"},children:l.dayName}),t.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(3, 1fr)",gap:"6px",marginBottom:"14px"},children:l.slots.map(e=>{const n=v===e,c=(a==null?void 0:a.date)===l.date&&a.time===e;return t.jsx("button",{type:"button",onClick:()=>z(l.date,e),disabled:o&&!c,"aria-pressed":n,style:{...n?u:f,padding:"7px 8px",borderRadius:"8px",fontSize:"13px",fontWeight:n?"600":"400",textAlign:"center",opacity:o&&!c?.4:1,transition:"opacity 0.15s ease"},children:e},e)})})]}),t.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"8px"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px"},children:[t.jsx(B,{size:11,color:s,"aria-hidden":"true"}),t.jsx("span",{style:{fontSize:"11px",color:s,letterSpacing:"0.01em"},children:i.timezone})]}),t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px"},children:[t.jsx(D,{size:11,color:s,"aria-hidden":"true"}),t.jsxs("span",{style:{fontSize:"11px",color:s,letterSpacing:"0.01em"},children:[i.meetingDuration," min"]})]})]})]})}try{S.displayName="BookingSlotPicker",S.__docgenInfo={description:"",displayName:"BookingSlotPicker",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}},onSlotSelected:{defaultValue:null,description:"",name:"onSlotSelected",required:!0,type:{name:"(date: string, time: string) => void"}},disabled:{defaultValue:{value:"false"},description:"",name:"disabled",required:!1,type:{name:"boolean | undefined"}}}}}catch{}export{S as B};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import{j as r}from"./jsx-runtime-Cf8x2fCZ.js";import{B as j}from"./BookingSlotPicker-BkfssueW.js";import"./index-yBjzXJbu.js";import"./index--qcDGAq6.js";import"./globe-BtMvkLMD.js";const A={title:"Components/BookingSlotPicker",component:j,parameters:{layout:"padded"},tags:["autodocs"],decorators:[e=>r.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:r.jsx(e,{})})]},n={availableDates:[{date:"2026-03-25",dayLabel:"Wed 25",dayName:"Wednesday",slots:["09:00","09:30","10:00","10:30","14:00","14:30","15:00"]},{date:"2026-03-26",dayLabel:"Thu 26",dayName:"Thursday",slots:["09:00","10:00","11:00","13:00","14:00"]},{date:"2026-03-27",dayLabel:"Fri 27",dayName:"Friday",slots:["09:30","10:30","11:30"]},{date:"2026-03-28",dayLabel:"Mon 30",dayName:"Monday",slots:["09:00","09:30","10:00","10:30","11:00","11:30","13:00","14:00","15:00","16:00"]},{date:"2026-03-31",dayLabel:"Tue 31",dayName:"Tuesday",slots:["14:00","14:30","15:00","15:30","16:00"]}],timezone:"Australia/Sydney",meetingDuration:30},t={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a)}},o={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a),theme:{primary:"#337eff",background:"#ffffff",text:"#1a1a2e",textMuted:"rgba(0,0,0,0.35)"}},decorators:[e=>r.jsx("div",{style:{maxWidth:360,background:"#ffffff",padding:16,borderRadius:12},children:r.jsx(e,{})})]},d={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a),disabled:!0}},s={args:{data:{availableDates:[{date:"2026-03-26",dayLabel:"Thu 26",dayName:"Thursday",slots:["14:00"]},{date:"2026-03-27",dayLabel:"Fri 27",dayName:"Friday",slots:["09:00","15:30"]}],timezone:"Australia/Sydney",meetingDuration:30},onSlotSelected:(e,a)=>console.log("Selected:",e,a)}};var i,l,c,m,y;t.parameters={...t.parameters,docs:{...(i=t.parameters)==null?void 0:i.docs,source:{originalSource:`{
|
|
2
|
+
args: {
|
|
3
|
+
data: mockBookingData,
|
|
4
|
+
onSlotSelected: (date, time) => console.log('Selected:', date, time)
|
|
5
|
+
}
|
|
6
|
+
}`,...(c=(l=t.parameters)==null?void 0:l.docs)==null?void 0:c.source},description:{story:"Default dark theme",...(y=(m=t.parameters)==null?void 0:m.docs)==null?void 0:y.description}}};var p,u,g,S,f;o.parameters={...o.parameters,docs:{...(p=o.parameters)==null?void 0:p.docs,source:{originalSource:`{
|
|
7
|
+
args: {
|
|
8
|
+
data: mockBookingData,
|
|
9
|
+
onSlotSelected: (date, time) => console.log('Selected:', date, time),
|
|
10
|
+
theme: {
|
|
11
|
+
primary: '#337eff',
|
|
12
|
+
background: '#ffffff',
|
|
13
|
+
text: '#1a1a2e',
|
|
14
|
+
textMuted: 'rgba(0,0,0,0.35)'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
decorators: [Story => <div style={{
|
|
18
|
+
maxWidth: 360,
|
|
19
|
+
background: '#ffffff',
|
|
20
|
+
padding: 16,
|
|
21
|
+
borderRadius: 12
|
|
22
|
+
}}>
|
|
23
|
+
<Story />
|
|
24
|
+
</div>]
|
|
25
|
+
}`,...(g=(u=o.parameters)==null?void 0:u.docs)==null?void 0:g.source},description:{story:"Light theme",...(f=(S=o.parameters)==null?void 0:S.docs)==null?void 0:f.description}}};var b,h,k,D,x;d.parameters={...d.parameters,docs:{...(b=d.parameters)==null?void 0:b.docs,source:{originalSource:`{
|
|
26
|
+
args: {
|
|
27
|
+
data: mockBookingData,
|
|
28
|
+
onSlotSelected: (date, time) => console.log('Selected:', date, time),
|
|
29
|
+
disabled: true
|
|
30
|
+
}
|
|
31
|
+
}`,...(k=(h=d.parameters)==null?void 0:h.docs)==null?void 0:k.source},description:{story:"Disabled state (after selection)",...(x=(D=d.parameters)==null?void 0:D.docs)==null?void 0:x.description}}};var L,T,N,v,B;s.parameters={...s.parameters,docs:{...(L=s.parameters)==null?void 0:L.docs,source:{originalSource:`{
|
|
32
|
+
args: {
|
|
33
|
+
data: {
|
|
34
|
+
availableDates: [{
|
|
35
|
+
date: '2026-03-26',
|
|
36
|
+
dayLabel: 'Thu 26',
|
|
37
|
+
dayName: 'Thursday',
|
|
38
|
+
slots: ['14:00']
|
|
39
|
+
}, {
|
|
40
|
+
date: '2026-03-27',
|
|
41
|
+
dayLabel: 'Fri 27',
|
|
42
|
+
dayName: 'Friday',
|
|
43
|
+
slots: ['09:00', '15:30']
|
|
44
|
+
}],
|
|
45
|
+
timezone: 'Australia/Sydney',
|
|
46
|
+
meetingDuration: 30
|
|
47
|
+
},
|
|
48
|
+
onSlotSelected: (date, time) => console.log('Selected:', date, time)
|
|
49
|
+
}
|
|
50
|
+
}`,...(N=(T=s.parameters)==null?void 0:T.docs)==null?void 0:N.source},description:{story:"Limited availability",...(B=(v=s.parameters)==null?void 0:v.docs)==null?void 0:B.description}}};const E=["Default","LightTheme","Disabled","LimitedSlots"];export{t as Default,d as Disabled,o as LightTheme,s as LimitedSlots,E as __namedExportsOrder,A as default};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";function l({size:a=24,color:i="white",className:r="",style:t}){return e.jsxs("svg",{width:a,height:a,viewBox:"0 0 32 32",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:r,style:t,"aria-hidden":"true",children:[e.jsx("path",{d:"M20.582 15.027L31.849 0.036H24.808L17.039 10.303L20.582 15.027ZM24.808 31.837H31.849L20.582 16.846L17.039 21.57L24.808 31.837Z",fill:i}),e.jsx("path",{d:"M14.313 15.027H18.402L7.135 0.036H0.185L9.406 12.392C10.587 13.983 12.359 15.027 14.313 15.027Z",fill:i}),e.jsx("path",{d:"M0.185 31.837H7.135L18.402 16.846H14.313C12.359 16.846 10.588 17.891 9.406 19.481L0.185 31.837Z",fill:i})]})}function s({size:a=36,color:i="white",className:r="",style:t}){return e.jsxs("svg",{width:a,height:a,viewBox:"0 0 32 32",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:r,style:t,"aria-hidden":"true",children:[e.jsx("circle",{cx:"16",cy:"3.5",r:"3",fill:"#67e8f9",opacity:.4}),e.jsx("circle",{cx:"16",cy:"3.5",r:"1.5",fill:"#67e8f9"}),e.jsx("rect",{x:"15",y:"4.5",width:"2",height:"4",rx:"1",fill:i}),e.jsx("rect",{x:"1",y:"15",width:"4",height:"6",rx:"2",fill:i,opacity:.5}),e.jsx("rect",{x:"27",y:"15",width:"4",height:"6",rx:"2",fill:i,opacity:.5}),e.jsx("rect",{x:"5",y:"8.5",width:"22",height:"19",rx:"5",fill:i}),e.jsx("circle",{cx:"12",cy:"16.5",r:"3",fill:"#1a4fd0"}),e.jsx("circle",{cx:"20",cy:"16.5",r:"3",fill:"#1a4fd0"}),e.jsx("circle",{cx:"13",cy:"15.5",r:"1.2",fill:i}),e.jsx("circle",{cx:"21",cy:"15.5",r:"1.2",fill:i}),e.jsx("path",{d:"M13 22.5q3 2.5 6 0",stroke:"#1a4fd0",strokeWidth:"1.5",strokeLinecap:"round"})]})}function n({size:a=40,className:i=""}){const r=Math.round(a*.55);return e.jsx("div",{className:`flex items-center justify-center rounded-full ${i}`,style:{width:a,height:a,background:"linear-gradient(135deg, #337eff, #005eff)"},children:e.jsx(l,{size:r,color:"white"})})}try{l.displayName="XcelsiorSymbol",l.__docgenInfo={description:`Xcelsior "X" symbol mark (from favicon-brand.svg)
|
|
2
|
+
Reusable across the chat widget — FAB button, avatar, header, etc.`,displayName:"XcelsiorSymbol",props:{size:{defaultValue:{value:"24"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{s.displayName="ChatBubbleIcon",s.__docgenInfo={description:`Expressive robot icon for the FAB launcher button.
|
|
3
|
+
Friendly robot with cyan antenna glow, ear nubs, eyes with highlights, and smile.`,displayName:"ChatBubbleIcon",props:{size:{defaultValue:{value:"36"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{n.displayName="XcelsiorAvatar",n.__docgenInfo={description:`Xcelsior "X" avatar — symbol inside a gradient circle
|
|
4
|
+
Used as bot avatar in messages and chat header`,displayName:"XcelsiorAvatar",props:{size:{defaultValue:{value:"40"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}}}}}catch{}export{s as C,l as X,n as a};
|