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,93 +1,38 @@
1
- /**
2
- * Safely reads an environment variable.
3
- * Supports both Next.js (NEXT_PUBLIC_) and React (REACT_APP_) prefixes.
4
- * Falls back gracefully if running in SSR or non-browser environments.
5
- */
6
- function getEnvVar(key) {
1
+ function getEnv(key) {
7
2
  var _a, _b, _c;
8
- const nextKey = `NEXT_PUBLIC_${key}`;
9
- const reactKey = `REACT_APP_${key}`;
10
- // Next.js / Node.js environment
11
3
  if (typeof process !== 'undefined' && process.env) {
12
- return ((_c = (_b = (_a = process.env[nextKey]) !== null && _a !== void 0 ? _a : process.env[reactKey]) !== null && _b !== void 0 ? _b : process.env[key]) !== null && _c !== void 0 ? _c : undefined);
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);
13
5
  }
14
6
  return undefined;
15
7
  }
16
- function validateStatus(value) {
17
- const valid = ['ACTIVE', 'DISABLE', 'MAINTENANCE'];
18
- if (value && valid.includes(value)) {
19
- return value;
20
- }
21
- console.warn(`[ChatWidget] Invalid CHAT_STATUS "${value}". Defaulting to "DISABLE".`);
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.`);
22
12
  return 'DISABLE';
23
13
  }
24
- function validateChatType(value) {
25
- const valid = ['SUPPORT', 'CHAT', 'BOTH'];
26
- if (value && valid.includes(value)) {
27
- return value;
28
- }
29
- console.warn(`[ChatWidget] Invalid CHAT_TYPE "${value}". Defaulting to "SUPPORT".`);
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.`);
30
18
  return 'SUPPORT';
31
19
  }
32
- function parseBool(value, defaultValue) {
33
- if (value === undefined || value === '')
34
- return defaultValue;
35
- const v = value.toLowerCase().trim();
36
- if (v === 'true' || v === '1' || v === 'yes')
37
- return true;
38
- if (v === 'false' || v === '0' || v === 'no')
39
- return false;
40
- return defaultValue;
41
- }
42
- function parsePositiveInt(value, fallback) {
43
- if (value === undefined || value === '')
44
- return fallback;
45
- const n = parseInt(value, 10);
46
- return Number.isFinite(n) && n > 0 ? n : fallback;
47
- }
48
- function parseOptionalPort(value) {
49
- if (value === undefined || value.trim() === '')
50
- return undefined;
51
- const n = parseInt(value, 10);
52
- return Number.isFinite(n) && n > 0 ? n : undefined;
53
- }
54
- function parseSizeRatio(value, fallback) {
55
- if (value === undefined || value === '')
56
- return fallback;
57
- const n = parseFloat(value);
58
- if (!Number.isFinite(n))
59
- return fallback;
60
- return Math.min(1, Math.max(0, n));
61
- }
62
20
  export function loadChatConfig() {
63
21
  var _a, _b;
64
- const hostUrl = (_a = getEnvVar('CHAT_HOST_URL')) !== null && _a !== void 0 ? _a : 'http://localhost';
65
- const hostPort = parseOptionalPort(getEnvVar('CHAT_HOST_PORT'));
66
- const userListEndpoint = (_b = getEnvVar('CHAT_USER_LIST')) !== null && _b !== void 0 ? _b : 'api/users';
67
- const status = validateStatus(getEnvVar('CHAT_STATUS'));
68
- const chatType = validateChatType(getEnvVar('CHAT_TYPE'));
69
- const showNeedSupport = parseBool(getEnvVar('CHAT_SHOW_NEED_SUPPORT'), true);
70
- const showNewConversation = parseBool(getEnvVar('CHAT_SHOW_NEW_CONVERSATION'), true);
22
+ const portStr = getEnv('CHAT_HOST_PORT');
23
+ const hostPort = portStr ? parseInt(portStr, 10) : null;
71
24
  return {
72
- hostUrl,
25
+ hostUrl: (_a = getEnv('CHAT_HOST_URL')) !== null && _a !== void 0 ? _a : 'http://localhost',
73
26
  hostPort,
74
- userListEndpoint,
75
- status,
76
- chatType,
77
- showNeedSupport,
78
- showNewConversation,
79
- widgetMinWidth: parsePositiveInt(getEnvVar('CHAT_WIDGET_MIN_WIDTH'), 320),
80
- widgetMaxWidth: parsePositiveInt(getEnvVar('CHAT_WIDGET_MAX_WIDTH'), 720),
81
- widgetMinHeight: parsePositiveInt(getEnvVar('CHAT_WIDGET_MIN_HEIGHT'), 420),
82
- widgetMaxHeight: parsePositiveInt(getEnvVar('CHAT_WIDGET_MAX_HEIGHT'), 720),
83
- widgetDefaultSize: parseSizeRatio(getEnvVar('CHAT_WIDGET_DEFAULT_SIZE'), 0.45),
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')),
84
30
  };
85
31
  }
