ajaxter-chat 3.0.9 → 3.0.11
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.js +256 -34
- package/dist/components/ChatWidget.js +21 -3
- package/dist/components/HomeScreen/index.d.ts +5 -1
- package/dist/components/HomeScreen/index.js +6 -4
- package/dist/components/RecentChatsScreen/index.d.ts +1 -0
- package/dist/components/RecentChatsScreen/index.js +28 -6
- package/dist/components/TicketFormScreen/index.js +19 -13
- package/dist/components/TicketScreen/index.d.ts +1 -0
- package/dist/components/TicketScreen/index.js +31 -9
- package/dist/components/UserListScreen/index.d.ts +4 -0
- package/dist/components/UserListScreen/index.js +40 -12
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ChatScreen/index.tsx +365 -62
- package/src/components/ChatWidget.tsx +28 -6
- package/src/components/HomeScreen/index.tsx +12 -5
- package/src/components/RecentChatsScreen/index.tsx +97 -44
- package/src/components/TicketFormScreen/index.tsx +17 -6
- package/src/components/TicketScreen/index.tsx +63 -11
- package/src/components/UserListScreen/index.tsx +87 -15
- package/src/types/index.ts +2 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
|
2
2
|
import { ChatUser, UserListContext } from '../../types';
|
|
3
3
|
import { avatarColor, initials } from '../../utils/chat';
|
|
4
4
|
|
|
@@ -12,11 +12,37 @@ interface UserListScreenProps {
|
|
|
12
12
|
onSelectUser: (user: ChatUser) => void;
|
|
13
13
|
/** Shown on “New Conversation” list — opens block list */
|
|
14
14
|
onBlockList?: () => void;
|
|
15
|
+
/** “Need Support” (user → agents): show home icon instead of back arrow */
|
|
16
|
+
useHomeHeader?: boolean;
|
|
17
|
+
/** Stagger animation — only when opening from home burger menu */
|
|
18
|
+
animateEntrance?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function matchesUser(u: ChatUser, q: string): boolean {
|
|
22
|
+
if (!q.trim()) return true;
|
|
23
|
+
const s = q.trim().toLowerCase();
|
|
24
|
+
return (
|
|
25
|
+
u.name.toLowerCase().includes(s) ||
|
|
26
|
+
u.email.toLowerCase().includes(s) ||
|
|
27
|
+
u.designation.toLowerCase().includes(s) ||
|
|
28
|
+
u.project.toLowerCase().includes(s)
|
|
29
|
+
);
|
|
15
30
|
}
|
|
16
31
|
|
|
17
32
|
export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
18
33
|
context, users, primaryColor, viewerType = 'user', onBack, onSelectUser, onBlockList,
|
|
34
|
+
useHomeHeader = false,
|
|
35
|
+
animateEntrance = false,
|
|
19
36
|
}) => {
|
|
37
|
+
const [query, setQuery] = useState('');
|
|
38
|
+
const searchRef = useRef<HTMLInputElement>(null);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
searchRef.current?.focus();
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const filtered = useMemo(() => users.filter(u => matchesUser(u, query)), [users, query]);
|
|
45
|
+
|
|
20
46
|
const isStaff = viewerType === 'developer';
|
|
21
47
|
const title = context === 'support'
|
|
22
48
|
? (isStaff ? 'Provide Support' : 'Need Support')
|
|
@@ -25,11 +51,13 @@ export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
25
51
|
? (isStaff ? 'All chat users — choose who to help' : 'Choose a support agent')
|
|
26
52
|
: (isStaff ? 'Chat with another developer or coordinate handoff' : 'Choose a colleague');
|
|
27
53
|
|
|
54
|
+
const rootAnim = animateEntrance ? 'cw-slideIn 0.22s ease' : undefined;
|
|
55
|
+
|
|
28
56
|
return (
|
|
29
|
-
<div style={{ display:'flex', flexDirection:'column', height:'100%', animation:
|
|
57
|
+
<div style={{ display:'flex', flexDirection:'column', height:'100%', animation: rootAnim }}>
|
|
30
58
|
{/* Header */}
|
|
31
59
|
<div style={{ background:`linear-gradient(135deg,${primaryColor},${primaryColor}cc)`, padding:'14px 18px', display:'flex', alignItems:'center', gap:12, flexShrink:0, position:'relative' }}>
|
|
32
|
-
<BackBtn onClick={onBack} />
|
|
60
|
+
{useHomeHeader ? <HomeBtn onClick={onBack} /> : <BackBtn onClick={onBack} />}
|
|
33
61
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
34
62
|
<div style={{ fontWeight:700, fontSize:16, color:'#fff' }}>{title}</div>
|
|
35
63
|
<div style={{ fontSize:12, color:'rgba(255,255,255,0.8)' }}>{subtitle}</div>
|
|
@@ -63,11 +91,42 @@ export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
63
91
|
)}
|
|
64
92
|
</div>
|
|
65
93
|
|
|
94
|
+
<div style={{ padding: '10px 14px', background: '#fff', borderBottom: '1px solid #eef0f5', flexShrink: 0 }}>
|
|
95
|
+
<label style={{ display: 'block', margin: 0 }}>
|
|
96
|
+
<span style={{ position: 'absolute', width: 1, height: 1, padding: 0, overflow: 'hidden', clip: 'rect(0,0,0,0)' }}>Search</span>
|
|
97
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '0 12px', borderRadius: 10, border: '1.5px solid #e5e7eb', background: '#f8fafc' }}>
|
|
98
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0, opacity: 0.55 }}>
|
|
99
|
+
<circle cx="11" cy="11" r="7" stroke="#64748b" strokeWidth="2" />
|
|
100
|
+
<path d="M20 20l-4-4" stroke="#64748b" strokeWidth="2" strokeLinecap="round" />
|
|
101
|
+
</svg>
|
|
102
|
+
<input
|
|
103
|
+
ref={searchRef}
|
|
104
|
+
type="search"
|
|
105
|
+
value={query}
|
|
106
|
+
onChange={e => setQuery(e.target.value)}
|
|
107
|
+
placeholder="Search by name…"
|
|
108
|
+
autoComplete="off"
|
|
109
|
+
style={{
|
|
110
|
+
flex: 1,
|
|
111
|
+
minWidth: 0,
|
|
112
|
+
border: 'none',
|
|
113
|
+
outline: 'none',
|
|
114
|
+
background: 'transparent',
|
|
115
|
+
fontSize: 14,
|
|
116
|
+
padding: '10px 0',
|
|
117
|
+
fontFamily: 'inherit',
|
|
118
|
+
color: '#1a2332',
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
</label>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
66
125
|
{/* User list */}
|
|
67
126
|
<div style={{ flex:1, overflowY:'auto' }}>
|
|
68
|
-
{
|
|
69
|
-
<Empty />
|
|
70
|
-
) :
|
|
127
|
+
{filtered.length === 0 ? (
|
|
128
|
+
<Empty hasQuery={!!query.trim()} />
|
|
129
|
+
) : filtered.map((u, i) => (
|
|
71
130
|
<button
|
|
72
131
|
key={u.uid}
|
|
73
132
|
onClick={() => onSelectUser(u)}
|
|
@@ -76,13 +135,14 @@ export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
76
135
|
alignItems:'center', gap:13, background:'transparent',
|
|
77
136
|
border:'none', borderBottom:'1px solid #f0f2f5',
|
|
78
137
|
cursor:'pointer', textAlign:'left',
|
|
79
|
-
|
|
138
|
+
...(animateEntrance
|
|
139
|
+
? { animation: `cw-fadeUp 0.28s ease both`, animationDelay: `${i * 0.05}s` }
|
|
140
|
+
: {}),
|
|
80
141
|
transition:'background 0.14s',
|
|
81
142
|
}}
|
|
82
143
|
onMouseEnter={e => (e.currentTarget as HTMLElement).style.background = '#f8faff'}
|
|
83
144
|
onMouseLeave={e => (e.currentTarget as HTMLElement).style.background = 'transparent'}
|
|
84
145
|
>
|
|
85
|
-
{/* Avatar with online dot */}
|
|
86
146
|
<div style={{ position:'relative', flexShrink:0 }}>
|
|
87
147
|
<div style={{
|
|
88
148
|
width:44, height:44, borderRadius:'50%',
|
|
@@ -96,14 +156,12 @@ export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
96
156
|
backgroundColor: u.status==='online' ? '#22c55e' : u.status==='away' ? '#f59e0b' : '#d1d5db',
|
|
97
157
|
}} />
|
|
98
158
|
</div>
|
|
99
|
-
{/* Info */}
|
|
100
159
|
<div style={{ flex:1, minWidth:0 }}>
|
|
101
160
|
<div style={{ fontWeight:700, fontSize:14, color:'#1a2332', marginBottom:2 }}>{u.name}</div>
|
|
102
161
|
<div style={{ fontSize:12, color:'#7b8fa1', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
|
103
162
|
{u.designation} · {u.project}
|
|
104
163
|
</div>
|
|
105
164
|
</div>
|
|
106
|
-
{/* Type badge */}
|
|
107
165
|
<span style={{
|
|
108
166
|
fontSize:10, fontWeight:700, padding:'3px 9px', borderRadius:20,
|
|
109
167
|
textTransform:'uppercase', letterSpacing:'0.05em', flexShrink:0,
|
|
@@ -119,7 +177,7 @@ export const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
119
177
|
};
|
|
120
178
|
|
|
121
179
|
const BackBtn: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
|
122
|
-
<button onClick={onClick} style={{
|
|
180
|
+
<button type="button" onClick={onClick} style={{
|
|
123
181
|
background:'rgba(255,255,255,0.22)', border:'none', borderRadius:'50%',
|
|
124
182
|
width:32, height:32, display:'flex', alignItems:'center', justifyContent:'center',
|
|
125
183
|
cursor:'pointer', flexShrink:0,
|
|
@@ -130,10 +188,24 @@ const BackBtn: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
|
|
130
188
|
</button>
|
|
131
189
|
);
|
|
132
190
|
|
|
133
|
-
const
|
|
191
|
+
const HomeBtn: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
|
192
|
+
<button type="button" onClick={onClick} title="Home" aria-label="Home" style={{
|
|
193
|
+
background:'rgba(255,255,255,0.22)', border:'none', borderRadius:'50%',
|
|
194
|
+
width:32, height:32, display:'flex', alignItems:'center', justifyContent:'center',
|
|
195
|
+
cursor:'pointer', flexShrink:0,
|
|
196
|
+
}}>
|
|
197
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
198
|
+
<path d="M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z"
|
|
199
|
+
stroke="#fff" strokeWidth="2.2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
200
|
+
<path d="M9 21V12h6v9" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
201
|
+
</svg>
|
|
202
|
+
</button>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const Empty: React.FC<{ hasQuery: boolean }> = ({ hasQuery }) => (
|
|
134
206
|
<div style={{ padding:'50px 24px', textAlign:'center' }}>
|
|
135
|
-
<div style={{ fontSize:36, marginBottom:10 }}
|
|
136
|
-
<div style={{ fontWeight:700, color:'#1a2332', marginBottom:6 }}>No users available</div>
|
|
137
|
-
<div style={{ fontSize:13, color:'#7b8fa1' }}>Check back later</div>
|
|
207
|
+
<div style={{ fontSize:36, marginBottom:10 }}>{hasQuery ? '🔍' : '👥'}</div>
|
|
208
|
+
<div style={{ fontWeight:700, color:'#1a2332', marginBottom:6 }}>{hasQuery ? 'No matches' : 'No users available'}</div>
|
|
209
|
+
<div style={{ fontSize:13, color:'#7b8fa1' }}>{hasQuery ? 'Try a different search' : 'Check back later'}</div>
|
|
138
210
|
</div>
|
|
139
211
|
);
|
package/src/types/index.ts
CHANGED
|
@@ -98,6 +98,8 @@ export interface ChatMessage {
|
|
|
98
98
|
attachmentSize?: string;
|
|
99
99
|
/** Blob URL for attachment download (local send) */
|
|
100
100
|
attachmentUrl?: string;
|
|
101
|
+
/** e.g. image/png — used for inline image preview in bubbles */
|
|
102
|
+
attachmentMime?: string;
|
|
101
103
|
voiceDuration?: number; // seconds
|
|
102
104
|
/** Blob URL for in-bubble audio playback (local recording) */
|
|
103
105
|
voiceUrl?: string;
|