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.
Files changed (94) hide show
  1. package/README.md +119 -128
  2. package/dist/components/BlockList/index.d.ts +10 -0
  3. package/dist/components/BlockList/index.js +33 -0
  4. package/dist/components/CallScreen/index.d.ts +13 -0
  5. package/dist/components/CallScreen/index.js +48 -0
  6. package/dist/components/ChatScreen/index.d.ts +10 -3
  7. package/dist/components/ChatScreen/index.js +142 -57
  8. package/dist/components/ChatWidget.js +192 -98
  9. package/dist/components/EmojiPicker/index.d.ts +8 -0
  10. package/dist/components/EmojiPicker/index.js +18 -0
  11. package/dist/components/HomeScreen/index.d.ts +2 -3
  12. package/dist/components/HomeScreen/index.js +25 -41
  13. package/dist/components/MaintenanceView/index.d.ts +0 -1
  14. package/dist/components/MaintenanceView/index.js +4 -6
  15. package/dist/components/RecentChatsScreen/index.d.ts +4 -3
  16. package/dist/components/RecentChatsScreen/index.js +7 -37
  17. package/dist/components/Tabs/BottomTabs.d.ts +1 -1
  18. package/dist/components/Tabs/BottomTabs.js +25 -20
  19. package/dist/components/TicketScreen/index.d.ts +3 -3
  20. package/dist/components/TicketScreen/index.js +39 -56
  21. package/dist/components/UserListScreen/index.d.ts +2 -4
  22. package/dist/components/UserListScreen/index.js +33 -62
  23. package/dist/config/index.d.ts +3 -3
  24. package/dist/config/index.js +18 -26
  25. package/dist/hooks/useChat.d.ts +8 -3
  26. package/dist/hooks/useChat.js +22 -18
  27. package/dist/hooks/useRemoteConfig.d.ts +6 -0
  28. package/dist/hooks/useRemoteConfig.js +22 -0
  29. package/dist/hooks/useWebRTC.d.ts +11 -0
  30. package/dist/hooks/useWebRTC.js +112 -0
  31. package/dist/index.d.ts +9 -5
  32. package/dist/index.js +8 -4
  33. package/dist/types/index.d.ts +62 -21
  34. package/dist/utils/chat.d.ts +13 -0
  35. package/dist/utils/chat.js +62 -0
  36. package/dist/utils/theme.d.ts +3 -1
  37. package/dist/utils/theme.js +14 -7
  38. package/package.json +4 -4
  39. package/public/chatData.json +162 -0
  40. package/src/components/BlockList/index.tsx +94 -0
  41. package/src/components/CallScreen/index.tsx +144 -0
  42. package/src/components/ChatScreen/index.tsx +403 -139
  43. package/src/components/ChatWidget.tsx +394 -250
  44. package/src/components/EmojiPicker/index.tsx +48 -0
  45. package/src/components/HomeScreen/index.tsx +58 -82
  46. package/src/components/MaintenanceView/index.tsx +6 -9
  47. package/src/components/RecentChatsScreen/index.tsx +51 -96
  48. package/src/components/Tabs/BottomTabs.tsx +45 -37
  49. package/src/components/TicketScreen/index.tsx +87 -133
  50. package/src/components/UserListScreen/index.tsx +75 -153
  51. package/src/config/index.ts +22 -28
  52. package/src/hooks/useChat.ts +31 -14
  53. package/src/hooks/useRemoteConfig.ts +20 -0
  54. package/src/hooks/useWebRTC.ts +130 -0
  55. package/src/index.ts +26 -15
  56. package/src/types/index.ts +85 -40
  57. package/src/utils/chat.ts +70 -0
  58. package/src/utils/theme.ts +18 -7
  59. package/dist/hooks/useUsers.d.ts +0 -7
  60. package/dist/hooks/useUsers.js +0 -26
  61. package/dist/services/userService.d.ts +0 -2
  62. package/dist/services/userService.js +0 -9
  63. package/dist/src/components/ChatScreen/index.d.ts +0 -12
  64. package/dist/src/components/ChatScreen/index.js +0 -83
  65. package/dist/src/components/ChatWidget.d.ts +0 -4
  66. package/dist/src/components/ChatWidget.js +0 -141
  67. package/dist/src/components/HomeScreen/index.d.ts +0 -9
  68. package/dist/src/components/HomeScreen/index.js +0 -71
  69. package/dist/src/components/MaintenanceView/index.d.ts +0 -7
  70. package/dist/src/components/MaintenanceView/index.js +0 -16
  71. package/dist/src/components/RecentChatsScreen/index.d.ts +0 -16
  72. package/dist/src/components/RecentChatsScreen/index.js +0 -38
  73. package/dist/src/components/Tabs/BottomTabs.d.ts +0 -10
  74. package/dist/src/components/Tabs/BottomTabs.js +0 -29
  75. package/dist/src/components/TicketScreen/index.d.ts +0 -9
  76. package/dist/src/components/TicketScreen/index.js +0 -71
  77. package/dist/src/components/UserListScreen/index.d.ts +0 -13
  78. package/dist/src/components/UserListScreen/index.js +0 -64
  79. package/dist/src/config/index.d.ts +0 -3
  80. package/dist/src/config/index.js +0 -38
  81. package/dist/src/hooks/useChat.d.ts +0 -8
  82. package/dist/src/hooks/useChat.js +0 -26
  83. package/dist/src/hooks/useUsers.d.ts +0 -7
  84. package/dist/src/hooks/useUsers.js +0 -26
  85. package/dist/src/index.d.ts +0 -14
  86. package/dist/src/index.js +0 -13
  87. package/dist/src/services/userService.d.ts +0 -2
  88. package/dist/src/services/userService.js +0 -9
  89. package/dist/src/types/index.d.ts +0 -59
  90. package/dist/src/types/index.js +0 -1
  91. package/dist/src/utils/theme.d.ts +0 -3
  92. package/dist/src/utils/theme.js +0 -13
  93. package/src/hooks/useUsers.ts +0 -27
  94. 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 { ChatConfig, ChatWidgetTheme, UserListContext } from '../../types';
