@regantis-sdk/react-native-chat 0.1.0 → 0.1.2
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 +18 -5
- package/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/RegantisChat.js +493 -203
- package/src/defaults.js +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @regantis/react-native-chat
|
|
1
|
+
# @regantis-sdk/react-native-chat
|
|
2
2
|
|
|
3
3
|
Native Regantis customer support chat for React Native Android and iOS.
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ Native Regantis customer support chat for React Native Android and iOS.
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install @regantis/react-native-chat
|
|
16
|
+
npm install @regantis-sdk/react-native-chat
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
For iOS, install pods after adding the package:
|
|
@@ -28,7 +28,7 @@ Android is autolinked by React Native.
|
|
|
28
28
|
## Basic usage
|
|
29
29
|
|
|
30
30
|
```jsx
|
|
31
|
-
import { RegantisChat } from '@regantis/react-native-chat';
|
|
31
|
+
import { RegantisChat } from '@regantis-sdk/react-native-chat';
|
|
32
32
|
|
|
33
33
|
export default function SupportScreen() {
|
|
34
34
|
return <RegantisChat apiKey="YOUR_REACT_NATIVE_CHAT_API_KEY" />;
|
|
@@ -42,7 +42,7 @@ The SDK downloads the Chat settings and UI texts from the CRM. Theme, colors, lo
|
|
|
42
42
|
Do not put the customer identity signing secret in the mobile app. Generate the identity timestamp and HMAC signature on your server and return them through your authenticated app API.
|
|
43
43
|
|
|
44
44
|
```jsx
|
|
45
|
-
import { RegantisChat } from '@regantis/react-native-chat';
|
|
45
|
+
import { RegantisChat } from '@regantis-sdk/react-native-chat';
|
|
46
46
|
|
|
47
47
|
export default function SupportScreen({ user, chatIdentity }) {
|
|
48
48
|
return (
|
|
@@ -131,7 +131,7 @@ The web-only launcher settings such as desktop/mobile corner placement and minim
|
|
|
131
131
|
The UI is optional. The same package exports the client if an app needs its own presentation layer:
|
|
132
132
|
|
|
133
133
|
```js
|
|
134
|
-
import { createRegantisChatClient } from '@regantis/react-native-chat';
|
|
134
|
+
import { createRegantisChatClient } from '@regantis-sdk/react-native-chat';
|
|
135
135
|
|
|
136
136
|
const chat = createRegantisChatClient({
|
|
137
137
|
apiKey: 'YOUR_REACT_NATIVE_CHAT_API_KEY',
|
|
@@ -155,3 +155,16 @@ chat.stop();
|
|
|
155
155
|
npm login
|
|
156
156
|
npm publish --access public
|
|
157
157
|
```
|
|
158
|
+
|
|
159
|
+
## Mobile widget-style navigation
|
|
160
|
+
|
|
161
|
+
The React Native component now mirrors the mobile web widget flow: it opens on the welcome/home screen and the visitor enters the conversation using the chat card or Chat navigation item.
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
<RegantisChat
|
|
165
|
+
apiKey="YOUR_REACT_NATIVE_CHAT_API_KEY"
|
|
166
|
+
initialView="home"
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Use `initialView="chat"` when an application should open directly in the conversation. `onClose` can be supplied when the host app wants the widget-style minimize action to navigate away from the chat page.
|
package/index.d.ts
CHANGED
|
@@ -60,6 +60,9 @@ export type RegantisChatProps = {
|
|
|
60
60
|
texts?: Record<string, string>;
|
|
61
61
|
screen?: string;
|
|
62
62
|
style?: StyleProp<ViewStyle>;
|
|
63
|
+
initialView?: 'home' | 'chat';
|
|
64
|
+
onClose?: () => void;
|
|
65
|
+
onViewChange?: (view: 'home' | 'chat') => void;
|
|
63
66
|
keyboardVerticalOffset?: number;
|
|
64
67
|
onMessage?: (message: any) => void;
|
|
65
68
|
onUnreadChange?: (count: number) => void;
|
package/package.json
CHANGED
package/src/RegantisChat.js
CHANGED
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
} = require('react-native');
|
|
20
20
|
const { RegantisChatClient } = require('./client');
|
|
21
21
|
|
|
22
|
-
const EMOJIS = ['🙂', '😁', '😂', '😊', '😍', '🤔', '😞', '😢', '🎉', '❤️', '👌', '👍', '👎', '🙏'];
|
|
22
|
+
const EMOJIS = ['🙂', '😁', '😂', '😊', '😍', '😐', '🤔', '😞', '😢', '😭', '🎉', '❤️', '👌', '👍', '👎', '🙏'];
|
|
23
23
|
|
|
24
24
|
function colorWithOpacity(hex, opacity) {
|
|
25
25
|
const value = String(hex || '#F5F7FB').replace('#', '');
|
|
@@ -35,18 +35,24 @@ function makePalette(settings) {
|
|
|
35
35
|
const dark = settings.theme_mode === 'dark';
|
|
36
36
|
const primary = settings.color_mode === 'advanced' ? settings.primary_color : settings.theme_color;
|
|
37
37
|
return {
|
|
38
|
+
dark,
|
|
38
39
|
primary,
|
|
39
40
|
background: colorWithOpacity(settings.chat_background, settings.chat_background_opacity),
|
|
40
|
-
surface: dark ? '#
|
|
41
|
-
surfaceAlt: dark ? '#
|
|
42
|
-
|
|
41
|
+
surface: dark ? '#1D2127' : '#FFFFFF',
|
|
42
|
+
surfaceAlt: dark ? '#252A31' : '#F5F7FB',
|
|
43
|
+
header: dark ? '#181B20' : '#F8F9FC',
|
|
44
|
+
border: dark ? 'rgba(255,255,255,0.10)' : 'rgba(20,32,54,0.10)',
|
|
43
45
|
text: dark ? '#F7F8FA' : '#17181B',
|
|
44
46
|
muted: settings.system_text || (dark ? '#A7ADB7' : '#6F7480'),
|
|
45
47
|
customerBubble: settings.color_mode === 'advanced' ? settings.customer_bubble : primary,
|
|
46
48
|
customerText: settings.color_mode === 'advanced' ? settings.customer_text : '#FFFFFF',
|
|
47
49
|
agentBubble: settings.color_mode === 'advanced' ? settings.agent_bubble : (dark ? '#22252A' : '#FFFFFF'),
|
|
48
50
|
agentText: settings.color_mode === 'advanced' ? settings.agent_text : (dark ? '#F7F8FA' : '#17181B'),
|
|
49
|
-
danger: '#
|
|
51
|
+
danger: '#DF2917',
|
|
52
|
+
heroBase: dark ? '#151B21' : '#EEF7FC',
|
|
53
|
+
heroBlobA: dark ? 'rgba(73,83,152,0.25)' : 'rgba(118,137,255,0.30)',
|
|
54
|
+
heroBlobB: dark ? 'rgba(34,126,168,0.22)' : 'rgba(96,222,215,0.28)',
|
|
55
|
+
heroBlobC: dark ? 'rgba(255,255,255,0.035)' : 'rgba(255,255,255,0.52)',
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -57,6 +63,11 @@ function formatTime(value) {
|
|
|
57
63
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
function isSupportMessage(message) {
|
|
67
|
+
const sender = String(message && message.sender_type || '').toLowerCase();
|
|
68
|
+
return sender === 'agent' || sender === 'admin' || sender === 'chatbot';
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
function RegantisChat(props) {
|
|
61
72
|
const {
|
|
62
73
|
apiKey,
|
|
@@ -68,6 +79,9 @@ function RegantisChat(props) {
|
|
|
68
79
|
texts,
|
|
69
80
|
screen = 'chat',
|
|
70
81
|
style,
|
|
82
|
+
initialView = 'home',
|
|
83
|
+
onClose,
|
|
84
|
+
onViewChange,
|
|
71
85
|
onMessage,
|
|
72
86
|
onUnreadChange,
|
|
73
87
|
onError,
|
|
@@ -88,11 +102,13 @@ function RegantisChat(props) {
|
|
|
88
102
|
}), [apiKey, serverUrl, language, translationLanguage, customer, theme, texts, screen, onMessage, onUnreadChange, onError]);
|
|
89
103
|
|
|
90
104
|
const [state, setState] = React.useState(client.getState());
|
|
105
|
+
const [activeView, setActiveView] = React.useState(initialView === 'chat' ? 'chat' : 'home');
|
|
91
106
|
const [draft, setDraft] = React.useState('');
|
|
92
107
|
const [contactName, setContactName] = React.useState(customer && customer.name || '');
|
|
93
108
|
const [contactEmail, setContactEmail] = React.useState(customer && customer.email || '');
|
|
94
109
|
const [contactError, setContactError] = React.useState('');
|
|
95
110
|
const [optionsOpen, setOptionsOpen] = React.useState(false);
|
|
111
|
+
const [languageOpen, setLanguageOpen] = React.useState(false);
|
|
96
112
|
const [attachOpen, setAttachOpen] = React.useState(false);
|
|
97
113
|
const [emojiOpen, setEmojiOpen] = React.useState(false);
|
|
98
114
|
const [closeOpen, setCloseOpen] = React.useState(false);
|
|
@@ -124,11 +140,11 @@ function RegantisChat(props) {
|
|
|
124
140
|
}, [state.conversation, contactName, contactEmail, transcriptEmail]);
|
|
125
141
|
|
|
126
142
|
React.useEffect(() => {
|
|
127
|
-
if (state.messages.length) {
|
|
143
|
+
if (activeView === 'chat' && state.messages.length) {
|
|
128
144
|
const timer = setTimeout(() => listRef.current && listRef.current.scrollToEnd({ animated: true }), 50);
|
|
129
145
|
return () => clearTimeout(timer);
|
|
130
146
|
}
|
|
131
|
-
}, [state.messages.length]);
|
|
147
|
+
}, [activeView, state.messages.length]);
|
|
132
148
|
|
|
133
149
|
const t = key => state.texts[key] || key;
|
|
134
150
|
const palette = makePalette(state.settings);
|
|
@@ -137,6 +153,7 @@ function RegantisChat(props) {
|
|
|
137
153
|
palette.background,
|
|
138
154
|
palette.surface,
|
|
139
155
|
palette.surfaceAlt,
|
|
156
|
+
palette.header,
|
|
140
157
|
palette.border,
|
|
141
158
|
palette.text,
|
|
142
159
|
palette.muted,
|
|
@@ -144,8 +161,29 @@ function RegantisChat(props) {
|
|
|
144
161
|
palette.customerText,
|
|
145
162
|
palette.agentBubble,
|
|
146
163
|
palette.agentText,
|
|
164
|
+
palette.heroBase,
|
|
147
165
|
]);
|
|
148
166
|
|
|
167
|
+
const c = state.conversation || {};
|
|
168
|
+
const active = Number(c.status || 0) === 1;
|
|
169
|
+
const contactConfirmed = Number(c.contact_confirmed || 0) === 1;
|
|
170
|
+
const canUpload = !!(state.upload && state.upload.enabled);
|
|
171
|
+
const hasExisting = state.messages.length > 0 || contactConfirmed;
|
|
172
|
+
const lastSupport = [...state.messages].reverse().find(isSupportMessage);
|
|
173
|
+
const homePreview = lastSupport
|
|
174
|
+
? String(lastSupport.message || '').trim() || (lastSupport.attachment ? `[${String(lastSupport.message_type || 'file')}]` : t('empty'))
|
|
175
|
+
: t('empty');
|
|
176
|
+
|
|
177
|
+
function showView(next) {
|
|
178
|
+
const view = next === 'chat' ? 'chat' : 'home';
|
|
179
|
+
setActiveView(view);
|
|
180
|
+
setOptionsOpen(false);
|
|
181
|
+
setLanguageOpen(false);
|
|
182
|
+
setAttachOpen(false);
|
|
183
|
+
setEmojiOpen(false);
|
|
184
|
+
if (typeof onViewChange === 'function') onViewChange(view);
|
|
185
|
+
}
|
|
186
|
+
|
|
149
187
|
async function submitContact() {
|
|
150
188
|
setContactError('');
|
|
151
189
|
if (!contactName.trim() || !/^\S+@\S+\.\S+$/.test(contactEmail.trim())) {
|
|
@@ -189,6 +227,7 @@ function RegantisChat(props) {
|
|
|
189
227
|
setBusyAction('language');
|
|
190
228
|
try {
|
|
191
229
|
await client.setTranslationLanguage(code);
|
|
230
|
+
setLanguageOpen(false);
|
|
192
231
|
setOptionsOpen(false);
|
|
193
232
|
} finally {
|
|
194
233
|
setBusyAction('');
|
|
@@ -254,6 +293,30 @@ function RegantisChat(props) {
|
|
|
254
293
|
}
|
|
255
294
|
}
|
|
256
295
|
|
|
296
|
+
function renderLogo(size, marginRight) {
|
|
297
|
+
if (!state.settings.show_logo) return null;
|
|
298
|
+
if (state.settings.logo_url) {
|
|
299
|
+
return <Image source={{ uri: state.settings.logo_url }} style={[styles.logoImage, { width: size, height: size, borderRadius: size / 2, marginRight: marginRight || 0 }]} resizeMode="cover" />;
|
|
300
|
+
}
|
|
301
|
+
return (
|
|
302
|
+
<View style={[styles.logoFallback, { width: size, height: size, borderRadius: size / 2, marginRight: marginRight || 0 }]}>
|
|
303
|
+
<Text style={[styles.logoFallbackText, { fontSize: Math.max(12, Math.round(size * 0.38)) }]}>R</Text>
|
|
304
|
+
</View>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function renderAgentVisual() {
|
|
309
|
+
const showLogo = !!state.settings.show_logo;
|
|
310
|
+
const showAgent = !!state.settings.show_agent_photo;
|
|
311
|
+
return (
|
|
312
|
+
<View style={styles.agentVisual}>
|
|
313
|
+
{showLogo ? renderLogo(28, 0) : null}
|
|
314
|
+
{showAgent ? <View style={[styles.agentAvatar, showLogo && styles.agentAvatarOverlap]}><Text style={styles.agentAvatarText}>R</Text></View> : null}
|
|
315
|
+
{!showLogo && !showAgent ? <View style={styles.agentFallback}><Text style={styles.agentFallbackText}>●</Text></View> : null}
|
|
316
|
+
</View>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
257
320
|
function renderMessage({ item }) {
|
|
258
321
|
const sender = String(item.sender_type || '').toLowerCase();
|
|
259
322
|
const system = sender === 'system' || String(item.message_type || '').startsWith('routing_') || String(item.message_type || '').startsWith('rating_');
|
|
@@ -267,66 +330,161 @@ function RegantisChat(props) {
|
|
|
267
330
|
const textStyle = visitor ? styles.customerText : styles.agentText;
|
|
268
331
|
const attachment = item.attachment || null;
|
|
269
332
|
const isImage = attachment && String(attachment.mime || '').startsWith('image/');
|
|
333
|
+
const senderName = sender === 'chatbot' ? 'Chatbot' : (item.sender_name || state.settings.operator_name || t('support_name'));
|
|
270
334
|
|
|
271
335
|
return (
|
|
272
336
|
<View style={[styles.messageRow, visitor ? styles.messageRowRight : styles.messageRowLeft]}>
|
|
273
|
-
{!visitor && state.settings.show_agent_photo ? <View style={styles.avatar}><Text style={styles.avatarText}>{String(item.sender_name || state.settings.operator_name || 'S').slice(0, 1).toUpperCase()}</Text></View> : null}
|
|
274
337
|
<View style={[styles.bubble, bubbleStyle]}>
|
|
275
|
-
{!visitor && item.sender_name ? <Text style={[styles.senderName, textStyle]}>{item.sender_name}</Text> : null}
|
|
276
338
|
{isImage ? (
|
|
277
339
|
<Pressable onPress={() => Linking.openURL(attachment.url)}>
|
|
278
340
|
<Image source={{ uri: attachment.url }} style={styles.attachmentImage} resizeMode="cover" />
|
|
279
341
|
</Pressable>
|
|
280
342
|
) : attachment ? (
|
|
281
343
|
<Pressable style={styles.fileAttachment} onPress={() => Linking.openURL(attachment.url)}>
|
|
282
|
-
<Text style={textStyle}>📎 {attachment.name || t('attach_file')}</Text>
|
|
344
|
+
<Text style={[styles.fileAttachmentText, textStyle]}>📎 {attachment.name || t('attach_file')}</Text>
|
|
283
345
|
</Pressable>
|
|
284
346
|
) : null}
|
|
285
347
|
{item.message ? <Text style={[styles.messageText, textStyle]}>{item.message}</Text> : null}
|
|
286
|
-
<
|
|
348
|
+
<View style={styles.messageMeta}>
|
|
349
|
+
{!visitor ? <Text numberOfLines={1} style={[styles.messageSender, textStyle]}>{senderName}</Text> : <View />}
|
|
350
|
+
<Text style={[styles.timeText, textStyle]}>{formatTime(item.date_added)}</Text>
|
|
351
|
+
</View>
|
|
287
352
|
</View>
|
|
288
353
|
</View>
|
|
289
354
|
);
|
|
290
355
|
}
|
|
291
356
|
|
|
292
|
-
|
|
293
|
-
|
|
357
|
+
function renderPoweredBy() {
|
|
358
|
+
if (state.settings.white_label) return null;
|
|
359
|
+
return <View style={styles.poweredBy}><Text style={styles.poweredByText}>{t('powered_by')} <Text style={styles.poweredByBrand}>Regantis</Text></Text></View>;
|
|
294
360
|
}
|
|
295
361
|
|
|
296
|
-
|
|
362
|
+
function renderHome() {
|
|
297
363
|
return (
|
|
298
|
-
<View style={
|
|
299
|
-
<
|
|
300
|
-
|
|
364
|
+
<View style={styles.screen}>
|
|
365
|
+
<View style={styles.homeHero}>
|
|
366
|
+
<View pointerEvents="none" style={styles.heroDecorationA} />
|
|
367
|
+
<View pointerEvents="none" style={styles.heroDecorationB} />
|
|
368
|
+
<View pointerEvents="none" style={styles.heroDecorationC} />
|
|
369
|
+
|
|
370
|
+
<View style={styles.homeTop}>
|
|
371
|
+
<View>{renderLogo(42, 0)}</View>
|
|
372
|
+
{typeof onClose === 'function' ? (
|
|
373
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('minimize')} style={styles.iconButton} onPress={onClose}>
|
|
374
|
+
<Text style={styles.minimizeIcon}>−</Text>
|
|
375
|
+
</Pressable>
|
|
376
|
+
) : <View style={styles.homeTopPlaceholder} />}
|
|
377
|
+
</View>
|
|
378
|
+
|
|
379
|
+
<View style={styles.homeTitle}>
|
|
380
|
+
<Text style={styles.homeTitleLine}>{t('welcome_title')}</Text>
|
|
381
|
+
<Text style={styles.homeTitleLine}>{t('welcome_text')}</Text>
|
|
382
|
+
</View>
|
|
383
|
+
|
|
384
|
+
<Pressable style={styles.homeCard} onPress={() => showView('chat')}>
|
|
385
|
+
<View style={styles.homeCardTop}>
|
|
386
|
+
{state.settings.show_agent_photo ? <View style={styles.homeAvatar}><Text style={styles.homeAvatarText}>R</Text></View> : null}
|
|
387
|
+
<View style={styles.homeCardCopy}>
|
|
388
|
+
<Text numberOfLines={1} style={styles.homeCardAgent}>{state.settings.operator_name || t('support_name')}</Text>
|
|
389
|
+
<Text numberOfLines={1} style={styles.homeCardPreview}>{homePreview}</Text>
|
|
390
|
+
</View>
|
|
391
|
+
</View>
|
|
392
|
+
<View style={styles.homeCardAction}>
|
|
393
|
+
<Text style={styles.homeCardActionText}>{hasExisting ? t('back_to_chat') : t('lets_chat')}</Text>
|
|
394
|
+
</View>
|
|
395
|
+
</Pressable>
|
|
396
|
+
</View>
|
|
397
|
+
|
|
398
|
+
<View style={styles.homeSpacer} />
|
|
399
|
+
|
|
400
|
+
<View style={styles.bottomNav}>
|
|
401
|
+
<Pressable style={[styles.bottomNavButton, styles.bottomNavButtonActive]} onPress={() => showView('home')}>
|
|
402
|
+
<Text style={[styles.bottomNavIcon, styles.bottomNavIconActive]}>⌂</Text>
|
|
403
|
+
<Text style={[styles.bottomNavText, styles.bottomNavTextActive]}>{t('home')}</Text>
|
|
404
|
+
</Pressable>
|
|
405
|
+
<Pressable style={styles.bottomNavButton} onPress={() => showView('chat')}>
|
|
406
|
+
<Text style={styles.bottomNavIcon}>◌</Text>
|
|
407
|
+
<Text style={styles.bottomNavText}>{t('chat')}</Text>
|
|
408
|
+
</Pressable>
|
|
409
|
+
</View>
|
|
410
|
+
{renderPoweredBy()}
|
|
301
411
|
</View>
|
|
302
412
|
);
|
|
303
413
|
}
|
|
304
414
|
|
|
305
|
-
|
|
415
|
+
function renderChatHeader() {
|
|
416
|
+
return (
|
|
417
|
+
<View style={styles.chatHeader}>
|
|
418
|
+
<View style={styles.chatHeaderLeft}>
|
|
419
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('back')} style={styles.headerIconButton} onPress={() => showView('home')}>
|
|
420
|
+
<Text style={styles.backIcon}>‹</Text>
|
|
421
|
+
</Pressable>
|
|
422
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('options')} style={styles.headerIconButton} onPress={() => setOptionsOpen(value => !value)}>
|
|
423
|
+
<Text style={styles.menuIcon}>•••</Text>
|
|
424
|
+
</Pressable>
|
|
425
|
+
</View>
|
|
306
426
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
427
|
+
<Pressable
|
|
428
|
+
accessibilityRole="button"
|
|
429
|
+
accessibilityLabel={state.settings.operator_name || t('support_name')}
|
|
430
|
+
style={styles.agentTrigger}
|
|
431
|
+
onPress={() => {
|
|
432
|
+
if (state.settings.rating_enabled && contactConfirmed) setRatingOpen(true);
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
{renderAgentVisual()}
|
|
436
|
+
</Pressable>
|
|
311
437
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<Pressable style={styles.iconButton} onPress={() => setOptionsOpen(true)}><Text style={styles.iconText}>⋯</Text></Pressable>
|
|
324
|
-
{active && state.conversation.conversation_id ? <Pressable style={styles.iconButton} onPress={() => setCloseOpen(true)}><Text style={styles.iconText}>×</Text></Pressable> : null}
|
|
438
|
+
<View style={styles.chatHeaderRight}>
|
|
439
|
+
{typeof onClose === 'function' ? (
|
|
440
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('minimize')} style={styles.headerIconButton} onPress={onClose}>
|
|
441
|
+
<Text style={styles.minimizeIcon}>−</Text>
|
|
442
|
+
</Pressable>
|
|
443
|
+
) : null}
|
|
444
|
+
{active && Number(c.conversation_id || 0) > 0 ? (
|
|
445
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('close')} style={styles.headerIconButton} onPress={() => setCloseOpen(true)}>
|
|
446
|
+
<Text style={styles.closeIcon}>×</Text>
|
|
447
|
+
</Pressable>
|
|
448
|
+
) : null}
|
|
325
449
|
</View>
|
|
450
|
+
|
|
451
|
+
{optionsOpen ? (
|
|
452
|
+
<>
|
|
453
|
+
<Pressable style={styles.inlineDismiss} onPress={() => setOptionsOpen(false)} />
|
|
454
|
+
<View style={styles.optionsPopover}>
|
|
455
|
+
{state.settings.transcript_enabled && contactConfirmed ? (
|
|
456
|
+
<Pressable style={styles.popoverRow} onPress={() => { setOptionsOpen(false); setTranscriptOpen(true); }}>
|
|
457
|
+
<Text style={styles.popoverIcon}>✉</Text>
|
|
458
|
+
<Text style={styles.popoverText}>{t('send_transcript')}</Text>
|
|
459
|
+
</Pressable>
|
|
460
|
+
) : null}
|
|
461
|
+
{state.translationCustomerEnabled ? (
|
|
462
|
+
<Pressable style={styles.popoverRow} onPress={() => { setOptionsOpen(false); setLanguageOpen(true); }}>
|
|
463
|
+
<Text style={styles.popoverIcon}>◎</Text>
|
|
464
|
+
<Text style={styles.popoverText}>{t('language')}</Text>
|
|
465
|
+
<Text style={styles.popoverChevron}>›</Text>
|
|
466
|
+
</Pressable>
|
|
467
|
+
) : null}
|
|
468
|
+
{state.settings.sound_enabled !== undefined ? (
|
|
469
|
+
<Pressable style={styles.popoverRow} onPress={() => client.setSoundEnabled(!state.settings.sound_enabled)}>
|
|
470
|
+
<Text style={styles.popoverIcon}>◖</Text>
|
|
471
|
+
<Text style={styles.popoverText}>{t('sounds')}</Text>
|
|
472
|
+
<View style={[styles.switchTrack, state.settings.sound_enabled && styles.switchTrackActive]}>
|
|
473
|
+
<View style={[styles.switchThumb, state.settings.sound_enabled && styles.switchThumbActive]} />
|
|
474
|
+
</View>
|
|
475
|
+
</Pressable>
|
|
476
|
+
) : null}
|
|
477
|
+
</View>
|
|
478
|
+
</>
|
|
479
|
+
) : null}
|
|
326
480
|
</View>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
327
483
|
|
|
328
|
-
|
|
329
|
-
|
|
484
|
+
function renderContactStage() {
|
|
485
|
+
return (
|
|
486
|
+
<ScrollView style={styles.contactStageScroll} contentContainerStyle={styles.contactStage} keyboardShouldPersistTaps="handled">
|
|
487
|
+
<View style={styles.contactCard}>
|
|
330
488
|
<Text style={styles.contactTitle}>{t('start_chat_title')}</Text>
|
|
331
489
|
<Text style={styles.label}>{t('name')}</Text>
|
|
332
490
|
<TextInput style={styles.input} value={contactName} onChangeText={setContactName} maxLength={190} placeholder={t('name')} placeholderTextColor={palette.muted} autoCapitalize="words" />
|
|
@@ -336,203 +494,335 @@ function RegantisChat(props) {
|
|
|
336
494
|
<Pressable disabled={busyAction === 'contact'} style={[styles.primaryButton, busyAction === 'contact' && styles.disabled]} onPress={submitContact}>
|
|
337
495
|
{busyAction === 'contact' ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.primaryButtonText}>{t('start_chat')}</Text>}
|
|
338
496
|
</Pressable>
|
|
339
|
-
</
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
ref={listRef}
|
|
344
|
-
data={state.messages}
|
|
345
|
-
keyExtractor={item => String(item.chat_message_id)}
|
|
346
|
-
renderItem={renderMessage}
|
|
347
|
-
style={styles.messageList}
|
|
348
|
-
contentContainerStyle={styles.messageListContent}
|
|
349
|
-
onScrollBeginDrag={() => state.hasMore && client.loadOlder().catch(() => {})}
|
|
350
|
-
ListEmptyComponent={<View style={styles.emptyState}><Text style={styles.emptyText}>{t('empty')}</Text></View>}
|
|
351
|
-
/>
|
|
497
|
+
</View>
|
|
498
|
+
</ScrollView>
|
|
499
|
+
);
|
|
500
|
+
}
|
|
352
501
|
|
|
353
|
-
|
|
502
|
+
function renderComposer() {
|
|
503
|
+
if (!active) {
|
|
504
|
+
return (
|
|
505
|
+
<View style={styles.closedStage}>
|
|
506
|
+
<Text style={styles.closedText}>{c.closed_by === 'visitor' ? t('chat_closed') : t('chat_closed_agent')}</Text>
|
|
507
|
+
<Pressable disabled={busyAction === 'reopen'} style={styles.primaryButton} onPress={reopenChat}>
|
|
508
|
+
<Text style={styles.primaryButtonText}>{t('restart_chat')}</Text>
|
|
509
|
+
</Pressable>
|
|
510
|
+
</View>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
354
513
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
514
|
+
return (
|
|
515
|
+
<View style={styles.composerArea}>
|
|
516
|
+
{String(c.routing_mode || '') === 'chatbot' ? <Text style={styles.aiCompliance}>ⓘ {t('ai_compliance')}</Text> : null}
|
|
517
|
+
|
|
518
|
+
{attachOpen ? (
|
|
519
|
+
<View style={styles.attachmentMenu}>
|
|
520
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('image')}><Text style={styles.attachmentActionIcon}>▣</Text><Text style={styles.attachmentActionText}>{t('attach_image')}</Text></Pressable>
|
|
521
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('file')}><Text style={styles.attachmentActionIcon}>↥</Text><Text style={styles.attachmentActionText}>{t('attach_file')}</Text></Pressable>
|
|
522
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('screenshot')}><Text style={styles.attachmentActionIcon}>▧</Text><Text style={styles.attachmentActionText}>{t('attach_screenshot')}</Text></Pressable>
|
|
523
|
+
</View>
|
|
524
|
+
) : null}
|
|
525
|
+
|
|
526
|
+
{emojiOpen ? (
|
|
527
|
+
<View style={styles.emojiPicker}>
|
|
528
|
+
{EMOJIS.map(emoji => (
|
|
529
|
+
<Pressable key={emoji} style={styles.emojiButton} onPress={() => { setDraft(value => value + emoji); setEmojiOpen(false); }}>
|
|
530
|
+
<Text style={styles.emojiText}>{emoji}</Text>
|
|
531
|
+
</Pressable>
|
|
532
|
+
))}
|
|
533
|
+
</View>
|
|
534
|
+
) : null}
|
|
535
|
+
|
|
536
|
+
<View style={styles.composer}>
|
|
537
|
+
<Pressable disabled={!canUpload || state.uploading} style={[styles.composerButton, (!canUpload || state.uploading) && styles.disabled]} onPress={() => { setEmojiOpen(false); setAttachOpen(value => !value); }}>
|
|
538
|
+
<Text style={styles.composerPlus}>+</Text>
|
|
539
|
+
</Pressable>
|
|
540
|
+
<Pressable style={styles.composerButton} onPress={() => { setAttachOpen(false); setEmojiOpen(value => !value); }}>
|
|
541
|
+
<Text style={styles.composerEmoji}>☺</Text>
|
|
542
|
+
</Pressable>
|
|
543
|
+
<TextInput
|
|
544
|
+
style={styles.composerInput}
|
|
545
|
+
value={draft}
|
|
546
|
+
onChangeText={value => { setDraft(value); client.sendTyping(value); }}
|
|
547
|
+
placeholder={t('placeholder')}
|
|
548
|
+
placeholderTextColor={palette.muted}
|
|
549
|
+
multiline
|
|
550
|
+
maxLength={5000}
|
|
551
|
+
/>
|
|
552
|
+
<Pressable disabled={!draft.trim() || state.sending} style={[styles.sendButton, (!draft.trim() || state.sending) && styles.disabled]} onPress={submitMessage}>
|
|
553
|
+
{state.sending ? <ActivityIndicator size="small" color="#FFFFFF" /> : <Text style={styles.sendIcon}>➤</Text>}
|
|
554
|
+
</Pressable>
|
|
555
|
+
</View>
|
|
556
|
+
</View>
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function renderChat() {
|
|
561
|
+
return (
|
|
562
|
+
<View style={styles.screen}>
|
|
563
|
+
{renderChatHeader()}
|
|
564
|
+
<View style={styles.chatContent}>
|
|
565
|
+
{!contactConfirmed ? renderContactStage() : (
|
|
566
|
+
<>
|
|
567
|
+
<FlatList
|
|
568
|
+
ref={listRef}
|
|
569
|
+
data={state.messages}
|
|
570
|
+
keyExtractor={item => String(item.chat_message_id)}
|
|
571
|
+
renderItem={renderMessage}
|
|
572
|
+
style={styles.messageList}
|
|
573
|
+
contentContainerStyle={styles.messageListContent}
|
|
574
|
+
onScrollBeginDrag={() => state.hasMore && client.loadOlder().catch(() => {})}
|
|
575
|
+
ListHeaderComponent={state.hasMore ? <View style={styles.historyLoader}><ActivityIndicator size="small" color={palette.primary} /></View> : null}
|
|
576
|
+
ListEmptyComponent={<View style={styles.emptyState}><Text style={styles.emptyText}>{t('empty')}</Text></View>}
|
|
577
|
+
/>
|
|
578
|
+
{state.remoteTyping ? <View style={styles.typingRow}><Text style={styles.typingText}>{state.remoteTyping === 'chatbot' ? t('chatbot_typing') : t('typing')}</Text></View> : null}
|
|
579
|
+
{renderComposer()}
|
|
580
|
+
</>
|
|
382
581
|
)}
|
|
383
|
-
|
|
384
|
-
|
|
582
|
+
{renderPoweredBy()}
|
|
583
|
+
</View>
|
|
584
|
+
</View>
|
|
585
|
+
);
|
|
586
|
+
}
|
|
385
587
|
|
|
386
|
-
|
|
588
|
+
if (state.loading) {
|
|
589
|
+
return <View style={[styles.root, styles.center, style]}><ActivityIndicator size="large" color={palette.primary} /><Text style={styles.loadingText}>{t('loading')}</Text></View>;
|
|
590
|
+
}
|
|
387
591
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
</Modal>
|
|
592
|
+
if (state.error) {
|
|
593
|
+
return (
|
|
594
|
+
<View style={[styles.root, styles.center, style]}>
|
|
595
|
+
<Text style={styles.errorText}>{t('error')}</Text>
|
|
596
|
+
<Pressable style={styles.primaryButton} onPress={() => client.start().catch(() => {})}><Text style={styles.primaryButtonText}>{t('restart_chat')}</Text></Pressable>
|
|
597
|
+
</View>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
397
600
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
</
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{state.settings.transcript_enabled && contactConfirmed ? <Pressable style={styles.optionRow} onPress={() => { setOptionsOpen(false); setTranscriptOpen(true); }}><Text style={styles.optionText}>{t('send_transcript')}</Text><Text style={styles.optionValue}>›</Text></Pressable> : null}
|
|
417
|
-
{state.settings.rating_enabled && contactConfirmed ? <Pressable style={styles.optionRow} onPress={() => { setOptionsOpen(false); setRatingOpen(true); }}><Text style={styles.optionText}>{t('rating_agent_label')}</Text><Text style={styles.optionValue}>›</Text></Pressable> : null}
|
|
601
|
+
if (state.banned) return <View style={[styles.root, styles.center, style]}><Text style={styles.errorText}>{t('error')}</Text></View>;
|
|
602
|
+
|
|
603
|
+
return (
|
|
604
|
+
<KeyboardAvoidingView style={[styles.root, style]} behavior={Platform.OS === 'ios' ? 'padding' : undefined} keyboardVerticalOffset={props.keyboardVerticalOffset || 0}>
|
|
605
|
+
{activeView === 'home' ? renderHome() : renderChat()}
|
|
606
|
+
|
|
607
|
+
<Modal transparent visible={languageOpen} animationType="fade" onRequestClose={() => setLanguageOpen(false)}>
|
|
608
|
+
<Pressable style={styles.modalBackdrop} onPress={() => setLanguageOpen(false)}>
|
|
609
|
+
<View style={styles.sheet} onStartShouldSetResponder={() => true}>
|
|
610
|
+
<Text style={styles.sheetTitle}>{t('language')}</Text>
|
|
611
|
+
<ScrollView style={styles.languageList}>
|
|
612
|
+
<Pressable style={styles.languageRow} onPress={() => selectLanguage('')}><Text style={styles.optionText}>{t('language_auto')}</Text><Text style={styles.optionValue}>{!c.translation_language_code ? '✓' : ''}</Text></Pressable>
|
|
613
|
+
{state.languages.map(item => {
|
|
614
|
+
const code = String(item.code || item.language_code || '').toLowerCase();
|
|
615
|
+
const name = item.name || item.language_name || code.toUpperCase();
|
|
616
|
+
return <Pressable key={code} style={styles.languageRow} onPress={() => selectLanguage(code)}><Text style={styles.optionText}>{name}</Text><Text style={styles.optionValue}>{String(c.translation_language_code || '').toLowerCase() === code ? '✓' : code.toUpperCase()}</Text></Pressable>;
|
|
617
|
+
})}
|
|
618
|
+
</ScrollView>
|
|
418
619
|
</View>
|
|
419
620
|
</Pressable>
|
|
420
621
|
</Modal>
|
|
421
622
|
|
|
422
623
|
<Modal transparent visible={closeOpen} animationType="fade" onRequestClose={() => setCloseOpen(false)}>
|
|
423
|
-
<View style={styles.
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
624
|
+
<View style={styles.modalBackdropTop}>
|
|
625
|
+
<View style={styles.modalCard}>
|
|
626
|
+
<View style={styles.modalIcon}><Text style={styles.modalIconText}>↪</Text></View>
|
|
627
|
+
<Text style={styles.modalText}>{t('close_chat_question')}</Text>
|
|
628
|
+
<View style={styles.modalActions}>
|
|
629
|
+
<Pressable style={styles.secondaryButton} onPress={() => setCloseOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable>
|
|
630
|
+
<Pressable style={styles.dangerButton} onPress={closeChat}><Text style={styles.primaryButtonText}>{t('close_chat_confirm')}</Text></Pressable>
|
|
631
|
+
</View>
|
|
632
|
+
</View>
|
|
633
|
+
</View>
|
|
427
634
|
</Modal>
|
|
428
635
|
|
|
429
636
|
<Modal transparent visible={transcriptOpen} animationType="fade" onRequestClose={() => setTranscriptOpen(false)}>
|
|
430
|
-
<View style={styles.
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
637
|
+
<View style={styles.modalBackdropTop}>
|
|
638
|
+
<View style={styles.modalCard}>
|
|
639
|
+
<View style={styles.modalIcon}><Text style={styles.modalIconText}>✉</Text></View>
|
|
640
|
+
<Text style={styles.modalText}>{t('send_transcript_title')}</Text>
|
|
641
|
+
<TextInput style={styles.input} value={transcriptEmail} onChangeText={setTranscriptEmail} keyboardType="email-address" autoCapitalize="none" placeholder={t('email')} placeholderTextColor={palette.muted} />
|
|
642
|
+
{transcriptStatus ? <Text style={styles.modalStatus}>{transcriptStatus}</Text> : null}
|
|
643
|
+
<Pressable style={styles.primaryButton} onPress={sendTranscript}><Text style={styles.primaryButtonText}>{t('send_transcript')}</Text></Pressable>
|
|
644
|
+
</View>
|
|
645
|
+
</View>
|
|
436
646
|
</Modal>
|
|
437
647
|
|
|
438
648
|
<Modal transparent visible={ratingOpen} animationType="fade" onRequestClose={() => setRatingOpen(false)}>
|
|
439
|
-
<View style={styles.
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
649
|
+
<View style={styles.modalBackdropTop}>
|
|
650
|
+
<View style={styles.modalCard}>
|
|
651
|
+
<Text style={styles.modalTitle}>{state.settings.operator_name || t('support_name')}</Text>
|
|
652
|
+
<Text style={styles.ratingLabel}>{t('rating_agent_label')}</Text>
|
|
653
|
+
<View style={styles.ratingButtons}>
|
|
654
|
+
<Pressable style={styles.ratingButton} onPress={() => rate(1)}><Text style={styles.ratingIcon}>👍</Text></Pressable>
|
|
655
|
+
<Pressable style={styles.ratingButton} onPress={() => rate(0)}><Text style={styles.ratingIcon}>👎</Text></Pressable>
|
|
656
|
+
</View>
|
|
657
|
+
{ratingStatus ? <Text style={styles.modalStatus}>{ratingStatus}</Text> : null}
|
|
658
|
+
<TextInput style={[styles.input, styles.commentInput]} value={ratingComment} onChangeText={setRatingComment} multiline maxLength={2000} placeholder={t('rating_comment_placeholder')} placeholderTextColor={palette.muted} />
|
|
659
|
+
<View style={styles.modalActions}>
|
|
660
|
+
<Pressable style={styles.secondaryButton} onPress={() => setRatingOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable>
|
|
661
|
+
<Pressable style={styles.primaryButtonCompact} onPress={sendRatingComment}><Text style={styles.primaryButtonText}>{t('rating_comment_send')}</Text></Pressable>
|
|
662
|
+
</View>
|
|
663
|
+
</View>
|
|
664
|
+
</View>
|
|
446
665
|
</Modal>
|
|
447
666
|
</KeyboardAvoidingView>
|
|
448
667
|
);
|
|
449
668
|
}
|
|
450
669
|
|
|
451
670
|
function buildStyles(p) {
|
|
671
|
+
const shadow = {
|
|
672
|
+
shadowColor: '#172236',
|
|
673
|
+
shadowOffset: { width: 0, height: 8 },
|
|
674
|
+
shadowOpacity: p.dark ? 0.24 : 0.10,
|
|
675
|
+
shadowRadius: 18,
|
|
676
|
+
elevation: 5,
|
|
677
|
+
};
|
|
678
|
+
|
|
452
679
|
return StyleSheet.create({
|
|
453
|
-
root: { flex: 1, backgroundColor: p.background },
|
|
680
|
+
root: { flex: 1, minWidth: 0, minHeight: 0, overflow: 'hidden', backgroundColor: p.background },
|
|
681
|
+
screen: { flex: 1, minWidth: 0, minHeight: 0, backgroundColor: p.background },
|
|
454
682
|
center: { alignItems: 'center', justifyContent: 'center', padding: 24, gap: 14 },
|
|
455
|
-
loadingText: { color: p.muted, fontSize:
|
|
456
|
-
errorText: { color: p.text, fontSize:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
683
|
+
loadingText: { color: p.muted, fontSize: 13 },
|
|
684
|
+
errorText: { color: p.text, fontSize: 14, textAlign: 'center', marginBottom: 12 },
|
|
685
|
+
|
|
686
|
+
homeHero: { position: 'relative', overflow: 'hidden', paddingHorizontal: 18, paddingTop: 24, paddingBottom: 28, backgroundColor: p.heroBase },
|
|
687
|
+
heroDecorationA: { position: 'absolute', width: 240, height: 240, borderRadius: 120, top: -138, left: -74, backgroundColor: p.heroBlobA },
|
|
688
|
+
heroDecorationB: { position: 'absolute', width: 230, height: 230, borderRadius: 115, top: 80, right: -92, backgroundColor: p.heroBlobB },
|
|
689
|
+
heroDecorationC: { position: 'absolute', width: 210, height: 210, borderRadius: 105, right: -82, bottom: -120, backgroundColor: p.heroBlobC },
|
|
690
|
+
homeTop: { minHeight: 42, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', zIndex: 2 },
|
|
691
|
+
homeTopPlaceholder: { width: 32, height: 32 },
|
|
692
|
+
logoImage: { backgroundColor: p.surface },
|
|
693
|
+
logoFallback: { backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center', shadowColor: p.primary, shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.22, shadowRadius: 10, elevation: 4 },
|
|
694
|
+
logoFallbackText: { color: '#FFFFFF', fontWeight: '800' },
|
|
695
|
+
iconButton: { width: 38, height: 38, borderRadius: 19, backgroundColor: p.dark ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.82)', alignItems: 'center', justifyContent: 'center', zIndex: 3 },
|
|
696
|
+
minimizeIcon: { color: p.text, fontSize: 25, lineHeight: 27, fontWeight: '400', marginTop: -3 },
|
|
697
|
+
homeTitle: { marginTop: 24, zIndex: 2 },
|
|
698
|
+
homeTitleLine: { color: p.text, fontSize: 34, lineHeight: 36, letterSpacing: -1.1, fontWeight: '800' },
|
|
699
|
+
homeCard: { marginTop: 22, padding: 14, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.dark ? p.surface : 'rgba(255,255,255,0.96)', zIndex: 2, ...shadow },
|
|
700
|
+
homeCardTop: { minHeight: 38, flexDirection: 'row', alignItems: 'center' },
|
|
701
|
+
homeAvatar: { width: 38, height: 38, borderRadius: 19, marginRight: 10, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
702
|
+
homeAvatarText: { color: '#FFFFFF', fontSize: 14, fontWeight: '700' },
|
|
703
|
+
homeCardCopy: { flex: 1, minWidth: 0 },
|
|
704
|
+
homeCardAgent: { color: p.muted, fontSize: 11, lineHeight: 15, fontWeight: '700' },
|
|
705
|
+
homeCardPreview: { marginTop: 3, color: p.text, fontSize: 13, lineHeight: 18 },
|
|
706
|
+
homeCardAction: { minHeight: 41, marginTop: 12, paddingHorizontal: 12, borderRadius: 11, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
707
|
+
homeCardActionText: { color: '#FFFFFF', fontSize: 13, lineHeight: 18, fontWeight: '700', textAlign: 'center' },
|
|
708
|
+
homeSpacer: { flex: 1, minHeight: 20, backgroundColor: p.background },
|
|
709
|
+
bottomNav: { minHeight: 64, flexDirection: 'row', gap: 6, marginHorizontal: 16, marginBottom: 10, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, ...shadow },
|
|
710
|
+
bottomNavButton: { flex: 1, minHeight: 48, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
|
|
711
|
+
bottomNavButtonActive: { backgroundColor: p.dark ? 'rgba(255,255,255,0.04)' : '#FAFBFC' },
|
|
712
|
+
bottomNavIcon: { color: p.muted, fontSize: 22, lineHeight: 23 },
|
|
713
|
+
bottomNavIconActive: { color: p.text },
|
|
714
|
+
bottomNavText: { marginTop: 2, color: p.muted, fontSize: 10, lineHeight: 12 },
|
|
715
|
+
bottomNavTextActive: { color: p.text },
|
|
716
|
+
|
|
717
|
+
chatHeader: { position: 'relative', zIndex: 20, minHeight: 64, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, borderBottomWidth: 1, borderBottomColor: p.border, backgroundColor: p.header, overflow: 'visible' },
|
|
718
|
+
chatHeaderLeft: { position: 'absolute', zIndex: 30, left: 12, top: 16, flexDirection: 'row', alignItems: 'center', gap: 5 },
|
|
719
|
+
chatHeaderRight: { position: 'absolute', zIndex: 30, right: 12, top: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 5 },
|
|
720
|
+
headerIconButton: { width: 32, height: 32, borderRadius: 16, backgroundColor: p.dark ? 'rgba(255,255,255,0.08)' : '#FFFFFF', alignItems: 'center', justifyContent: 'center' },
|
|
721
|
+
backIcon: { color: p.text, fontSize: 31, lineHeight: 31, marginTop: -4 },
|
|
722
|
+
menuIcon: { color: p.text, fontSize: 17, lineHeight: 18, letterSpacing: -1.5, fontWeight: '700', marginTop: -4 },
|
|
723
|
+
closeIcon: { color: p.text, fontSize: 23, lineHeight: 24, marginTop: -2 },
|
|
724
|
+
agentTrigger: { position: 'absolute', zIndex: 18, top: 0, left: '50%', width: 218, height: 48, marginLeft: -109, borderWidth: 1, borderTopWidth: 0, borderColor: p.border, borderBottomLeftRadius: 24, borderBottomRightRadius: 24, backgroundColor: p.surface, alignItems: 'center', justifyContent: 'center', shadowColor: '#172236', shadowOffset: { width: 0, height: 5 }, shadowOpacity: p.dark ? 0.18 : 0.07, shadowRadius: 8, elevation: 2 },
|
|
725
|
+
agentVisual: { height: 30, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
|
|
726
|
+
agentAvatar: { width: 28, height: 28, borderRadius: 14, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
727
|
+
agentAvatarOverlap: { marginLeft: -8, borderWidth: 2, borderColor: p.surface },
|
|
728
|
+
agentAvatarText: { color: '#FFFFFF', fontSize: 11, fontWeight: '700' },
|
|
729
|
+
agentFallback: { width: 28, height: 28, borderRadius: 14, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
730
|
+
agentFallbackText: { color: p.muted, fontSize: 12 },
|
|
731
|
+
inlineDismiss: { position: 'absolute', zIndex: 23, top: 0, left: -12, right: -12, bottom: -1000 },
|
|
732
|
+
optionsPopover: { position: 'absolute', zIndex: 40, top: 52, left: 46, width: 230, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
733
|
+
popoverRow: { minHeight: 43, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 9, borderRadius: 9 },
|
|
734
|
+
popoverIcon: { width: 26, color: p.text, fontSize: 16 },
|
|
735
|
+
popoverText: { flex: 1, color: p.text, fontSize: 13 },
|
|
736
|
+
popoverChevron: { color: p.muted, fontSize: 20 },
|
|
737
|
+
switchTrack: { width: 34, height: 20, borderRadius: 10, padding: 2, backgroundColor: p.dark ? '#3D434B' : '#D7DBE1' },
|
|
738
|
+
switchTrackActive: { backgroundColor: '#24935B' },
|
|
739
|
+
switchThumb: { width: 16, height: 16, borderRadius: 8, backgroundColor: '#FFFFFF' },
|
|
740
|
+
switchThumbActive: { transform: [{ translateX: 14 }] },
|
|
741
|
+
|
|
742
|
+
chatContent: { flex: 1, minHeight: 0, backgroundColor: p.background },
|
|
743
|
+
contactStageScroll: { flex: 1 },
|
|
744
|
+
contactStage: { paddingHorizontal: 16, paddingTop: 18, paddingBottom: 24 },
|
|
745
|
+
contactCard: { padding: 18, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, ...shadow },
|
|
746
|
+
contactTitle: { color: p.text, fontSize: 14, lineHeight: 20, marginBottom: 13 },
|
|
747
|
+
label: { color: p.text, fontSize: 12, lineHeight: 16, marginBottom: 5, marginTop: 9 },
|
|
748
|
+
input: { minHeight: 42, borderWidth: 1, borderColor: p.dark ? '#555B64' : '#B7BDC7', borderRadius: 8, backgroundColor: p.surface, color: p.text, paddingHorizontal: 11, paddingVertical: 9, fontSize: 13 },
|
|
749
|
+
inlineError: { color: '#B42318', fontSize: 11, lineHeight: 15, marginTop: 8 },
|
|
750
|
+
primaryButton: { minHeight: 40, borderRadius: 9, backgroundColor: p.primary, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', marginTop: 12 },
|
|
751
|
+
primaryButtonCompact: { flex: 1, minHeight: 40, borderRadius: 9, backgroundColor: p.primary, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
752
|
+
primaryButtonText: { color: '#FFFFFF', fontSize: 13, fontWeight: '700', textAlign: 'center' },
|
|
753
|
+
secondaryButton: { flex: 1, minHeight: 40, borderWidth: 1, borderColor: p.dark ? '#555B64' : '#C7CCD4', borderRadius: 9, backgroundColor: p.surface, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
754
|
+
secondaryButtonText: { color: p.text, fontSize: 13, fontWeight: '700', textAlign: 'center' },
|
|
755
|
+
dangerButton: { flex: 1, minHeight: 40, borderRadius: 9, backgroundColor: p.danger, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
478
756
|
disabled: { opacity: 0.55 },
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
bubble: { maxWidth: '100%', borderRadius: 16,
|
|
757
|
+
|
|
758
|
+
messageList: { flex: 1, minHeight: 0 },
|
|
759
|
+
messageListContent: { paddingHorizontal: 16, paddingTop: 18, paddingBottom: 12, flexGrow: 1 },
|
|
760
|
+
historyLoader: { minHeight: 34, alignItems: 'center', justifyContent: 'center', paddingBottom: 10 },
|
|
761
|
+
emptyState: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 30, paddingVertical: 20 },
|
|
762
|
+
emptyText: { maxWidth: 260, paddingHorizontal: 16, paddingVertical: 14, borderRadius: 15, backgroundColor: p.dark ? p.surface : 'rgba(255,255,255,0.84)', color: p.muted, fontSize: 13, lineHeight: 19, textAlign: 'center' },
|
|
763
|
+
messageRow: { width: '100%', marginBottom: 11, flexDirection: 'row' },
|
|
764
|
+
messageRowLeft: { justifyContent: 'flex-start', paddingRight: 52 },
|
|
765
|
+
messageRowRight: { justifyContent: 'flex-end', paddingLeft: 52 },
|
|
766
|
+
bubble: { maxWidth: '100%', paddingHorizontal: 12, paddingTop: 10, paddingBottom: 8, borderRadius: 15, shadowColor: '#172236', shadowOffset: { width: 0, height: 4 }, shadowOpacity: p.dark ? 0.16 : 0.07, shadowRadius: 7, elevation: 2 },
|
|
489
767
|
customerBubble: { backgroundColor: p.customerBubble, borderBottomRightRadius: 5 },
|
|
490
|
-
agentBubble: { backgroundColor: p.agentBubble, borderBottomLeftRadius: 5
|
|
768
|
+
agentBubble: { backgroundColor: p.agentBubble, borderBottomLeftRadius: 5 },
|
|
491
769
|
customerText: { color: p.customerText },
|
|
492
770
|
agentText: { color: p.agentText },
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
771
|
+
messageText: { fontSize: 14, lineHeight: 20 },
|
|
772
|
+
messageMeta: { minHeight: 13, marginTop: 5, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 },
|
|
773
|
+
messageSender: { flex: 1, fontSize: 10, lineHeight: 12, fontWeight: '600', opacity: 0.62 },
|
|
774
|
+
timeText: { fontSize: 10, lineHeight: 12, opacity: 0.62 },
|
|
496
775
|
systemRow: { alignSelf: 'center', paddingHorizontal: 16, paddingVertical: 7, marginVertical: 3 },
|
|
497
|
-
systemText: { color: p.muted, fontSize: 11, textAlign: 'center' },
|
|
498
|
-
attachmentImage: { width:
|
|
499
|
-
fileAttachment: { paddingVertical:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
776
|
+
systemText: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center' },
|
|
777
|
+
attachmentImage: { width: 220, maxWidth: '100%', height: 180, borderRadius: 10, marginBottom: 4, backgroundColor: p.surfaceAlt },
|
|
778
|
+
fileAttachment: { paddingVertical: 4 },
|
|
779
|
+
fileAttachmentText: { fontSize: 13, fontWeight: '600' },
|
|
780
|
+
typingRow: { flex: 0, paddingHorizontal: 18, paddingTop: 8, paddingBottom: 7 },
|
|
781
|
+
typingText: { color: p.muted, fontSize: 11, lineHeight: 15 },
|
|
782
|
+
|
|
783
|
+
closedStage: { flex: 0, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12, borderTopWidth: 1, borderTopColor: p.border, backgroundColor: p.background },
|
|
784
|
+
closedText: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center' },
|
|
785
|
+
composerArea: { position: 'relative', flex: 0, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12, backgroundColor: p.background },
|
|
786
|
+
composer: { minHeight: 54, flexDirection: 'row', alignItems: 'flex-end', gap: 8, paddingLeft: 13, paddingRight: 7, paddingVertical: 7, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, shadowColor: '#172236', shadowOffset: { width: 0, height: 8 }, shadowOpacity: p.dark ? 0.22 : 0.10, shadowRadius: 12, elevation: 4 },
|
|
787
|
+
composerButton: { width: 34, height: 38, borderRadius: 19, backgroundColor: 'transparent', alignItems: 'center', justifyContent: 'center' },
|
|
788
|
+
composerPlus: { color: p.text, fontSize: 25, lineHeight: 27, fontWeight: '300', marginTop: -2 },
|
|
789
|
+
composerEmoji: { color: p.text, fontSize: 23, lineHeight: 25 },
|
|
790
|
+
composerInput: { flex: 1, minWidth: 0, minHeight: 38, maxHeight: 110, backgroundColor: 'transparent', color: p.text, paddingHorizontal: 0, paddingTop: 9, paddingBottom: 7, fontSize: 14, lineHeight: 19, textAlignVertical: 'top' },
|
|
791
|
+
sendButton: { width: 38, height: 38, borderRadius: 19, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
792
|
+
sendIcon: { color: '#FFFFFF', fontSize: 17, lineHeight: 18, transform: [{ rotate: '-3deg' }] },
|
|
793
|
+
aiCompliance: { color: p.muted, fontSize: 9, lineHeight: 13, textAlign: 'center', paddingHorizontal: 5, paddingBottom: 8 },
|
|
794
|
+
attachmentMenu: { position: 'absolute', zIndex: 12, left: 12, bottom: 72, width: 210, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
795
|
+
attachmentAction: { minHeight: 40, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, borderRadius: 9 },
|
|
796
|
+
attachmentActionIcon: { width: 26, color: p.text, fontSize: 15 },
|
|
797
|
+
attachmentActionText: { flex: 1, color: p.text, fontSize: 13 },
|
|
798
|
+
emojiPicker: { position: 'absolute', zIndex: 12, left: 12, right: 12, bottom: 72, flexDirection: 'row', flexWrap: 'wrap', gap: 5, padding: 10, borderWidth: 1, borderColor: p.border, borderRadius: 16, backgroundColor: p.surface, ...shadow },
|
|
799
|
+
emojiButton: { width: '11%', aspectRatio: 1, minHeight: 34, borderRadius: 8, alignItems: 'center', justifyContent: 'center' },
|
|
800
|
+
emojiText: { fontSize: 20, lineHeight: 22 },
|
|
801
|
+
|
|
802
|
+
poweredBy: { flex: 0, minHeight: 17, alignItems: 'center', justifyContent: 'center', paddingBottom: 5, backgroundColor: p.background },
|
|
803
|
+
poweredByText: { color: '#9398A2', fontSize: 9, lineHeight: 11, textAlign: 'center' },
|
|
804
|
+
poweredByBrand: { color: p.text, fontWeight: '700' },
|
|
805
|
+
|
|
806
|
+
modalBackdrop: { flex: 1, backgroundColor: 'rgba(16,20,27,0.55)', justifyContent: 'flex-end', padding: 16 },
|
|
807
|
+
modalBackdropTop: { flex: 1, backgroundColor: 'rgba(16,20,27,0.55)', justifyContent: 'flex-start', padding: 16 },
|
|
808
|
+
modalCard: { width: '100%', maxHeight: '82%', marginTop: 34, paddingHorizontal: 16, paddingTop: 24, paddingBottom: 16, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
809
|
+
modalIcon: { width: 48, height: 48, borderRadius: 24, alignSelf: 'center', alignItems: 'center', justifyContent: 'center', backgroundColor: p.surfaceAlt },
|
|
810
|
+
modalIconText: { color: p.text, fontSize: 22 },
|
|
811
|
+
modalText: { marginHorizontal: 4, marginVertical: 16, color: p.text, fontSize: 14, lineHeight: 20, textAlign: 'center' },
|
|
812
|
+
modalTitle: { color: p.text, fontSize: 16, fontWeight: '700', textAlign: 'center' },
|
|
813
|
+
modalActions: { flexDirection: 'row', gap: 8, marginTop: 12 },
|
|
814
|
+
modalStatus: { color: p.muted, fontSize: 11, lineHeight: 15, marginTop: 9, textAlign: 'center' },
|
|
815
|
+
sheet: { maxHeight: '72%', borderRadius: 18, paddingHorizontal: 14, paddingTop: 14, paddingBottom: 8, backgroundColor: p.surface, ...shadow },
|
|
816
|
+
sheetTitle: { color: p.text, fontSize: 16, fontWeight: '700', marginBottom: 8 },
|
|
817
|
+
languageList: { maxHeight: 360 },
|
|
818
|
+
languageRow: { minHeight: 46, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 8, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: p.border },
|
|
819
|
+
optionText: { flex: 1, color: p.text, fontSize: 14 },
|
|
527
820
|
optionValue: { color: p.muted, fontSize: 12, marginLeft: 12 },
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
ratingButton: { width: 66, height: 54, borderRadius: 14, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
534
|
-
ratingIcon: { fontSize: 27 },
|
|
535
|
-
commentInput: { minHeight: 82, textAlignVertical: 'top', marginTop: 10 },
|
|
821
|
+
ratingLabel: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center', marginTop: 3 },
|
|
822
|
+
ratingButtons: { flexDirection: 'row', gap: 12, justifyContent: 'center', marginVertical: 12 },
|
|
823
|
+
ratingButton: { width: 60, height: 48, borderWidth: 1, borderColor: p.border, borderRadius: 12, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
824
|
+
ratingIcon: { fontSize: 24 },
|
|
825
|
+
commentInput: { minHeight: 72, textAlignVertical: 'top', marginTop: 6 },
|
|
536
826
|
});
|
|
537
827
|
}
|
|
538
828
|
|