ajaxter-chat 3.0.8 → 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 +111 -15
- 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 +139 -17
- 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,22 +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
22
|
export const ChatWidget = ({ theme: localTheme }) => {
|
|
19
|
-
var _a, _b, _c, _d;
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
20
24
|
/* SSR guard */
|
|
21
25
|
const [mounted, setMounted] = useState(false);
|
|
22
26
|
useEffect(() => { setMounted(true); }, []);
|
|
@@ -33,6 +37,9 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
33
37
|
const [activeTab, setActiveTab] = useState('home');
|
|
34
38
|
const [screen, setScreen] = useState('home');
|
|
35
39
|
const [userListCtx, setUserListCtx] = useState('support');
|
|
40
|
+
const [chatReturnCtx, setChatReturnCtx] = useState('conversation');
|
|
41
|
+
const [viewingTicketId, setViewingTicketId] = useState(null);
|
|
42
|
+
const [messageSoundEnabled, setMessageSoundEnabledState] = useState(true);
|
|
36
43
|
/* App state */
|
|
37
44
|
const [tickets, setTickets] = useState((_a = data === null || data === void 0 ? void 0 : data.sampleTickets) !== null && _a !== void 0 ? _a : []);
|
|
38
45
|
const [recentChats, setRecentChats] = useState([]);
|
|
@@ -71,16 +78,83 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
71
78
|
setClosing(false);
|
|
72
79
|
setIsOpen(true);
|
|
73
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]);
|
|
74
96
|
const closeDrawer = useCallback(() => {
|
|
97
|
+
persistWidgetState();
|
|
75
98
|
setClosing(true);
|
|
76
99
|
setTimeout(() => {
|
|
77
100
|
setIsOpen(false);
|
|
78
101
|
setClosing(false);
|
|
79
|
-
setScreen('home');
|
|
80
|
-
setActiveTab('home');
|
|
81
|
-
clearChat();
|
|
82
102
|
}, 300);
|
|
83
|
-
}, [
|
|
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]);
|
|
84
158
|
/* ── Navigation ──────────────────────────────────────────────────────── */
|
|
85
159
|
const handleCardClick = useCallback((ctx) => {
|
|
86
160
|
if (ctx === 'ticket') {
|
|
@@ -103,20 +177,34 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
103
177
|
setScreen('user-list');
|
|
104
178
|
}
|
|
105
179
|
}, [clearChat]);
|
|
106
|
-
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) => {
|
|
107
186
|
var _a;
|
|
108
|
-
|
|
187
|
+
setChatReturnCtx(returnCtxOverride !== null && returnCtxOverride !== void 0 ? returnCtxOverride : userListCtx);
|
|
109
188
|
const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
|
|
110
189
|
selectUser(user, history);
|
|
111
190
|
setScreen('chat');
|
|
112
|
-
// Update recent chats
|
|
113
191
|
setRecentChats(prev => {
|
|
114
192
|
const exists = prev.find(r => r.user.uid === user.uid);
|
|
115
193
|
if (exists)
|
|
116
194
|
return prev;
|
|
117
195
|
return [{ id: `rc_${user.uid}`, user, lastMessage: '', lastTime: new Date().toISOString(), unread: 0, isPaused: false }, ...prev];
|
|
118
196
|
});
|
|
119
|
-
}, [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
|
+
}, []);
|
|
120
208
|
const handleTabChange = useCallback((tab) => {
|
|
121
209
|
setActiveTab(tab);
|
|
122
210
|
setScreen(tab === 'home' ? 'home' : tab === 'chats' ? 'recent-chats' : 'tickets');
|
|
@@ -127,7 +215,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
127
215
|
return;
|
|
128
216
|
setBlockedUids(prev => [...prev, activeUser.uid]);
|
|
129
217
|
clearChat();
|
|
130
|
-
setScreen('
|
|
218
|
+
setScreen('block-list');
|
|
131
219
|
setActiveTab('home');
|
|
132
220
|
}, [activeUser, clearChat]);
|
|
133
221
|
const handleUnblock = useCallback((uid) => {
|
|
@@ -142,7 +230,10 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
142
230
|
updatedAt: new Date().toISOString(),
|
|
143
231
|
assignedTo: null,
|
|
144
232
|
};
|
|
145
|
-
setTickets(prev => [
|
|
233
|
+
setTickets(prev => [...prev, t]);
|
|
234
|
+
setViewingTicketId(t.id);
|
|
235
|
+
setScreen('ticket-detail');
|
|
236
|
+
setActiveTab('tickets');
|
|
146
237
|
}, []);
|
|
147
238
|
/* ── Pause sync back into recent chats ──────────────────────────────── */
|
|
148
239
|
const handleTogglePause = useCallback(() => {
|
|
@@ -182,7 +273,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
182
273
|
return u.type === 'user';
|
|
183
274
|
})
|
|
184
275
|
: [];
|
|
185
|
-
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 : [];
|
|
186
277
|
const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
|
|
187
278
|
const handleTransferToDeveloper = useCallback((dev) => {
|
|
188
279
|
var _a;
|
|
@@ -273,11 +364,16 @@ export const ChatWidget = ({ theme: localTheme }) => {
|
|
|
273
364
|
right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
|
|
274
365
|
left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
|
|
275
366
|
zIndex: 20, display: 'flex', gap: 6,
|
|
276
|
-
}, 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, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (
|
|
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' &&
|
|
277
371
|
screen !== 'chat' &&
|
|
278
372
|
screen !== 'call' &&
|
|
279
373
|
screen !== 'user-list' &&
|
|
280
|
-
screen !== 'block-list' &&
|
|
374
|
+
screen !== 'block-list' &&
|
|
375
|
+
screen !== 'ticket-detail' &&
|
|
376
|
+
screen !== 'ticket-new' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor }))] }))] }))] }));
|
|
281
377
|
};
|
|
282
378
|
export default ChatWidget;
|
|
283
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 {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
function inputStyle(primaryColor) {
|
|
4
|
+
return {
|
|
5
|
+
width: '100%',
|
|
6
|
+
padding: '11px 14px',
|
|
7
|
+
borderRadius: 10,
|
|
8
|
+
border: '1.5px solid #e5e7eb',
|
|
9
|
+
outline: 'none',
|
|
10
|
+
fontSize: 14,
|
|
11
|
+
color: '#1a2332',
|
|
12
|
+
boxSizing: 'border-box',
|
|
13
|
+
fontFamily: 'inherit',
|
|
14
|
+
transition: 'border-color 0.2s',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const TicketFormScreen = ({ config, onSubmit, onCancel }) => {
|
|
18
|
+
const [title, setTitle] = useState('');
|
|
19
|
+
const [desc, setDesc] = useState('');
|
|
20
|
+
const [priority, setPriority] = useState('medium');
|
|
21
|
+
const pm = {
|
|
22
|
+
low: { label: 'Low', color: '#6b7280' },
|
|
23
|
+
medium: { label: 'Medium', color: '#d97706' },
|
|
24
|
+
high: { label: 'High', color: '#ef4444' },
|
|
25
|
+
};
|
|
26
|
+
const handleSubmit = () => {
|
|
27
|
+
if (!title.trim())
|
|
28
|
+
return;
|
|
29
|
+
onSubmit(title.trim(), desc.trim(), priority);
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#fff' }, children: [_jsxs("div", { style: {
|
|
32
|
+
background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
|
|
33
|
+
padding: '14px 18px',
|
|
34
|
+
display: 'flex',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
gap: 12,
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
}, children: [_jsx("button", { type: "button", onClick: onCancel, style: {
|
|
39
|
+
background: 'rgba(255,255,255,0.22)',
|
|
40
|
+
border: 'none',
|
|
41
|
+
borderRadius: '50%',
|
|
42
|
+
width: 36,
|
|
43
|
+
height: 36,
|
|
44
|
+
display: 'flex',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
justifyContent: 'center',
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
}, 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", { children: [_jsx("h2", { style: { margin: 0, fontSize: 18, fontWeight: 800, color: '#fff' }, children: "New ticket" }), _jsx("p", { style: { margin: '4px 0 0', fontSize: 12, color: 'rgba(255,255,255,0.85)' }, children: "Describe your issue below" })] })] }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px' }, children: [_jsx("input", { placeholder: "Title *", value: title, onChange: e => setTitle(e.target.value), style: inputStyle(config.primaryColor), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("textarea", { placeholder: "Describe the issue\u2026", value: desc, onChange: e => setDesc(e.target.value), rows: 5, style: Object.assign(Object.assign({}, inputStyle(config.primaryColor)), { resize: 'none', marginTop: 12 }), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("div", { style: { display: 'flex', gap: 8, marginTop: 12, marginBottom: 20 }, children: ['low', 'medium', 'high'].map(p => (_jsx("button", { type: "button", onClick: () => setPriority(p), style: {
|
|
49
|
+
flex: 1,
|
|
50
|
+
padding: '8px',
|
|
51
|
+
border: `1.5px solid ${priority === p ? pm[p].color : '#e5e7eb'}`,
|
|
52
|
+
borderRadius: 8,
|
|
53
|
+
background: priority === p ? `${pm[p].color}15` : '#fff',
|
|
54
|
+
color: pm[p].color,
|
|
55
|
+
fontWeight: 700,
|
|
56
|
+
fontSize: 12,
|
|
57
|
+
cursor: 'pointer',
|
|
58
|
+
textTransform: 'capitalize',
|
|
59
|
+
}, children: pm[p].label }, p))) }), _jsx("button", { type: "button", onClick: handleSubmit, disabled: !title.trim(), style: {
|
|
60
|
+
width: '100%',
|
|
61
|
+
padding: '12px',
|
|
62
|
+
borderRadius: 10,
|
|
63
|
+
border: 'none',
|
|
64
|
+
background: title.trim() ? config.primaryColor : '#e5e7eb',
|
|
65
|
+
color: title.trim() ? '#fff' : '#9ca3af',
|
|
66
|
+
fontWeight: 700,
|
|
67
|
+
fontSize: 15,
|
|
68
|
+
cursor: title.trim() ? 'pointer' : 'not-allowed',
|
|
69
|
+
}, children: "Submit ticket" })] })] }));
|
|
70
|
+
};
|
|
@@ -3,7 +3,8 @@ import { Ticket, WidgetConfig } from '../../types';
|
|
|
3
3
|
interface TicketScreenProps {
|
|
4
4
|
tickets: Ticket[];
|
|
5
5
|
config: WidgetConfig;
|
|
6
|
-
|
|
6
|
+
onNewTicket: () => void;
|
|
7
|
+
onSelectTicket: (id: string) => void;
|
|
7
8
|
}
|
|
8
9
|
export declare const TicketScreen: React.FC<TicketScreenProps>;
|
|
9
10
|
export {};
|