3
- import { mergeTheme } from '../../utils/theme';
2
+ import { WidgetConfig, UserListContext } from '../../types';
4
3
 
5
4
  interface HomeScreenProps {
6
- config: ChatConfig;
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, theme, onNavigate }) => {
12
- const t = mergeTheme(theme);
13
- const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
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: 'support' as UserListContext,
19
- title: 'Need Support',
15
+ key: 'support' as UserListContext,
16
+ icon: '🛠',
17
+ title: 'Need Support',
20
18
  subtitle: 'We typically reply in a few minutes',
21
- onClick: () => onNavigate('support'),
19
+ onClick: () => onNavigate('support'),
22
20
  },
23
- showConversation && {
24
- key: 'conversation' as UserListContext,
25
- title: 'New Conversation',
21
+ showChat && {
22
+ key: 'conversation' as UserListContext,
23
+ icon: '💬',
24
+ title: 'New Conversation',
26
25
  subtitle: 'With your colleague',
27
- onClick: () => onNavigate('conversation'),
26
+ onClick: () => onNavigate('conversation'),
28
27
  },
29
- // Raise Ticket is always shown
30
28
  {
31
- key: 'ticket' as const,
32
- title: 'Raise Ticket',
29
+ key: 'ticket',
30
+ icon: '🎫',
31
+ title: 'Raise Ticket',
33
32
  subtitle: 'For major changes',
34
- onClick: () => onNavigate('ticket'),
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
- {/* Teal hero header */}
41
- <div
42
- style={{
43
- backgroundColor: t.primaryColor,
44
- padding: '32px 24px 40px',
45
- flexShrink: 0,
46
- }}
47
- >
48
- <h1
49
- style={{
50
- margin: '0 0 8px',
51
- fontSize: '28px',
52
- fontWeight: 800,
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: 0, fontSize: '15px', color: 'rgba(255,255,255,0.85)', fontFamily: t.fontFamily }}>
61
- Need help? start a conversation:
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 float over the header with negative margin trick */}
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: '100%',
83
- background: '#fff',
84
- border: 'none',
85
- borderRadius: '14px',
86
- padding: '18px 20px',
87
- display: 'flex',
88
- alignItems: 'center',
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 6px 20px rgba(0,0,0,0.13)';
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 12px rgba(0,0,0,0.09)';
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={{ fontWeight: 700, fontSize: '15px', color: '#1a2332', marginBottom: '3px' }}>
109
- {card.title}
110
- </div>
111
- <div style={{ fontSize: '13px', color: '#7b8fa1' }}>
112
- {card.subtitle}
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={t.primaryColor} />
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="22" height="22" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
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, fontFamily }) => (
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: '17px', fontWeight: 800, color: '#1a2332', letterSpacing: '-0.02em' }}>
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: '14px', color: '#7b8fa1', lineHeight: 1.6, maxWidth: 220 }}>
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: '12px', fontWeight: 700,
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, ChatWidgetTheme } from '../../types';
3
- import { mergeTheme } from '../../utils/theme';
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: RecentChat[];
15
- theme?: ChatWidgetTheme;
16
- onSelectChat: (user: ChatUser) => void;
10
+ chats: RecentChat[];
11
+ config: WidgetConfig;
12
+ onSelectChat: (user: ChatUser) => void;
17
13
  }
