@regantis-sdk/react-native-chat 0.1.1 → 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 CHANGED
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@regantis-sdk/react-native-chat",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Regantis customer support chat SDK for React Native Android and iOS",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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 ? '#17181B' : '#FFFFFF',
41
- surfaceAlt: dark ? '#22252A' : '#EEF1F5',
42
- border: dark ? '#30343B' : '#E0E4EA',
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: '#C93A3A',
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
- <Text style={[styles.timeText, textStyle]}>{formatTime(item.date_added)}</Text>
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
- if (state.loading) {
293
- return <View style={[styles.root, styles.center, style]}><ActivityIndicator size="large" color={palette.primary} /><Text style={styles.loadingText}>{t('loading')}</Text></View>;
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
- if (state.error) {
362
+ function renderHome() {
297
363
  return (
298
- <View style={[styles.root, styles.center, style]}>
299
- <Text style={styles.errorText}>{t('error')}</Text>
300
- <Pressable style={styles.primaryButton} onPress={() => client.start().catch(() => {})}><Text style={styles.primaryButtonText}>{t('restart_chat')}</Text></Pressable>
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
- if (state.banned) return <View style={[styles.root, styles.center, style]}><Text style={styles.errorText}>{t('error')}</Text></View>;
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
- const c = state.conversation || {};
308
- const active = Number(c.status || 0) === 1;
309
- const contactConfirmed = Number(c.contact_confirmed || 0) === 1;
310
- const canUpload = !!(state.upload && state.upload.enabled);
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
- return (
313
- <KeyboardAvoidingView style={[styles.root, style]} behavior={Platform.OS === 'ios' ? 'padding' : undefined} keyboardVerticalOffset={props.keyboardVerticalOffset || 0}>
314
- <View style={styles.header}>
315
- <View style={styles.headerBrand}>
316
- {state.settings.show_logo && state.settings.logo_url ? <Image source={{ uri: state.settings.logo_url }} style={styles.logo} resizeMode="contain" /> : <View style={styles.logoFallback}><Text style={styles.logoFallbackText}>R</Text></View>}
317
- <View style={styles.headerCopy}>
318
- <Text numberOfLines={1} style={styles.headerTitle}>{state.settings.operator_name || t('support_name')}</Text>
319
- <Text numberOfLines={1} style={styles.headerStatus}>{t('welcome_text')}</Text>
320
- </View>
321
- </View>
322
- <View style={styles.headerActions}>
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
- {!contactConfirmed ? (
329
- <ScrollView contentContainerStyle={styles.contactStage} keyboardShouldPersistTaps="handled">
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
- </ScrollView>
340
- ) : (
341
- <>
342
- <FlatList
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
- {state.remoteTyping ? <View style={styles.typingRow}><Text style={styles.typingText}>{state.remoteTyping === 'chatbot' ? t('chatbot_typing') : t('typing')}</Text></View> : null}
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
- {!active ? (
356
- <View style={styles.closedStage}>
357
- <Text style={styles.closedText}>{c.closed_by === 'visitor' ? t('chat_closed') : t('chat_closed_agent')}</Text>
358
- <Pressable disabled={busyAction === 'reopen'} style={styles.primaryButton} onPress={reopenChat}><Text style={styles.primaryButtonText}>{t('restart_chat')}</Text></Pressable>
359
- {state.settings.rating_enabled ? <Pressable style={styles.secondaryButton} onPress={() => setRatingOpen(true)}><Text style={styles.secondaryButtonText}>{t('rating_agent_label')}</Text></Pressable> : null}
360
- </View>
361
- ) : (
362
- <View style={styles.composerArea}>
363
- {emojiOpen ? <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.emojiRow}>{EMOJIS.map(emoji => <Pressable key={emoji} style={styles.emojiButton} onPress={() => { setDraft(value => value + emoji); setEmojiOpen(false); }}><Text style={styles.emojiText}>{emoji}</Text></Pressable>)}</ScrollView> : null}
364
- <View style={styles.composer}>
365
- <Pressable disabled={!canUpload || state.uploading} style={styles.composerButton} onPress={() => setAttachOpen(true)}><Text style={styles.composerIcon}>+</Text></Pressable>
366
- <Pressable style={styles.composerButton} onPress={() => setEmojiOpen(value => !value)}><Text style={styles.composerIcon}>☺</Text></Pressable>
367
- <TextInput
368
- style={styles.composerInput}
369
- value={draft}
370
- onChangeText={value => { setDraft(value); client.sendTyping(value); }}
371
- placeholder={t('placeholder')}
372
- placeholderTextColor={palette.muted}
373
- multiline
374
- maxLength={5000}
375
- />
376
- <Pressable disabled={!draft.trim() || state.sending} style={[styles.sendButton, (!draft.trim() || state.sending) && styles.disabled]} onPress={submitMessage}>
377
- {state.sending ? <ActivityIndicator size="small" color="#FFFFFF" /> : <Text style={styles.sendIcon}>➤</Text>}
378
- </Pressable>
379
- </View>
380
- {String(c.routing_mode || '') === 'chatbot' ? <Text style={styles.aiCompliance}>{t('ai_compliance')}</Text> : null}
381
- </View>
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
- {!state.settings.white_label ? <View style={styles.poweredBy}><Text style={styles.poweredByText}>{t('powered_by')} Regantis</Text></View> : null}
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
- <Modal transparent visible={attachOpen} animationType="fade" onRequestClose={() => setAttachOpen(false)}>
389
- <Pressable style={styles.modalBackdrop} onPress={() => setAttachOpen(false)}>
390
- <View style={styles.sheet}>
391
- <Pressable style={styles.sheetRow} onPress={() => pick('image')}><Text style={styles.sheetText}>▣ {t('attach_image')}</Text></Pressable>
392
- <Pressable style={styles.sheetRow} onPress={() => pick('file')}><Text style={styles.sheetText}>📎 {t('attach_file')}</Text></Pressable>
393
- <Pressable style={styles.sheetRow} onPress={() => pick('screenshot')}><Text style={styles.sheetText}>▧ {t('attach_screenshot')}</Text></Pressable>
394
- </View>
395
- </Pressable>
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
- <Modal transparent visible={optionsOpen} animationType="fade" onRequestClose={() => setOptionsOpen(false)}>
399
- <Pressable style={styles.modalBackdrop} onPress={() => setOptionsOpen(false)}>
400
- <View style={styles.modalCard} onStartShouldSetResponder={() => true}>
401
- <Text style={styles.modalTitle}>{t('options')}</Text>
402
- <Pressable style={styles.optionRow} onPress={() => client.setSoundEnabled(!state.settings.sound_enabled)}><Text style={styles.optionText}>{t('sounds')}</Text><Text style={styles.optionValue}>{state.settings.sound_enabled ? '✓' : '—'}</Text></Pressable>
403
- {state.translationCustomerEnabled ? (
404
- <View style={styles.optionSection}>
405
- <Text style={styles.optionSectionTitle}>{t('language')}</Text>
406
- <ScrollView style={styles.languageList}>
407
- <Pressable style={styles.languageRow} onPress={() => selectLanguage('')}><Text style={styles.optionText}>{t('language_auto')}</Text><Text style={styles.optionValue}>{!c.translation_language_code ? '✓' : ''}</Text></Pressable>
408
- {state.languages.map(item => {
409
- const code = String(item.code || item.language_code || '').toLowerCase();
410
- const name = item.name || item.language_name || code.toUpperCase();
411
- 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>;
412
- })}
413
- </ScrollView>
414
- </View>
415
- ) : null}
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.modalBackdrop}><View style={styles.modalCard}>
424
- <Text style={styles.modalTitle}>{t('close_chat_question')}</Text>
425
- <View style={styles.modalActions}><Pressable style={styles.secondaryButton} onPress={() => setCloseOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable><Pressable style={styles.dangerButton} onPress={closeChat}><Text style={styles.primaryButtonText}>{t('close_chat_confirm')}</Text></Pressable></View>
426
- </View></View>
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.modalBackdrop}><View style={styles.modalCard}>
431
- <Text style={styles.modalTitle}>{t('send_transcript_title')}</Text>
432
- <TextInput style={styles.input} value={transcriptEmail} onChangeText={setTranscriptEmail} keyboardType="email-address" autoCapitalize="none" placeholder={t('email')} placeholderTextColor={palette.muted} />
433
- {transcriptStatus ? <Text style={styles.modalStatus}>{transcriptStatus}</Text> : null}
434
- <View style={styles.modalActions}><Pressable style={styles.secondaryButton} onPress={() => setTranscriptOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable><Pressable style={styles.primaryButton} onPress={sendTranscript}><Text style={styles.primaryButtonText}>{t('send_transcript')}</Text></Pressable></View>
435
- </View></View>
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.modalBackdrop}><View style={styles.modalCard}>
440
- <Text style={styles.modalTitle}>{t('rating_agent_label')}</Text>
441
- <View style={styles.ratingButtons}><Pressable style={styles.ratingButton} onPress={() => rate(1)}><Text style={styles.ratingIcon}>👍</Text></Pressable><Pressable style={styles.ratingButton} onPress={() => rate(0)}><Text style={styles.ratingIcon}>👎</Text></Pressable></View>
442
- {ratingStatus ? <Text style={styles.modalStatus}>{ratingStatus}</Text> : null}
443
- <TextInput style={[styles.input, styles.commentInput]} value={ratingComment} onChangeText={setRatingComment} multiline maxLength={2000} placeholder={t('rating_comment_placeholder')} placeholderTextColor={palette.muted} />
444
- <View style={styles.modalActions}><Pressable style={styles.secondaryButton} onPress={() => setRatingOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable><Pressable style={styles.primaryButton} onPress={sendRatingComment}><Text style={styles.primaryButtonText}>{t('rating_comment_send')}</Text></Pressable></View>
445
- </View></View>
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: 14 },
456
- errorText: { color: p.text, fontSize: 15, textAlign: 'center', marginBottom: 16 },
457
- header: { minHeight: 68, paddingHorizontal: 14, paddingVertical: 10, backgroundColor: p.surface, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: p.border, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
458
- headerBrand: { flex: 1, flexDirection: 'row', alignItems: 'center', minWidth: 0 },
459
- headerCopy: { flex: 1, minWidth: 0 },
460
- headerTitle: { color: p.text, fontSize: 16, fontWeight: '700' },
461
- headerStatus: { color: p.muted, fontSize: 11, marginTop: 2 },
462
- headerActions: { flexDirection: 'row', alignItems: 'center', gap: 6 },
463
- logo: { width: 38, height: 38, borderRadius: 10, marginRight: 10 },
464
- logoFallback: { width: 38, height: 38, borderRadius: 10, marginRight: 10, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
465
- logoFallbackText: { color: '#FFFFFF', fontWeight: '800', fontSize: 18 },
466
- iconButton: { width: 38, height: 38, borderRadius: 10, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
467
- iconText: { color: p.text, fontSize: 24, lineHeight: 26 },
468
- contactStage: { flexGrow: 1, justifyContent: 'center', padding: 22 },
469
- contactTitle: { color: p.text, fontSize: 20, lineHeight: 27, fontWeight: '700', marginBottom: 24 },
470
- label: { color: p.text, fontSize: 13, fontWeight: '600', marginBottom: 7, marginTop: 10 },
471
- input: { minHeight: 48, borderWidth: 1, borderColor: p.border, borderRadius: 12, backgroundColor: p.surface, color: p.text, paddingHorizontal: 13, paddingVertical: 10, fontSize: 15 },
472
- inlineError: { color: p.danger, fontSize: 12, marginTop: 8 },
473
- primaryButton: { minHeight: 48, borderRadius: 12, backgroundColor: p.primary, paddingHorizontal: 18, alignItems: 'center', justifyContent: 'center', marginTop: 14 },
474
- primaryButtonText: { color: '#FFFFFF', fontSize: 14, fontWeight: '700' },
475
- secondaryButton: { minHeight: 46, borderRadius: 12, backgroundColor: p.surfaceAlt, paddingHorizontal: 16, alignItems: 'center', justifyContent: 'center', marginTop: 10 },
476
- secondaryButtonText: { color: p.text, fontSize: 14, fontWeight: '700' },
477
- dangerButton: { minHeight: 46, borderRadius: 12, backgroundColor: p.danger, paddingHorizontal: 16, alignItems: 'center', justifyContent: 'center', marginTop: 10 },
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
- messageList: { flex: 1 },
480
- messageListContent: { paddingHorizontal: 12, paddingVertical: 14, flexGrow: 1 },
481
- emptyState: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 30 },
482
- emptyText: { color: p.muted, textAlign: 'center', lineHeight: 21 },
483
- messageRow: { marginBottom: 8, flexDirection: 'row', alignItems: 'flex-end', maxWidth: '88%' },
484
- messageRowLeft: { alignSelf: 'flex-start' },
485
- messageRowRight: { alignSelf: 'flex-end', justifyContent: 'flex-end' },
486
- avatar: { width: 28, height: 28, borderRadius: 14, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center', marginRight: 6 },
487
- avatarText: { color: '#FFFFFF', fontSize: 12, fontWeight: '800' },
488
- bubble: { maxWidth: '100%', borderRadius: 16, paddingHorizontal: 12, paddingVertical: 9 },
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, borderWidth: StyleSheet.hairlineWidth, borderColor: p.border },
768
+ agentBubble: { backgroundColor: p.agentBubble, borderBottomLeftRadius: 5 },
491
769
  customerText: { color: p.customerText },
492
770
  agentText: { color: p.agentText },
493
- senderName: { fontSize: 11, fontWeight: '700', marginBottom: 4, opacity: 0.85 },
494
- messageText: { fontSize: 15, lineHeight: 20 },
495
- timeText: { fontSize: 9, marginTop: 4, opacity: 0.6, alignSelf: 'flex-end' },
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: 210, height: 160, borderRadius: 10, marginBottom: 5, backgroundColor: p.surfaceAlt },
499
- fileAttachment: { paddingVertical: 5 },
500
- typingRow: { paddingHorizontal: 16, paddingVertical: 5 },
501
- typingText: { color: p.muted, fontSize: 12, fontStyle: 'italic' },
502
- closedStage: { padding: 14, backgroundColor: p.surface, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: p.border },
503
- closedText: { color: p.muted, textAlign: 'center', marginBottom: 4 },
504
- composerArea: { backgroundColor: p.surface, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: p.border, paddingHorizontal: 8, paddingTop: 7, paddingBottom: 8 },
505
- composer: { flexDirection: 'row', alignItems: 'flex-end', gap: 5 },
506
- composerButton: { width: 38, height: 42, borderRadius: 11, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
507
- composerIcon: { color: p.text, fontSize: 21 },
508
- composerInput: { flex: 1, minHeight: 42, maxHeight: 120, borderRadius: 13, backgroundColor: p.surfaceAlt, color: p.text, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 9, fontSize: 15 },
509
- sendButton: { width: 42, height: 42, borderRadius: 13, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
510
- sendIcon: { color: '#FFFFFF', fontSize: 18 },
511
- aiCompliance: { color: p.muted, fontSize: 9, textAlign: 'center', marginTop: 5, paddingHorizontal: 8 },
512
- emojiRow: { paddingVertical: 6, gap: 4 },
513
- emojiButton: { width: 39, height: 39, borderRadius: 10, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
514
- emojiText: { fontSize: 21 },
515
- poweredBy: { backgroundColor: p.surface, paddingBottom: 4, alignItems: 'center' },
516
- poweredByText: { color: p.muted, fontSize: 8 },
517
- modalBackdrop: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'center', padding: 20 },
518
- modalCard: { backgroundColor: p.surface, borderRadius: 18, padding: 16, maxHeight: '82%' },
519
- modalTitle: { color: p.text, fontSize: 17, fontWeight: '700', marginBottom: 12 },
520
- modalActions: { flexDirection: 'row', gap: 8, justifyContent: 'flex-end' },
521
- modalStatus: { color: p.muted, fontSize: 12, marginTop: 9 },
522
- sheet: { alignSelf: 'stretch', backgroundColor: p.surface, borderRadius: 18, paddingVertical: 6 },
523
- sheetRow: { minHeight: 54, justifyContent: 'center', paddingHorizontal: 18, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: p.border },
524
- sheetText: { color: p.text, fontSize: 16 },
525
- optionRow: { minHeight: 50, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: p.border },
526
- optionText: { color: p.text, fontSize: 14 },
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
- optionSection: { paddingTop: 10 },
529
- optionSectionTitle: { color: p.muted, fontSize: 11, fontWeight: '700', marginBottom: 4 },
530
- languageList: { maxHeight: 220 },
531
- languageRow: { minHeight: 44, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingLeft: 8 },
532
- ratingButtons: { flexDirection: 'row', gap: 12, justifyContent: 'center', marginVertical: 8 },
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
 
package/src/defaults.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module.exports = {
4
4
  DEFAULT_SERVER_URL: 'https://www.crm.regantis.technology',
5
- SDK_VERSION: '0.1.1',
5
+ SDK_VERSION: '0.1.2',
6
6
  DEFAULT_SETTINGS: {
7
7
  operator_name: 'Operator',
8
8
  use_profile_names: 0,