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.
- package/dist/components/ChatScreen/index.d.ts +2 -0
- package/dist/components/ChatScreen/index.js +36 -9
- package/dist/components/ChatWidget.js +136 -27
- package/dist/components/HomeScreen/index.d.ts +2 -0
- package/dist/components/HomeScreen/index.js +2 -2
- package/dist/components/Tabs/BottomTabs.d.ts +0 -1
- package/dist/components/Tabs/BottomTabs.js +13 -20
- package/dist/components/TicketDetailScreen/index.d.ts +9 -0
- package/dist/components/TicketDetailScreen/index.js +46 -0
- package/dist/components/TicketFormScreen/index.d.ts +9 -0
- package/dist/components/TicketFormScreen/index.js +70 -0
- package/dist/components/TicketScreen/index.d.ts +2 -1
- package/dist/components/TicketScreen/index.js +8 -35
- package/dist/components/UserListScreen/index.d.ts +2 -0
- package/dist/components/UserListScreen/index.js +15 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/fileName.d.ts +2 -0
- package/dist/utils/fileName.js +7 -0
- package/dist/utils/messageSound.d.ts +4 -0
- package/dist/utils/messageSound.js +51 -0
- package/dist/utils/widgetSession.d.ts +13 -0
- package/dist/utils/widgetSession.js +24 -0
- package/package.json +1 -1
- package/src/components/ChatScreen/index.tsx +70 -16
- package/src/components/ChatWidget.tsx +163 -35
- package/src/components/HomeScreen/index.tsx +4 -2
- package/src/components/Tabs/BottomTabs.tsx +2 -22
- package/src/components/TicketDetailScreen/index.tsx +111 -0
- package/src/components/TicketFormScreen/index.tsx +140 -0
- package/src/components/TicketScreen/index.tsx +18 -58
- package/src/components/UserListScreen/index.tsx +32 -3
- package/src/types/index.ts +2 -0
- package/src/utils/fileName.ts +6 -0
- package/src/utils/messageSound.ts +47 -0
- 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 }),
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
|
|
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('
|
|
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 => [
|
|
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 = (
|
|
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
|
-
? {
|
|
214
|
-
|
|
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,
|
|
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'
|
|
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' && (
|
|
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:
|
|
262
|
-
|
|
263
|
-
|
|
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' &&
|
|
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: () =>
|
|
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',
|
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export const BottomTabs = ({ active, onChange, primaryColor
|
|
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 (
|
|
8
|
+
return (_jsx("div", { style: {
|
|
9
9
|
display: 'flex', borderTop: '1px solid #eef0f5',
|
|
10
10
|
backgroundColor: '#fff', flexShrink: 0,
|
|
11
|
-
}, children:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 {};
|