ajaxter-chat 1.0.1 → 2.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 (87) hide show
  1. package/README.md +96 -191
  2. package/dist/components/ChatScreen/index.d.ts +12 -0
  3. package/dist/components/ChatScreen/index.js +83 -0
  4. package/dist/components/ChatWidget.d.ts +0 -24
  5. package/dist/components/ChatWidget.js +129 -38
  6. package/dist/components/HomeScreen/index.d.ts +9 -0
  7. package/dist/components/HomeScreen/index.js +71 -0
  8. package/dist/components/MaintenanceView/index.d.ts +1 -1
  9. package/dist/components/MaintenanceView/index.js +15 -52
  10. package/dist/components/RecentChatsScreen/index.d.ts +16 -0
  11. package/dist/components/RecentChatsScreen/index.js +38 -0
  12. package/dist/components/Tabs/BottomTabs.d.ts +10 -0
  13. package/dist/components/Tabs/BottomTabs.js +29 -0
  14. package/dist/components/TicketScreen/index.d.ts +9 -0
  15. package/dist/components/TicketScreen/index.js +71 -0
  16. package/dist/components/UserListScreen/index.d.ts +13 -0
  17. package/dist/components/UserListScreen/index.js +64 -0
  18. package/dist/config/index.js +19 -74
  19. package/dist/hooks/useChat.d.ts +3 -7
  20. package/dist/hooks/useChat.js +8 -30
  21. package/dist/hooks/useUsers.d.ts +3 -10
  22. package/dist/hooks/useUsers.js +5 -11
  23. package/dist/index.d.ts +8 -7
  24. package/dist/index.js +7 -12
  25. package/dist/services/userService.d.ts +0 -5
  26. package/dist/services/userService.js +6 -13
  27. package/dist/src/components/ChatScreen/index.d.ts +12 -0
  28. package/dist/src/components/ChatScreen/index.js +83 -0
  29. package/dist/src/components/ChatWidget.d.ts +4 -0
  30. package/dist/src/components/ChatWidget.js +141 -0
  31. package/dist/src/components/HomeScreen/index.d.ts +9 -0
  32. package/dist/src/components/HomeScreen/index.js +71 -0
  33. package/dist/src/components/MaintenanceView/index.d.ts +7 -0
  34. package/dist/src/components/MaintenanceView/index.js +16 -0
  35. package/dist/src/components/RecentChatsScreen/index.d.ts +16 -0
  36. package/dist/src/components/RecentChatsScreen/index.js +38 -0
  37. package/dist/src/components/Tabs/BottomTabs.d.ts +10 -0
  38. package/dist/src/components/Tabs/BottomTabs.js +29 -0
  39. package/dist/src/components/TicketScreen/index.d.ts +9 -0
  40. package/dist/src/components/TicketScreen/index.js +71 -0
  41. package/dist/src/components/UserListScreen/index.d.ts +13 -0
  42. package/dist/src/components/UserListScreen/index.js +64 -0
  43. package/dist/src/config/index.d.ts +3 -0
  44. package/dist/src/config/index.js +38 -0
  45. package/dist/src/hooks/useChat.d.ts +8 -0
  46. package/dist/src/hooks/useChat.js +26 -0
  47. package/dist/src/hooks/useUsers.d.ts +7 -0
  48. package/dist/src/hooks/useUsers.js +26 -0
  49. package/dist/src/index.d.ts +14 -0
  50. package/dist/src/index.js +13 -0
  51. package/dist/src/services/userService.d.ts +2 -0
  52. package/dist/src/services/userService.js +9 -0
  53. package/dist/src/types/index.d.ts +59 -0
  54. package/dist/src/types/index.js +1 -0
  55. package/dist/src/utils/theme.d.ts +3 -0
  56. package/dist/src/utils/theme.js +13 -0
  57. package/dist/types/index.d.ts +23 -36
  58. package/dist/utils/theme.d.ts +0 -1
  59. package/dist/utils/theme.js +3 -18
  60. package/package.json +10 -20
  61. package/src/components/ChatScreen/index.tsx +205 -0
  62. package/src/components/ChatWidget.tsx +327 -0
  63. package/src/components/HomeScreen/index.tsx +130 -0
  64. package/src/components/MaintenanceView/index.tsx +41 -0
  65. package/src/components/RecentChatsScreen/index.tsx +108 -0
  66. package/src/components/Tabs/BottomTabs.tsx +82 -0
  67. package/src/components/TicketScreen/index.tsx +170 -0
  68. package/src/components/UserListScreen/index.tsx +181 -0
  69. package/src/config/index.ts +46 -0
  70. package/src/hooks/useChat.ts +31 -0
  71. package/src/hooks/useUsers.ts +27 -0
  72. package/src/index.ts +18 -0
  73. package/src/services/userService.ts +9 -0
  74. package/src/types/index.ts +82 -0
  75. package/src/utils/theme.ts +16 -0
  76. package/dist/components/BottomNav/index.d.ts +0 -10
  77. package/dist/components/BottomNav/index.js +0 -32
  78. package/dist/components/ChatBox/index.d.ts +0 -15
  79. package/dist/components/ChatBox/index.js +0 -228
  80. package/dist/components/ChatButton/index.d.ts +0 -9
  81. package/dist/components/ChatButton/index.js +0 -17
  82. package/dist/components/ChatWindow/index.d.ts +0 -10
  83. package/dist/components/ChatWindow/index.js +0 -286
  84. package/dist/components/HomeView/index.d.ts +0 -12
  85. package/dist/components/HomeView/index.js +0 -51
  86. package/dist/components/UserList/index.d.ts +0 -13
  87. package/dist/components/UserList/index.js +0 -136
