ajaxter-chat 3.0.7 → 3.0.9

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.
Files changed (35) hide show
  1. package/dist/components/ChatScreen/index.d.ts +2 -0
  2. package/dist/components/ChatScreen/index.js +36 -9
  3. package/dist/components/ChatWidget.js +136 -27
  4. package/dist/components/HomeScreen/index.d.ts +2 -0
  5. package/dist/components/HomeScreen/index.js +2 -2
  6. package/dist/components/Tabs/BottomTabs.d.ts +0 -1
  7. package/dist/components/Tabs/BottomTabs.js +13 -20
  8. package/dist/components/TicketDetailScreen/index.d.ts +9 -0
  9. package/dist/components/TicketDetailScreen/index.js +46 -0
  10. package/dist/components/TicketFormScreen/index.d.ts +9 -0
  11. package/dist/components/TicketFormScreen/index.js +70 -0
  12. package/dist/components/TicketScreen/index.d.ts +2 -1
  13. package/dist/components/TicketScreen/index.js +8 -35
  14. package/dist/components/UserListScreen/index.d.ts +2 -0
  15. package/dist/components/UserListScreen/index.js +15 -2
  16. package/dist/types/index.d.ts +1 -1
  17. package/dist/utils/fileName.d.ts +2 -0
  18. package/dist/utils/fileName.js +7 -0
  19. package/dist/utils/messageSound.d.ts +4 -0
  20. package/dist/utils/messageSound.js +51 -0
  21. package/dist/utils/widgetSession.d.ts +13 -0
  22. package/dist/utils/widgetSession.js +24 -0
  23. package/package.json +1 -1
  24. package/src/components/ChatScreen/index.tsx +70 -16
  25. package/src/components/ChatWidget.tsx +163 -35
  26. package/src/components/HomeScreen/index.tsx +4 -2
  27. package/src/components/Tabs/BottomTabs.tsx +2 -22
  28. package/src/components/TicketDetailScreen/index.tsx +111 -0
  29. package/src/components/TicketFormScreen/index.tsx +140 -0
  30. package/src/components/TicketScreen/index.tsx +18 -58
  31. package/src/components/UserListScreen/index.tsx +32 -3
  32. package/src/types/index.ts +2 -0
  33. package/src/utils/fileName.ts +6 -0
  34. package/src/utils/messageSound.ts +47 -0
  35. package/src/utils/widgetSession.ts +34 -0
@@ -19,6 +19,8 @@ interface ChatScreenProps {
19
19
  /** Other devs (excl. viewer) — for transfer when staff chats with a customer */
20
20
  otherDevelopers?: ChatUser[];
21
21
  onTransferToDeveloper?: (dev: ChatUser) => void;
22
+ messageSoundEnabled?: boolean;
23
+ onToggleMessageSound?: (enabled: boolean) => void;
22
24
  }
23
25
  export declare const ChatScreen: React.FC<ChatScreenProps>;
24
26
  export {};
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from '../../utils/chat';
4
+ import { shortAttachmentLabel } from '../../utils/fileName';
4
5
  import { shouldShowPrivacyNotice, dismissPrivacyNotice } from '../../utils/privacyConsent';
5
6
  import { EmojiPicker } from '../EmojiPicker';
6
7
  import { SlideNavMenu } from '../SlideNavMenu';
