ajaxter-chat 1.0.3 → 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 (88) hide show
  1. package/README.md +96 -204
  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.d.ts +0 -13
  19. package/dist/config/index.js +20 -95
  20. package/dist/hooks/useChat.d.ts +3 -7
  21. package/dist/hooks/useChat.js +8 -30
  22. package/dist/hooks/useUsers.d.ts +3 -10
  23. package/dist/hooks/useUsers.js +5 -11
  24. package/dist/index.d.ts +8 -7
  25. package/dist/index.js +7 -12
  26. package/dist/services/userService.d.ts +0 -5
  27. package/dist/services/userService.js +6 -15
  28. package/dist/src/components/ChatScreen/index.d.ts +12 -0
  29. package/dist/src/components/ChatScreen/index.js +83 -0
  30. package/dist/src/components/ChatWidget.d.ts +4 -0
  31. package/dist/src/components/ChatWidget.js +141 -0
  32. package/dist/src/components/HomeScreen/index.d.ts +9 -0
  33. package/dist/src/components/HomeScreen/index.js +71 -0
  34. package/dist/src/components/MaintenanceView/index.d.ts +7 -0
  35. package/dist/src/components/MaintenanceView/index.js +16 -0
  36. package/dist/src/components/RecentChatsScreen/index.d.ts +16 -0
  37. package/dist/src/components/RecentChatsScreen/index.js +38 -0
  38. package/dist/src/components/Tabs/BottomTabs.d.ts +10 -0
  39. package/dist/src/components/Tabs/BottomTabs.js +29 -0
  40. package/dist/src/components/TicketScreen/index.d.ts +9 -0
  41. package/dist/src/components/TicketScreen/index.js +71 -0
  42. package/dist/src/components/UserListScreen/index.d.ts +13 -0
  43. package/dist/src/components/UserListScreen/index.js +64 -0
  44. package/dist/src/config/index.d.ts +3 -0
  45. package/dist/src/config/index.js +38 -0
  46. package/dist/src/hooks/useChat.d.ts +8 -0
  47. package/dist/src/hooks/useChat.js +26 -0
  48. package/dist/src/hooks/useUsers.d.ts +7 -0
  49. package/dist/src/hooks/useUsers.js +26 -0
  50. package/dist/src/index.d.ts +14 -0
  51. package/dist/src/index.js +13 -0
  52. package/dist/src/services/userService.d.ts +2 -0
  53. package/dist/src/services/userService.js +9 -0
  54. package/dist/src/types/index.d.ts +59 -0
  55. package/dist/src/types/index.js +1 -0
  56. package/dist/src/utils/theme.d.ts +3 -0
  57. package/dist/src/utils/theme.js +13 -0
  58. package/dist/types/index.d.ts +23 -36
  59. package/dist/utils/theme.d.ts +0 -1
  60. package/dist/utils/theme.js +3 -18
  61. package/package.json +10 -20
  62. package/src/components/ChatScreen/index.tsx +205 -0
  63. package/src/components/ChatWidget.tsx +327 -0
  64. package/src/components/HomeScreen/index.tsx +130 -0
  65. package/src/components/MaintenanceView/index.tsx +41 -0
  66. package/src/components/RecentChatsScreen/index.tsx +108 -0
  67. package/src/components/Tabs/BottomTabs.tsx +82 -0
  68. package/src/components/TicketScreen/index.tsx +170 -0
  69. package/src/components/UserListScreen/index.tsx +181 -0
  70. package/src/config/index.ts +46 -0
  71. package/src/hooks/useChat.ts +31 -0
  72. package/src/hooks/useUsers.ts +27 -0
  73. package/src/index.ts +18 -0
  74. package/src/services/userService.ts +9 -0
  75. package/src/types/index.ts +82 -0
  76. package/src/utils/theme.ts +16 -0
  77. package/dist/components/BottomNav/index.d.ts +0 -10
  78. package/dist/components/BottomNav/index.js +0 -32
  79. package/dist/components/ChatBox/index.d.ts +0 -15
  80. package/dist/components/ChatBox/index.js +0 -228
  81. package/dist/components/ChatButton/index.d.ts +0 -9
  82. package/dist/components/ChatButton/index.js +0 -17
  83. package/dist/components/ChatWindow/index.d.ts +0 -10
  84. package/dist/components/ChatWindow/index.js +0 -286
  85. package/dist/components/HomeView/index.d.ts +0 -12
  86. package/dist/components/HomeView/index.js +0 -51
  87. package/dist/components/UserList/index.d.ts +0 -13
  88. package/dist/components/UserList/index.js +0 -136
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { mergeTheme } from '../../utils/theme';
3
+ export const HomeScreen = ({ config, theme, onNavigate }) => {
4
+ const t = mergeTheme(theme);
5
+ const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
6
+ const showConversation = config.chatType === 'CHAT' || config.chatType === 'BOTH';
7
+ const cards = [
8
+ showSupport && {
9
+ key: 'support',
10
+ title: 'Need Support',
11
+ subtitle: 'We typically reply in a few minutes',
12
+ onClick: () => onNavigate('support'),
13
+ },
14
+ showConversation && {
15
+ key: 'conversation',
16
+ title: 'New Conversation',
17
+ subtitle: 'With your colleague',
18
+ onClick: () => onNavigate('conversation'),
19
+ },
20
+ // Raise Ticket is always shown
21
+ {
22
+ key: 'ticket',
23
+ title: 'Raise Ticket',
24
+ subtitle: 'For major changes',
25
+ onClick: () => onNavigate('ticket'),
26
+ },
27
+ ].filter(Boolean);
28
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: {
29
+ backgroundColor: t.primaryColor,
30
+ padding: '32px 24px 40px',
31
+ flexShrink: 0,
32
+ }, children: [_jsx("h1", { style: {
33
+ margin: '0 0 8px',
34
+ fontSize: '28px',
35
+ fontWeight: 800,
36
+ color: '#fff',
37
+ letterSpacing: '-0.03em',
38
+ fontFamily: t.fontFamily,
39
+ }, children: "Hi there \uD83D\uDC4B" }), _jsx("p", { style: { margin: 0, fontSize: '15px', color: 'rgba(255,255,255,0.85)', fontFamily: t.fontFamily }, children: "Need help? start a conversation:" })] }), _jsx("div", { style: {
40
+ flex: 1,
41
+ overflowY: 'auto',
42
+ padding: '0 16px 16px',
43
+ marginTop: '-24px',
44
+ display: 'flex',
45
+ flexDirection: 'column',
46
+ gap: '10px',
47
+ }, children: cards.map((card, i) => (_jsxs("button", { onClick: card.onClick, style: {
48
+ width: '100%',
49
+ background: '#fff',
50
+ border: 'none',
51
+ borderRadius: '14px',
52
+ padding: '18px 20px',
53
+ display: 'flex',
54
+ alignItems: 'center',
55
+ justifyContent: 'space-between',
56
+ cursor: 'pointer',
57
+ textAlign: 'left',
58
+ boxShadow: '0 2px 12px rgba(0,0,0,0.09)',
59
+ transition: 'transform 0.15s ease, box-shadow 0.15s ease',
60
+ animation: `cw-fadeUp 0.35s ease both`,
61
+ animationDelay: `${i * 0.08}s`,
62
+ fontFamily: t.fontFamily,
63
+ }, onMouseEnter: e => {
64
+ e.currentTarget.style.transform = 'translateY(-2px)';
65
+ e.currentTarget.style.boxShadow = '0 6px 20px rgba(0,0,0,0.13)';
66
+ }, onMouseLeave: e => {
67
+ e.currentTarget.style.transform = 'translateY(0)';
68
+ e.currentTarget.style.boxShadow = '0 2px 12px rgba(0,0,0,0.09)';
69
+ }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: '15px', color: '#1a2332', marginBottom: '3px' }, children: card.title }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1' }, children: card.subtitle })] }), _jsx(SendArrow, { color: t.primaryColor })] }, card.key))) })] }));
70
+ };
71
+ const SendArrow = ({ color }) => (_jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface MaintenanceViewProps {
3
+ primaryColor: string;
4
+ fontFamily: string;
5
+ }
6
+ export declare const MaintenanceView: React.FC<MaintenanceViewProps>;
7
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const MaintenanceView = ({ primaryColor, fontFamily }) => (_jsxs("div", { style: {
3
+ display: 'flex', flexDirection: 'column', alignItems: 'center',
4
+ justifyContent: 'center', height: '100%', padding: '32px',
5
+ fontFamily, textAlign: 'center', gap: 16,
6
+ }, children: [_jsx("div", { style: {
7
+ width: 72, height: 72, borderRadius: '50%',
8
+ backgroundColor: `${primaryColor}15`,
9
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
10
+ }, children: _jsx("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", children: _jsx("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", stroke: primaryColor, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx("h3", { style: { margin: 0, fontSize: '17px', fontWeight: 800, color: '#1a2332', letterSpacing: '-0.02em' }, children: "Under Maintenance" }), _jsx("p", { style: { margin: 0, fontSize: '14px', color: '#7b8fa1', lineHeight: 1.6, maxWidth: 220 }, children: "Chat is under maintenance. We'll be back shortly!" }), _jsxs("span", { style: {
11
+ display: 'inline-flex', alignItems: 'center', gap: 6,
12
+ padding: '6px 14px', borderRadius: 20,
13
+ backgroundColor: '#fff3cd', color: '#856404',
14
+ fontSize: '12px', fontWeight: 700,
15
+ border: '1px solid #ffc10730',
16
+ }, children: [_jsx("span", { style: { width: 6, height: 6, borderRadius: '50%', backgroundColor: '#ffc107', display: 'inline-block' } }), "Temporarily Unavailable"] })] }));
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { ChatUser, ChatWidgetTheme } from '../../types';
3
+ interface RecentChat {
4
+ id: string;
5
+ user: ChatUser;
6
+ lastMessage: string;
7
+ lastTime: Date;
8
+ unread: number;
9
+ }
10
+ interface RecentChatsScreenProps {
11
+ chats: RecentChat[];
12
+ theme?: ChatWidgetTheme;
13
+ onSelectChat: (user: ChatUser) => void;
14
+ }
15
+ export declare const RecentChatsScreen: React.FC<RecentChatsScreenProps>;
16
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { mergeTheme } from '../../utils/theme';
3
+ export const RecentChatsScreen = ({ chats, theme, onSelectChat }) => {
4
+ const t = mergeTheme(theme);
5
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: {
6
+ backgroundColor: t.primaryColor,
7
+ padding: '20px 20px 24px',
8
+ flexShrink: 0,
9
+ }, children: [_jsx("h2", { style: { margin: 0, fontSize: '20px', fontWeight: 800, color: '#fff', fontFamily: t.fontFamily, letterSpacing: '-0.02em' }, children: "Recent Chats" }), _jsx("p", { style: { margin: '4px 0 0', fontSize: '13px', color: 'rgba(255,255,255,0.8)', fontFamily: t.fontFamily }, children: "Your conversation history" })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto' }, children: chats.length === 0 ? (_jsxs("div", { style: { padding: '50px 24px', textAlign: 'center', fontFamily: t.fontFamily }, children: [_jsx("div", { style: { fontSize: '36px', marginBottom: 12 }, children: "\uD83D\uDCAC" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: 6 }, children: "No chats yet" }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1' }, children: "Start a conversation from the home tab" })] })) : (chats.map((chat, i) => {
10
+ const initials = chat.user.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
11
+ const avatarColors = ['#1aaa96', '#2563EB', '#7C3AED', '#D97706', '#DC2626'];
12
+ const bg = avatarColors[chat.user.name.charCodeAt(0) % avatarColors.length];
13
+ const timeStr = new Date(chat.lastTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
14
+ return (_jsxs("button", { onClick: () => onSelectChat(chat.user), style: {
15
+ width: '100%', padding: '14px 20px',
16
+ display: 'flex', alignItems: 'center', gap: '14px',
17
+ background: 'transparent', border: 'none',
18
+ borderBottom: '1px solid #f3f4f6',
19
+ cursor: 'pointer', textAlign: 'left',
20
+ fontFamily: t.fontFamily,
21
+ animation: `cw-fadeUp 0.3s ease both`,
22
+ animationDelay: `${i * 0.05}s`,
23
+ transition: 'background 0.15s',
24
+ }, onMouseEnter: e => e.currentTarget.style.background = '#f8fdfc', onMouseLeave: e => e.currentTarget.style.background = 'transparent', children: [_jsxs("div", { style: {
25
+ width: 46, height: 46, borderRadius: '50%', backgroundColor: bg,
26
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
27
+ color: '#fff', fontWeight: 700, fontSize: '15px', flexShrink: 0,
28
+ position: 'relative',
29
+ }, children: [initials, chat.unread > 0 && (_jsx("span", { style: {
30
+ position: 'absolute', top: -2, right: -2,
31
+ width: 18, height: 18, borderRadius: '50%',
32
+ backgroundColor: '#ff4757', color: '#fff',
33
+ fontSize: '10px', fontWeight: 700,
34
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
35
+ border: '2px solid #fff',
36
+ }, children: chat.unread }))] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginBottom: 3 }, children: [_jsx("span", { style: { fontWeight: 700, fontSize: '14px', color: '#1a2332' }, children: chat.user.name }), _jsx("span", { style: { fontSize: '11px', color: '#b0bec5' }, children: timeStr })] }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: chat.lastMessage })] })] }, chat.id));
37
+ })) })] }));
38
+ };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { BottomTab } from '../../types';
3
+ interface BottomTabsProps {
4
+ active: BottomTab;
5
+ onChange: (tab: BottomTab) => void;
6
+ primaryColor: string;
7
+ fontFamily: string;
8
+ }
9
+ export declare const BottomTabs: React.FC<BottomTabsProps>;
10
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const BottomTabs = ({ active, onChange, primaryColor, fontFamily }) => {
3
+ const tabs = [
4
+ { key: 'home', label: 'Home', Icon: HomeIcon },
5
+ { key: 'chats', label: 'Chats', Icon: ChatsIcon },
6
+ { key: 'tickets', label: 'Tickets', Icon: TicketsIcon },
7
+ ];
8
+ return (_jsx("div", { style: {
9
+ display: 'flex',
10
+ borderTop: '1px solid #eef0f5',
11
+ backgroundColor: '#fff',
12
+ flexShrink: 0,
13
+ }, children: tabs.map(tab => {
14
+ const isActive = active === tab.key;
15
+ return (_jsxs("button", { onClick: () => onChange(tab.key), style: {
16
+ flex: 1, padding: '10px 0 8px',
17
+ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
18
+ background: 'transparent', border: 'none', cursor: 'pointer',
19
+ fontFamily, fontSize: '10px', fontWeight: isActive ? 700 : 500,
20
+ color: isActive ? primaryColor : '#9aa3af',
21
+ transition: 'color 0.15s',
22
+ borderTop: isActive ? `2px solid ${primaryColor}` : '2px solid transparent',
23
+ }, children: [_jsx(tab.Icon, { active: isActive, color: isActive ? primaryColor : '#b0bec5' }), tab.label] }, tab.key));
24
+ }) }));
25
+ };
26
+ // ── Icons ─────────────────────────────────────────────────────────────────────
27
+ const HomeIcon = ({ active, color }) => (_jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z", stroke: color, strokeWidth: active ? 2.2 : 1.8, strokeLinecap: "round", strokeLinejoin: "round", fill: active ? `${color}20` : 'none' }), _jsx("path", { d: "M9 21V12h6v9", stroke: color, strokeWidth: active ? 2.2 : 1.8, strokeLinecap: "round", strokeLinejoin: "round" })] }));
28
+ const ChatsIcon = ({ active, color }) => (_jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z", stroke: color, strokeWidth: active ? 2.2 : 1.8, strokeLinecap: "round", strokeLinejoin: "round", fill: active ? `${color}20` : 'none' }) }));
29
+ const TicketsIcon = ({ active, color }) => (_jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M15 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V9l-4-4z", stroke: color, strokeWidth: active ? 2.2 : 1.8, strokeLinecap: "round", strokeLinejoin: "round", fill: active ? `${color}20` : 'none' }), _jsx("path", { d: "M15 5v4h4M9 13h6M9 17h4", stroke: color, strokeWidth: active ? 2.2 : 1.8, strokeLinecap: "round" })] }));
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { ChatWidgetTheme, Ticket } from '../../types';
3
+ interface TicketScreenProps {
4
+ tickets: Ticket[];
5
+ theme?: ChatWidgetTheme;
6
+ onRaiseTicket: (title: string, description: string) => void;
7
+ }
8
+ export declare const TicketScreen: React.FC<TicketScreenProps>;
9
+ export {};
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { mergeTheme } from '../../utils/theme';
4
+ export const TicketScreen = ({ tickets, theme, onRaiseTicket }) => {
5
+ const t = mergeTheme(theme);
6
+ const [showForm, setShowForm] = useState(false);
7
+ const [title, setTitle] = useState('');
8
+ const [desc, setDesc] = useState('');
9
+ const handleSubmit = () => {
10
+ if (!title.trim())
11
+ return;
12
+ onRaiseTicket(title.trim(), desc.trim());
13
+ setTitle('');
14
+ setDesc('');
15
+ setShowForm(false);
16
+ };
17
+ const statusMeta = {
18
+ 'open': { label: 'Open', bg: '#e6faf8', color: t.primaryColor },
19
+ 'in-progress': { label: 'In Progress', bg: '#fffbeb', color: '#d97706' },
20
+ 'resolved': { label: 'Resolved', bg: '#f0fdf4', color: '#16a34a' },
21
+ 'closed': { label: 'Closed', bg: '#f3f4f6', color: '#6b7280' },
22
+ };
23
+ const priorityMeta = {
24
+ low: { label: '↓ Low', color: '#6b7280' },
25
+ medium: { label: '→ Medium', color: '#d97706' },
26
+ high: { label: '↑ High', color: '#dc2626' },
27
+ };
28
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsx("div", { style: { backgroundColor: t.primaryColor, padding: '20px 20px 24px', flexShrink: 0 }, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("div", { children: [_jsx("h2", { style: { margin: 0, fontSize: '20px', fontWeight: 800, color: '#fff', fontFamily: t.fontFamily, letterSpacing: '-0.02em' }, children: "Tickets" }), _jsxs("p", { style: { margin: '4px 0 0', fontSize: '13px', color: 'rgba(255,255,255,0.8)', fontFamily: t.fontFamily }, children: [tickets.length, " ticket", tickets.length !== 1 ? 's' : '', " raised"] })] }), _jsx("button", { onClick: () => setShowForm(v => !v), style: {
29
+ background: 'rgba(255,255,255,0.25)', border: 'none', borderRadius: '20px',
30
+ padding: '8px 16px', color: '#fff', fontWeight: 700, fontSize: '13px',
31
+ cursor: 'pointer', fontFamily: t.fontFamily, display: 'flex', alignItems: 'center', gap: 5,
32
+ }, children: showForm ? '✕ Cancel' : '+ New Ticket' })] }) }), showForm && (_jsxs("div", { style: {
33
+ padding: '16px 20px',
34
+ borderBottom: '1px solid #eef0f5',
35
+ backgroundColor: '#fafcff',
36
+ animation: 'cw-fadeUp 0.2s ease',
37
+ flexShrink: 0,
38
+ }, children: [_jsx("input", { placeholder: "Ticket title *", value: title, onChange: e => setTitle(e.target.value), style: {
39
+ width: '100%', padding: '10px 14px', borderRadius: 10,
40
+ border: '1.5px solid #e2e8f0', outline: 'none',
41
+ fontFamily: t.fontFamily, fontSize: '14px', color: '#1a2332',
42
+ marginBottom: 10, boxSizing: 'border-box',
43
+ }, onFocus: e => (e.target.style.borderColor = t.primaryColor), onBlur: e => (e.target.style.borderColor = '#e2e8f0') }), _jsx("textarea", { placeholder: "Describe the issue (optional)", value: desc, onChange: e => setDesc(e.target.value), rows: 3, style: {
44
+ width: '100%', padding: '10px 14px', borderRadius: 10,
45
+ border: '1.5px solid #e2e8f0', outline: 'none', resize: 'none',
46
+ fontFamily: t.fontFamily, fontSize: '14px', color: '#1a2332',
47
+ marginBottom: 10, boxSizing: 'border-box',
48
+ }, onFocus: e => (e.target.style.borderColor = t.primaryColor), onBlur: e => (e.target.style.borderColor = '#e2e8f0') }), _jsx("button", { onClick: handleSubmit, disabled: !title.trim(), style: {
49
+ width: '100%', padding: '11px', borderRadius: 10,
50
+ backgroundColor: title.trim() ? t.primaryColor : '#e2e8f0',
51
+ color: title.trim() ? '#fff' : '#9aa3af',
52
+ border: 'none', cursor: title.trim() ? 'pointer' : 'not-allowed',
53
+ fontWeight: 700, fontSize: '14px', fontFamily: t.fontFamily,
54
+ transition: 'background 0.2s',
55
+ }, children: "Submit Ticket" })] })), _jsx("div", { style: { flex: 1, overflowY: 'auto' }, children: tickets.length === 0 ? (_jsxs("div", { style: { padding: '50px 24px', textAlign: 'center', fontFamily: t.fontFamily }, children: [_jsx("div", { style: { fontSize: '36px', marginBottom: 12 }, children: "\uD83C\uDFAB" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: 6 }, children: "No tickets yet" }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1' }, children: "Raise a ticket for major issues or changes" })] })) : (tickets.map((ticket, i) => {
56
+ const sm = statusMeta[ticket.status];
57
+ const pm = priorityMeta[ticket.priority];
58
+ const date = new Date(ticket.createdAt).toLocaleDateString([], { month: 'short', day: 'numeric' });
59
+ return (_jsxs("div", { style: {
60
+ padding: '14px 20px',
61
+ borderBottom: '1px solid #f3f4f6',
62
+ fontFamily: t.fontFamily,
63
+ animation: `cw-fadeUp 0.3s ease both`,
64
+ animationDelay: `${i * 0.05}s`,
65
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 6 }, children: [_jsx("span", { style: { fontWeight: 700, fontSize: '14px', color: '#1a2332', flex: 1, paddingRight: 12 }, children: ticket.title }), _jsx("span", { style: {
66
+ fontSize: '10px', fontWeight: 700, padding: '3px 9px', borderRadius: 20,
67
+ backgroundColor: sm.bg, color: sm.color, whiteSpace: 'nowrap',
68
+ textTransform: 'uppercase', letterSpacing: '0.04em',
69
+ }, children: sm.label })] }), ticket.description && (_jsx("div", { style: { fontSize: '13px', color: '#7b8fa1', marginBottom: 8, lineHeight: 1.5 }, children: ticket.description })), _jsxs("div", { style: { display: 'flex', gap: 12, fontSize: '11px', color: '#b0bec5' }, children: [_jsx("span", { style: { color: pm.color, fontWeight: 600 }, children: pm.label }), _jsxs("span", { children: ["#", ticket.id.slice(-6).toUpperCase()] }), _jsx("span", { children: date })] })] }, ticket.id));
70
+ })) })] }));
71
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { ChatUser, ChatWidgetTheme, UserListContext } from '../../types';
3
+ interface UserListScreenProps {
4
+ context: UserListContext;
5
+ users: ChatUser[];
6
+ loading: boolean;
7
+ error: string | null;
8
+ theme?: ChatWidgetTheme;
9
+ onBack: () => void;
10
+ onSelectUser: (user: ChatUser) => void;
11
+ }
12
+ export declare const UserListScreen: React.FC<UserListScreenProps>;
13
+ export {};
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { mergeTheme } from '../../utils/theme';
3
+ export const UserListScreen = ({ context, users, loading, error, theme, onBack, onSelectUser, }) => {
4
+ const t = mergeTheme(theme);
5
+ const title = context === 'support' ? 'Need Support' : 'New Conversation';
6
+ const subtitle = context === 'support'
7
+ ? 'Choose a support agent'
8
+ : 'Choose a colleague to chat with';
9
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideInRight 0.25s ease' }, children: [_jsxs("div", { style: {
10
+ backgroundColor: t.primaryColor,
11
+ padding: '16px 20px',
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ gap: '12px',
15
+ flexShrink: 0,
16
+ }, children: [_jsx("button", { onClick: onBack, style: {
17
+ background: 'rgba(255,255,255,0.2)',
18
+ border: 'none',
19
+ borderRadius: '50%',
20
+ width: '34px',
21
+ height: '34px',
22
+ display: 'flex',
23
+ alignItems: 'center',
24
+ justifyContent: 'center',
25
+ cursor: 'pointer',
26
+ flexShrink: 0,
27
+ }, children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: '16px', color: '#fff', fontFamily: t.fontFamily }, children: title }), _jsx("div", { style: { fontSize: '12px', color: 'rgba(255,255,255,0.8)', fontFamily: t.fontFamily }, children: subtitle })] })] }), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '8px 0' }, children: [loading && _jsx(UserListSkeleton, {}), error && _jsx(ErrorState, { message: error, color: t.primaryColor, font: t.fontFamily }), !loading && !error && users.length === 0 && (_jsx(EmptyState, { color: t.primaryColor, font: t.fontFamily })), !loading && !error && users.map((user, i) => (_jsx(UserRow, { user: user, index: i, primaryColor: t.primaryColor, fontFamily: t.fontFamily, onClick: () => onSelectUser(user) }, user.uid)))] })] }));
28
+ };
29
+ // ── Sub-components ─────────────────────────────────────────────────────────────
30
+ const UserRow = ({ user, index, primaryColor, fontFamily, onClick }) => {
31
+ const initials = user.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
32
+ const avatarColors = ['#1aaa96', '#2563EB', '#7C3AED', '#D97706', '#DC2626', '#059669'];
33
+ const bg = avatarColors[user.name.charCodeAt(0) % avatarColors.length];
34
+ return (_jsxs("button", { onClick: onClick, style: {
35
+ width: '100%',
36
+ padding: '14px 20px',
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ gap: '14px',
40
+ background: 'transparent',
41
+ border: 'none',
42
+ borderBottom: '1px solid #f3f4f6',
43
+ cursor: 'pointer',
44
+ textAlign: 'left',
45
+ fontFamily,
46
+ animation: 'cw-fadeUp 0.3s ease both',
47
+ animationDelay: `${index * 0.05}s`,
48
+ transition: 'background 0.15s',
49
+ }, onMouseEnter: e => e.currentTarget.style.background = '#f8fdfc', onMouseLeave: e => e.currentTarget.style.background = 'transparent', children: [_jsx("div", { style: {
50
+ width: '46px', height: '46px', borderRadius: '50%',
51
+ backgroundColor: bg, flexShrink: 0,
52
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
53
+ color: '#fff', fontWeight: 700, fontSize: '15px',
54
+ }, children: initials }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: '14px', color: '#1a2332', marginBottom: '2px' }, children: user.name }), _jsx("div", { style: { fontSize: '12px', color: '#7b8fa1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: user.project || user.email })] }), _jsx("span", { style: {
55
+ fontSize: '10px', fontWeight: 700, padding: '3px 9px',
56
+ borderRadius: '20px', textTransform: 'uppercase', letterSpacing: '0.05em',
57
+ backgroundColor: user.type === 'developer' ? '#e6faf8' : '#eff6ff',
58
+ color: user.type === 'developer' ? primaryColor : '#2563EB',
59
+ border: `1px solid ${user.type === 'developer' ? primaryColor + '30' : '#2563eb30'}`,
60
+ }, children: user.type === 'developer' ? 'Dev' : 'User' })] }));
61
+ };
62
+ const UserListSkeleton = () => (_jsx(_Fragment, { children: [1, 2, 3, 4].map(i => (_jsxs("div", { style: { padding: '14px 20px', display: 'flex', gap: '14px', alignItems: 'center' }, children: [_jsx("div", { style: { width: 46, height: 46, borderRadius: '50%', background: '#f0f0f0', flexShrink: 0 } }), _jsxs("div", { style: { flex: 1 }, children: [_jsx("div", { style: { height: 13, width: '55%', background: '#f0f0f0', borderRadius: 6, marginBottom: 7 } }), _jsx("div", { style: { height: 11, width: '38%', background: '#f0f0f0', borderRadius: 6 } })] })] }, i))) }));
63
+ const ErrorState = ({ message, color, font }) => (_jsxs("div", { style: { padding: '40px 24px', textAlign: 'center', fontFamily: font }, children: [_jsx("div", { style: { fontSize: '32px', marginBottom: '10px' }, children: "\u26A0\uFE0F" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: '6px' }, children: "Could not load users" }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1' }, children: message })] }));
64
+ const EmptyState = ({ color, font }) => (_jsxs("div", { style: { padding: '40px 24px', textAlign: 'center', fontFamily: font }, children: [_jsx("div", { style: { fontSize: '32px', marginBottom: '10px' }, children: "\uD83D\uDC65" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: '6px' }, children: "No users available" }), _jsx("div", { style: { fontSize: '13px', color: '#7b8fa1' }, children: "Check back later" })] }));
@@ -0,0 +1,3 @@
1
+ import { ChatConfig } from '../types';
2
+ export declare function loadChatConfig(): ChatConfig;
3
+ export declare function buildUserListUrl(config: ChatConfig): string;
@@ -0,0 +1,38 @@
1
+ function getEnv(key) {
2
+ var _a, _b, _c;
3
+ if (typeof process !== 'undefined' && process.env) {
4
+ return ((_c = (_b = (_a = process.env[`NEXT_PUBLIC_${key}`]) !== null && _a !== void 0 ? _a : process.env[`REACT_APP_${key}`]) !== null && _b !== void 0 ? _b : process.env[key]) !== null && _c !== void 0 ? _c : undefined);
5
+ }
6
+ return undefined;
7
+ }
8
+ function validateStatus(v) {
9
+ if (v === 'ACTIVE' || v === 'DISABLE' || v === 'MAINTENANCE')
10
+ return v;
11
+ console.warn(`[ChatWidget] Invalid CHAT_STATUS "${v}". Defaulting to DISABLE.`);
12
+ return 'DISABLE';
13
+ }
14
+ function validateChatType(v) {
15
+ if (v === 'SUPPORT' || v === 'CHAT' || v === 'BOTH')
16
+ return v;
17
+ console.warn(`[ChatWidget] Invalid CHAT_TYPE "${v}". Defaulting to SUPPORT.`);
18
+ return 'SUPPORT';
19
+ }
20
+ export function loadChatConfig() {
21
+ var _a, _b;
22
+ const portStr = getEnv('CHAT_HOST_PORT');
23
+ const hostPort = portStr ? parseInt(portStr, 10) : null;
24
+ return {
25
+ hostUrl: (_a = getEnv('CHAT_HOST_URL')) !== null && _a !== void 0 ? _a : 'http://localhost',
26
+ hostPort,
27
+ userListEndpoint: (_b = getEnv('CHAT_USER_LIST')) !== null && _b !== void 0 ? _b : 'api/users',
28
+ status: validateStatus(getEnv('CHAT_STATUS')),
29
+ chatType: validateChatType(getEnv('CHAT_TYPE')),
30
+ };
31
+ }
32
+ export function buildUserListUrl(config) {
33
+ const base = config.hostUrl.replace(/\/$/, '');
34
+ const endpoint = config.userListEndpoint.replace(/^\//, '');
35
+ // Port is optional
36
+ const portPart = config.hostPort ? `:${config.hostPort}` : '';
37
+ return `${base}${portPart}/${endpoint}`;
38
+ }
@@ -0,0 +1,8 @@
1
+ import { ChatMessage, ChatUser } from '../types';
2
+ export declare function useChat(): {
3
+ messages: ChatMessage[];
4
+ activeUser: ChatUser | null;
5
+ selectUser: (user: ChatUser) => void;
6
+ sendMessage: (text: string) => void;
7
+ clearChat: () => void;
8
+ };
@@ -0,0 +1,26 @@
1
+ import { useState, useCallback } from 'react';
2
+ export function useChat() {
3
+ const [messages, setMessages] = useState([]);
4
+ const [activeUser, setActiveUser] = useState(null);
5
+ const selectUser = useCallback((user) => {
6
+ setActiveUser(user);
7
+ setMessages([]);
8
+ // TODO: socket.emit('join', user.uid); socket.on('message', handler)
9
+ }, []);
10
+ const sendMessage = useCallback((text) => {
11
+ if (!activeUser || !text.trim())
12
+ return;
13
+ const msg = {
14
+ id: `msg_${Date.now()}`,
15
+ senderId: 'me',
16
+ receiverId: activeUser.uid,
17
+ text: text.trim(),
18
+ timestamp: new Date(),
19
+ status: 'sent',
20
+ };
21
+ setMessages(prev => [...prev, msg]);
22
+ // TODO: socket.emit('message', msg)
23
+ }, [activeUser]);
24
+ const clearChat = useCallback(() => { setMessages([]); setActiveUser(null); }, []);
25
+ return { messages, activeUser, selectUser, sendMessage, clearChat };
26
+ }
@@ -0,0 +1,7 @@
1
+ import { ChatUser, UserType } from '../types';
2
+ export declare function useUsers(url: string, filterType?: UserType, enabled?: boolean): {
3
+ users: ChatUser[];
4
+ loading: boolean;
5
+ error: string | null;
6
+ refetch: () => Promise<void>;
7
+ };
@@ -0,0 +1,26 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { fetchUsers } from '../services/userService';
3
+ export function useUsers(url, filterType, enabled = true) {
4
+ const [users, setUsers] = useState([]);
5
+ const [loading, setLoading] = useState(false);
6
+ const [error, setError] = useState(null);
7
+ const load = useCallback(async () => {
8
+ if (!enabled)
9
+ return;
10
+ setLoading(true);
11
+ setError(null);
12
+ try {
13
+ const data = await fetchUsers(url);
14
+ setUsers(filterType ? data.filter(u => u.type === filterType) : data);
15
+ }
16
+ catch (e) {
17
+ setError(e instanceof Error ? e.message : 'Unknown error');
18
+ setUsers([]);
19
+ }
20
+ finally {
21
+ setLoading(false);
22
+ }
23
+ }, [url, filterType, enabled]);
24
+ useEffect(() => { load(); }, [load]);
25
+ return { users, loading, error, refetch: load };
26
+ }
@@ -0,0 +1,14 @@
1
+ export { ChatWidget, default } from './components/ChatWidget';
2
+ export { HomeScreen } from './components/HomeScreen';
3
+ export { UserListScreen } from './components/UserListScreen';
4
+ export { ChatScreen } from './components/ChatScreen';
5
+ export { RecentChatsScreen } from './components/RecentChatsScreen';
6
+ export { TicketScreen } from './components/TicketScreen';
7
+ export { MaintenanceView } from './components/MaintenanceView';
8
+ export { BottomTabs } from './components/Tabs/BottomTabs';
9
+ export { useUsers } from './hooks/useUsers';
10
+ 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';
@@ -0,0 +1,13 @@
1
+ export { ChatWidget, default } from './components/ChatWidget';
2
+ export { HomeScreen } from './components/HomeScreen';
3
+ export { UserListScreen } from './components/UserListScreen';
4
+ export { ChatScreen } from './components/ChatScreen';
5
+ export { RecentChatsScreen } from './components/RecentChatsScreen';
6
+ export { TicketScreen } from './components/TicketScreen';
7
+ export { MaintenanceView } from './components/MaintenanceView';
8
+ export { BottomTabs } from './components/Tabs/BottomTabs';
9
+ export { useUsers } from './hooks/useUsers';
10
+ export { useChat } from './hooks/useChat';
11
+ export { loadChatConfig, buildUserListUrl } from './config';
12
+ export { fetchUsers } from './services/userService';
13
+ export { defaultTheme, mergeTheme } from './utils/theme';
@@ -0,0 +1,2 @@
1
+ import { ChatUser } from '../types';
2
+ export declare function fetchUsers(url: string): Promise<ChatUser[]>;
@@ -0,0 +1,9 @@
1
+ export async function fetchUsers(url) {
2
+ const res = await fetch(url, { headers: { 'Content-Type': 'application/json' } });
3
+ if (!res.ok)
4
+ throw new Error(`Failed to fetch users: ${res.status}`);
5
+ const data = await res.json();
6
+ if (!Array.isArray(data))
7
+ throw new Error('User API did not return an array');
8
+ return data;
9
+ }
@@ -0,0 +1,59 @@
1
+ export type ChatStatus = 'ACTIVE' | 'DISABLE' | 'MAINTENANCE';
2
+ export type ChatType = 'SUPPORT' | 'CHAT' | 'BOTH';
3
+ export type UserType = 'developer' | 'user';
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';
8
+ export interface ChatUser {
9
+ name: string;
10
+ uid: string;
11
+ email: string;
12
+ mobile: string;
13
+ project: string;
14
+ type: UserType;
15
+ }
16
+ export interface ChatMessage {
17
+ id: string;
18
+ senderId: string;
19
+ receiverId: string;
20
+ text: string;
21
+ timestamp: Date;
22
+ status: 'sent' | 'delivered' | 'read';
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
+ }
40
+ export interface ChatConfig {
41
+ hostUrl: string;
42
+ hostPort: number | null;
43
+ userListEndpoint: string;
44
+ status: ChatStatus;
45
+ chatType: ChatType;
46
+ }
47
+ export interface ChatWidgetTheme {
48
+ primaryColor?: string;
49
+ fontFamily?: string;
50
+ buttonColor?: string;
51
+ buttonTextColor?: string;
52
+ buttonLabel?: string;
53
+ buttonPosition?: 'bottom-right' | 'bottom-left';
54
+ borderRadius?: string;
55
+ backgroundColor?: string;
56
+ }
57
+ export interface ChatWidgetProps {
58
+ theme?: ChatWidgetTheme;
59
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { ChatWidgetTheme } from '../types';
2
+ export declare const defaultTheme: Required<ChatWidgetTheme>;
3
+ export declare function mergeTheme(custom?: ChatWidgetTheme): Required<ChatWidgetTheme>;
@@ -0,0 +1,13 @@
1
+ export const defaultTheme = {
2
+ primaryColor: '#1aaa96',
3
+ fontFamily: "'DM Sans', 'Segoe UI', sans-serif",
4
+ buttonColor: '#1aaa96',
5
+ buttonTextColor: '#ffffff',
6
+ buttonLabel: 'Chat with us',
7
+ buttonPosition: 'bottom-right',
8
+ borderRadius: '16px',
9
+ backgroundColor: '#ffffff',
10
+ };
11
+ export function mergeTheme(custom) {
12
+ return Object.assign(Object.assign({}, defaultTheme), custom);
13
+ }