@@ -1,9 +1,10 @@
1
1
  export type ChatStatus = 'ACTIVE' | 'DISABLE' | 'MAINTENANCE';
2
2
  export type ChatType = 'SUPPORT' | 'CHAT' | 'BOTH';
3
3
  export type UserType = 'developer' | 'user';
4
- export type TabType = 'developers' | 'users';
5
- export type BottomNavTab = 'home' | 'chats' | 'tickets';
6
- export type HomeFlow = 'home' | 'pickUser' | 'chat' | 'raiseTicket';
4
+ export type Screen = 'home' | 'user-list' | 'chat' | 'recent-chats' | 'tickets';
5
+ export type UserListContext = 'support' | 'conversation';
6
+ export type BottomTab = 'home' | 'chats' | 'tickets';
7
+ export type WidgetSize = 'normal' | 'maximized';
7
8
  export interface ChatUser {
8
9
  name: string;
9
10
  uid: string;
@@ -20,52 +21,38 @@ export interface ChatMessage {
20
21
  timestamp: Date;
21
22
  status: 'sent' | 'delivered' | 'read';
22
23
  }
24
+ export interface RecentChat {
25
+ id: string;
26
+ user: ChatUser;
27
+ lastMessage: string;
28
+ lastTime: Date;
29
+ unread: number;
30
+ }
31
+ export interface Ticket {
32
+ id: string;
33
+ title: string;
34
+ description: string;
35
+ status: 'open' | 'in-progress' | 'resolved' | 'closed';
36
+ priority: 'low' | 'medium' | 'high';
37
+ createdAt: Date;
38
+ updatedAt: Date;
39
+ }
23
40
  export interface ChatConfig {
24
41
  hostUrl: string;
25
- /** When omitted, URLs use `hostUrl` only (no `:port` segment). */
26
- hostPort?: number;
42
+ hostPort: number | null;
27
43
  userListEndpoint: string;
28
44
  status: ChatStatus;
29
45
  chatType: ChatType;
30
- /** Show “Need Support” card on home (env `CHAT_SHOW_NEED_SUPPORT`). */
31
- showNeedSupport: boolean;
32
- /** Show “New Conversation” card on home (env `CHAT_SHOW_NEW_CONVERSATION`). */
33
- showNewConversation: boolean;
34
- /** Pixel bounds for the resize slider (env `CHAT_WIDGET_*`). */
35
- widgetMinWidth: number;
36
- widgetMaxWidth: number;
37
- widgetMinHeight: number;
38
- widgetMaxHeight: number;
39
- /** Default widget size as ratio 0–1 between min and max. */
40
- widgetDefaultSize: number;
41
- }
42
- export interface RaisedTicket {
43
- id: string;
44
- subject: string;
45
- body: string;
46
- createdAt: Date;
47
- status: 'open' | 'closed';
48
- }
49
- export interface RecentChat {
50
- user: ChatUser;
51
- lastMessage: string;
52
- updatedAt: Date;
53
46
  }
54
47
  export interface ChatWidgetTheme {
55
- fontFamily?: string;
56
48
  primaryColor?: string;
57
- backgroundColor?: string;
49
+ fontFamily?: string;
58
50
  buttonColor?: string;
59
51
  buttonTextColor?: string;
60
52
  buttonLabel?: string;
61
53
  buttonPosition?: 'bottom-right' | 'bottom-left';
62
54
  borderRadius?: string;
63
- /** Override config min width (px). */
64
- widgetMinWidth?: number;
65
- widgetMaxWidth?: number;
66
- widgetMinHeight?: number;
67
- widgetMaxHeight?: number;
68
- widgetDefaultSize?: number;
55
+ backgroundColor?: string;
69
56
  }
70
57
  export interface ChatWidgetProps {
71
58
  theme?: ChatWidgetTheme;
@@ -1,4 +1,3 @@
1
1
  import { ChatWidgetTheme } from '../types';
2
2
  export declare const defaultTheme: Required<ChatWidgetTheme>;
3
3
  export declare function mergeTheme(custom?: ChatWidgetTheme): Required<ChatWidgetTheme>;
4
- export declare function themeToCSS(theme: Required<ChatWidgetTheme>): string;
@@ -1,28 +1,13 @@
1
1
  export const defaultTheme = {
2
+ primaryColor: '#1aaa96',
2
3
  fontFamily: "'DM Sans', 'Segoe UI', sans-serif",
3
- primaryColor: '#13947e',
4
- backgroundColor: '#ffffff',
5
- buttonColor: '#13947e',
4
+ buttonColor: '#1aaa96',
6
5
  buttonTextColor: '#ffffff',
7
6
  buttonLabel: 'Chat with us',
8
7
  buttonPosition: 'bottom-right',
9
8
  borderRadius: '16px',
10
- widgetMinWidth: 320,
11
- widgetMaxWidth: 720,
12
- widgetMinHeight: 420,
13
- widgetMaxHeight: 720,
14
- widgetDefaultSize: 0.45,
9
+ backgroundColor: '#ffffff',
15
10
  };
16
11
  export function mergeTheme(custom) {
17
12
  return Object.assign(Object.assign({}, defaultTheme), custom);
18
13
  }
19
- export function themeToCSS(theme) {
20
- return `
21
- --cw-font: ${theme.fontFamily};
22
- --cw-primary: ${theme.primaryColor};
23
- --cw-bg: ${theme.backgroundColor};
24
- --cw-btn-bg: ${theme.buttonColor};
25
- --cw-btn-text: ${theme.buttonTextColor};
26
- --cw-radius: ${theme.borderRadius};
27
- `;
28
- }
package/package.json CHANGED
@@ -1,18 +1,14 @@
1
1
  {
2
2
  "name": "ajaxter-chat",
3
- "version": "1.0.1",
4
- "description": "A reusable, configurable chat widget for React.js and Next.js applications.",
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.",
5
5
  "main": "dist/index.js",
6
- "module": "dist/index.esm.js",
7
6
  "types": "dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
7
+ "files": ["dist", "src"],
11
8
  "sideEffects": false,
12
9
  "scripts": {
13
10
  "build": "tsc",
14
11
  "dev": "tsc --watch",
15
- "lint": "eslint src --ext .ts,.tsx",
16
12
  "type-check": "tsc --noEmit"
17
13
  },
18
14
  "peerDependencies": {
@@ -20,19 +16,13 @@
20
16
  "react-dom": ">=17.0.0"
21
17
  },
22
18
  "devDependencies": {
23
- "@types/node": "^25.5.0",
24
- "@types/react": "^18.3.28",
25
- "@types/react-dom": "^18.3.7",
26
- "typescript": "^5.9.3"
19
+ "@types/node": "^22.0.0",
20
+ "@types/react": "^18.0.0",
21
+ "@types/react-dom": "^18.0.0",
22
+ "react": "^18.0.0",
23
+ "react-dom": "^18.0.0",
24
+ "typescript": "^5.0.0"
27
25
  },
28
- "keywords": [
29
- "react",
30
- "nextjs",
31
- "chat",
32
- "widget",
33
- "support",
34
- "typescript",
35
- "ajaxter"
36
- ],
26
+ "keywords": ["react", "nextjs", "chat", "widget", "support", "tickets", "typescript"],
37
27
  "license": "MIT"
38
28
  }
@@ -0,0 +1,205 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { ChatMessage, ChatUser, ChatWidgetTheme } from '../../types';
3
+ import { mergeTheme } from '../../utils/theme';
4
+
5
+ interface ChatScreenProps {
6
+ activeUser: ChatUser;
7
+ messages: ChatMessage[];
8
+ onSend: (text: string) => void;
9
+ onBack: () => void;
10
+ onClose: () => void;
11
+ theme?: ChatWidgetTheme;
12
+ }
13
+
14
+ export const ChatScreen: React.FC<ChatScreenProps> = ({
15
+ activeUser, messages, onSend, onBack, onClose, theme,
16
+ }) => {
17
+ const t = mergeTheme(theme);
18
+ const [text, setText] = useState('');
19
+ const endRef = useRef<HTMLDivElement>(null);
20
+ const inputRef = useRef<HTMLTextAreaElement>(null);
21
+
22
+ useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
23
+
24
+ const handleSend = () => {
25
+ if (!text.trim()) return;
26
+ onSend(text);
27
+ setText('');
28
+ inputRef.current?.focus();
29
+ };
30
+
31
+ const handleKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
32
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); }
33
+ };
34
+
35
+ const initials = activeUser.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
36
+
37
+ return (
38
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideInRight 0.25s ease' }}>
39
+ {/* Teal header */}
40
+ <div style={{
41
+ backgroundColor: t.primaryColor,
42
+ padding: '14px 18px',
43
+ display: 'flex',
44
+ alignItems: 'center',
45
+ gap: '10px',
46
+ flexShrink: 0,
47
+ }}>
48
+ <button onClick={onBack} style={iconBtnStyle}>
49
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
50
+ <path d="M19 12H5M5 12L12 19M5 12L12 5" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
51
+ </svg>
52
+ </button>
53
+
54
+ <div style={{
55
+ width: 36, height: 36, borderRadius: '50%',
56
+ backgroundColor: 'rgba(255,255,255,0.25)',
57
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
58
+ fontWeight: 700, fontSize: '13px', color: '#fff', flexShrink: 0,
59
+ }}>
60
+ {initials}
61
+ </div>
62
+
63
+ <div style={{ flex: 1, minWidth: 0 }}>
64
+ <div style={{ fontWeight: 700, fontSize: '14px', color: '#fff', fontFamily: t.fontFamily }}>
65
+ {activeUser.name}
66
+ </div>
67
+ <div style={{ fontSize: '11px', color: 'rgba(255,255,255,0.8)', display: 'flex', alignItems: 'center', gap: 4 }}>
68
+ <span style={{ width: 6, height: 6, borderRadius: '50%', backgroundColor: '#a8f0c6', display: 'inline-block' }} />
69
+ Online
70
+ </div>
71
+ </div>
72
+
73
+ <button onClick={onClose} style={iconBtnStyle}>
74
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
75
+ <path d="M18 6L6 18M6 6l12 12" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" />
76
+ </svg>
77
+ </button>
78
+ </div>
79
+
80
+ {/* Messages area */}
81
+ <div style={{
82
+ flex: 1, overflowY: 'auto', padding: '18px 16px',
83
+ display: 'flex', flexDirection: 'column', gap: '10px',
84
+ backgroundColor: '#f7f8fc',
85
+ }}>
86
+ {messages.length === 0 && (
87
+ <div style={{
88
+ margin: 'auto', textAlign: 'center',
89
+ fontSize: '13px', color: '#b0bec5',
90
+ fontFamily: t.fontFamily,
91
+ }}>
92
+ <div style={{ fontSize: '28px', marginBottom: 8 }}>💬</div>
93
+ Say hi to {activeUser.name}!
94
+ </div>
95
+ )}
96
+ {messages.map(msg => (
97
+ <Bubble key={msg.id} msg={msg} primaryColor={t.primaryColor} font={t.fontFamily} />
98
+ ))}
99
+ <div ref={endRef} />
100
+ </div>
101
+
102
+ {/* Input bar — matching image 2 exactly */}
103
+ <div style={{
104
+ borderTop: '1px solid #eef0f5',
105
+ padding: '10px 14px',
106
+ backgroundColor: '#fff',
107
+ display: 'flex',
108
+ alignItems: 'flex-end',
109
+ gap: '10px',
110
+ flexShrink: 0,
111
+ }}>
112
+ <textarea
113
+ ref={inputRef}
114
+ value={text}
115
+ onChange={e => setText(e.target.value)}
116
+ onKeyDown={handleKey}
117
+ placeholder="Type and press [enter].."
118
+ rows={1}
119
+ style={{
120
+ flex: 1, resize: 'none', border: 'none', outline: 'none',
121
+ fontFamily: t.fontFamily, fontSize: '14px', lineHeight: '1.5',
122
+ maxHeight: '80px', overflowY: 'auto', color: '#1a2332',
123
+ padding: '6px 0', backgroundColor: 'transparent',
124
+ }}
125
+ />
126
+ {/* Action icons: like, attachment, emoji — matching image 2 */}
127
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>
128
+ <IconBtn onClick={() => {}} title="Reaction">
129
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
130
+ <path d="M14 9h.01M10 9h.01M12 2a10 10 0 100 20A10 10 0 0012 2zm0 14s-4-1.5-4-4" stroke="#9aa3af" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
131
+ <path d="M8 15s1.5 2 4 2 4-2 4-2" stroke="#9aa3af" strokeWidth="1.8" strokeLinecap="round"/>
132
+ </svg>
133
+ </IconBtn>
134
+ <IconBtn onClick={() => {}} title="Attach">
135
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
136
+ <path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48" stroke="#9aa3af" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
137
+ </svg>
138
+ </IconBtn>
139
+ <IconBtn onClick={() => {}} title="Emoji">
140
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
141
+ <circle cx="12" cy="12" r="10" stroke="#9aa3af" strokeWidth="1.8"/>
142
+ <path d="M8 14s1.5 2 4 2 4-2 4-2" stroke="#9aa3af" strokeWidth="1.8" strokeLinecap="round"/>
143
+ <line x1="9" y1="9" x2="9.01" y2="9" stroke="#9aa3af" strokeWidth="2.5" strokeLinecap="round"/>
144
+ <line x1="15" y1="9" x2="15.01" y2="9" stroke="#9aa3af" strokeWidth="2.5" strokeLinecap="round"/>
145
+ </svg>
146
+ </IconBtn>
147
+ {text.trim() && (
148
+ <button
149
+ onClick={handleSend}
150
+ style={{
151
+ width: 36, height: 36, borderRadius: '50%',
152
+ backgroundColor: t.primaryColor, border: 'none',
153
+ cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
154
+ transition: 'transform 0.15s',
155
+ }}
156
+ >
157
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
158
+ <path d="M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
159
+ </svg>
160
+ </button>
161
+ )}
162
+ </div>
163
+ </div>
164
+ </div>
165
+ );
166
+ };
167
+
168
+ const Bubble: React.FC<{ msg: ChatMessage; primaryColor: string; font: string }> = ({ msg, primaryColor, font }) => {
169
+ const isMe = msg.senderId === 'me';
170
+ const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
171
+ return (
172
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }}>
173
+ <div style={{
174
+ maxWidth: '75%', padding: '10px 14px',
175
+ borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
176
+ backgroundColor: isMe ? primaryColor : '#fff',
177
+ color: isMe ? '#fff' : '#1a2332',
178
+ fontSize: '14px', lineHeight: '1.5',
179
+ boxShadow: '0 1px 4px rgba(0,0,0,0.07)',
180
+ fontFamily: font, wordBreak: 'break-word',
181
+ }}>
182
+ {msg.text}
183
+ </div>
184
+ <span style={{ fontSize: '11px', color: '#b0bec5', padding: '0 4px' }}>{time}</span>
185
+ </div>
186
+ );
187
+ };
188
+
189
+ const IconBtn: React.FC<{ onClick: () => void; title: string; children: React.ReactNode }> = ({ onClick, title, children }) => (
190
+ <button
191
+ onClick={onClick}
192
+ title={title}
193
+ style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '4px', display: 'flex', alignItems: 'center', borderRadius: '6px' }}
194
+ onMouseEnter={e => (e.currentTarget as HTMLElement).style.background = '#f3f4f6'}
195
+ onMouseLeave={e => (e.currentTarget as HTMLElement).style.background = 'none'}
196
+ >
197
+ {children}
198
+ </button>
199
+ );
200
+
201
+ const iconBtnStyle: React.CSSProperties = {
202
+ background: 'rgba(255,255,255,0.2)', border: 'none', borderRadius: '50%',
203
+ width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
204
+ cursor: 'pointer', flexShrink: 0,
205
+ };