7
- export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, }) => {
8
+ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, messageSoundEnabled = true, onToggleMessageSound, }) => {
8
9
  var _a;
9
10
  const [text, setText] = useState('');
10
11
  const [showEmoji, setShowEmoji] = useState(false);
@@ -125,7 +126,32 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
125
126
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsx(SlideNavMenu, { open: slideMenuOpen, onClose: () => setSlideMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, viewerType: (_a = config.viewerType) !== null && _a !== void 0 ? _a : 'user', onSelect: onNavAction, onBackHome: onBack }), _jsxs("div", { style: {
126
127
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
127
128
  padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
128
- }, children: [_jsx("button", { type: "button", onClick: () => setSlideMenuOpen(true), style: hdrBtn, "aria-label": "Open menu", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [_jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" })] }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: headerRole }), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), viewerIsDev && activeUser.type === 'user' && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx(MenuItem, { icon: "\uD83D\uDD00", label: "Transfer to developer", onClick: () => { setShowMenu(false); setTransferOpen(true); } })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [privacyEnabled && showPrivacy && (_jsxs("div", { style: {
129
+ }, children: [_jsx("button", { type: "button", onClick: () => setSlideMenuOpen(true), style: hdrBtn, "aria-label": "Open menu", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [_jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" })] }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: headerRole }), onToggleMessageSound && (_jsxs("label", { style: {
130
+ display: 'flex',
131
+ alignItems: 'center',
132
+ gap: 6,
133
+ cursor: 'pointer',
134
+ flexShrink: 0,
135
+ marginLeft: 4,
136
+ }, children: [_jsx("span", { style: { fontSize: 10, color: 'rgba(255,255,255,0.85)', fontWeight: 600 }, children: "Sound" }), _jsx("button", { type: "button", role: "switch", "aria-checked": messageSoundEnabled, onClick: () => onToggleMessageSound(!messageSoundEnabled), style: {
137
+ width: 36,
138
+ height: 20,
139
+ borderRadius: 10,
140
+ border: 'none',
141
+ background: messageSoundEnabled ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.2)',
142
+ position: 'relative',
143
+ cursor: 'pointer',
144
+ padding: 0,
145
+ }, children: _jsx("span", { style: {
146
+ position: 'absolute',
147
+ top: 2,
148
+ left: messageSoundEnabled ? 18 : 2,
149
+ width: 16,
150
+ height: 16,
151
+ borderRadius: '50%',
152
+ background: '#fff',
153
+ transition: 'left 0.15s ease',
154
+ } }) })] })), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), viewerIsDev && activeUser.type === 'user' && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx(MenuItem, { icon: "\uD83D\uDD00", label: "Transfer to developer", onClick: () => { setShowMenu(false); setTransferOpen(true); } })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [privacyEnabled && showPrivacy && (_jsxs("div", { style: {
129
155
  position: 'relative',
130
156
  marginBottom: 10,
131
157
  padding: '12px 36px 12px 12px',
@@ -289,13 +315,14 @@ const AttachmentRow = ({ msg, isMe, primaryColor }) => {
289
315
  var _a;
290
316
  const name = (_a = msg.attachmentName) !== null && _a !== void 0 ? _a : 'File';
291
317
  const href = msg.attachmentUrl;
292
- return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }, children: [_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: isMe ? '#fff' : '#334155', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, wordBreak: 'break-word' }, children: name }), msg.attachmentSize && _jsx("div", { style: { fontSize: 11, opacity: 0.8 }, children: msg.attachmentSize })] }), href && (_jsx("a", { href: href, download: name, style: {
293
- fontSize: 12,
294
- fontWeight: 700,
295
- color: isMe ? '#fff' : primaryColor,
296
- textDecoration: 'underline',
297
- whiteSpace: 'nowrap',
298
- }, children: "Download" }))] }));
318
+ const label = shortAttachmentLabel(name, 10);
319
+ return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }, children: [_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: isMe ? '#fff' : '#334155', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [href ? (_jsxs("a", { href: href, download: name, title: name, style: {
320
+ fontWeight: 700,
321
+ fontSize: 14,
322
+ wordBreak: 'break-word',
323
+ color: isMe ? '#fff' : primaryColor,
324
+ textDecoration: 'underline',
325
+ }, children: ["[", label, "]"] })) : (_jsxs("div", { style: { fontWeight: 700, fontSize: 14, wordBreak: 'break-word' }, title: name, children: ["[", label, "]"] })), msg.attachmentSize && _jsx("div", { style: { fontSize: 11, opacity: 0.8 }, children: msg.attachmentSize })] })] }));
299
326
  };
300
327
  const Bubble = ({ msg, primaryColor }) => {
301
328
  const isMe = msg.senderId === 'me';
@@ -1,25 +1,26 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useEffect, useCallback } from 'react';
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { loadLocalConfig } from '../config';
5
5
  import { mergeTheme } from '../utils/theme';
6
6
  import { useRemoteConfig } from '../hooks/useRemoteConfig';
7
7
  import { useChat } from '../hooks/useChat';
8
8
  import { useWebRTC } from '../hooks/useWebRTC';
9
+ import { saveSession, loadSession } from '../utils/widgetSession';
10
+ import { playMessageSound, getMessageSoundEnabled, setMessageSoundEnabled } from '../utils/messageSound';
9
11
  import { HomeScreen } from './HomeScreen';
10
12
  import { UserListScreen } from './UserListScreen';
11
13
  import { ChatScreen } from './ChatScreen';
12
14
  import { RecentChatsScreen } from './RecentChatsScreen';
13
15
  import { TicketScreen } from './TicketScreen';
16
+ import { TicketDetailScreen } from './TicketDetailScreen';
17
+ import { TicketFormScreen } from './TicketFormScreen';
14
18
  import { BlockListScreen } from './BlockList';
15
19
  import { CallScreen } from './CallScreen';
16
20
  import { MaintenanceView } from './MaintenanceView';
17
21
  import { BottomTabs } from './Tabs/BottomTabs';