86
32
  export function buildUserListUrl(config) {
87
33
  const base = config.hostUrl.replace(/\/$/, '');
88
34
  const endpoint = config.userListEndpoint.replace(/^\//, '');
89
- if (config.hostPort !== undefined) {
90
- return `${base}:${config.hostPort}/${endpoint}`;
91
- }
92
- return `${base}/${endpoint}`;
35
+ // Port is optional
36
+ const portPart = config.hostPort ? `:${config.hostPort}` : '';
37
+ return `${base}${portPart}/${endpoint}`;
93
38
  }
@@ -1,12 +1,8 @@
1
- import { ChatMessage, ChatUser, RecentChat } from '../types';
2
- interface UseChatReturn {
1
+ import { ChatMessage, ChatUser } from '../types';
2
+ export declare function useChat(): {
3
3
  messages: ChatMessage[];
4
4
  activeUser: ChatUser | null;
5
- recentChats: RecentChat[];
6
5
  selectUser: (user: ChatUser) => void;
7
6
  sendMessage: (text: string) => void;
8
7
  clearChat: () => void;
9
- openRecent: (user: ChatUser) => void;
10
- }
11
- export declare function useChat(): UseChatReturn;
12
- export {};
8
+ };
@@ -2,47 +2,25 @@ import { useState, useCallback } from 'react';
2
2
  export function useChat() {
3
3
  const [messages, setMessages] = useState([]);
4
4
  const [activeUser, setActiveUser] = useState(null);
5
- const [recentChats, setRecentChats] = useState([]);
6
- const upsertRecent = useCallback((user, lastMessage) => {
7
- const updatedAt = new Date();
8
- setRecentChats((prev) => {
9
- const rest = prev.filter((r) => r.user.uid !== user.uid);
10
- return [{ user, lastMessage, updatedAt }, ...rest].slice(0, 50);
11
- });
12
- }, []);
13
5
  const selectUser = useCallback((user) => {
14
6
  setActiveUser(user);
15
7
  setMessages([]);
16
- }, []);
17
- const openRecent = useCallback((user) => {
18
- setActiveUser(user);
19
- setMessages([]);
8
+ // TODO: socket.emit('join', user.uid); socket.on('message', handler)
20
9
  }, []);
21
10
  const sendMessage = useCallback((text) => {
22
11
  if (!activeUser || !text.trim())
23
12
  return;
24
- const newMsg = {
25
- id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
13
+ const msg = {
14
+ id: `msg_${Date.now()}`,
26
15
  senderId: 'me',
27
16
  receiverId: activeUser.uid,
28
17
  text: text.trim(),
29
18
  timestamp: new Date(),
30
19
  status: 'sent',
31
20
  };
32
- setMessages((prev) => [...prev, newMsg]);
33
- upsertRecent(activeUser, text.trim());
34
- }, [activeUser, upsertRecent]);
35
- const clearChat = useCallback(() => {
36
- setMessages([]);
37
- setActiveUser(null);
38
- }, []);
39
- return {
40
- messages,
41
- activeUser,
42
- recentChats,
43
- selectUser,
44
- sendMessage,
45
- clearChat,
46
- openRecent,
47
- };
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 };
48
26
  }
@@ -1,14 +1,7 @@
1
1
  import { ChatUser, UserType } from '../types';
2
- interface UseUsersOptions {
3
- url: string;
4
- filterType?: UserType;
5
- enabled?: boolean;
6
- }
7
- interface UseUsersReturn {
2
+ export declare function useUsers(url: string, filterType?: UserType, enabled?: boolean): {
8
3
  users: ChatUser[];
9
4
  loading: boolean;
10
5
  error: string | null;
11
- refetch: () => void;
12
- }
13
- export declare function useUsers({ url, filterType, enabled, }: UseUsersOptions): UseUsersReturn;
14
- export {};
6
+ refetch: () => Promise<void>;
7
+ };
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
2
  import { fetchUsers } from '../services/userService';
3
- export function useUsers({ url, filterType, enabled = true, }) {
3
+ export function useUsers(url, filterType, enabled = true) {
4
4
  const [users, setUsers] = useState([]);
5
5
  const [loading, setLoading] = useState(false);
6
6
  const [error, setError] = useState(null);
@@ -11,22 +11,16 @@ export function useUsers({ url, filterType, enabled = true, }) {
11
11
  setError(null);
12
12
  try {
13
13
  const data = await fetchUsers(url);
14
- const filtered = filterType
15
- ? data.filter((u) => u.type === filterType)
16
- : data;
17
- setUsers(filtered);
14
+ setUsers(filterType ? data.filter(u => u.type === filterType) : data);
18
15
  }
19
- catch (err) {
20
- const message = err instanceof Error ? err.message : 'Unknown error occurred.';
21
- setError(message);
16
+ catch (e) {
17
+ setError(e instanceof Error ? e.message : 'Unknown error');
22
18
  setUsers([]);
23
19
  }
24
20
  finally {
25
21
  setLoading(false);
26
22
  }
27
23
  }, [url, filterType, enabled]);
28
- useEffect(() => {
29
- load();
30
- }, [load]);
24
+ useEffect(() => { load(); }, [load]);
31
25
  return { users, loading, error, refetch: load };
32
26
  }
package/dist/index.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- export { ChatWidget } from './components/ChatWidget';
2
- export { default } from './components/ChatWidget';
3
- export { ChatButton } from './components/ChatButton';
4
- export { ChatWindow } from './components/ChatWindow';
5
- export { UserList } from './components/UserList';
6
- export { ChatBox } from './components/ChatBox';
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
7
  export { MaintenanceView } from './components/MaintenanceView';
8
+ export { BottomTabs } from './components/Tabs/BottomTabs';
8
9
  export { useUsers } from './hooks/useUsers';
9
10
  export { useChat } from './hooks/useChat';
10
11
  export { loadChatConfig, buildUserListUrl } from './config';
11
12
  export { fetchUsers } from './services/userService';
12
- export type { ChatUser, ChatMessage, ChatConfig, ChatWidgetTheme, ChatWidgetProps, ChatStatus, ChatType, UserType, TabType, BottomNavTab, HomeFlow, RaisedTicket, RecentChat, } from './types';
13
13
  export { defaultTheme, mergeTheme } from './utils/theme';
14
+ export type { ChatUser, ChatMessage, ChatConfig, ChatWidgetTheme, ChatWidgetProps, ChatStatus, ChatType, UserType, Screen, BottomTab, UserListContext, Ticket, } from './types';
package/dist/index.js CHANGED
@@ -1,18 +1,13 @@
1
- // Main component
2
- export { ChatWidget } from './components/ChatWidget';
3
- export { default } from './components/ChatWidget';
4
- // Sub-components (for advanced customization)
5
- export { ChatButton } from './components/ChatButton';
6
- export { ChatWindow } from './components/ChatWindow';
7
- export { UserList } from './components/UserList';
8
- export { ChatBox } from './components/ChatBox';
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';
9
7
  export { MaintenanceView } from './components/MaintenanceView';
10
- // Hooks
8
+ export { BottomTabs } from './components/Tabs/BottomTabs';
11
9
  export { useUsers } from './hooks/useUsers';
12
10
  export { useChat } from './hooks/useChat';
13
- // Config utilities
14
11
  export { loadChatConfig, buildUserListUrl } from './config';
15
- // Services
16
12
  export { fetchUsers } from './services/userService';
17
- // Theme utilities
18
13
  export { defaultTheme, mergeTheme } from './utils/theme';
@@ -1,7 +1,2 @@
1
1
  import { ChatUser } from '../types';
2
- export interface FetchUsersResult {
3
- data: ChatUser[] | null;
4
- error: string | null;
5
- loading: boolean;
6
- }
7
2
  export declare function fetchUsers(url: string): Promise<ChatUser[]>;
@@ -1,16 +1,9 @@
1
1
  export async function fetchUsers(url) {
2
- const response = await fetch(url, {
3
- method: 'GET',
4
- headers: {
5
- 'Content-Type': 'application/json',
6
- },
7
- });
8
- if (!response.ok) {
9
- throw new Error(`[ChatWidget] Failed to fetch users: ${response.status} ${response.statusText}`);
10
- }
11
- const data = await response.json();
12
- if (!Array.isArray(data)) {
13
- throw new Error('[ChatWidget] User list API did not return an array.');
14
- }
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');
15
8
  return data;
16
9
  }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { ChatMessage, ChatUser, ChatWidgetTheme } from '../../types';
3
+ interface ChatScreenProps {
4
+ activeUser: ChatUser;
5
+ messages: ChatMessage[];
6
+ onSend: (text: string) => void;
7
+ onBack: () => void;
8
+ onClose: () => void;
9
+ theme?: ChatWidgetTheme;
10
+ }
11
+ export declare const ChatScreen: React.FC<ChatScreenProps>;
12
+ export {};
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from 'react';
3
+ import { mergeTheme } from '../../utils/theme';
4
+ export const ChatScreen = ({ activeUser, messages, onSend, onBack, onClose, theme, }) => {
5
+ const t = mergeTheme(theme);
6
+ const [text, setText] = useState('');
7
+ const endRef = useRef(null);
8
+ const inputRef = useRef(null);
9
+ useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
10
+ const handleSend = () => {
11
+ var _a;
12
+ if (!text.trim())
13
+ return;
14
+ onSend(text);
15
+ setText('');
16
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
17
+ };
18
+ const handleKey = (e) => {
19
+ if (e.key === 'Enter' && !e.shiftKey) {
20
+ e.preventDefault();
21
+ handleSend();
22
+ }
23
+ };
24
+ const initials = activeUser.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
25
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideInRight 0.25s ease' }, children: [_jsxs("div", { style: {
26
+ backgroundColor: t.primaryColor,
27
+ padding: '14px 18px',
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ gap: '10px',
31
+ flexShrink: 0,
32
+ }, children: [_jsx("button", { onClick: onBack, style: iconBtnStyle, 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" }) }) }), _jsx("div", { style: {
33
+ width: 36, height: 36, borderRadius: '50%',
34
+ backgroundColor: 'rgba(255,255,255,0.25)',
35
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
36
+ fontWeight: 700, fontSize: '13px', color: '#fff', flexShrink: 0,
37
+ }, children: initials }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: '14px', color: '#fff', fontFamily: t.fontFamily }, children: activeUser.name }), _jsxs("div", { style: { fontSize: '11px', color: 'rgba(255,255,255,0.8)', display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("span", { style: { width: 6, height: 6, borderRadius: '50%', backgroundColor: '#a8f0c6', display: 'inline-block' } }), "Online"] })] }), _jsx("button", { onClick: onClose, style: iconBtnStyle, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })] }), _jsxs("div", { style: {
38
+ flex: 1, overflowY: 'auto', padding: '18px 16px',
39
+ display: 'flex', flexDirection: 'column', gap: '10px',
40
+ backgroundColor: '#f7f8fc',
41
+ }, children: [messages.length === 0 && (_jsxs("div", { style: {
42
+ margin: 'auto', textAlign: 'center',
43
+ fontSize: '13px', color: '#b0bec5',
44
+ fontFamily: t.fontFamily,
45
+ }, children: [_jsx("div", { style: { fontSize: '28px', marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hi to ", activeUser.name, "!"] })), messages.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: t.primaryColor, font: t.fontFamily }, msg.id))), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: {
46
+ borderTop: '1px solid #eef0f5',
47
+ padding: '10px 14px',
48
+ backgroundColor: '#fff',
49
+ display: 'flex',
50
+ alignItems: 'flex-end',
51
+ gap: '10px',
52
+ flexShrink: 0,
53
+ }, children: [_jsx("textarea", { ref: inputRef, value: text, onChange: e => setText(e.target.value), onKeyDown: handleKey, placeholder: "Type and press [enter]..", rows: 1, style: {
54
+ flex: 1, resize: 'none', border: 'none', outline: 'none',
55
+ fontFamily: t.fontFamily, fontSize: '14px', lineHeight: '1.5',
56
+ maxHeight: '80px', overflowY: 'auto', color: '#1a2332',
57
+ padding: '6px 0', backgroundColor: 'transparent',
58
+ } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }, children: [_jsx(IconBtn, { onClick: () => { }, title: "Reaction", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("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" }), _jsx("path", { d: "M8 15s1.5 2 4 2 4-2 4-2", stroke: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round" })] }) }), _jsx(IconBtn, { onClick: () => { }, title: "Attach", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("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" }) }) }), _jsx(IconBtn, { onClick: () => { }, title: "Emoji", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#9aa3af", strokeWidth: "1.8" }), _jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2", stroke: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("line", { x1: "9", y1: "9", x2: "9.01", y2: "9", stroke: "#9aa3af", strokeWidth: "2.5", strokeLinecap: "round" }), _jsx("line", { x1: "15", y1: "9", x2: "15.01", y2: "9", stroke: "#9aa3af", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), text.trim() && (_jsx("button", { onClick: handleSend, style: {
59
+ width: 36, height: 36, borderRadius: '50%',
60
+ backgroundColor: t.primaryColor, border: 'none',
61
+ cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
62
+ transition: 'transform 0.15s',
63
+ }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] })] })] }));
64
+ };
65
+ const Bubble = ({ msg, primaryColor, font }) => {
66
+ const isMe = msg.senderId === 'me';
67
+ const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
68
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }, children: [_jsx("div", { style: {
69
+ maxWidth: '75%', padding: '10px 14px',
70
+ borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
71
+ backgroundColor: isMe ? primaryColor : '#fff',
72
+ color: isMe ? '#fff' : '#1a2332',
73
+ fontSize: '14px', lineHeight: '1.5',
74
+ boxShadow: '0 1px 4px rgba(0,0,0,0.07)',
75
+ fontFamily: font, wordBreak: 'break-word',
76
+ }, children: msg.text }), _jsx("span", { style: { fontSize: '11px', color: '#b0bec5', padding: '0 4px' }, children: time })] }));
77
+ };
78
+ const IconBtn = ({ onClick, title, children }) => (_jsx("button", { onClick: onClick, title: title, style: { background: 'none', border: 'none', cursor: 'pointer', padding: '4px', display: 'flex', alignItems: 'center', borderRadius: '6px' }, onMouseEnter: e => e.currentTarget.style.background = '#f3f4f6', onMouseLeave: e => e.currentTarget.style.background = 'none', children: children }));
79
+ const iconBtnStyle = {
80
+ background: 'rgba(255,255,255,0.2)', border: 'none', borderRadius: '50%',
81
+ width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
82
+ cursor: 'pointer', flexShrink: 0,
83
+ };
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { ChatWidgetProps } from '../types';
3
+ export declare const ChatWidget: React.FC<ChatWidgetProps>;
4
+ export default ChatWidget;
@@ -0,0 +1,141 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { loadChatConfig, buildUserListUrl } from '../config';
5
+ import { mergeTheme } from '../utils/theme';
6
+ import { useUsers } from '../hooks/useUsers';
7
+ import { useChat } from '../hooks/useChat';
8
+ // Screens
9
+ import { HomeScreen } from './HomeScreen';
10
+ import { UserListScreen } from './UserListScreen';
11
+ import { ChatScreen } from './ChatScreen';
12
+ import { RecentChatsScreen } from './RecentChatsScreen';
13
+ import { TicketScreen } from './TicketScreen';
14
+ import { MaintenanceView } from './MaintenanceView';
15
+ import { BottomTabs } from './Tabs/BottomTabs';
16
+ export const ChatWidget = ({ theme }) => {
17
+ const [isMounted, setIsMounted] = useState(false);
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ const [isMaximized, setIsMaximized] = useState(false);
20
+ const [activeTab, setActiveTab] = useState('home');
21
+ const [screen, setScreen] = useState('home');
22
+ const [userListCtx, setUserListCtx] = useState('support');
23
+ const [tickets, setTickets] = useState([]);
24
+ // SSR guard
25
+ useEffect(() => { setIsMounted(true); }, []);
26
+ const config = loadChatConfig();
27
+ const t = mergeTheme(theme);
28
+ const apiUrl = buildUserListUrl(config);
29
+ // Determine filter based on context
30
+ const filterType = userListCtx === 'support' ? 'developer' : 'user';
31
+ const { users, loading, error } = useUsers(apiUrl, filterType, config.status === 'ACTIVE' && screen === 'user-list');
32
+ const { messages, activeUser, selectUser, sendMessage, clearChat } = useChat();
33
+ // ── Navigation helpers ─────────────────────────────────────────────────────
34
+ const goHome = useCallback(() => { setScreen('home'); setActiveTab('home'); }, []);
35
+ const handleTabChange = useCallback((tab) => {
36
+ setActiveTab(tab);
37
+ if (tab === 'home') {
38
+ setScreen('home');
39
+ }
40
+ if (tab === 'chats') {
41
+ setScreen('recent-chats');
42
+ }
43
+ if (tab === 'tickets') {
44
+ setScreen('tickets');
45
+ }
46
+ }, []);
47
+ const handleCardClick = useCallback((ctx) => {
48
+ if (ctx === 'ticket') {
49
+ setActiveTab('tickets');
50
+ setScreen('tickets');
51
+ }
52
+ else {
53
+ setUserListCtx(ctx);
54
+ setScreen('user-list');
55
+ }
56
+ }, []);
57
+ const handleSelectUser = useCallback((user) => {
58
+ selectUser(user);
59
+ setScreen('chat');
60
+ }, [selectUser]);
61
+ const handleBackFromUserList = useCallback(() => {
62
+ setScreen('home');
63
+ }, []);
64
+ const handleBackFromChat = useCallback(() => {
65
+ clearChat();
66
+ setScreen('user-list');
67
+ }, [clearChat]);
68
+ const handleRaiseTicket = useCallback((title, description) => {
69
+ const t = {
70
+ id: `ticket_${Date.now()}`,
71
+ title,
72
+ description,
73
+ status: 'open',
74
+ priority: 'medium',
75
+ createdAt: new Date(),
76
+ updatedAt: new Date(),
77
+ };
78
+ setTickets(prev => [t, ...prev]);
79
+ }, []);
80
+ // ── Sizing ─────────────────────────────────────────────────────────────────
81
+ const normalW = 380;
82
+ const normalH = 560;
83
+ const maxW = 480;
84
+ const maxH = 720;
85
+ const width = isMaximized ? maxW : normalW;
86
+ const height = isMaximized ? maxH : normalH;
87
+ const posStyle = t.buttonPosition === 'bottom-left'
88
+ ? { left: 24, right: 'auto' }
89
+ : { right: 24, left: 'auto' };
90
+ if (!isMounted)
91
+ return null;
92
+ if (config.status === 'DISABLE')
93
+ return null;
94
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
95
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
96
+ @keyframes cw-fadeUp {
97
+ from { opacity:0; transform:translateY(10px); }
98
+ to { opacity:1; transform:translateY(0); }
99
+ }
100
+ @keyframes cw-slideInRight {
101
+ from { opacity:0; transform:translateX(18px); }
102
+ to { opacity:1; transform:translateX(0); }
103
+ }
104
+ @keyframes cw-popIn {
105
+ from { opacity:0; transform:scale(0.88) translateY(16px); }
106
+ to { opacity:1; transform:scale(1) translateY(0); }
107
+ }
108
+ .cw-scrollbar::-webkit-scrollbar { width:4px; }
109
+ .cw-scrollbar::-webkit-scrollbar-track { background:transparent; }
110
+ .cw-scrollbar::-webkit-scrollbar-thumb { background:#e0e0e0; border-radius:4px; }
111
+ ` }), _jsx("button", { onClick: () => setIsOpen(o => !o), "aria-label": isOpen ? 'Close chat' : t.buttonLabel, style: Object.assign(Object.assign({ position: 'fixed', bottom: 24 }, posStyle), { zIndex: 9999, display: 'flex', alignItems: 'center', gap: 10, padding: isOpen ? '14px' : '13px 22px', backgroundColor: t.buttonColor, color: t.buttonTextColor, border: 'none', borderRadius: 50, cursor: 'pointer', fontFamily: t.fontFamily, fontSize: '15px', fontWeight: 700, boxShadow: `0 8px 28px ${t.buttonColor}55`, transition: 'all 0.3s cubic-bezier(0.34,1.56,0.64,1)', transform: isOpen ? 'scale(0.94)' : 'scale(1)', minWidth: isOpen ? 50 : 'auto', justifyContent: 'center' }), onMouseEnter: e => {
112
+ if (!isOpen) {
113
+ e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
114
+ e.currentTarget.style.boxShadow = `0 12px 36px ${t.buttonColor}77`;
115
+ }
116
+ }, onMouseLeave: e => {
117
+ e.currentTarget.style.transform = isOpen ? 'scale(0.94)' : 'scale(1)';
118
+ e.currentTarget.style.boxShadow = `0 8px 28px ${t.buttonColor}55`;
119
+ }, children: isOpen
120
+ ? _jsx(CloseIcon, { color: t.buttonTextColor })
121
+ : _jsxs(_Fragment, { children: [_jsx(ChatBubbleIcon, { color: t.buttonTextColor }), _jsx("span", { children: t.buttonLabel })] }) }), isOpen && (_jsxs("div", { style: Object.assign(Object.assign({ position: 'fixed', bottom: 86 }, posStyle), { zIndex: 9998, width,
122
+ height, maxWidth: 'calc(100vw - 32px)', maxHeight: 'calc(100vh - 110px)', backgroundColor: '#fff', borderRadius: t.borderRadius, boxShadow: '0 20px 70px rgba(0,0,0,0.2), 0 6px 20px rgba(0,0,0,0.08)', display: 'flex', flexDirection: 'column', overflow: 'hidden', fontFamily: t.fontFamily, animation: 'cw-popIn 0.3s cubic-bezier(0.34,1.56,0.64,1)', transition: 'width 0.3s ease, height 0.3s ease' }), children: [screen !== 'chat' && (_jsx("button", { onClick: () => setIsMaximized(m => !m), title: isMaximized ? 'Minimize' : 'Maximize', style: {
123
+ position: 'absolute', top: 12, right: 48, zIndex: 10,
124
+ background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
125
+ width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center',
126
+ cursor: 'pointer', transition: 'background 0.15s',
127
+ }, onMouseEnter: e => e.currentTarget.style.background = 'rgba(255,255,255,0.38)', onMouseLeave: e => e.currentTarget.style.background = 'rgba(255,255,255,0.22)', children: isMaximized
128
+ ? _jsx(MinimizeIcon, {})
129
+ : _jsx(MaximizeIcon, {}) })), screen !== 'chat' && (_jsx("button", { onClick: () => setIsOpen(false), title: "Close", style: {
130
+ position: 'absolute', top: 12, right: 12, zIndex: 10,
131
+ background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
132
+ width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center',
133
+ cursor: 'pointer',
134
+ }, children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })), _jsxs("div", { className: "cw-scrollbar", style: { flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }, children: [config.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: t.primaryColor, fontFamily: t.fontFamily })), config.status === 'ACTIVE' && (_jsxs(_Fragment, { children: [screen === 'home' && (_jsx(HomeScreen, { config: config, theme: theme, onNavigate: handleCardClick })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: users, loading: loading, error: error, theme: theme, onBack: handleBackFromUserList, onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, onSend: sendMessage, onBack: handleBackFromChat, onClose: () => setIsOpen(false), theme: theme })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: [], theme: theme, onSelectChat: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, theme: theme, onRaiseTicket: handleRaiseTicket }))] }))] }), screen !== 'chat' && screen !== 'user-list' && config.status !== 'MAINTENANCE' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: t.primaryColor, fontFamily: t.fontFamily }))] }))] }));
135
+ };
136
+ export default ChatWidget;
137
+ // ── Tiny SVG icons ─────────────────────────────────────────────────────────────
138
+ const ChatBubbleIcon = ({ color }) => (_jsx("svg", { width: "20", height: "20", 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: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
139
+ const CloseIcon = ({ color }) => (_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: color, strokeWidth: "2.5", strokeLinecap: "round" }) }));
140
+ const MaximizeIcon = () => (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }));
141
+ const MinimizeIcon = () => (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3v5H3M21 8h-5V3M3 16h5v5M16 21v-5h5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }));
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { ChatConfig, ChatWidgetTheme, UserListContext } from '../../types';
3
+ interface HomeScreenProps {
4
+ config: ChatConfig;
5
+ theme?: ChatWidgetTheme;
6
+ onNavigate: (ctx: UserListContext | 'ticket') => void;
7
+ }
8
+ export declare const HomeScreen: React.FC<HomeScreenProps>;
9
+ export {};