18
14
 
19
- export const RecentChatsScreen: React.FC<RecentChatsScreenProps> = ({ chats, theme, onSelectChat }) => {
20
- const t = mergeTheme(theme);
21
-
22
- return (
23
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
24
- {/* Header */}
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
- <div style={{ flex: 1, overflowY: 'auto' }}>
39
- {chats.length === 0 ? (
40
- <div style={{ padding: '50px 24px', textAlign: 'center', fontFamily: t.fontFamily }}>
41
- <div style={{ fontSize: '36px', marginBottom: 12 }}>💬</div>
42
- <div style={{ fontWeight: 700, color: '#1a2332', marginBottom: 6 }}>No chats yet</div>
43
- <div style={{ fontSize: '13px', color: '#7b8fa1' }}>
44
- Start a conversation from the home tab
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
- chats.map((chat, i) => {
49
- const initials = chat.user.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
50
- const avatarColors = ['#1aaa96','#2563EB','#7C3AED','#D97706','#DC2626'];
51
- const bg = avatarColors[chat.user.name.charCodeAt(0) % avatarColors.length];
52
- const timeStr = new Date(chat.lastTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
53
-
54
- return (
55
- <button
56
- key={chat.id}
57
- onClick={() => onSelectChat(chat.user)}
58
- style={{
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: BottomTab;
6
- onChange: (tab: BottomTab) => void;
5
+ active: BottomTab;
6
+ onChange: (tab: BottomTab) => void;
7
7
  primaryColor: string;
8
- fontFamily: string;
8
+ onBlockList: () => void;
9
9
  }
10
10
 
11
- export const BottomTabs: React.FC<BottomTabsProps> = ({ active, onChange, primaryColor, fontFamily }) => {
12
- const tabs: { key: BottomTab; label: string; Icon: React.FC<{ active: boolean; color: string }> }[] = [
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
- borderTop: '1px solid #eef0f5',
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
- fontFamily, fontSize: '10px', fontWeight: isActive ? 700 : 500,
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 active={isActive} color={isActive ? primaryColor : '#b0bec5'} />
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
- // ── Icons ─────────────────────────────────────────────────────────────────────
51
-
52
- const HomeIcon: React.FC<{ active: boolean; color: string }> = ({ active, color }) => (
53
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
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<{ active: boolean; color: string }> = ({ active, color }) => (
64
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
65
- <path
66
- d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"
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<{ active: boolean; color: string }> = ({ active, color }) => (
74
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
75
- <path
76
- d="M15 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V9l-4-4z"
77
- stroke={color} strokeWidth={active ? 2.2 : 1.8} strokeLinecap="round" strokeLinejoin="round"
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
  );