ajaxter-chat 2.0.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -128
- package/dist/components/BlockList/index.d.ts +10 -0
- package/dist/components/BlockList/index.js +33 -0
- package/dist/components/CallScreen/index.d.ts +13 -0
- package/dist/components/CallScreen/index.js +48 -0
- package/dist/components/ChatScreen/index.d.ts +10 -3
- package/dist/components/ChatScreen/index.js +142 -57
- package/dist/components/ChatWidget.js +192 -98
- package/dist/components/EmojiPicker/index.d.ts +8 -0
- package/dist/components/EmojiPicker/index.js +18 -0
- package/dist/components/HomeScreen/index.d.ts +2 -3
- package/dist/components/HomeScreen/index.js +25 -41
- package/dist/components/MaintenanceView/index.d.ts +0 -1
- package/dist/components/MaintenanceView/index.js +4 -6
- package/dist/components/RecentChatsScreen/index.d.ts +4 -3
- package/dist/components/RecentChatsScreen/index.js +7 -37
- package/dist/components/Tabs/BottomTabs.d.ts +1 -1
- package/dist/components/Tabs/BottomTabs.js +25 -20
- package/dist/components/TicketScreen/index.d.ts +3 -3
- package/dist/components/TicketScreen/index.js +39 -56
- package/dist/components/UserListScreen/index.d.ts +2 -4
- package/dist/components/UserListScreen/index.js +33 -62
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +18 -26
- package/dist/hooks/useChat.d.ts +8 -3
- package/dist/hooks/useChat.js +22 -18
- package/dist/hooks/useRemoteConfig.d.ts +6 -0
- package/dist/hooks/useRemoteConfig.js +22 -0
- package/dist/hooks/useWebRTC.d.ts +11 -0
- package/dist/hooks/useWebRTC.js +112 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +8 -4
- package/dist/types/index.d.ts +62 -21
- package/dist/utils/chat.d.ts +13 -0
- package/dist/utils/chat.js +62 -0
- package/dist/utils/theme.d.ts +3 -1
- package/dist/utils/theme.js +14 -7
- package/package.json +4 -4
- package/public/chatData.json +162 -0
- package/src/components/BlockList/index.tsx +94 -0
- package/src/components/CallScreen/index.tsx +144 -0
- package/src/components/ChatScreen/index.tsx +403 -139
- package/src/components/ChatWidget.tsx +394 -250
- package/src/components/EmojiPicker/index.tsx +48 -0
- package/src/components/HomeScreen/index.tsx +58 -82
- package/src/components/MaintenanceView/index.tsx +6 -9
- package/src/components/RecentChatsScreen/index.tsx +51 -96
- package/src/components/Tabs/BottomTabs.tsx +45 -37
- package/src/components/TicketScreen/index.tsx +87 -133
- package/src/components/UserListScreen/index.tsx +75 -153
- package/src/config/index.ts +22 -28
- package/src/hooks/useChat.ts +31 -14
- package/src/hooks/useRemoteConfig.ts +20 -0
- package/src/hooks/useWebRTC.ts +130 -0
- package/src/index.ts +26 -15
- package/src/types/index.ts +85 -40
- package/src/utils/chat.ts +70 -0
- package/src/utils/theme.ts +18 -7
- package/dist/hooks/useUsers.d.ts +0 -7
- package/dist/hooks/useUsers.js +0 -26
- package/dist/services/userService.d.ts +0 -2
- package/dist/services/userService.js +0 -9
- package/dist/src/components/ChatScreen/index.d.ts +0 -12
- package/dist/src/components/ChatScreen/index.js +0 -83
- package/dist/src/components/ChatWidget.d.ts +0 -4
- package/dist/src/components/ChatWidget.js +0 -141
- package/dist/src/components/HomeScreen/index.d.ts +0 -9
- package/dist/src/components/HomeScreen/index.js +0 -71
- package/dist/src/components/MaintenanceView/index.d.ts +0 -7
- package/dist/src/components/MaintenanceView/index.js +0 -16
- package/dist/src/components/RecentChatsScreen/index.d.ts +0 -16
- package/dist/src/components/RecentChatsScreen/index.js +0 -38
- package/dist/src/components/Tabs/BottomTabs.d.ts +0 -10
- package/dist/src/components/Tabs/BottomTabs.js +0 -29
- package/dist/src/components/TicketScreen/index.d.ts +0 -9
- package/dist/src/components/TicketScreen/index.js +0 -71
- package/dist/src/components/UserListScreen/index.d.ts +0 -13
- package/dist/src/components/UserListScreen/index.js +0 -64
- package/dist/src/config/index.d.ts +0 -3
- package/dist/src/config/index.js +0 -38
- package/dist/src/hooks/useChat.d.ts +0 -8
- package/dist/src/hooks/useChat.js +0 -26
- package/dist/src/hooks/useUsers.d.ts +0 -7
- package/dist/src/hooks/useUsers.js +0 -26
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -13
- package/dist/src/services/userService.d.ts +0 -2
- package/dist/src/services/userService.js +0 -9
- package/dist/src/types/index.d.ts +0 -59
- package/dist/src/types/index.js +0 -1
- package/dist/src/utils/theme.d.ts +0 -3
- package/dist/src/utils/theme.js +0 -13
- package/src/hooks/useUsers.ts +0 -27
- package/src/services/userService.ts +0 -9
package/README.md
CHANGED
|
@@ -1,82 +1,71 @@
|
|
|
1
|
-
# react-chat-widget-extension
|
|
1
|
+
# react-chat-widget-extension v3
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A production-ready **drawer/slider-based** chat widget for React.js and Next.js.
|
|
4
|
+
All configuration is loaded remotely from your hosted `chatData.json`.
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Floating Button
|
|
11
|
-
└── Opens Widget Window
|
|
12
|
-
├── [Home Screen] ← default on open
|
|
13
|
-
│ ├── Need Support → [User List Screen: developers] → [Chat Screen]
|
|
14
|
-
│ ├── New Convo → [User List Screen: users] → [Chat Screen]
|
|
15
|
-
│ └── Raise Ticket → [Ticket Screen] (always shown)
|
|
16
|
-
│
|
|
17
|
-
├── [Bottom Tab: Home] ← Home screen
|
|
18
|
-
├── [Bottom Tab: Chats] ← Recent conversations list
|
|
19
|
-
└── [Bottom Tab: Tickets] ← All raised tickets + new ticket form
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Environment Variables
|
|
8
|
+
## Setup (2 env vars only)
|
|
25
9
|
|
|
26
10
|
### React (.env)
|
|
27
11
|
```env
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
REACT_APP_CHAT_USER_LIST=api/v1/users
|
|
31
|
-
REACT_APP_CHAT_STATUS=ACTIVE # ACTIVE | DISABLE | MAINTENANCE
|
|
32
|
-
REACT_APP_CHAT_TYPE=BOTH # SUPPORT | CHAT | BOTH
|
|
12
|
+
REACT_APP_CHAT_API_KEY=demo1234
|
|
13
|
+
REACT_APP_CHAT_WIDGET_ID=demo
|
|
33
14
|
```
|
|
34
15
|
|
|
35
16
|
### Next.js (.env.local)
|
|
36
17
|
```env
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
NEXT_PUBLIC_CHAT_USER_LIST=api/v1/users
|
|
40
|
-
NEXT_PUBLIC_CHAT_STATUS=ACTIVE
|
|
41
|
-
NEXT_PUBLIC_CHAT_TYPE=BOTH
|
|
18
|
+
NEXT_PUBLIC_CHAT_API_KEY=demo1234
|
|
19
|
+
NEXT_PUBLIC_CHAT_WIDGET_ID=demo
|
|
42
20
|
```
|
|
43
21
|
|
|
44
22
|
---
|
|
45
23
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
| Value | Behaviour |
|
|
49
|
-
|---------------|--------------------------------------------------------|
|
|
50
|
-
| `ACTIVE` | Widget fully enabled |
|
|
51
|
-
| `DISABLE` | Widget **not rendered at all** — zero DOM footprint |
|
|
52
|
-
| `MAINTENANCE` | Widget opens but shows a maintenance message |
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## CHAT_TYPE Behaviour
|
|
57
|
-
|
|
58
|
-
| Value | Home Cards Shown | User List Filter |
|
|
59
|
-
|-----------|-------------------------------------|-------------------|
|
|
60
|
-
| `SUPPORT` | Need Support only | `type=developer` |
|
|
61
|
-
| `CHAT` | New Conversation only | `type=user` |
|
|
62
|
-
| `BOTH` | Both cards | Per card clicked |
|
|
24
|
+
## Remote Config URL
|
|
63
25
|
|
|
64
|
-
|
|
26
|
+
The widget fetches all config from:
|
|
27
|
+
```
|
|
28
|
+
GET https://window.mscorpres.com/TEST/chatData.json
|
|
29
|
+
```
|
|
30
|
+
With headers:
|
|
31
|
+
```
|
|
32
|
+
X-Chat-Api-Key: demo1234
|
|
33
|
+
X-Chat-Widget-Id: demo
|
|
34
|
+
```
|
|
65
35
|
|
|
66
36
|
---
|
|
67
37
|
|
|
68
|
-
##
|
|
38
|
+
## chatData.json Schema
|
|
69
39
|
|
|
70
|
-
|
|
71
|
-
GET {CHAT_HOST_URL}[:{CHAT_HOST_PORT}]/{CHAT_USER_LIST}
|
|
72
|
-
```
|
|
40
|
+
Host this file at the URL above:
|
|
73
41
|
|
|
74
|
-
Expected response:
|
|
75
42
|
```json
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
43
|
+
{
|
|
44
|
+
"widget": {
|
|
45
|
+
"id": "demo",
|
|
46
|
+
"apiKey": "demo1234",
|
|
47
|
+
"status": "ACTIVE",
|
|
48
|
+
"chatType": "BOTH",
|
|
49
|
+
"primaryColor": "#2563EB",
|
|
50
|
+
"buttonLabel": "Support",
|
|
51
|
+
"buttonPosition": "bottom-right",
|
|
52
|
+
"welcomeTitle": "Hi there 👋",
|
|
53
|
+
"welcomeSubtitle": "Need help? Start a conversation:",
|
|
54
|
+
"allowVoiceMessage": true,
|
|
55
|
+
"allowAttachment": true,
|
|
56
|
+
"allowEmoji": true,
|
|
57
|
+
"allowWebCall": true,
|
|
58
|
+
"maxEmojiCount": 20,
|
|
59
|
+
"allowTranscriptDownload": true,
|
|
60
|
+
"allowReport": true,
|
|
61
|
+
"allowBlock": true
|
|
62
|
+
},
|
|
63
|
+
"developers": [ { "uid":"dev_001","name":"...","type":"developer","status":"online", ... } ],
|
|
64
|
+
"users": [ { "uid":"usr_001","name":"...","type":"user","status":"online", ... } ],
|
|
65
|
+
"sampleChats": { "dev_001": [ { "id":"msg_001","senderId":"me","text":"Hi!","type":"text", ... } ] },
|
|
66
|
+
"sampleTickets": [ { "id":"TKT-0001","title":"...","status":"open","priority":"high", ... } ],
|
|
67
|
+
"blockedUsers": []
|
|
68
|
+
}
|
|
80
69
|
```
|
|
81
70
|
|
|
82
71
|
---
|
|
@@ -85,111 +74,113 @@ Expected response:
|
|
|
85
74
|
|
|
86
75
|
### React
|
|
87
76
|
```tsx
|
|
88
|
-
// App.tsx
|
|
89
77
|
import { ChatWidget } from 'react-chat-widget-extension';
|
|
90
|
-
|
|
91
78
|
export default function App() {
|
|
92
|
-
return
|
|
93
|
-
<>
|
|
94
|
-
<main>Your app</main>
|
|
95
|
-
<ChatWidget
|
|
96
|
-
theme={{
|
|
97
|
-
primaryColor: '#1aaa96',
|
|
98
|
-
buttonLabel: 'Chat with us',
|
|
99
|
-
buttonPosition: 'bottom-right',
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
</>
|
|
103
|
-
);
|
|
79
|
+
return <><main>App</main><ChatWidget /></>;
|
|
104
80
|
}
|
|
105
81
|
```
|
|
106
82
|
|
|
107
|
-
### Next.js
|
|
83
|
+
### Next.js App Router
|
|
108
84
|
```tsx
|
|
109
85
|
// app/ChatWidgetWrapper.tsx
|
|
110
86
|
'use client';
|
|
111
87
|
import { ChatWidget } from 'react-chat-widget-extension';
|
|
112
|
-
export function ChatWidgetWrapper() {
|
|
113
|
-
return <ChatWidget theme={{ primaryColor: '#1aaa96' }} />;
|
|
114
|
-
}
|
|
88
|
+
export function ChatWidgetWrapper() { return <ChatWidget />; }
|
|
115
89
|
|
|
116
90
|
// app/layout.tsx
|
|
117
91
|
import { ChatWidgetWrapper } from './ChatWidgetWrapper';
|
|
118
|
-
export default function
|
|
92
|
+
export default function Layout({ children }) {
|
|
119
93
|
return <html><body>{children}<ChatWidgetWrapper /></body></html>;
|
|
120
94
|
}
|
|
121
95
|
```
|
|
122
96
|
|
|
123
|
-
### Next.js
|
|
97
|
+
### Next.js Pages Router
|
|
124
98
|
```tsx
|
|
125
99
|
// pages/_app.tsx
|
|
126
100
|
import { ChatWidget } from 'react-chat-widget-extension';
|
|
127
101
|
export default function MyApp({ Component, pageProps }) {
|
|
128
|
-
return <><Component {...pageProps}
|
|
102
|
+
return <><Component {...pageProps}/><ChatWidget /></>;
|
|
129
103
|
}
|
|
130
104
|
```
|
|
131
105
|
|
|
132
106
|
---
|
|
133
107
|
|
|
134
|
-
##
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
├── utils/theme.ts ← defaultTheme + mergeTheme
|
|
161
|
-
└── components/
|
|
162
|
-
├── ChatWidget.tsx ← 🏠 Root — mount this in your app
|
|
163
|
-
├── HomeScreen/ ← Hi there + option cards
|
|
164
|
-
├── UserListScreen/ ← Slide-in user picker
|
|
165
|
-
├── ChatScreen/ ← Conversation + input bar
|
|
166
|
-
├── RecentChatsScreen/ ← Bottom tab: Chats
|
|
167
|
-
├── TicketScreen/ ← Bottom tab: Tickets + raise form
|
|
168
|
-
├── MaintenanceView/ ← Shown when MAINTENANCE
|
|
169
|
-
└── Tabs/BottomTabs.tsx ← Home / Chats / Tickets tabs
|
|
170
|
-
```
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
| Feature | Details |
|
|
111
|
+
|---|---|
|
|
112
|
+
| **Drawer/Slider UI** | Slides in from right (or left) with smooth animation. Backdrop closes it. |
|
|
113
|
+
| **Home Screen** | "Hi there 👋" hero, cards for Support / New Conversation / Raise Ticket |
|
|
114
|
+
| **User List** | Slide-in panel with online status dot, designation, project |
|
|
115
|
+
| **Chat Screen** | Matches the UI image — hamburger back, title "Support", phone + fullscreen icons |
|
|
116
|
+
| **Enter Your Details** | Blue card at top of chat (ticket chat body placeholder) |
|
|
117
|
+
| **Voice Messages** | Record via MediaRecorder API, shows duration |
|
|
118
|
+
| **Attachments** | File picker, shows filename + size as message |
|
|
119
|
+
| **Emoji Picker** | 20 curated emojis, limited count via `maxEmojiCount` |
|
|
120
|
+
| **Pause Chat** | Developer can pause — user cannot send messages |
|
|
121
|
+
| **Resume Chat** | One-click resume from banner or menu |
|
|
122
|
+
| **Report Chat** | Flags chat, shows warning banner |
|
|
123
|
+
| **Block User** | Blocks users only (not developers), with confirm dialog |
|
|
124
|
+
| **Block List Tab** | View all blocked users, one-click unblock |
|
|
125
|
+
| **Download Transcript** | Saves plain-text .txt file of full conversation |
|
|
126
|
+
| **WebRTC Calls** | Secure P2P voice/video call with mute + camera toggle |
|
|
127
|
+
| **Ticket Screen** | Raise tickets with title, description, priority selector |
|
|
128
|
+
| **Ticket History** | Shows all tickets with status badge and priority color |
|
|
129
|
+
| **Recent Chats Tab** | Shows past conversations with unread badges |
|
|
130
|
+
| **Maximize/Minimize** | Toggle drawer width 380px ↔ 480px |
|
|
131
|
+
| **Slide Close** | Smooth slide-out animation on close |
|
|
132
|
+
| **SSR Safe** | isMounted guard, works in Next.js App Router and Pages Router |
|
|
133
|
+
| **CHAT_STATUS** | ACTIVE / DISABLE / MAINTENANCE handled |
|
|
171
134
|
|
|
172
135
|
---
|
|
173
136
|
|
|
174
137
|
## WebSocket Integration Points
|
|
175
138
|
|
|
176
|
-
|
|
177
|
-
|
|
139
|
+
In `src/hooks/useChat.ts` — find the `// TODO:` comments:
|
|
178
140
|
```ts
|
|
179
|
-
// In selectUser
|
|
180
|
-
// socket.emit('join', user.uid);
|
|
181
|
-
// socket.on('message',
|
|
141
|
+
// In selectUser:
|
|
142
|
+
// socket.emit('join', { roomId: user.uid });
|
|
143
|
+
// socket.on('message', msg => setMessages(prev => [...prev, msg]));
|
|
182
144
|
|
|
183
|
-
// In sendMessage
|
|
184
|
-
// socket.emit('message',
|
|
145
|
+
// In sendMessage:
|
|
146
|
+
// socket.emit('message', msg);
|
|
185
147
|
```
|
|
186
148
|
|
|
187
|
-
|
|
149
|
+
In `src/hooks/useWebRTC.ts` — find the `// TODO:` comments:
|
|
150
|
+
```ts
|
|
151
|
+
// ICE candidate: socket.emit('ice-candidate', { candidate, to: peer.uid });
|
|
152
|
+
// Offer: socket.emit('call-offer', { offer, to: peer.uid });
|
|
153
|
+
// Answer: socket.emit('call-answer', { answer, to: peer.uid });
|
|
154
|
+
```
|
|
188
155
|
|
|
189
|
-
|
|
156
|
+
---
|
|
190
157
|
|
|
191
|
-
|
|
192
|
-
- **Normal size**: 380×560px
|
|
193
|
-
- **Maximized**: 480×720px
|
|
194
|
-
- Both sizes respect `max-width: calc(100vw - 32px)` and `max-height: calc(100vh - 110px)` for mobile safety.
|
|
158
|
+
## Folder Structure
|
|
195
159
|
|
|
160
|
+
```
|
|
161
|
+
src/
|
|
162
|
+
├── index.ts
|
|
163
|
+
├── types/index.ts
|
|
164
|
+
├── config/index.ts ← reads CHAT_API_KEY + CHAT_WIDGET_ID, fetches chatData.json
|
|
165
|
+
├── utils/theme.ts
|
|
166
|
+
├── utils/chat.ts ← transcript, avatarColor, formatTime, downloadText
|
|
167
|
+
├── hooks/
|
|
168
|
+
│ ├── useRemoteConfig.ts ← fetches chatData.json
|
|
169
|
+
│ ├── useChat.ts ← messages, pause, report
|
|
170
|
+
│ └── useWebRTC.ts ← P2P WebRTC calling
|
|
171
|
+
└── components/
|
|
172
|
+
├── ChatWidget.tsx ← 🏠 Root drawer orchestrator
|
|
173
|
+
├── HomeScreen/
|
|
174
|
+
├── UserListScreen/
|
|
175
|
+
├── ChatScreen/ ← voice, attach, emoji, pause, report, block, transcript, call
|
|
176
|
+
├── CallScreen/ ← WebRTC video/audio UI
|
|
177
|
+
├── EmojiPicker/
|
|
178
|
+
├── TicketScreen/
|
|
179
|
+
├── RecentChatsScreen/
|
|
180
|
+
├── BlockList/
|
|
181
|
+
├── MaintenanceView/
|
|
182
|
+
└── Tabs/BottomTabs.tsx ← Home / Chats / Tickets / Blocked
|
|
183
|
+
|
|
184
|
+
public/
|
|
185
|
+
└── chatData.json ← Sample JSON — host at window.mscorpres.com/TEST/chatData.json
|
|
186
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChatUser, WidgetConfig } from '../../types';
|
|
3
|
+
interface BlockListScreenProps {
|
|
4
|
+
blockedUsers: ChatUser[];
|
|
5
|
+
config: WidgetConfig;
|
|
6
|
+
onUnblock: (uid: string) => void;
|
|
7
|
+
onBack: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const BlockListScreen: React.FC<BlockListScreenProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { avatarColor, initials } from '../../utils/chat';
|
|
3
|
+
export const BlockListScreen = ({ blockedUsers, config, onUnblock, onBack, }) => (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: {
|
|
4
|
+
background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
|
|
5
|
+
padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0,
|
|
6
|
+
}, children: [_jsx("button", { onClick: onBack, style: backBtnStyle, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 16, color: '#fff' }, children: "Block List" }), _jsxs("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.8)' }, children: [blockedUsers.length, " blocked user", blockedUsers.length !== 1 ? 's' : ''] })] })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto' }, children: blockedUsers.length === 0 ? (_jsxs("div", { style: { padding: '50px 24px', textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 36, marginBottom: 10 }, children: "\u2705" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: 6 }, children: "No blocked users" }), _jsx("div", { style: { fontSize: 13, color: '#7b8fa1' }, children: "Users you block will appear here" })] })) : (blockedUsers.map((user, i) => (_jsxs("div", { style: {
|
|
7
|
+
padding: '13px 16px', display: 'flex', alignItems: 'center', gap: 13,
|
|
8
|
+
borderBottom: '1px solid #f0f2f5',
|
|
9
|
+
animation: `cw-fadeUp 0.28s ease both`, animationDelay: `${i * 0.05}s`,
|
|
10
|
+
}, children: [_jsx("div", { style: {
|
|
11
|
+
width: 44, height: 44, borderRadius: '50%',
|
|
12
|
+
backgroundColor: avatarColor(user.name),
|
|
13
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
14
|
+
color: '#fff', fontWeight: 700, fontSize: 14, flexShrink: 0,
|
|
15
|
+
filter: 'grayscale(0.6)', opacity: 0.7,
|
|
16
|
+
}, children: initials(user.name) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#6b7280' }, children: user.name }), _jsx("div", { style: { fontSize: 12, color: '#9ca3af', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: user.email })] }), _jsx("button", { onClick: () => onUnblock(user.uid), style: {
|
|
17
|
+
padding: '6px 14px', borderRadius: 20,
|
|
18
|
+
border: `1.5px solid ${config.primaryColor}`,
|
|
19
|
+
background: 'transparent', color: config.primaryColor,
|
|
20
|
+
fontSize: 12, fontWeight: 700, cursor: 'pointer',
|
|
21
|
+
transition: 'all 0.15s', flexShrink: 0,
|
|
22
|
+
}, onMouseEnter: e => {
|
|
23
|
+
e.currentTarget.style.background = config.primaryColor;
|
|
24
|
+
e.currentTarget.style.color = '#fff';
|
|
25
|
+
}, onMouseLeave: e => {
|
|
26
|
+
e.currentTarget.style.background = 'transparent';
|
|
27
|
+
e.currentTarget.style.color = config.primaryColor;
|
|
28
|
+
}, children: "Unblock" })] }, user.uid)))) })] }));
|
|
29
|
+
const backBtnStyle = {
|
|
30
|
+
background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
|
|
31
|
+
width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
32
|
+
cursor: 'pointer', flexShrink: 0,
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CallSession } from '../../types';
|
|
3
|
+
interface CallScreenProps {
|
|
4
|
+
session: CallSession;
|
|
5
|
+
localVideoRef: React.RefObject<HTMLVideoElement | null>;
|
|
6
|
+
remoteVideoRef: React.RefObject<HTMLVideoElement | null>;
|
|
7
|
+
onEnd: () => void;
|
|
8
|
+
onToggleMute: () => void;
|
|
9
|
+
onToggleCamera: () => void;
|
|
10
|
+
primaryColor: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const CallScreen: React.FC<CallScreenProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { avatarColor, initials } from '../../utils/chat';
|
|
4
|
+
export const CallScreen = ({ session, localVideoRef, remoteVideoRef, onEnd, onToggleMute, onToggleCamera, primaryColor, }) => {
|
|
5
|
+
const [duration, setDuration] = useState(0);
|
|
6
|
+
const peer = session.peer;
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (session.state !== 'connected' || !session.startedAt)
|
|
9
|
+
return;
|
|
10
|
+
const t = setInterval(() => {
|
|
11
|
+
setDuration(Math.floor((Date.now() - session.startedAt.getTime()) / 1000));
|
|
12
|
+
}, 1000);
|
|
13
|
+
return () => clearInterval(t);
|
|
14
|
+
}, [session.state, session.startedAt]);
|
|
15
|
+
const mins = String(Math.floor(duration / 60)).padStart(2, '0');
|
|
16
|
+
const secs = String(duration % 60).padStart(2, '0');
|
|
17
|
+
return (_jsxs("div", { style: {
|
|
18
|
+
display: 'flex', flexDirection: 'column', height: '100%',
|
|
19
|
+
background: session.isCameraOn ? '#000' : `linear-gradient(145deg,${primaryColor}dd,#0f172a)`,
|
|
20
|
+
color: '#fff', animation: 'cw-slideIn 0.22s ease',
|
|
21
|
+
position: 'relative', overflow: 'hidden',
|
|
22
|
+
}, children: [_jsx("video", { ref: remoteVideoRef, autoPlay: true, playsInline: true, style: { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', opacity: session.state === 'connected' ? 1 : 0 } }), _jsx("video", { ref: localVideoRef, autoPlay: true, playsInline: true, muted: true, style: {
|
|
23
|
+
position: 'absolute', bottom: 120, right: 14,
|
|
24
|
+
width: 90, height: 120, borderRadius: 10,
|
|
25
|
+
objectFit: 'cover', border: '2px solid rgba(255,255,255,0.3)',
|
|
26
|
+
display: session.isCameraOn ? 'block' : 'none',
|
|
27
|
+
zIndex: 10,
|
|
28
|
+
} }), _jsxs("div", { style: { position: 'relative', zIndex: 5, display: 'flex', flexDirection: 'column', height: '100%', background: 'rgba(0,0,0,0.35)' }, children: [_jsx("div", { style: { padding: '16px 18px', display: 'flex', alignItems: 'center', gap: 10 }, children: _jsx("div", { style: { flex: 1 }, children: _jsxs("div", { style: { fontWeight: 700, fontSize: 15, color: '#fff' }, children: [session.state === 'calling' && 'Calling...', session.state === 'connected' && 'Connected', session.state === 'ended' && 'Call Ended'] }) }) }), _jsx("div", { style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16 }, children: peer && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
29
|
+
width: 90, height: 90, borderRadius: '50%',
|
|
30
|
+
backgroundColor: avatarColor(peer.name),
|
|
31
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
32
|
+
fontSize: 28, fontWeight: 700, color: '#fff',
|
|
33
|
+
boxShadow: '0 0 0 4px rgba(255,255,255,0.2)',
|
|
34
|
+
animation: session.state === 'calling' ? 'cw-pulse 1.5s ease infinite' : 'none',
|
|
35
|
+
}, children: initials(peer.name) }), _jsxs("div", { style: { textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 20, fontWeight: 800 }, children: peer.name }), _jsxs("div", { style: { fontSize: 13, opacity: 0.8, marginTop: 4 }, children: [session.state === 'calling' && 'Ringing...', session.state === 'connected' && `${mins}:${secs}`, session.state === 'ended' && 'Call ended'] })] })] })) }), _jsxs("div", { style: { padding: '24px', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 20 }, children: [_jsx(CallBtn, { active: session.isMuted, activeColor: "#374151", onClick: onToggleMute, title: session.isMuted ? 'Unmute' : 'Mute', children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: session.isMuted
|
|
36
|
+
? _jsxs(_Fragment, { children: [_jsx("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", stroke: "#fff", strokeWidth: "2" }), _jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23", stroke: "#ef4444", strokeWidth: "2" })] })
|
|
37
|
+
: _jsxs(_Fragment, { children: [_jsx("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round" }), _jsx("path", { d: "M19 10v2a7 7 0 01-14 0v-2M12 19v4", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round" })] }) }) }), _jsx("button", { onClick: onEnd, style: {
|
|
38
|
+
width: 60, height: 60, borderRadius: '50%', backgroundColor: '#ef4444',
|
|
39
|
+
border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
40
|
+
boxShadow: '0 4px 16px rgba(239,68,68,0.5)', flexShrink: 0,
|
|
41
|
+
}, children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff", transform: "rotate(135 12 12)" }) }) }), _jsx(CallBtn, { active: session.isCameraOn, activeColor: primaryColor, onClick: onToggleCamera, title: "Camera", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M23 7l-7 5 7 5V7zM1 5h13a2 2 0 012 2v10a2 2 0 01-2 2H1a2 2 0 01-2-2V7a2 2 0 012-2z", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] })] }));
|
|
42
|
+
};
|
|
43
|
+
const CallBtn = ({ active, activeColor, onClick, title, children, }) => (_jsx("button", { onClick: onClick, title: title, style: {
|
|
44
|
+
width: 50, height: 50, borderRadius: '50%',
|
|
45
|
+
backgroundColor: active ? activeColor : 'rgba(255,255,255,0.2)',
|
|
46
|
+
border: 'none', cursor: 'pointer',
|
|
47
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
|
|
48
|
+
}, children: children }));
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ChatMessage, ChatUser,
|
|
2
|
+
import { ChatMessage, ChatUser, WidgetConfig } from '../../types';
|
|
3
3
|
interface ChatScreenProps {
|
|
4
4
|
activeUser: ChatUser;
|
|
5
5
|
messages: ChatMessage[];
|
|
6
|
-
|
|
6
|
+
config: WidgetConfig;
|
|
7
|
+
isPaused: boolean;
|
|
8
|
+
isReported: boolean;
|
|
9
|
+
isBlocked: boolean;
|
|
10
|
+
onSend: (text: string, type?: ChatMessage['type'], extra?: Partial<ChatMessage>) => void;
|
|
7
11
|
onBack: () => void;
|
|
8
12
|
onClose: () => void;
|
|
9
|
-
|
|
13
|
+
onTogglePause: () => void;
|
|
14
|
+
onReport: () => void;
|
|
15
|
+
onBlock: () => void;
|
|
16
|
+
onStartCall: (withVideo: boolean) => void;
|
|
10
17
|
}
|
|
11
18
|
export declare const ChatScreen: React.FC<ChatScreenProps>;
|
|
12
19
|
export {};
|