ajaxter-chat 2.0.1 → 3.0.3

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 +7 -3
  24. package/dist/config/index.js +28 -25
  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 +26 -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 +32 -26
  52. package/src/hooks/useChat.ts +31 -14
  53. package/src/hooks/useRemoteConfig.ts +26 -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,112 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'react';
2
+ const ICE_SERVERS = [
3
+ { urls: 'stun:stun.l.google.com:19302' },
4
+ { urls: 'stun:stun1.l.google.com:19302' },
5
+ ];
6
+ export function useWebRTC() {
7
+ const [session, setSession] = useState({
8
+ state: 'idle',
9
+ peer: null,
10
+ startedAt: null,
11
+ isMuted: false,
12
+ isCameraOn: false,
13
+ });
14
+ const pcRef = useRef(null);
15
+ const localStream = useRef(null);
16
+ const localVideoRef = useRef(null);
17
+ const remoteVideoRef = useRef(null);
18
+ const updateSession = (patch) => setSession(prev => (Object.assign(Object.assign({}, prev), patch)));
19
+ /** Start an outgoing call */
20
+ const startCall = useCallback(async (peer, withVideo = false) => {
21
+ updateSession({ state: 'calling', peer });
22
+ const stream = await navigator.mediaDevices.getUserMedia({
23
+ audio: true,
24
+ video: withVideo,
25
+ });
26
+ localStream.current = stream;
27
+ if (localVideoRef.current)
28
+ localVideoRef.current.srcObject = stream;
29
+ const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
30
+ pcRef.current = pc;
31
+ stream.getTracks().forEach(t => pc.addTrack(t, stream));
32
+ pc.ontrack = e => {
33
+ if (remoteVideoRef.current)
34
+ remoteVideoRef.current.srcObject = e.streams[0];
35
+ updateSession({ state: 'connected', startedAt: new Date(), isCameraOn: withVideo });
36
+ };
37
+ pc.onicecandidate = e => {
38
+ if (e.candidate) {
39
+ // TODO: socket.emit('ice-candidate', { candidate: e.candidate, to: peer.uid });
40
+ console.log('[WebRTC] ICE candidate ready — send via signalling:', e.candidate);
41
+ }
42
+ };
43
+ const offer = await pc.createOffer();
44
+ await pc.setLocalDescription(offer);
45
+ // TODO: socket.emit('call-offer', { offer, to: peer.uid, from: 'me' });
46
+ console.log('[WebRTC] Offer created — send via signalling server:', offer);
47
+ }, []);
48
+ /** Accept an incoming call offer */
49
+ const acceptCall = useCallback(async (offer, peer, withVideo = false) => {
50
+ updateSession({ state: 'connected', peer, startedAt: new Date(), isCameraOn: withVideo });
51
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: withVideo });
52
+ localStream.current = stream;
53
+ if (localVideoRef.current)
54
+ localVideoRef.current.srcObject = stream;
55
+ const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
56
+ pcRef.current = pc;
57
+ stream.getTracks().forEach(t => pc.addTrack(t, stream));
58
+ pc.ontrack = e => {
59
+ if (remoteVideoRef.current)
60
+ remoteVideoRef.current.srcObject = e.streams[0];
61
+ };
62
+ pc.onicecandidate = e => {
63
+ if (e.candidate) {
64
+ // TODO: socket.emit('ice-candidate', { candidate: e.candidate, to: peer.uid });
65
+ }
66
+ };
67
+ await pc.setRemoteDescription(offer);
68
+ const answer = await pc.createAnswer();
69
+ await pc.setLocalDescription(answer);
70
+ // TODO: socket.emit('call-answer', { answer, to: peer.uid });
71
+ }, []);
72
+ /** Hang up */
73
+ const endCall = useCallback(() => {
74
+ var _a, _b;
75
+ (_a = localStream.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(t => t.stop());
76
+ (_b = pcRef.current) === null || _b === void 0 ? void 0 : _b.close();
77
+ pcRef.current = null;
78
+ localStream.current = null;
79
+ updateSession({ state: 'ended', peer: null, startedAt: null });
80
+ setTimeout(() => updateSession({ state: 'idle' }), 1800);
81
+ }, []);
82
+ const toggleMute = useCallback(() => {
83
+ if (!localStream.current)
84
+ return;
85
+ const enabled = !session.isMuted;
86
+ localStream.current.getAudioTracks().forEach(t => { t.enabled = enabled; });
87
+ updateSession({ isMuted: !session.isMuted });
88
+ }, [session.isMuted]);
89
+ const toggleCamera = useCallback(() => {
90
+ if (!localStream.current)
91
+ return;
92
+ const enabled = !session.isCameraOn;
93
+ localStream.current.getVideoTracks().forEach(t => { t.enabled = enabled; });
94
+ updateSession({ isCameraOn: enabled });
95
+ }, [session.isCameraOn]);
96
+ // Cleanup on unmount
97
+ useEffect(() => () => {
98
+ var _a, _b;
99
+ (_a = localStream.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(t => t.stop());
100
+ (_b = pcRef.current) === null || _b === void 0 ? void 0 : _b.close();
101
+ }, []);
102
+ return {
103
+ session,
104
+ localVideoRef,
105
+ remoteVideoRef,
106
+ startCall,
107
+ acceptCall,
108
+ endCall,
109
+ toggleMute,
110
+ toggleCamera,
111
+ };
112
+ }
package/dist/index.d.ts CHANGED
@@ -4,11 +4,15 @@ export { UserListScreen } from './components/UserListScreen';
4
4
  export { ChatScreen } from './components/ChatScreen';
5
5
  export { RecentChatsScreen } from './components/RecentChatsScreen';
6
6
  export { TicketScreen } from './components/TicketScreen';
7
+ export { BlockListScreen } from './components/BlockList';
8
+ export { CallScreen } from './components/CallScreen';
7
9
  export { MaintenanceView } from './components/MaintenanceView';
8
10
  export { BottomTabs } from './components/Tabs/BottomTabs';
9
- export { useUsers } from './hooks/useUsers';
11
+ export { EmojiPicker } from './components/EmojiPicker';
10
12
  export { useChat } from './hooks/useChat';
11
- export { loadChatConfig, buildUserListUrl } from './config';
12
- export { fetchUsers } from './services/userService';
13
- export { defaultTheme, mergeTheme } from './utils/theme';
14
- export type { ChatUser, ChatMessage, ChatConfig, ChatWidgetTheme, ChatWidgetProps, ChatStatus, ChatType, UserType, Screen, BottomTab, UserListContext, Ticket, } from './types';
13
+ export { useWebRTC } from './hooks/useWebRTC';
14
+ export { useRemoteConfig } from './hooks/useRemoteConfig';
15
+ export { loadLocalConfig, fetchRemoteChatData } from './config';
16
+ export { mergeTheme, darken } from './utils/theme';
17
+ export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from './utils/chat';
18
+ export type { ChatWidgetProps, ChatWidgetTheme, WidgetConfig, RemoteChatData, ChatUser, ChatMessage, Ticket, RecentChat, CallSession, CallState, ChatStatus, ChatType, UserType, OnlineStatus, Screen, BottomTab, UserListContext, MessageType, LocalEnvConfig, } from './types';
package/dist/index.js CHANGED
@@ -4,10 +4,14 @@ export { UserListScreen } from './components/UserListScreen';
4
4
  export { ChatScreen } from './components/ChatScreen';
5
5
  export { RecentChatsScreen } from './components/RecentChatsScreen';
6
6
  export { TicketScreen } from './components/TicketScreen';
7
+ export { BlockListScreen } from './components/BlockList';
8
+ export { CallScreen } from './components/CallScreen';
7
9
  export { MaintenanceView } from './components/MaintenanceView';
8
10
  export { BottomTabs } from './components/Tabs/BottomTabs';
9
- export { useUsers } from './hooks/useUsers';
11
+ export { EmojiPicker } from './components/EmojiPicker';
10
12
  export { useChat } from './hooks/useChat';
11
- export { loadChatConfig, buildUserListUrl } from './config';
12
- export { fetchUsers } from './services/userService';
13
- export { defaultTheme, mergeTheme } from './utils/theme';
13
+ export { useWebRTC } from './hooks/useWebRTC';
14
+ export { useRemoteConfig } from './hooks/useRemoteConfig';
15
+ export { loadLocalConfig, fetchRemoteChatData } from './config';
16
+ export { mergeTheme, darken } from './utils/theme';
17
+ export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from './utils/chat';
@@ -1,32 +1,60 @@
1
+ export interface WidgetConfig {
2
+ id: string;
3
+ apiKey: string;
4
+ status: ChatStatus;
5
+ chatType: ChatType;
6
+ primaryColor: string;
7
+ buttonLabel: string;
8
+ buttonPosition: 'bottom-right' | 'bottom-left';
9
+ welcomeTitle: string;
10
+ welcomeSubtitle: string;
11
+ allowVoiceMessage: boolean;
12
+ allowAttachment: boolean;
13
+ allowEmoji: boolean;
14
+ allowWebCall: boolean;
15
+ maxEmojiCount: number;
16
+ allowTranscriptDownload: boolean;
17
+ allowReport: boolean;
18
+ allowBlock: boolean;
19
+ }
20
+ export interface RemoteChatData {
21
+ widget: WidgetConfig;
22
+ developers: ChatUser[];
23
+ users: ChatUser[];
24
+ sampleChats: Record<string, ChatMessage[]>;
25
+ sampleTickets: Ticket[];
26
+ blockedUsers: string[];
27
+ }
1
28
  export type ChatStatus = 'ACTIVE' | 'DISABLE' | 'MAINTENANCE';
2
29
  export type ChatType = 'SUPPORT' | 'CHAT' | 'BOTH';
3
30
  export type UserType = 'developer' | 'user';
4
- export type Screen = 'home' | 'user-list' | 'chat' | 'recent-chats' | 'tickets';
5
- export type UserListContext = 'support' | 'conversation';
31
+ export type OnlineStatus = 'online' | 'away' | 'offline';
6
32
  export type BottomTab = 'home' | 'chats' | 'tickets';
7
- export type WidgetSize = 'normal' | 'maximized';
33
+ export type Screen = 'home' | 'user-list' | 'chat' | 'recent-chats' | 'tickets' | 'block-list' | 'call';
34
+ export type UserListContext = 'support' | 'conversation';
35
+ export type MessageType = 'text' | 'voice' | 'attachment' | 'emoji';
8
36
  export interface ChatUser {
9
- name: string;
10
37
  uid: string;
38
+ name: string;
11
39
  email: string;
12
40
  mobile: string;
13
41
  project: string;
14
42
  type: UserType;
43
+ avatar: string | null;
44
+ status: OnlineStatus;
45
+ designation: string;
15
46
  }
16
47
  export interface ChatMessage {
17
48
  id: string;
18
49
  senderId: string;
19
50
  receiverId: string;
20
51
  text: string;
21
- timestamp: Date;
52
+ timestamp: string;
53
+ type: MessageType;
22
54
  status: 'sent' | 'delivered' | 'read';
23
- }
24
- export interface RecentChat {
25
- id: string;
26
- user: ChatUser;
27
- lastMessage: string;
28
- lastTime: Date;
29
- unread: number;
55
+ attachmentName?: string;
56
+ attachmentSize?: string;
57
+ voiceDuration?: number;
30
58
  }
31
59
  export interface Ticket {
32
60
  id: string;
@@ -34,15 +62,29 @@ export interface Ticket {
34
62
  description: string;
35
63
  status: 'open' | 'in-progress' | 'resolved' | 'closed';
36
64
  priority: 'low' | 'medium' | 'high';
37
- createdAt: Date;
38
- updatedAt: Date;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ assignedTo: string | null;
39
68
  }
40
- export interface ChatConfig {
41
- hostUrl: string;
42
- hostPort: number | null;
43
- userListEndpoint: string;
44
- status: ChatStatus;
45
- chatType: ChatType;
69
+ export interface RecentChat {
70
+ id: string;
71
+ user: ChatUser;
72
+ lastMessage: string;
73
+ lastTime: string;
74
+ unread: number;
75
+ isPaused: boolean;
76
+ }
77
+ export type CallState = 'idle' | 'calling' | 'connected' | 'ended';
78
+ export interface CallSession {
79
+ state: CallState;
80
+ peer: ChatUser | null;
81
+ startedAt: Date | null;
82
+ isMuted: boolean;
83
+ isCameraOn: boolean;
84
+ }
85
+ export interface LocalEnvConfig {
86
+ apiKey: string;
87
+ widgetId: string;
46
88
  }
47
89
  export interface ChatWidgetTheme {
48
90
  primaryColor?: string;
@@ -52,7 +94,6 @@ export interface ChatWidgetTheme {
52
94
  buttonLabel?: string;
53
95
  buttonPosition?: 'bottom-right' | 'bottom-left';
54
96
  borderRadius?: string;
55
- backgroundColor?: string;
56
97
  }
57
98
  export interface ChatWidgetProps {
58
99
  theme?: ChatWidgetTheme;
@@ -0,0 +1,13 @@
1
+ import { ChatMessage, ChatUser } from '../types';
2
+ /** Generate a consistent avatar color from a name */
3
+ export declare function avatarColor(name: string): string;
4
+ /** Get initials from full name */
5
+ export declare function initials(name: string): string;
6
+ /** Format timestamp to readable time string */
7
+ export declare function formatTime(ts: string | Date): string;
8
+ /** Format date for chat separators */
9
+ export declare function formatDate(ts: string | Date): string;
10
+ /** Generate a plain-text transcript from messages */
11
+ export declare function generateTranscript(messages: ChatMessage[], peer: ChatUser, myName?: string): string;
12
+ /** Trigger a file download in the browser */
13
+ export declare function downloadText(content: string, filename: string): void;
@@ -0,0 +1,62 @@
1
+ /** Generate a consistent avatar color from a name */
2
+ export function avatarColor(name) {
3
+ const palette = [
4
+ '#2563EB', '#7C3AED', '#059669', '#D97706',
5
+ '#DC2626', '#0891B2', '#4F46E5', '#BE185D',
6
+ ];
7
+ let hash = 0;
8
+ for (let i = 0; i < name.length; i++)
9
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
10
+ return palette[Math.abs(hash) % palette.length];
11
+ }
12
+ /** Get initials from full name */
13
+ export function initials(name) {
14
+ return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
15
+ }
16
+ /** Format timestamp to readable time string */
17
+ export function formatTime(ts) {
18
+ return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
19
+ }
20
+ /** Format date for chat separators */
21
+ export function formatDate(ts) {
22
+ const d = new Date(ts);
23
+ const today = new Date();
24
+ const yesterday = new Date(today);
25
+ yesterday.setDate(today.getDate() - 1);
26
+ if (d.toDateString() === today.toDateString())
27
+ return 'Today';
28
+ if (d.toDateString() === yesterday.toDateString())
29
+ return 'Yesterday';
30
+ return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
31
+ }
32
+ /** Generate a plain-text transcript from messages */
33
+ export function generateTranscript(messages, peer, myName = 'Me') {
34
+ const header = [
35
+ '═══════════════════════════════════════',
36
+ ' CHAT TRANSCRIPT',
37
+ ` Conversation with: ${peer.name} (${peer.email})`,
38
+ ` Downloaded: ${new Date().toLocaleString()}`,
39
+ '═══════════════════════════════════════',
40
+ '',
41
+ ].join('\n');
42
+ const body = messages.map(m => {
43
+ var _a;
44
+ const sender = m.senderId === 'me' ? myName : peer.name;
45
+ const time = new Date(m.timestamp).toLocaleString();
46
+ const label = m.type === 'voice' ? '[Voice Message]'
47
+ : m.type === 'attachment' ? `[Attachment: ${(_a = m.attachmentName) !== null && _a !== void 0 ? _a : 'file'}]`
48
+ : m.text;
49
+ return `[${time}] ${sender}: ${label}`;
50
+ }).join('\n');
51
+ return header + body;
52
+ }
53
+ /** Trigger a file download in the browser */
54
+ export function downloadText(content, filename) {
55
+ const blob = new Blob([content], { type: 'text/plain' });
56
+ const url = URL.createObjectURL(blob);
57
+ const a = document.createElement('a');
58
+ a.href = url;
59
+ a.download = filename;
60
+ a.click();
61
+ URL.revokeObjectURL(url);
62
+ }
@@ -1,3 +1,5 @@
1
1
  import { ChatWidgetTheme } from '../types';
2
2
  export declare const defaultTheme: Required<ChatWidgetTheme>;
3
- export declare function mergeTheme(custom?: ChatWidgetTheme): Required<ChatWidgetTheme>;
3
+ export declare function mergeTheme(remote?: Partial<ChatWidgetTheme>, local?: ChatWidgetTheme): Required<ChatWidgetTheme>;
4
+ /** Darken a hex color by `amount` (0-255) */
5
+ export declare function darken(hex: string, amount?: number): string;
@@ -1,13 +1,20 @@
1
1
  export const defaultTheme = {
2
- primaryColor: '#1aaa96',
2
+ primaryColor: '#2563EB',
3
3
  fontFamily: "'DM Sans', 'Segoe UI', sans-serif",
4
- buttonColor: '#1aaa96',
4
+ buttonColor: '#2563EB',
5
5
  buttonTextColor: '#ffffff',
6
- buttonLabel: 'Chat with us',
6
+ buttonLabel: 'Support',
7
7
  buttonPosition: 'bottom-right',
8
- borderRadius: '16px',
9
- backgroundColor: '#ffffff',
8
+ borderRadius: '0px', // drawer uses 16px only on top-left / bottom-left
10
9
  };
11
- export function mergeTheme(custom) {
12
- return Object.assign(Object.assign({}, defaultTheme), custom);
10
+ export function mergeTheme(remote, local) {
11
+ return Object.assign(Object.assign(Object.assign({}, defaultTheme), remote), local);
12
+ }
13
+ /** Darken a hex color by `amount` (0-255) */
14
+ export function darken(hex, amount = 20) {
15
+ const n = parseInt(hex.replace('#', ''), 16);
16
+ const r = Math.max(0, (n >> 16) - amount);
17
+ const g = Math.max(0, ((n >> 8) & 0xff) - amount);
18
+ const b = Math.max(0, (n & 0xff) - amount);
19
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
13
20
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "ajaxter-chat",
3
- "version": "2.0.1",
4
- "description": "A reusable configurable chat widget for React.js and Next.js with support chat, user chat, and ticket raising.",
3
+ "version": "3.0.3",
4
+ "description": "Drawer-based chat widget with support chat, tickets, WebRTC calling, voice messages, block list, and transcript download.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "files": ["dist", "src"],
7
+ "files": ["dist", "src", "public"],
8
8
  "sideEffects": false,
9
9
  "scripts": {
10
10
  "build": "tsc",
@@ -23,6 +23,6 @@
23
23
  "react-dom": "^18.0.0",
24
24
  "typescript": "^5.0.0"
25
25
  },
26
- "keywords": ["react", "nextjs", "chat", "widget", "support", "tickets", "typescript"],
26
+ "keywords": ["react","nextjs","chat","widget","webrtc","support","tickets","drawer","typescript"],
27
27
  "license": "MIT"
28
28
  }
@@ -0,0 +1,162 @@
1
+ {
2
+ "widget": {
3
+ "id": "demo",
4
+ "apiKey": "demo1234",
5
+ "status": "ACTIVE",
6
+ "chatType": "BOTH",
7
+ "primaryColor": "#2563EB",
8
+ "buttonLabel": "Support",
9
+ "buttonPosition": "bottom-right",
10
+ "welcomeTitle": "Hi there 👋",
11
+ "welcomeSubtitle": "Need help? Start a conversation:",
12
+ "allowVoiceMessage": true,
13
+ "allowAttachment": true,
14
+ "allowEmoji": true,
15
+ "allowWebCall": true,
16
+ "maxEmojiCount": 20,
17
+ "allowTranscriptDownload": true,
18
+ "allowReport": true,
19
+ "allowBlock": true
20
+ },
21
+ "developers": [
22
+ {
23
+ "uid": "dev_001",
24
+ "name": "Arjun Sharma",
25
+ "email": "arjun.sharma@mscorpres.com",
26
+ "mobile": "+91-9876543210",
27
+ "project": "Platform Engineering",
28
+ "type": "developer",
29
+ "avatar": null,
30
+ "status": "online",
31
+ "designation": "Senior Developer"
32
+ },
33
+ {
34
+ "uid": "dev_002",
35
+ "name": "Priya Nair",
36
+ "email": "priya.nair@mscorpres.com",
37
+ "mobile": "+91-9876543211",
38
+ "project": "Product Support",
39
+ "type": "developer",
40
+ "avatar": null,
41
+ "status": "online",
42
+ "designation": "Support Engineer"
43
+ }
44
+ ],
45
+ "users": [
46
+ {
47
+ "uid": "usr_001",
48
+ "name": "Ravi Kumar",
49
+ "email": "ravi.kumar@client.com",
50
+ "mobile": "+91-9988776655",
51
+ "project": "Client Portal",
52
+ "type": "user",
53
+ "avatar": null,
54
+ "status": "online",
55
+ "designation": "Product Manager"
56
+ },
57
+ {
58
+ "uid": "usr_002",
59
+ "name": "Sneha Patel",
60
+ "email": "sneha.patel@client.com",
61
+ "mobile": "+91-9988776644",
62
+ "project": "Client Portal",
63
+ "type": "user",
64
+ "avatar": null,
65
+ "status": "away",
66
+ "designation": "Business Analyst"
67
+ },
68
+ {
69
+ "uid": "usr_003",
70
+ "name": "Mohan Das",
71
+ "email": "mohan.das@client.com",
72
+ "mobile": "+91-9988776633",
73
+ "project": "Mobile App",
74
+ "type": "user",
75
+ "avatar": null,
76
+ "status": "offline",
77
+ "designation": "QA Engineer"
78
+ }
79
+ ],
80
+ "sampleChats": {
81
+ "dev_001": [
82
+ {
83
+ "id": "msg_001",
84
+ "senderId": "me",
85
+ "receiverId": "dev_001",
86
+ "text": "Hi Arjun, I'm getting a 500 error on the login API.",
87
+ "timestamp": "2025-03-27T09:10:00Z",
88
+ "type": "text",
89
+ "status": "read"
90
+ },
91
+ {
92
+ "id": "msg_002",
93
+ "senderId": "dev_001",
94
+ "receiverId": "me",
95
+ "text": "Hey! Can you share the request payload? I'll check on my end.",
96
+ "timestamp": "2025-03-27T09:11:30Z",
97
+ "type": "text",
98
+ "status": "read"
99
+ },
100
+ {
101
+ "id": "msg_003",
102
+ "senderId": "me",
103
+ "receiverId": "dev_001",
104
+ "text": "Sure, here it is: { \"email\": \"test@demo.com\", \"password\": \"***\" }",
105
+ "timestamp": "2025-03-27T09:12:00Z",
106
+ "type": "text",
107
+ "status": "read"
108
+ },
109
+ {
110
+ "id": "msg_004",
111
+ "senderId": "dev_001",
112
+ "receiverId": "me",
113
+ "text": "Got it. The issue was a DB connection timeout. Fixed in the latest deploy 🚀",
114
+ "timestamp": "2025-03-27T09:20:00Z",
115
+ "type": "text",
116
+ "status": "read"
117
+ },
118
+ {
119
+ "id": "msg_005",
120
+ "senderId": "me",
121
+ "receiverId": "dev_001",
122
+ "text": "Perfect, it's working now. Thanks!",
123
+ "timestamp": "2025-03-27T09:21:00Z",
124
+ "type": "text",
125
+ "status": "read"
126
+ }
127
+ ]
128
+ },
129
+ "sampleTickets": [
130
+ {
131
+ "id": "TKT-0001",
132
+ "title": "Login page 500 error",
133
+ "description": "Getting a server error when logging in with valid credentials.",
134
+ "status": "resolved",
135
+ "priority": "high",
136
+ "createdAt": "2025-03-25T10:00:00Z",
137
+ "updatedAt": "2025-03-27T09:22:00Z",
138
+ "assignedTo": "dev_001"
139
+ },
140
+ {
141
+ "id": "TKT-0002",
142
+ "title": "Export to CSV not working",
143
+ "description": "The export button on the reports page does nothing when clicked.",
144
+ "status": "in-progress",
145
+ "priority": "medium",
146
+ "createdAt": "2025-03-26T14:30:00Z",
147
+ "updatedAt": "2025-03-27T08:00:00Z",
148
+ "assignedTo": "dev_002"
149
+ },
150
+ {
151
+ "id": "TKT-0003",
152
+ "title": "Add dark mode support",
153
+ "description": "Feature request to add a dark mode toggle for the dashboard.",
154
+ "status": "open",
155
+ "priority": "low",
156
+ "createdAt": "2025-03-27T07:00:00Z",
157
+ "updatedAt": "2025-03-27T07:00:00Z",
158
+ "assignedTo": null
159
+ }
160
+ ],
161
+ "blockedUsers": []
162
+ }
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { ChatUser, WidgetConfig } from '../../types';
3
+ import { avatarColor, initials } from '../../utils/chat';
4
+
5
+ interface BlockListScreenProps {
6
+ blockedUsers: ChatUser[];
7
+ config: WidgetConfig;
8
+ onUnblock: (uid: string) => void;
9
+ onBack: () => void;
10
+ }
11
+
12
+ export const BlockListScreen: React.FC<BlockListScreenProps> = ({
13
+ blockedUsers, config, onUnblock, onBack,
14
+ }) => (
15
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }}>
16
+ {/* Header */}
17
+ <div style={{
18
+ background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
19
+ padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0,
20
+ }}>
21
+ <button onClick={onBack} style={backBtnStyle}>
22
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
23
+ <path d="M19 12H5M5 12L12 19M5 12L12 5" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
24
+ </svg>
25
+ </button>
26
+ <div>
27
+ <div style={{ fontWeight: 700, fontSize: 16, color: '#fff' }}>Block List</div>
28
+ <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.8)' }}>
29
+ {blockedUsers.length} blocked user{blockedUsers.length !== 1 ? 's' : ''}
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div style={{ flex: 1, overflowY: 'auto' }}>
35
+ {blockedUsers.length === 0 ? (
36
+ <div style={{ padding: '50px 24px', textAlign: 'center' }}>
37
+ <div style={{ fontSize: 36, marginBottom: 10 }}>✅</div>
38
+ <div style={{ fontWeight: 700, color: '#1a2332', marginBottom: 6 }}>No blocked users</div>
39
+ <div style={{ fontSize: 13, color: '#7b8fa1' }}>Users you block will appear here</div>
40
+ </div>
41
+ ) : (
42
+ blockedUsers.map((user, i) => (
43
+ <div key={user.uid} style={{
44
+ padding: '13px 16px', display: 'flex', alignItems: 'center', gap: 13,
45
+ borderBottom: '1px solid #f0f2f5',
46
+ animation: `cw-fadeUp 0.28s ease both`, animationDelay: `${i * 0.05}s`,
47
+ }}>
48
+ <div style={{
49
+ width: 44, height: 44, borderRadius: '50%',
50
+ backgroundColor: avatarColor(user.name),
51
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
52
+ color: '#fff', fontWeight: 700, fontSize: 14, flexShrink: 0,
53
+ filter: 'grayscale(0.6)', opacity: 0.7,
54
+ }}>
55
+ {initials(user.name)}
56
+ </div>
57
+ <div style={{ flex: 1, minWidth: 0 }}>
58
+ <div style={{ fontWeight: 700, fontSize: 14, color: '#6b7280' }}>{user.name}</div>
59
+ <div style={{ fontSize: 12, color: '#9ca3af', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
60
+ {user.email}
61
+ </div>
62
+ </div>
63
+ <button
64
+ onClick={() => onUnblock(user.uid)}
65
+ style={{
66
+ padding: '6px 14px', borderRadius: 20,
67
+ border: `1.5px solid ${config.primaryColor}`,
68
+ background: 'transparent', color: config.primaryColor,
69
+ fontSize: 12, fontWeight: 700, cursor: 'pointer',
70
+ transition: 'all 0.15s', flexShrink: 0,
71
+ }}
72
+ onMouseEnter={e => {
73
+ (e.currentTarget as HTMLElement).style.background = config.primaryColor;
74
+ (e.currentTarget as HTMLElement).style.color = '#fff';
75
+ }}
76
+ onMouseLeave={e => {
77
+ (e.currentTarget as HTMLElement).style.background = 'transparent';
78
+ (e.currentTarget as HTMLElement).style.color = config.primaryColor;
79
+ }}
80
+ >
81
+ Unblock
82
+ </button>
83
+ </div>
84
+ ))
85
+ )}
86
+ </div>
87
+ </div>
88
+ );
89
+
90
+ const backBtnStyle: React.CSSProperties = {
91
+ background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
92
+ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
93
+ cursor: 'pointer', flexShrink: 0,
94
+ };