ajaxter-chat 2.0.1 → 3.0.1
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/README.md +119 -128
- package/dist/components/BlockList/index.d.ts +10 -0
- package/dist/components/BlockList/index.js +33 -0
- package/dist/components/CallScreen/index.d.ts +13 -0
- package/dist/components/CallScreen/index.js +48 -0
- package/dist/components/ChatScreen/index.d.ts +10 -3
- package/dist/components/ChatScreen/index.js +142 -57
- package/dist/components/ChatWidget.js +192 -98
- package/dist/components/EmojiPicker/index.d.ts +8 -0
- package/dist/components/EmojiPicker/index.js +18 -0
- package/dist/components/HomeScreen/index.d.ts +2 -3
- package/dist/components/HomeScreen/index.js +25 -41
- package/dist/components/MaintenanceView/index.d.ts +0 -1
- package/dist/components/MaintenanceView/index.js +4 -6
- package/dist/components/RecentChatsScreen/index.d.ts +4 -3
- package/dist/components/RecentChatsScreen/index.js +7 -37
- package/dist/components/Tabs/BottomTabs.d.ts +1 -1
- package/dist/components/Tabs/BottomTabs.js +25 -20
- package/dist/components/TicketScreen/index.d.ts +3 -3
- package/dist/components/TicketScreen/index.js +39 -56
- package/dist/components/UserListScreen/index.d.ts +2 -4
- package/dist/components/UserListScreen/index.js +33 -62
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +18 -26
- package/dist/hooks/useChat.d.ts +8 -3
- package/dist/hooks/useChat.js +22 -18
- package/dist/hooks/useRemoteConfig.d.ts +6 -0
- package/dist/hooks/useRemoteConfig.js +22 -0
- package/dist/hooks/useWebRTC.d.ts +11 -0
- package/dist/hooks/useWebRTC.js +112 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +8 -4
- package/dist/types/index.d.ts +62 -21
- package/dist/utils/chat.d.ts +13 -0
- package/dist/utils/chat.js +62 -0
- package/dist/utils/theme.d.ts +3 -1
- package/dist/utils/theme.js +14 -7
- package/package.json +4 -4
- package/public/chatData.json +162 -0
- package/src/components/BlockList/index.tsx +94 -0
- package/src/components/CallScreen/index.tsx +144 -0
- package/src/components/ChatScreen/index.tsx +403 -139
- package/src/components/ChatWidget.tsx +394 -250
- package/src/components/EmojiPicker/index.tsx +48 -0
- package/src/components/HomeScreen/index.tsx +58 -82
- package/src/components/MaintenanceView/index.tsx +6 -9
- package/src/components/RecentChatsScreen/index.tsx +51 -96
- package/src/components/Tabs/BottomTabs.tsx +45 -37
- package/src/components/TicketScreen/index.tsx +87 -133
- package/src/components/UserListScreen/index.tsx +75 -153
- package/src/config/index.ts +22 -28
- package/src/hooks/useChat.ts +31 -14
- package/src/hooks/useRemoteConfig.ts +20 -0
- package/src/hooks/useWebRTC.ts +130 -0
- package/src/index.ts +26 -15
- package/src/types/index.ts +85 -40
- package/src/utils/chat.ts +70 -0
- package/src/utils/theme.ts +18 -7
- package/dist/hooks/useUsers.d.ts +0 -7
- package/dist/hooks/useUsers.js +0 -26
- package/dist/services/userService.d.ts +0 -2
- package/dist/services/userService.js +0 -9
- package/dist/src/components/ChatScreen/index.d.ts +0 -12
- package/dist/src/components/ChatScreen/index.js +0 -83
- package/dist/src/components/ChatWidget.d.ts +0 -4
- package/dist/src/components/ChatWidget.js +0 -141
- package/dist/src/components/HomeScreen/index.d.ts +0 -9
- package/dist/src/components/HomeScreen/index.js +0 -71
- package/dist/src/components/MaintenanceView/index.d.ts +0 -7
- package/dist/src/components/MaintenanceView/index.js +0 -16
- package/dist/src/components/RecentChatsScreen/index.d.ts +0 -16
- package/dist/src/components/RecentChatsScreen/index.js +0 -38
- package/dist/src/components/Tabs/BottomTabs.d.ts +0 -10
- package/dist/src/components/Tabs/BottomTabs.js +0 -29
- package/dist/src/components/TicketScreen/index.d.ts +0 -9
- package/dist/src/components/TicketScreen/index.js +0 -71
- package/dist/src/components/UserListScreen/index.d.ts +0 -13
- package/dist/src/components/UserListScreen/index.js +0 -64
- package/dist/src/config/index.d.ts +0 -3
- package/dist/src/config/index.js +0 -38
- package/dist/src/hooks/useChat.d.ts +0 -8
- package/dist/src/hooks/useChat.js +0 -26
- package/dist/src/hooks/useUsers.d.ts +0 -7
- package/dist/src/hooks/useUsers.js +0 -26
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -13
- package/dist/src/services/userService.d.ts +0 -2
- package/dist/src/services/userService.js +0 -9
- package/dist/src/types/index.d.ts +0 -59
- package/dist/src/types/index.js +0 -1
- package/dist/src/utils/theme.d.ts +0 -3
- package/dist/src/utils/theme.js +0 -13
- package/src/hooks/useUsers.ts +0 -27
- package/src/services/userService.ts +0 -9
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const EMOJIS = [
|
|
4
|
+
'😀','😂','😊','😍','🤔','😎','😢','😡',
|
|
5
|
+
'👍','👎','👏','🙏','🎉','❤️','🔥','✅',
|
|
6
|
+
'🚀','💡','⚠️','🎫',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
interface EmojiPickerProps {
|
|
10
|
+
onSelect: (emoji: string) => void;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
primaryColor: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const EmojiPicker: React.FC<EmojiPickerProps> = ({ onSelect, onClose, primaryColor }) => (
|
|
16
|
+
<div style={{
|
|
17
|
+
position:'absolute', bottom:'100%', right:0,
|
|
18
|
+
background:'#fff', borderRadius:14,
|
|
19
|
+
boxShadow:'0 8px 32px rgba(0,0,0,0.18)',
|
|
20
|
+
padding:'12px', zIndex:100,
|
|
21
|
+
animation:'cw-fadeUp 0.18s ease',
|
|
22
|
+
marginBottom:8,
|
|
23
|
+
}}>
|
|
24
|
+
{/* Header */}
|
|
25
|
+
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8 }}>
|
|
26
|
+
<span style={{ fontSize:11, fontWeight:700, color:'#7b8fa1', textTransform:'uppercase', letterSpacing:'0.06em' }}>
|
|
27
|
+
Emojis
|
|
28
|
+
</span>
|
|
29
|
+
<button onClick={onClose} style={{ background:'none', border:'none', cursor:'pointer', padding:2, color:'#7b8fa1', fontSize:14 }}>✕</button>
|
|
30
|
+
</div>
|
|
31
|
+
{/* Grid */}
|
|
32
|
+
<div style={{ display:'grid', gridTemplateColumns:'repeat(5, 1fr)', gap:4, width:200 }}>
|
|
33
|
+
{EMOJIS.map(e => (
|
|
34
|
+
<button
|
|
35
|
+
key={e}
|
|
36
|
+
onClick={() => { onSelect(e); onClose(); }}
|
|
37
|
+
style={{
|
|
38
|
+
background:'none', border:'none', cursor:'pointer',
|
|
39
|
+
fontSize:22, padding:'6px', borderRadius:8,
|
|
40
|
+
transition:'background 0.12s',
|
|
41
|
+
}}
|
|
42
|
+
onMouseEnter={el => (el.currentTarget as HTMLElement).style.background = `${primaryColor}15`}
|
|
43
|
+
onMouseLeave={el => (el.currentTarget as HTMLElement).style.background = 'none'}
|
|
44
|
+
>{e}</button>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
@@ -1,118 +1,97 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { mergeTheme } from '../../utils/theme';
|
|
2
|
+
import { WidgetConfig, UserListContext } from '../../types';
|
|
4
3
|
|
|
5
4
|
interface HomeScreenProps {
|
|
6
|
-
config:
|
|
7
|
-
theme?: ChatWidgetTheme;
|
|
5
|
+
config: WidgetConfig;
|
|
8
6
|
onNavigate: (ctx: UserListContext | 'ticket') => void;
|
|
9
7
|
}
|
|
10
8
|
|
|
11
|
-
export const HomeScreen: React.FC<HomeScreenProps> = ({ config,
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const showConversation = config.chatType === 'CHAT' || config.chatType === 'BOTH';
|
|
9
|
+
export const HomeScreen: React.FC<HomeScreenProps> = ({ config, onNavigate }) => {
|
|
10
|
+
const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
|
|
11
|
+
const showChat = config.chatType === 'CHAT' || config.chatType === 'BOTH';
|
|
15
12
|
|
|
16
13
|
const cards = [
|
|
17
14
|
showSupport && {
|
|
18
|
-
key:
|
|
19
|
-
|
|
15
|
+
key: 'support' as UserListContext,
|
|
16
|
+
icon: '🛠',
|
|
17
|
+
title: 'Need Support',
|
|
20
18
|
subtitle: 'We typically reply in a few minutes',
|
|
21
|
-
onClick:
|
|
19
|
+
onClick: () => onNavigate('support'),
|
|
22
20
|
},
|
|
23
|
-
|
|
24
|
-
key:
|
|
25
|
-
|
|
21
|
+
showChat && {
|
|
22
|
+
key: 'conversation' as UserListContext,
|
|
23
|
+
icon: '💬',
|
|
24
|
+
title: 'New Conversation',
|
|
26
25
|
subtitle: 'With your colleague',
|
|
27
|
-
onClick:
|
|
26
|
+
onClick: () => onNavigate('conversation'),
|
|
28
27
|
},
|
|
29
|
-
// Raise Ticket is always shown
|
|
30
28
|
{
|
|
31
|
-
key:
|
|
32
|
-
|
|
29
|
+
key: 'ticket',
|
|
30
|
+
icon: '🎫',
|
|
31
|
+
title: 'Raise Ticket',
|
|
33
32
|
subtitle: 'For major changes',
|
|
34
|
-
onClick:
|
|
33
|
+
onClick: () => onNavigate('ticket'),
|
|
35
34
|
},
|
|
36
|
-
].filter(Boolean) as Array<{ key: string; title: string; subtitle: string; onClick: () => void }>;
|
|
35
|
+
].filter(Boolean) as Array<{ key: string; icon: string; title: string; subtitle: string; onClick: () => void }>;
|
|
37
36
|
|
|
38
37
|
return (
|
|
39
38
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
40
|
-
{/*
|
|
41
|
-
<div
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
color: '#fff',
|
|
54
|
-
letterSpacing: '-0.03em',
|
|
55
|
-
fontFamily: t.fontFamily,
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
Hi there 👋
|
|
39
|
+
{/* Hero */}
|
|
40
|
+
<div style={{
|
|
41
|
+
background: `linear-gradient(145deg, ${config.primaryColor}, ${config.primaryColor}dd)`,
|
|
42
|
+
padding: '36px 24px 52px',
|
|
43
|
+
flexShrink: 0,
|
|
44
|
+
position: 'relative',
|
|
45
|
+
overflow: 'hidden',
|
|
46
|
+
}}>
|
|
47
|
+
{/* Decorative circles */}
|
|
48
|
+
<div style={{ position:'absolute', top:-40, right:-40, width:140, height:140, borderRadius:'50%', background:'rgba(255,255,255,0.07)' }} />
|
|
49
|
+
<div style={{ position:'absolute', bottom:-20, left:-20, width:90, height:90, borderRadius:'50%', background:'rgba(255,255,255,0.05)' }} />
|
|
50
|
+
<h1 style={{ margin:'0 0 8px', fontSize:26, fontWeight:800, color:'#fff', letterSpacing:'-0.03em' }}>
|
|
51
|
+
{config.welcomeTitle}
|
|
59
52
|
</h1>
|
|
60
|
-
<p style={{ margin:
|
|
61
|
-
|
|
53
|
+
<p style={{ margin:0, fontSize:14, color:'rgba(255,255,255,0.85)', lineHeight:1.6 }}>
|
|
54
|
+
{config.welcomeSubtitle}
|
|
62
55
|
</p>
|
|
63
56
|
</div>
|
|
64
57
|
|
|
65
|
-
{/* Cards
|
|
66
|
-
<div
|
|
67
|
-
style={{
|
|
68
|
-
flex: 1,
|
|
69
|
-
overflowY: 'auto',
|
|
70
|
-
padding: '0 16px 16px',
|
|
71
|
-
marginTop: '-24px',
|
|
72
|
-
display: 'flex',
|
|
73
|
-
flexDirection: 'column',
|
|
74
|
-
gap: '10px',
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
58
|
+
{/* Cards float over hero */}
|
|
59
|
+
<div style={{ flex:1, overflowY:'auto', padding:'0 16px 20px', marginTop:-28, display:'flex', flexDirection:'column', gap:10 }}>
|
|
77
60
|
{cards.map((card, i) => (
|
|
78
61
|
<button
|
|
79
62
|
key={card.key}
|
|
80
63
|
onClick={card.onClick}
|
|
81
64
|
style={{
|
|
82
|
-
width:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
justifyContent: 'space-between',
|
|
90
|
-
cursor: 'pointer',
|
|
91
|
-
textAlign: 'left',
|
|
92
|
-
boxShadow: '0 2px 12px rgba(0,0,0,0.09)',
|
|
93
|
-
transition: 'transform 0.15s ease, box-shadow 0.15s ease',
|
|
94
|
-
animation: `cw-fadeUp 0.35s ease both`,
|
|
95
|
-
animationDelay: `${i * 0.08}s`,
|
|
96
|
-
fontFamily: t.fontFamily,
|
|
65
|
+
width:'100%', background:'#fff', border:'none', borderRadius:14,
|
|
66
|
+
padding:'18px 20px', display:'flex', alignItems:'center',
|
|
67
|
+
justifyContent:'space-between', cursor:'pointer', textAlign:'left',
|
|
68
|
+
boxShadow:'0 2px 14px rgba(0,0,0,0.10)',
|
|
69
|
+
animation:`cw-fadeUp 0.35s ease both`,
|
|
70
|
+
animationDelay:`${i * 0.08}s`,
|
|
71
|
+
transition:'transform 0.15s, box-shadow 0.15s',
|
|
97
72
|
}}
|
|
98
73
|
onMouseEnter={e => {
|
|
99
74
|
(e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)';
|
|
100
|
-
(e.currentTarget as HTMLElement).style.boxShadow = '0
|
|
75
|
+
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 24px rgba(0,0,0,0.14)';
|
|
101
76
|
}}
|
|
102
77
|
onMouseLeave={e => {
|
|
103
78
|
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
|
|
104
|
-
(e.currentTarget as HTMLElement).style.boxShadow = '0 2px
|
|
79
|
+
(e.currentTarget as HTMLElement).style.boxShadow = '0 2px 14px rgba(0,0,0,0.10)';
|
|
105
80
|
}}
|
|
106
81
|
>
|
|
107
|
-
<div>
|
|
108
|
-
<div style={{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
<div style={{ display:'flex', alignItems:'center', gap:14 }}>
|
|
83
|
+
<div style={{
|
|
84
|
+
width:44, height:44, borderRadius:12,
|
|
85
|
+
backgroundColor:`${config.primaryColor}14`,
|
|
86
|
+
display:'flex', alignItems:'center', justifyContent:'center',
|
|
87
|
+
fontSize:20, flexShrink:0,
|
|
88
|
+
}}>{card.icon}</div>
|
|
89
|
+
<div>
|
|
90
|
+
<div style={{ fontWeight:700, fontSize:15, color:'#1a2332', marginBottom:2 }}>{card.title}</div>
|
|
91
|
+
<div style={{ fontSize:12, color:'#7b8fa1' }}>{card.subtitle}</div>
|
|
113
92
|
</div>
|
|
114
93
|
</div>
|
|
115
|
-
<SendArrow color={
|
|
94
|
+
<SendArrow color={config.primaryColor} />
|
|
116
95
|
</button>
|
|
117
96
|
))}
|
|
118
97
|
</div>
|
|
@@ -121,10 +100,7 @@ export const HomeScreen: React.FC<HomeScreenProps> = ({ config, theme, onNavigat
|
|
|
121
100
|
};
|
|
122
101
|
|
|
123
102
|
const SendArrow: React.FC<{ color: string }> = ({ color }) => (
|
|
124
|
-
<svg width="
|
|
125
|
-
<path
|
|
126
|
-
d="M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z"
|
|
127
|
-
stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
128
|
-
/>
|
|
103
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" style={{ flexShrink:0 }}>
|
|
104
|
+
<path d="M5 12h14M12 5l7 7-7 7" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
129
105
|
</svg>
|
|
130
106
|
);
|
|
@@ -2,14 +2,12 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
interface MaintenanceViewProps {
|
|
4
4
|
primaryColor: string;
|
|
5
|
-
fontFamily: string;
|
|
6
5
|
}
|
|
7
6
|
|
|
8
|
-
export const MaintenanceView: React.FC<MaintenanceViewProps> = ({ primaryColor
|
|
7
|
+
export const MaintenanceView: React.FC<MaintenanceViewProps> = ({ primaryColor }) => (
|
|
9
8
|
<div style={{
|
|
10
9
|
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
11
|
-
justifyContent: 'center', height: '100%', padding: '32px',
|
|
12
|
-
fontFamily, textAlign: 'center', gap: 16,
|
|
10
|
+
justifyContent: 'center', height: '100%', padding: '32px', textAlign: 'center', gap: 16,
|
|
13
11
|
}}>
|
|
14
12
|
<div style={{
|
|
15
13
|
width: 72, height: 72, borderRadius: '50%',
|
|
@@ -18,21 +16,20 @@ export const MaintenanceView: React.FC<MaintenanceViewProps> = ({ primaryColor,
|
|
|
18
16
|
}}>
|
|
19
17
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
|
|
20
18
|
<path d="M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
|
|
21
|
-
stroke={primaryColor} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
19
|
+
stroke={primaryColor} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
22
20
|
</svg>
|
|
23
21
|
</div>
|
|
24
|
-
<h3 style={{ margin: 0, fontSize:
|
|
22
|
+
<h3 style={{ margin: 0, fontSize: 17, fontWeight: 800, color: '#1a2332', letterSpacing: '-0.02em' }}>
|
|
25
23
|
Under Maintenance
|
|
26
24
|
</h3>
|
|
27
|
-
<p style={{ margin: 0, fontSize:
|
|
25
|
+
<p style={{ margin: 0, fontSize: 14, color: '#7b8fa1', lineHeight: 1.6, maxWidth: 220 }}>
|
|
28
26
|
Chat is under maintenance. We'll be back shortly!
|
|
29
27
|
</p>
|
|
30
28
|
<span style={{
|
|
31
29
|
display: 'inline-flex', alignItems: 'center', gap: 6,
|
|
32
30
|
padding: '6px 14px', borderRadius: 20,
|
|
33
31
|
backgroundColor: '#fff3cd', color: '#856404',
|
|
34
|
-
fontSize:
|
|
35
|
-
border: '1px solid #ffc10730',
|
|
32
|
+
fontSize: 12, fontWeight: 700, border: '1px solid #ffc10730',
|
|
36
33
|
}}>
|
|
37
34
|
<span style={{ width: 6, height: 6, borderRadius: '50%', backgroundColor: '#ffc107', display: 'inline-block' }} />
|
|
38
35
|
Temporarily Unavailable
|
|
@@ -1,108 +1,63 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ChatUser,
|
|
3
|
-
import {
|
|
2
|
+
import { ChatUser, WidgetConfig } from '../../types';
|
|
3
|
+
import { avatarColor, initials, formatTime } from '../../utils/chat';
|
|
4
4
|
|
|
5
5
|
interface RecentChat {
|
|
6
|
-
id: string;
|
|
7
|
-
user: ChatUser;
|
|
8
|
-
lastMessage: string;
|
|
9
|
-
lastTime: Date;
|
|
10
|
-
unread: number;
|
|
6
|
+
id: string; user: ChatUser; lastMessage: string; lastTime: string; unread: number; isPaused: boolean;
|
|
11
7
|
}
|
|
12
8
|
|
|
13
9
|
interface RecentChatsScreenProps {
|
|
14
|
-
chats:
|
|
15
|
-
|
|
16
|
-
onSelectChat:
|
|
10
|
+
chats: RecentChat[];
|
|
11
|
+
config: WidgetConfig;
|
|
12
|
+
onSelectChat: (user: ChatUser) => void;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
export const RecentChatsScreen: React.FC<RecentChatsScreenProps> = ({ chats,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<div style={{
|
|
26
|
-
backgroundColor: t.primaryColor,
|
|
27
|
-
padding: '20px 20px 24px',
|
|
28
|
-
flexShrink: 0,
|
|
29
|
-
}}>
|
|
30
|
-
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 800, color: '#fff', fontFamily: t.fontFamily, letterSpacing: '-0.02em' }}>
|
|
31
|
-
Recent Chats
|
|
32
|
-
</h2>
|
|
33
|
-
<p style={{ margin: '4px 0 0', fontSize: '13px', color: 'rgba(255,255,255,0.8)', fontFamily: t.fontFamily }}>
|
|
34
|
-
Your conversation history
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
15
|
+
export const RecentChatsScreen: React.FC<RecentChatsScreenProps> = ({ chats, config, onSelectChat }) => (
|
|
16
|
+
<div style={{ display:'flex', flexDirection:'column', height:'100%' }}>
|
|
17
|
+
<div style={{ background:`linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`, padding:'18px 18px 22px', flexShrink:0 }}>
|
|
18
|
+
<h2 style={{ margin:0, fontSize:20, fontWeight:800, color:'#fff', letterSpacing:'-0.02em' }}>Recent Chats</h2>
|
|
19
|
+
<p style={{ margin:'3px 0 0', fontSize:12, color:'rgba(255,255,255,0.8)' }}>Your conversation history</p>
|
|
20
|
+
</div>
|
|
37
21
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
<div style={{ flex:1, overflowY:'auto' }}>
|
|
23
|
+
{chats.length === 0 ? (
|
|
24
|
+
<div style={{ padding:'50px 24px', textAlign:'center' }}>
|
|
25
|
+
<div style={{ fontSize:36, marginBottom:10 }}>💬</div>
|
|
26
|
+
<div style={{ fontWeight:700, color:'#1a2332', marginBottom:6 }}>No chats yet</div>
|
|
27
|
+
<div style={{ fontSize:13, color:'#7b8fa1' }}>Start a conversation from home</div>
|
|
28
|
+
</div>
|
|
29
|
+
) : chats.map((chat, i) => (
|
|
30
|
+
<button key={chat.id} onClick={() => onSelectChat(chat.user)} style={{
|
|
31
|
+
width:'100%', padding:'13px 16px', display:'flex', alignItems:'center', gap:13,
|
|
32
|
+
background:'transparent', border:'none', borderBottom:'1px solid #f0f2f5',
|
|
33
|
+
cursor:'pointer', textAlign:'left', animation:`cw-fadeUp 0.28s ease both`, animationDelay:`${i*0.05}s`,
|
|
34
|
+
transition:'background 0.14s',
|
|
35
|
+
}}
|
|
36
|
+
onMouseEnter={e=>(e.currentTarget as HTMLElement).style.background='#f8faff'}
|
|
37
|
+
onMouseLeave={e=>(e.currentTarget as HTMLElement).style.background='transparent'}
|
|
38
|
+
>
|
|
39
|
+
<div style={{ position:'relative', flexShrink:0 }}>
|
|
40
|
+
<div style={{ width:46, height:46, borderRadius:'50%', backgroundColor:avatarColor(chat.user.name), display:'flex', alignItems:'center', justifyContent:'center', color:'#fff', fontWeight:700, fontSize:15 }}>
|
|
41
|
+
{initials(chat.user.name)}
|
|
45
42
|
</div>
|
|
43
|
+
{chat.unread > 0 && (
|
|
44
|
+
<span style={{ position:'absolute', top:-2, right:-2, width:18, height:18, borderRadius:'50%', background:'#ef4444', color:'#fff', fontSize:10, fontWeight:700, display:'flex', alignItems:'center', justifyContent:'center', border:'2px solid #fff' }}>
|
|
45
|
+
{chat.unread}
|
|
46
|
+
</span>
|
|
47
|
+
)}
|
|
46
48
|
</div>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
width: '100%', padding: '14px 20px',
|
|
60
|
-
display: 'flex', alignItems: 'center', gap: '14px',
|
|
61
|
-
background: 'transparent', border: 'none',
|
|
62
|
-
borderBottom: '1px solid #f3f4f6',
|
|
63
|
-
cursor: 'pointer', textAlign: 'left',
|
|
64
|
-
fontFamily: t.fontFamily,
|
|
65
|
-
animation: `cw-fadeUp 0.3s ease both`,
|
|
66
|
-
animationDelay: `${i * 0.05}s`,
|
|
67
|
-
transition: 'background 0.15s',
|
|
68
|
-
}}
|
|
69
|
-
onMouseEnter={e => (e.currentTarget as HTMLElement).style.background = '#f8fdfc'}
|
|
70
|
-
onMouseLeave={e => (e.currentTarget as HTMLElement).style.background = 'transparent'}
|
|
71
|
-
>
|
|
72
|
-
<div style={{
|
|
73
|
-
width: 46, height: 46, borderRadius: '50%', backgroundColor: bg,
|
|
74
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
75
|
-
color: '#fff', fontWeight: 700, fontSize: '15px', flexShrink: 0,
|
|
76
|
-
position: 'relative',
|
|
77
|
-
}}>
|
|
78
|
-
{initials}
|
|
79
|
-
{chat.unread > 0 && (
|
|
80
|
-
<span style={{
|
|
81
|
-
position: 'absolute', top: -2, right: -2,
|
|
82
|
-
width: 18, height: 18, borderRadius: '50%',
|
|
83
|
-
backgroundColor: '#ff4757', color: '#fff',
|
|
84
|
-
fontSize: '10px', fontWeight: 700,
|
|
85
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
86
|
-
border: '2px solid #fff',
|
|
87
|
-
}}>
|
|
88
|
-
{chat.unread}
|
|
89
|
-
</span>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
93
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 3 }}>
|
|
94
|
-
<span style={{ fontWeight: 700, fontSize: '14px', color: '#1a2332' }}>{chat.user.name}</span>
|
|
95
|
-
<span style={{ fontSize: '11px', color: '#b0bec5' }}>{timeStr}</span>
|
|
96
|
-
</div>
|
|
97
|
-
<div style={{ fontSize: '13px', color: '#7b8fa1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
98
|
-
{chat.lastMessage}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
</button>
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
)}
|
|
105
|
-
</div>
|
|
49
|
+
<div style={{ flex:1, minWidth:0 }}>
|
|
50
|
+
<div style={{ display:'flex', justifyContent:'space-between', marginBottom:3 }}>
|
|
51
|
+
<span style={{ fontWeight:700, fontSize:14, color:'#1a2332' }}>{chat.user.name}</span>
|
|
52
|
+
<span style={{ fontSize:11, color:'#b0bec5' }}>{formatTime(chat.lastTime)}</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div style={{ fontSize:13, color:'#7b8fa1', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', display:'flex', alignItems:'center', gap:5 }}>
|
|
55
|
+
{chat.isPaused && <span style={{ fontSize:10, background:'#fef3c7', color:'#92400e', padding:'1px 5px', borderRadius:4, fontWeight:700 }}>PAUSED</span>}
|
|
56
|
+
{chat.lastMessage}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</button>
|
|
60
|
+
))}
|
|
106
61
|
</div>
|
|
107
|
-
|
|
108
|
-
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
@@ -2,14 +2,14 @@ import React from 'react';
|
|
|
2
2
|
import { BottomTab } from '../../types';
|
|
3
3
|
|
|
4
4
|
interface BottomTabsProps {
|
|
5
|
-
active:
|
|
6
|
-
onChange:
|
|
5
|
+
active: BottomTab;
|
|
6
|
+
onChange: (tab: BottomTab) => void;
|
|
7
7
|
primaryColor: string;
|
|
8
|
-
|
|
8
|
+
onBlockList: () => void;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export const BottomTabs: React.FC<BottomTabsProps> = ({ active, onChange, primaryColor,
|
|
12
|
-
const tabs: { key: BottomTab; label: string; Icon: React.FC<{
|
|
11
|
+
export const BottomTabs: React.FC<BottomTabsProps> = ({ active, onChange, primaryColor, onBlockList }) => {
|
|
12
|
+
const tabs: { key: BottomTab; label: string; Icon: React.FC<{ a: boolean; c: string }> }[] = [
|
|
13
13
|
{ key: 'home', label: 'Home', Icon: HomeIcon },
|
|
14
14
|
{ key: 'chats', label: 'Chats', Icon: ChatsIcon },
|
|
15
15
|
{ key: 'tickets', label: 'Tickets', Icon: TicketsIcon },
|
|
@@ -17,10 +17,8 @@ export const BottomTabs: React.FC<BottomTabsProps> = ({ active, onChange, primar
|
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<div style={{
|
|
20
|
-
display: 'flex',
|
|
21
|
-
|
|
22
|
-
backgroundColor: '#fff',
|
|
23
|
-
flexShrink: 0,
|
|
20
|
+
display: 'flex', borderTop: '1px solid #eef0f5',
|
|
21
|
+
backgroundColor: '#fff', flexShrink: 0,
|
|
24
22
|
}}>
|
|
25
23
|
{tabs.map(tab => {
|
|
26
24
|
const isActive = active === tab.key;
|
|
@@ -32,51 +30,61 @@ export const BottomTabs: React.FC<BottomTabsProps> = ({ active, onChange, primar
|
|
|
32
30
|
flex: 1, padding: '10px 0 8px',
|
|
33
31
|
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
|
|
34
32
|
background: 'transparent', border: 'none', cursor: 'pointer',
|
|
35
|
-
|
|
33
|
+
fontSize: '10px', fontWeight: isActive ? 700 : 500,
|
|
36
34
|
color: isActive ? primaryColor : '#9aa3af',
|
|
37
|
-
transition: 'color 0.15s',
|
|
38
35
|
borderTop: isActive ? `2px solid ${primaryColor}` : '2px solid transparent',
|
|
36
|
+
transition: 'color 0.15s',
|
|
37
|
+
fontFamily: 'inherit',
|
|
39
38
|
}}
|
|
40
39
|
>
|
|
41
|
-
<tab.Icon
|
|
40
|
+
<tab.Icon a={isActive} c={isActive ? primaryColor : '#b0bec5'} />
|
|
42
41
|
{tab.label}
|
|
43
42
|
</button>
|
|
44
43
|
);
|
|
45
44
|
})}
|
|
45
|
+
|
|
46
|
+
{/* Block list icon */}
|
|
47
|
+
<button
|
|
48
|
+
onClick={onBlockList}
|
|
49
|
+
style={{
|
|
50
|
+
padding: '10px 14px 8px',
|
|
51
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
|
|
52
|
+
background: 'transparent', border: 'none', cursor: 'pointer',
|
|
53
|
+
fontSize: '10px', fontWeight: 500, color: '#9aa3af',
|
|
54
|
+
borderTop: '2px solid transparent',
|
|
55
|
+
transition: 'color 0.15s', fontFamily: 'inherit',
|
|
56
|
+
}}
|
|
57
|
+
title="Block List"
|
|
58
|
+
>
|
|
59
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
60
|
+
<circle cx="12" cy="12" r="10" stroke="#b0bec5" strokeWidth="1.8" />
|
|
61
|
+
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07" stroke="#b0bec5" strokeWidth="1.8" strokeLinecap="round" />
|
|
62
|
+
</svg>
|
|
63
|
+
Blocked
|
|
64
|
+
</button>
|
|
46
65
|
</div>
|
|
47
66
|
);
|
|
48
67
|
};
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<path
|
|
55
|
-
d="M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z"
|
|
56
|
-
stroke={color} strokeWidth={active ? 2.2 : 1.8} strokeLinecap="round" strokeLinejoin="round"
|
|
57
|
-
fill={active ? `${color}20` : 'none'}
|
|
58
|
-
/>
|
|
59
|
-
<path d="M9 21V12h6v9" stroke={color} strokeWidth={active ? 2.2 : 1.8} strokeLinecap="round" strokeLinejoin="round"/>
|
|
69
|
+
const HomeIcon: React.FC<{ a: boolean; c: string }> = ({ a, c }) => (
|
|
70
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
71
|
+
<path d="M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z"
|
|
72
|
+
stroke={c} strokeWidth={a ? 2.2 : 1.8} fill={a ? `${c}20` : 'none'} strokeLinecap="round" strokeLinejoin="round" />
|
|
73
|
+
<path d="M9 21V12h6v9" stroke={c} strokeWidth={a ? 2.2 : 1.8} strokeLinecap="round" strokeLinejoin="round" />
|
|
60
74
|
</svg>
|
|
61
75
|
);
|
|
62
76
|
|
|
63
|
-
const ChatsIcon: React.FC<{
|
|
64
|
-
<svg width="
|
|
65
|
-
<path
|
|
66
|
-
|
|
67
|
-
stroke={color} strokeWidth={active ? 2.2 : 1.8} strokeLinecap="round" strokeLinejoin="round"
|
|
68
|
-
fill={active ? `${color}20` : 'none'}
|
|
69
|
-
/>
|
|
77
|
+
const ChatsIcon: React.FC<{ a: boolean; c: string }> = ({ a, c }) => (
|
|
78
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
79
|
+
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"
|
|
80
|
+
stroke={c} strokeWidth={a ? 2.2 : 1.8} fill={a ? `${c}20` : 'none'} strokeLinecap="round" strokeLinejoin="round" />
|
|
70
81
|
</svg>
|
|
71
82
|
);
|
|
72
83
|
|
|
73
|
-
const TicketsIcon: React.FC<{
|
|
74
|
-
<svg width="
|
|
75
|
-
<path
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
fill={active ? `${color}20` : 'none'}
|
|
79
|
-
/>
|
|
80
|
-
<path d="M15 5v4h4M9 13h6M9 17h4" stroke={color} strokeWidth={active ? 2.2 : 1.8} strokeLinecap="round"/>
|
|
84
|
+
const TicketsIcon: React.FC<{ a: boolean; c: string }> = ({ a, c }) => (
|
|
85
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
86
|
+
<path d="M15 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V9l-4-4z"
|
|
87
|
+
stroke={c} strokeWidth={a ? 2.2 : 1.8} fill={a ? `${c}20` : 'none'} strokeLinecap="round" strokeLinejoin="round" />
|
|
88
|
+
<path d="M15 5v4h4M9 13h6M9 17h4" stroke={c} strokeWidth={a ? 2.2 : 1.8} strokeLinecap="round" />
|
|
81
89
|
</svg>
|
|
82
90
|
);
|