18
- /* ─── Drawer width ─────────────────────────────────────────────────────────── */
19
- const DRAWER_W_NORMAL = 380;
20
- const DRAWER_W_MAX = 480;
21
22
  export const ChatWidget = ({ theme: localTheme }) => {
22
- var _a, _b, _c, _d;
23
+ var _a, _b, _c, _d, _e;
23
24
  /* SSR guard */
24
25
  const [mounted, setMounted] = useState(false);
25
26
  useEffect(() => { setMounted(true); }, []);
@@ -31,12 +32,14 @@ export const ChatWidget = ({ theme: localTheme }) => {
31
32
  const theme = mergeTheme((data === null || data === void 0 ? void 0 : data.widget) ? { primaryColor: data.widget.primaryColor, buttonLabel: data.widget.buttonLabel, buttonPosition: data.widget.buttonPosition } : undefined, localTheme);
32
33
  /* Drawer open state */
33
34
  const [isOpen, setIsOpen] = useState(false);
34
- const [isMaximized, setIsMaximized] = useState(false);
35
35
  const [closing, setClosing] = useState(false); // for slide-out animation
36
36
  /* Navigation */
37
37
  const [activeTab, setActiveTab] = useState('home');
38
38
  const [screen, setScreen] = useState('home');
39
39
  const [userListCtx, setUserListCtx] = useState('support');
40
+ const [chatReturnCtx, setChatReturnCtx] = useState('conversation');
41
+ const [viewingTicketId, setViewingTicketId] = useState(null);
42
+ const [messageSoundEnabled, setMessageSoundEnabledState] = useState(true);
40
43
  /* App state */
41
44
  const [tickets, setTickets] = useState((_a = data === null || data === void 0 ? void 0 : data.sampleTickets) !== null && _a !== void 0 ? _a : []);
42
45
  const [recentChats, setRecentChats] = useState([]);
@@ -75,16 +78,83 @@ export const ChatWidget = ({ theme: localTheme }) => {
75
78
  setClosing(false);
76
79
  setIsOpen(true);
77
80
  };
81
+ const persistWidgetState = useCallback(() => {
82
+ var _a;
83
+ const w = data === null || data === void 0 ? void 0 : data.widget;
84
+ if (!w)
85
+ return;
86
+ saveSession(w.id, {
87
+ screen,
88
+ activeTab,
89
+ userListCtx,
90
+ activeUserUid: (_a = activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid) !== null && _a !== void 0 ? _a : null,
91
+ messages,
92
+ viewingTicketId,
93
+ chatReturnCtx,
94
+ });
95
+ }, [data === null || data === void 0 ? void 0 : data.widget, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx]);
78
96
  const closeDrawer = useCallback(() => {
97
+ persistWidgetState();
79
98
  setClosing(true);
80
99
  setTimeout(() => {
81
100
  setIsOpen(false);
82
101
  setClosing(false);
83
- setScreen('home');
84
- setActiveTab('home');
85
- clearChat();
86
102
  }, 300);
87
- }, [clearChat]);
103
+ }, [persistWidgetState]);
104
+ const restoredRef = useRef(false);
105
+ useEffect(() => {
106
+ var _a, _b, _c;
107
+ if (!(data === null || data === void 0 ? void 0 : data.widget) || restoredRef.current)
108
+ return;
109
+ const w = data.widget;
110
+ setMessageSoundEnabledState(getMessageSoundEnabled(w.id));
111
+ const p = loadSession(w.id);
112
+ if (p) {
113
+ setScreen(p.screen);
114
+ setActiveTab(p.activeTab);
115
+ setUserListCtx(p.userListCtx);
116
+ setViewingTicketId((_a = p.viewingTicketId) !== null && _a !== void 0 ? _a : null);
117
+ setChatReturnCtx((_b = p.chatReturnCtx) !== null && _b !== void 0 ? _b : 'conversation');
118
+ if (p.activeUserUid) {
119
+ const u = [...data.developers, ...data.users].find(x => x.uid === p.activeUserUid);
120
+ if (u) {
121
+ const hist = Array.isArray(p.messages) && p.messages.length
122
+ ? p.messages
123
+ : ((_c = data.sampleChats[u.uid]) !== null && _c !== void 0 ? _c : []);
124
+ selectUser(u, hist);
125
+ }
126
+ }
127
+ }
128
+ restoredRef.current = true;
129
+ }, [data, selectUser]);
130
+ useEffect(() => {
131
+ if (!(data === null || data === void 0 ? void 0 : data.widget))
132
+ return;
133
+ persistWidgetState();
134
+ }, [(_c = data === null || data === void 0 ? void 0 : data.widget) === null || _c === void 0 ? void 0 : _c.id, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx, persistWidgetState]);
135
+ const incomingSoundRef = useRef(0);
136
+ useEffect(() => {
137
+ incomingSoundRef.current = messages.length;
138
+ }, [activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid]);
139
+ useEffect(() => {
140
+ if (!messageSoundEnabled || !activeUser || !(data === null || data === void 0 ? void 0 : data.widget))
141
+ return;
142
+ if (messages.length < incomingSoundRef.current) {
143
+ incomingSoundRef.current = messages.length;
144
+ return;
145
+ }
146
+ const added = messages.slice(incomingSoundRef.current);
147
+ incomingSoundRef.current = messages.length;
148
+ if (added.some(m => m.senderId !== 'me'))
149
+ playMessageSound();
150
+ }, [messages, messageSoundEnabled, activeUser, data === null || data === void 0 ? void 0 : data.widget]);
151
+ const toggleMessageSound = useCallback((enabled) => {
152
+ const w = data === null || data === void 0 ? void 0 : data.widget;
153
+ if (!w)
154
+ return;
155
+ setMessageSoundEnabled(w.id, enabled);
156
+ setMessageSoundEnabledState(enabled);
157
+ }, [data === null || data === void 0 ? void 0 : data.widget]);
88
158
  /* ── Navigation ──────────────────────────────────────────────────────── */
89
159
  const handleCardClick = useCallback((ctx) => {
90
160
  if (ctx === 'ticket') {
@@ -107,20 +177,34 @@ export const ChatWidget = ({ theme: localTheme }) => {
107
177
  setScreen('user-list');
108
178
  }
109
179
  }, [clearChat]);
110
- const handleSelectUser = useCallback((user) => {
180
+ const listCtxForUser = useCallback((user, viewerIsDev) => {
181
+ if (viewerIsDev)
182
+ return user.type === 'user' ? 'support' : 'conversation';
183
+ return user.type === 'developer' ? 'support' : 'conversation';
184
+ }, []);
185
+ const handleSelectUser = useCallback((user, returnCtxOverride) => {
111
186
  var _a;
112
- // Load history from sample chats if available
187
+ setChatReturnCtx(returnCtxOverride !== null && returnCtxOverride !== void 0 ? returnCtxOverride : userListCtx);
113
188
  const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
114
189
  selectUser(user, history);
115
190
  setScreen('chat');
116
- // Update recent chats
117
191
  setRecentChats(prev => {
118
192
  const exists = prev.find(r => r.user.uid === user.uid);
119
193
  if (exists)
120
194
  return prev;
121
195
  return [{ id: `rc_${user.uid}`, user, lastMessage: '', lastTime: new Date().toISOString(), unread: 0, isPaused: false }, ...prev];
122
196
  });
123
- }, [data, selectUser]);
197
+ }, [data, selectUser, userListCtx]);
198
+ const handleBackFromChat = useCallback(() => {
199
+ clearChat();
200
+ setUserListCtx(chatReturnCtx);
201
+ setScreen('user-list');
202
+ }, [clearChat, chatReturnCtx]);
203
+ const handleOpenTicket = useCallback((id) => {
204
+ setViewingTicketId(id);
205
+ setScreen('ticket-detail');
206
+ setActiveTab('tickets');
207
+ }, []);
124
208
  const handleTabChange = useCallback((tab) => {
125
209
  setActiveTab(tab);
126
210
  setScreen(tab === 'home' ? 'home' : tab === 'chats' ? 'recent-chats' : 'tickets');
@@ -131,7 +215,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
131
215
  return;
132
216
  setBlockedUids(prev => [...prev, activeUser.uid]);
133
217
  clearChat();
134
- setScreen('home');
218
+ setScreen('block-list');
135
219
  setActiveTab('home');
136
220
  }, [activeUser, clearChat]);
137
221
  const handleUnblock = useCallback((uid) => {
@@ -146,7 +230,10 @@ export const ChatWidget = ({ theme: localTheme }) => {
146
230
  updatedAt: new Date().toISOString(),
147
231
  assignedTo: null,
148
232
  };
149
- setTickets(prev => [t, ...prev]);
233
+ setTickets(prev => [...prev, t]);
234
+ setViewingTicketId(t.id);
235
+ setScreen('ticket-detail');
236
+ setActiveTab('tickets');
150
237
  }, []);
151
238
  /* ── Pause sync back into recent chats ──────────────────────────────── */
152
239
  const handleTogglePause = useCallback(() => {
@@ -168,7 +255,6 @@ export const ChatWidget = ({ theme: localTheme }) => {
168
255
  }, [endCall]);
169
256
  /* ── Derived ─────────────────────────────────────────────────────────── */
170
257
  const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
171
- const drawerW = isMaximized ? DRAWER_W_MAX : DRAWER_W_NORMAL;
172
258
  const widgetConfig = data === null || data === void 0 ? void 0 : data.widget;
173
259
  const primaryColor = theme.primaryColor;
174
260
  const allUsers = data ? [...data.developers, ...data.users] : [];
@@ -187,7 +273,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
187
273
  return u.type === 'user';
188
274
  })
189
275
  : [];
190
- const otherDevelopers = (_c = data === null || data === void 0 ? void 0 : data.developers.filter(d => d.uid !== viewerUid)) !== null && _c !== void 0 ? _c : [];
276
+ const otherDevelopers = (_d = data === null || data === void 0 ? void 0 : data.developers.filter(d => d.uid !== viewerUid)) !== null && _d !== void 0 ? _d : [];
191
277
  const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
192
278
  const handleTransferToDeveloper = useCallback((dev) => {
193
279
  var _a;
@@ -209,9 +295,20 @@ export const ChatWidget = ({ theme: localTheme }) => {
209
295
  const posStyle = theme.buttonPosition === 'bottom-left'
210
296
  ? { left: 24, right: 'auto' }
211
297
  : { right: 24, left: 'auto' };
298
+ /* No radius on top-left / bottom-left; left-docked panel keeps inner TR/BR curve */
212
299
  const drawerPosStyle = theme.buttonPosition === 'bottom-left'
213
- ? { left: 0, borderRadius: '0 16px 16px 0' }
214
- : { right: 0, borderRadius: '16px 0 0 16px' };
300
+ ? {
301
+ left: 0,
302
+ borderTopLeftRadius: 0,
303
+ borderBottomLeftRadius: 0,
304
+ borderTopRightRadius: 16,
305
+ borderBottomRightRadius: 16,
306
+ }
307
+ : {
308
+ right: 0,
309
+ borderTopLeftRadius: 0,
310
+ borderBottomLeftRadius: 0,
311
+ };
215
312
  /* ── Don't render until mounted (SSR safe) ──────────────────────────── */
216
313
  if (!mounted)
217
314
  return null;
@@ -235,6 +332,15 @@ export const ChatWidget = ({ theme: localTheme }) => {
235
332
 
236
333
  .cw-drawer-enter { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideInLeft' : 'cw-slideInRight'} 0.32s cubic-bezier(0.22,1,0.36,1) both; }
237
334
  .cw-drawer-exit { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideOutLeft' : 'cw-slideOutRight'} 0.28s cubic-bezier(0.55,0,1,0.45) both; }
335
+
336
+ .cw-drawer-panel {
337
+ width: 30%;
338
+ max-width: 100vw;
339
+ min-width: 0;
340
+ }
341
+ @media (max-width: 1024px) {
342
+ .cw-drawer-panel { width: 100%; }
343
+ }
238
344
  ` }), !isOpen && (_jsxs("button", { className: "cw-root", onClick: openDrawer, "aria-label": theme.buttonLabel, style: Object.assign(Object.assign({ position: 'fixed', bottom: 24, zIndex: 9999 }, posStyle), { display: 'flex', alignItems: 'center', gap: 10, padding: '13px 22px', backgroundColor: theme.buttonColor, color: theme.buttonTextColor, border: 'none', borderRadius: 50, cursor: 'pointer', fontSize: 15, fontWeight: 700, boxShadow: `0 8px 28px ${theme.buttonColor}55`, animation: 'cw-btnPop 0.4s cubic-bezier(0.34,1.56,0.64,1)', transition: 'transform 0.2s, box-shadow 0.2s' }), onMouseEnter: e => {
239
345
  e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
240
346
  e.currentTarget.style.boxShadow = `0 14px 36px ${theme.buttonColor}66`;
@@ -246,25 +352,28 @@ export const ChatWidget = ({ theme: localTheme }) => {
246
352
  backgroundColor: 'rgba(0,0,0,0.35)',
247
353
  opacity: closing ? 0 : 1,
248
354
  transition: 'opacity 0.3s',
249
- } })), isOpen && (_jsxs("div", { className: `cw-root ${closing ? 'cw-drawer-exit' : 'cw-drawer-enter'}`, style: Object.assign(Object.assign({ position: 'fixed', top: 0, bottom: 0 }, drawerPosStyle), { zIndex: 9998, width: drawerW, maxWidth: '100vw', backgroundColor: '#fff', boxShadow: theme.buttonPosition === 'bottom-left'
355
+ } })), isOpen && (_jsxs("div", { className: `cw-root cw-drawer-panel ${closing ? 'cw-drawer-exit' : 'cw-drawer-enter'}`, style: Object.assign(Object.assign({ position: 'fixed', top: 0, bottom: 0 }, drawerPosStyle), { zIndex: 9998, backgroundColor: '#fff', boxShadow: theme.buttonPosition === 'bottom-left'
250
356
  ? '4px 0 40px rgba(0,0,0,0.18)'
251
- : '-4px 0 40px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', overflow: 'hidden', transition: 'width 0.28s ease' }), children: [cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 }, children: [_jsx("div", { style: {
357
+ : '-4px 0 40px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }), children: [cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 }, children: [_jsx("div", { style: {
252
358
  width: 40, height: 40, borderRadius: '50%',
253
359
  border: `3px solid ${primaryColor}30`,
254
360
  borderTopColor: primaryColor,
255
361
  animation: 'spin 0.8s linear infinite',
256
- } }), _jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }), _jsx("p", { style: { fontSize: 14, color: '#7b8fa1' }, children: "Loading chat\u2026" })] })), cfgError && !cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, padding: 32, textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\u26A0\uFE0F" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Could not load chat configuration" }), _jsx("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6 }, children: cfgError }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), !cfgLoading && !cfgError && widgetConfig && (_jsxs(_Fragment, { children: [screen !== 'chat' && screen !== 'call' && (_jsxs("div", { style: {
362
+ } }), _jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }), _jsx("p", { style: { fontSize: 14, color: '#7b8fa1' }, children: "Loading chat\u2026" })] })), cfgError && !cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, padding: 32, textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\u26A0\uFE0F" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Could not load chat configuration" }), _jsx("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6 }, children: cfgError }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), !cfgLoading && !cfgError && widgetConfig && (_jsxs(_Fragment, { children: [screen !== 'chat' && screen !== 'call' && (_jsx("div", { style: {
257
363
  position: 'absolute', top: 12,
258
364
  right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
259
365
  left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
260
366
  zIndex: 20, display: 'flex', gap: 6,
261
- }, children: [_jsx(CornerBtn, { onClick: () => setIsMaximized(m => !m), title: isMaximized ? 'Minimize' : 'Maximize', children: isMaximized
262
- ? _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3v5H3M21 8h-5V3M3 16h5v5M16 21v-5h5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) })
263
- : _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }) }), _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })] })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_d = widgetConfig.viewerType) !== null && _d !== void 0 ? _d : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: () => { clearChat(); setScreen('home'); setActiveTab('home'); }, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onRaiseTicket: handleRaiseTicket })), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
367
+ }, children: _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) }) })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)) })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => setScreen('ticket-new'), onSelectTicket: id => { setViewingTicketId(id); setScreen('ticket-detail'); } })), screen === 'ticket-new' && (_jsx(TicketFormScreen, { config: widgetConfig, onSubmit: handleRaiseTicket, onCancel: () => setScreen('tickets') })), screen === 'ticket-detail' && viewingTicketId && ((() => {
368
+ const t = tickets.find(x => x.id === viewingTicketId);
369
+ return t ? (_jsx(TicketDetailScreen, { ticket: t, config: widgetConfig, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
370
+ })()), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
264
371
  screen !== 'chat' &&
265
372
  screen !== 'call' &&
266
373
  screen !== 'user-list' &&
267
- screen !== 'block-list' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor, onBlockList: () => setScreen('block-list') }))] }))] }))] }));
374
+ screen !== 'block-list' &&
375
+ screen !== 'ticket-detail' &&
376
+ screen !== 'ticket-new' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor }))] }))] }))] }));
268
377
  };
269
378
  export default ChatWidget;
270
379
  /* ── Tiny corner button ────────────────────────────────────────────────────── */
@@ -3,6 +3,8 @@ import { WidgetConfig, UserListContext, Ticket } from '../../types';
3
3
  interface HomeScreenProps {
4
4
  config: WidgetConfig;
5
5
  onNavigate: (ctx: UserListContext | 'ticket') => void;
6
+ /** Open a specific pending ticket (full detail) */
7
+ onOpenTicket: (ticketId: string) => void;
6
8
  tickets: Ticket[];
7
9
  }
8
10
  export declare const HomeScreen: React.FC<HomeScreenProps>;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useState, useMemo } from 'react';
3
3
  import { SlideNavMenu } from '../SlideNavMenu';
4
4
  import { truncateWords } from '../../utils/chat';
5
- export const HomeScreen = ({ config, onNavigate, tickets }) => {
5
+ export const HomeScreen = ({ config, onNavigate, onOpenTicket, tickets }) => {
6
6
  var _a, _b, _c, _d;
7
7
  const [menuOpen, setMenuOpen] = useState(false);
8
8
  const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
@@ -51,7 +51,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
51
51
  color: '#0f172a',
52
52
  letterSpacing: '-0.03em',
53
53
  lineHeight: 1.2,
54
- }, children: config.welcomeTitle }), _jsx("p", { style: { margin: '0 0 28px', fontSize: 14, color: '#64748b', lineHeight: 1.55 }, children: config.welcomeSubtitle }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Continue Conversations" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 }, children: pendingTickets.length > 0 ? (pendingTickets.map(t => (_jsxs("button", { type: "button", onClick: () => onNavigate('ticket'), style: {
54
+ }, children: config.welcomeTitle }), _jsx("p", { style: { margin: '0 0 28px', fontSize: 14, color: '#64748b', lineHeight: 1.55 }, children: config.welcomeSubtitle }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Continue Conversations" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 }, children: pendingTickets.length > 0 ? (pendingTickets.map(t => (_jsxs("button", { type: "button", onClick: () => onOpenTicket(t.id), style: {
55
55
  width: '100%',
56
56
  textAlign: 'left',
57
57
  padding: '14px 16px',
@@ -4,7 +4,6 @@ interface BottomTabsProps {
4
4
  active: BottomTab;
5
5
  onChange: (tab: BottomTab) => void;
6
6
  primaryColor: string;
7
- onBlockList: () => void;
8
7
  }
9
8
  export declare const BottomTabs: React.FC<BottomTabsProps>;
10
9
  export {};
@@ -1,33 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const BottomTabs = ({ active, onChange, primaryColor, onBlockList }) => {
2
+ export const BottomTabs = ({ active, onChange, primaryColor }) => {
3
3
  const tabs = [
4
4
  { key: 'home', label: 'Home', Icon: HomeIcon },
5
5
  { key: 'chats', label: 'Chats', Icon: ChatsIcon },
6
6
  { key: 'tickets', label: 'Tickets', Icon: TicketsIcon },
7
7
  ];
8
- return (_jsxs("div", { style: {
8
+ return (_jsx("div", { style: {
9
9
  display: 'flex', borderTop: '1px solid #eef0f5',
10
10
  backgroundColor: '#fff', flexShrink: 0,
11
- }, children: [tabs.map(tab => {
12
- const isActive = active === tab.key;
13
- return (_jsxs("button", { onClick: () => onChange(tab.key), style: {
14
- flex: 1, padding: '10px 0 8px',
15
- display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
16
- background: 'transparent', border: 'none', cursor: 'pointer',
17
- fontSize: '10px', fontWeight: isActive ? 700 : 500,
18
- color: isActive ? primaryColor : '#9aa3af',
19
- borderTop: isActive ? `2px solid ${primaryColor}` : '2px solid transparent',
20
- transition: 'color 0.15s',
21
- fontFamily: 'inherit',
22
- }, children: [_jsx(tab.Icon, { a: isActive, c: isActive ? primaryColor : '#b0bec5' }), tab.label] }, tab.key));
23
- }), _jsxs("button", { onClick: onBlockList, style: {
24
- padding: '10px 14px 8px',
11
+ }, children: tabs.map(tab => {
12
+ const isActive = active === tab.key;
13
+ return (_jsxs("button", { type: "button", onClick: () => onChange(tab.key), style: {
14
+ flex: 1, padding: '10px 0 8px',
25
15
  display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
26
16
  background: 'transparent', border: 'none', cursor: 'pointer',
27
- fontSize: '10px', fontWeight: 500, color: '#9aa3af',
28
- borderTop: '2px solid transparent',
29
- transition: 'color 0.15s', fontFamily: 'inherit',
30
- }, title: "Block List", children: [_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#b0bec5", strokeWidth: "1.8" }), _jsx("line", { x1: "4.93", y1: "4.93", x2: "19.07", y2: "19.07", stroke: "#b0bec5", strokeWidth: "1.8", strokeLinecap: "round" })] }), "Blocked"] })] }));
17
+ fontSize: '10px', fontWeight: isActive ? 700 : 500,
18
+ color: isActive ? primaryColor : '#9aa3af',
19
+ borderTop: isActive ? `2px solid ${primaryColor}` : '2px solid transparent',
20
+ transition: 'color 0.15s',
21
+ fontFamily: 'inherit',
22
+ }, children: [_jsx(tab.Icon, { a: isActive, c: isActive ? primaryColor : '#b0bec5' }), tab.label] }, tab.key));
23
+ }) }));
31
24
  };
32
25
  const HomeIcon = ({ a, c }) => (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z", stroke: c, strokeWidth: a ? 2.2 : 1.8, fill: a ? `${c}20` : 'none', strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M9 21V12h6v9", stroke: c, strokeWidth: a ? 2.2 : 1.8, strokeLinecap: "round", strokeLinejoin: "round" })] }));
33
26
  const ChatsIcon = ({ a, c }) => (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z", stroke: c, strokeWidth: a ? 2.2 : 1.8, fill: a ? `${c}20` : 'none', strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { Ticket, WidgetConfig } from '../../types';
3
+ interface TicketDetailScreenProps {
4
+ ticket: Ticket;
5
+ config: WidgetConfig;
6
+ onBack: () => void;
7
+ }
8
+ export declare const TicketDetailScreen: React.FC<TicketDetailScreenProps>;
9
+ export {};
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const sm = {
3
+ open: { label: 'Open', bg: '', color: '' },
4
+ 'in-progress': { label: 'In Progress', bg: '#fef3c7', color: '#d97706' },
5
+ resolved: { label: 'Resolved', bg: '#f0fdf4', color: '#16a34a' },
6
+ closed: { label: 'Closed', bg: '#f3f4f6', color: '#6b7280' },
7
+ };
8
+ const pm = {
9
+ low: { label: 'Low', color: '#6b7280' },
10
+ medium: { label: 'Medium', color: '#d97706' },
11
+ high: { label: 'High', color: '#ef4444' },
12
+ };
13
+ export const TicketDetailScreen = ({ ticket, config, onBack }) => {
14
+ const st = sm[ticket.status];
15
+ const pr = pm[ticket.priority];
16
+ const stBg = ticket.status === 'open' ? `${config.primaryColor}14` : st.bg;
17
+ const stColor = ticket.status === 'open' ? config.primaryColor : st.color;
18
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: {
19
+ background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
20
+ padding: '14px 18px',
21
+ display: 'flex',
22
+ alignItems: 'center',
23
+ gap: 12,
24
+ flexShrink: 0,
25
+ }, children: [_jsx("button", { type: "button", onClick: onBack, style: {
26
+ background: 'rgba(255,255,255,0.22)',
27
+ border: 'none',
28
+ borderRadius: '50%',
29
+ width: 36,
30
+ height: 36,
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ justifyContent: 'center',
34
+ cursor: 'pointer',
35
+ flexShrink: 0,
36
+ }, children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 800, fontSize: 16, color: '#fff', lineHeight: 1.25 }, children: ticket.title }), _jsxs("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.85)', marginTop: 2 }, children: ["#", ticket.id] })] })] }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px' }, children: [_jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 16 }, children: [_jsx("span", { style: {
37
+ fontSize: 11,
38
+ fontWeight: 700,
39
+ padding: '5px 12px',
40
+ borderRadius: 20,
41
+ backgroundColor: stBg,
42
+ color: stColor,
43
+ textTransform: 'uppercase',
44
+ letterSpacing: '0.04em',
45
+ }, children: st.label }), _jsxs("span", { style: { fontSize: 11, fontWeight: 700, padding: '5px 12px', borderRadius: 20, color: pr.color, background: `${pr.color}15` }, children: ["\u25CF ", pr.label, " priority"] })] }), _jsx("h3", { style: { margin: '0 0 8px', fontSize: 13, fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.06em' }, children: "Description" }), _jsx("p", { style: { margin: '0 0 24px', fontSize: 15, color: '#1e293b', lineHeight: 1.65, whiteSpace: 'pre-wrap' }, children: ticket.description || '—' }), _jsxs("div", { style: { fontSize: 13, color: '#94a3b8', display: 'grid', gap: 8 }, children: [_jsxs("div", { children: [_jsx("strong", { style: { color: '#64748b' }, children: "Created" }), " \u00B7 ", new Date(ticket.createdAt).toLocaleString()] }), _jsxs("div", { children: [_jsx("strong", { style: { color: '#64748b' }, children: "Updated" }), " \u00B7 ", new Date(ticket.updatedAt).toLocaleString()] }), ticket.assignedTo && (_jsxs("div", { children: [_jsx("strong", { style: { color: '#64748b' }, children: "Assigned" }), " \u00B7 ", ticket.assignedTo] }))] })] })] }));
46
+ };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { Ticket, WidgetConfig } from '../../types';
3
+ interface TicketFormScreenProps {
4
+ config: WidgetConfig;
5
+ onSubmit: (title: string, desc: string, priority: Ticket['priority']) => void;
6
+ onCancel: () => void;
7
+ }
8
+ export declare const TicketFormScreen: React.FC<TicketFormScreenProps>;
9
+ export {};