@solucx/react-native-solucx-widget 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.intern.md +513 -513
  2. package/README.md +285 -285
  3. package/package.json +50 -23
  4. package/src/SoluCXWidget.tsx +172 -119
  5. package/src/__mocks__/expo-modules-core-web.js +16 -0
  6. package/src/__mocks__/expo-modules-core.js +33 -0
  7. package/src/__tests__/ClientVersionCollector.test.ts +55 -0
  8. package/src/__tests__/CloseButton.test.tsx +47 -0
  9. package/src/__tests__/Constants.test.ts +17 -0
  10. package/src/__tests__/InlineWidget.rendering.test.tsx +81 -0
  11. package/src/__tests__/ModalWidget.rendering.test.tsx +157 -0
  12. package/src/__tests__/OverlayWidget.rendering.test.tsx +123 -0
  13. package/src/__tests__/SoluCXWidget.rendering.test.tsx +315 -0
  14. package/src/__tests__/e2e/widget-lifecycle.test.tsx +353 -0
  15. package/src/__tests__/integration/webview-communication-simple.test.tsx +147 -0
  16. package/src/__tests__/integration/webview-communication.test.tsx +417 -0
  17. package/src/__tests__/urlUtils.test.ts +56 -56
  18. package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
  19. package/src/__tests__/useWidgetState.test.ts +181 -189
  20. package/src/__tests__/widgetBootstrapService.test.ts +182 -0
  21. package/src/components/CloseButton.tsx +36 -36
  22. package/src/components/InlineWidget.tsx +36 -36
  23. package/src/components/ModalWidget.tsx +57 -59
  24. package/src/components/OverlayWidget.tsx +88 -88
  25. package/src/constants/Constants.ts +4 -0
  26. package/src/constants/webViewConstants.ts +15 -14
  27. package/src/hooks/index.ts +2 -2
  28. package/src/hooks/useDeviceInfoCollector.ts +67 -0
  29. package/src/hooks/useHeightAnimation.ts +22 -22
  30. package/src/hooks/useWidgetHeight.ts +38 -38
  31. package/src/hooks/useWidgetState.ts +101 -101
  32. package/src/index.ts +12 -8
  33. package/src/interfaces/WidgetCallbacks.ts +15 -0
  34. package/src/interfaces/WidgetData.ts +19 -19
  35. package/src/interfaces/WidgetOptions.ts +7 -7
  36. package/src/interfaces/WidgetResponse.ts +15 -15
  37. package/src/interfaces/WidgetSamplerLog.ts +5 -5
  38. package/src/interfaces/index.ts +25 -24
  39. package/src/services/ClientVersionCollector.ts +15 -0
  40. package/src/services/storage.ts +21 -21
  41. package/src/services/widgetBootstrapService.ts +67 -0
  42. package/src/services/widgetEventService.ts +110 -111
  43. package/src/services/widgetValidationService.ts +102 -86
  44. package/src/setupTests.js +43 -0
  45. package/src/styles/widgetStyles.ts +58 -58
  46. package/src/utils/urlUtils.ts +13 -13
package/README.intern.md CHANGED
@@ -1,513 +1,513 @@
1
- # SoluCX Widget
2
-
3
- Um widget React Native modular para coleta de feedback e pesquisas, desenvolvido seguindo princípios de Clean Code e arquitetura escalável.
4
-
5
- ## 📋 Visão Geral
6
-
7
- O SoluCX Widget oferece quatro modos de renderização flexíveis para integração em aplicações React Native/Expo:
8
-
9
- - **Top**: Widget fixo no topo da tela
10
- - **Bottom**: Widget fixo na parte inferior
11
- - **Modal**: Widget em sobreposição centralizada
12
- - **Inline**: Widget integrado ao fluxo do layout
13
-
14
- ## 🏗️ Arquitetura
15
-
16
- ### Componentes Principais
17
-
18
- - [`SoluCXWidget.tsx`](src/SoluCXWidget.tsx) - Componente principal
19
- - [`useWidgetState.ts`](src/hooks/useWidgetState.ts) - Hook para gerenciamento de estado
20
- - [`useWidgetHeight.ts`](src/hooks/useWidgetHeight.ts) - Hook para gerenciamento de altura (dinâmica/fixa)
21
- - [`WidgetEventService`](src/services/widgetEventService.ts) - Serviço de eventos
22
- - [`StorageService`](src/services/storage.ts) - Persistência local
23
-
24
- ### Estrutura de Arquivos
25
-
26
- ```
27
- src/
28
- ├── SoluCXWidget.tsx # Componente principal
29
- ├── components/ # Componentes especializados
30
- │ ├── ModalWidget.tsx # Widget modal
31
- │ ├── InlineWidget.tsx # Widget inline
32
- │ └── OverlayWidget.tsx # Widget overlay (top/bottom)
33
- ├── hooks/ # Hooks personalizados
34
- │ ├── useWidgetState.ts # Estado do widget
35
- │ ├── useWidgetHeight.ts # Gerenciamento de altura (dinâmica/fixa)
36
- │ └── useHeightAnimation.ts # Animação de altura
37
- ├── services/ # Serviços
38
- │ ├── widgetEventService.ts # Gerenciamento de eventos
39
- │ └── storage.ts # Persistência de dados
40
- ├── interfaces/ # Tipos TypeScript
41
- │ ├── index.ts # Exports centralizados
42
- │ ├── WidgetData.ts # Dados do widget
43
- │ ├── WidgetOptions.ts # Opções de configuração
44
- │ ├── WidgetResponse.ts # Respostas e erros
45
- │ └── WidgetSamplerLog.ts # Log de tentativas
46
- ├── styles/ # Estilos
47
- │ └── widgetStyles.ts # Estilos por tipo
48
- ├── utils/ # Utilitários
49
- │ └── urlUtils.ts # Construção de URLs
50
- ├── constants/ # Constantes
51
- │ └── webViewConstants.ts # URLs e configurações
52
- └── __tests__/ # Testes
53
- ├── urlUtils.test.ts
54
- └── useWidgetState.test.ts
55
- ```
56
-
57
- ## 🚀 Instalação e Uso
58
-
59
- ### Instalação
60
-
61
- ```bash
62
- # Como dependência local (monorepo)
63
- npm install solucx_widget@file:./modules/solucx_widget
64
-
65
- # Dependências peer necessárias
66
- npm install react-native-webview @react-native-async-storage/async-storage
67
- ```
68
-
69
- ### Uso Básico
70
-
71
- ```tsx
72
- import React from 'react';
73
- import { SoluCXWidget } from 'solucx_widget';
74
-
75
- export default function MyComponent() {
76
- return (
77
- <SoluCXWidget
78
- soluCXKey='sua-chave-solucx'
79
- type='bottom' // 'top' | 'bottom' | 'modal' | 'inline'
80
- data={{
81
- journey: 'nome_da_jornada',
82
- name: 'Nome do Cliente',
83
- email: 'cliente@email.com',
84
- phone: '11999999999',
85
- store_id: '1',
86
- employee_id: '1',
87
- amount: 100,
88
- param_REGIAO: 'SUDESTE'
89
- }}
90
- options={{
91
- height: 400
92
- }}
93
- />
94
- );
95
- }
96
- ```
97
-
98
- ## 🎛️ API do Componente
99
-
100
- ### Props do SoluCXWidget
101
-
102
- ```typescript
103
- interface SoluCXWidgetProps {
104
- soluCXKey: string; // Chave de autenticação SoluCX
105
- type: WidgetType; // Modo de renderização
106
- data: WidgetData; // Dados do cliente/transação
107
- options: WidgetOptions; // Configurações do widget
108
- }
109
- ```
110
-
111
- ### Tipos de Widget
112
-
113
- ```typescript
114
- type WidgetType = 'bottom' | 'top' | 'inline' | 'modal';
115
- ```
116
-
117
- ### Dados do Widget
118
-
119
- ```typescript
120
- interface WidgetData {
121
- // Identificadores
122
- transaction_id?: string;
123
- customer_id?: string;
124
-
125
- // Dados do cliente
126
- name?: string;
127
- email?: string;
128
- phone?: string;
129
- birth_date?: string;
130
- document?: string;
131
-
132
- // Dados da transação
133
- store_id?: string;
134
- store_name?: string;
135
- employee_id?: string;
136
- employee_name?: string;
137
- amount?: number;
138
- score?: number;
139
-
140
- // Parâmetros customizados
141
- journey?: string;
142
- [key: string]: string | number | undefined;
143
- }
144
- ```
145
-
146
- ### Opções de Configuração
147
-
148
- ```typescript
149
- interface WidgetOptions {
150
- height?: number; // Altura fixa em pontos (não pixels)
151
- // Se não fornecido: altura dinâmica baseada em eventos de resize
152
- // Se fornecido: altura fixa para todos os tipos de widget
153
- retry?: {
154
- // Configuração de retry
155
- attempts?: number; // Número de tentativas
156
- interval?: number; // Intervalo entre tentativas (ms)
157
- };
158
- waitDelayAfterRating?: number; // Delay após avaliação (ms)
159
- }
160
- ```
161
-
162
- **⚙️ Gerenciamento de Altura:**
163
-
164
- O widget possui dois modos de altura:
165
-
166
- 1. **Altura Dinâmica (padrão)**: Quando `height` não é fornecido, o widget se ajusta automaticamente através de eventos `FORM_RESIZE` do conteúdo. Funciona para todos os tipos (`bottom`, `top`, `inline`, `modal`).
167
-
168
- 2. **Altura Fixa**: Quando `height` é especificado, o valor é fixo e eventos de resize são ignorados. Funciona para todos os tipos de widget.
169
-
170
- ```tsx
171
- // Altura dinâmica - se adapta ao conteúdo
172
- <SoluCXWidget
173
- type="bottom"
174
- options={{}} // height não especificado
175
- {...props}
176
- />
177
-
178
- // Altura fixa de 400 pontos
179
- <SoluCXWidget
180
- type="modal"
181
- options={{ height: 400 }} // altura fixa
182
- {...props}
183
- />
184
- ```
185
-
186
- **⚠️ Importante**: O valor de `height` é sempre em **pontos** (points), não pixels, seguindo o padrão do React e React Native. O sistema operacional converte automaticamente para pixels considerando a densidade da tela do dispositivo.
187
-
188
- ## 🔄 Sistema de Eventos
189
-
190
- O widget comunica através de mensagens WebView bidirecionais:
191
-
192
- ### Eventos Suportados
193
-
194
- ```typescript
195
- type EventKey =
196
- | 'FORM_OPENED' // Widget foi aberto
197
- | 'FORM_CLOSE' // Usuário fechou o widget
198
- | 'FORM_ERROR' // Erro no carregamento
199
- | 'FORM_RESIZE' // Widget redimensionado
200
- | 'FORM_PAGECHANGED' // Mudança de página
201
- | 'QUESTION_ANSWERED' // Pergunta respondida
202
- | 'FORM_COMPLETED' // Formulário concluído
203
- | 'FORM_PARTIALCOMPLETED'; // Completado parcialmente
204
- ```
205
-
206
- ### Tratamento de Eventos
207
-
208
- Os eventos são processados automaticamente pelo [`WidgetEventService`](src/services/widgetEventService.ts):
209
-
210
- ```typescript
211
- const eventService = new WidgetEventService(
212
- setIsWidgetVisible, // Função para controlar visibilidade
213
- resize, // Função para redimensionar
214
- open // Função para abrir widget
215
- );
216
- ```
217
-
218
- ## 💾 Persistência de Dados
219
-
220
- O widget utiliza [`AsyncStorage`](@react-native-async-storage/async-storage) para persistir:
221
-
222
- - **Histórico de tentativas**: Controla quantas vezes o widget foi exibido
223
- - **Última avaliação**: Data da última interação
224
- - **Controle de frequência**: Evita spam de widgets
225
-
226
- ### Estrutura dos Dados
227
-
228
- ```typescript
229
- interface WidgetSamplerLog {
230
- attempts: number; // Número de tentativas
231
- lastAttempt: number; // Timestamp da última tentativa
232
- lastRating: number; // Timestamp da última avaliação
233
- lastParcial: number; // Timestamp do último parcial
234
- }
235
- ```
236
-
237
- ## 🎨 Customização de Estilos
238
-
239
- ### Estilos por Tipo
240
-
241
- Cada tipo de widget possui estilos específicos definidos em [`widgetStyles.ts`](src/styles/widgetStyles.ts):
242
-
243
- ```typescript
244
- export const getWidgetStyles = (type: WidgetType) => {
245
- const styleMap = {
246
- bottom: { container: styles.wrapper, content: styles.bottom },
247
- top: { container: styles.wrapper, content: styles.top },
248
- inline: { container: styles.inlineWrapper, content: styles.inline },
249
- modal: { container: styles.wrapper, content: styles.inline }
250
- };
251
-
252
- return styleMap[type] || styleMap.bottom;
253
- };
254
- ```
255
-
256
- ### Características Visuais
257
-
258
- - **Bottom/Top**: Position absolute com z-index elevado
259
- - **Modal**: Overlay com background semitransparente
260
- - **Inline**: Integrado ao fluxo normal do layout
261
-
262
- ## 🔧 Utilitários
263
-
264
- ### Construção de URLs
265
-
266
- O [`urlUtils.ts`](src/utils/urlUtils.ts) gerencia a construção de URLs da pesquisa:
267
-
268
- ```typescript
269
- export function buildWidgetURL(key: string, data: WidgetData): string {
270
- const params = new URLSearchParams(data as Record<string, string>);
271
- const baseURL = `${BASE_URL}/${key}/?mode=widget`;
272
-
273
- if (data.transaction_id) {
274
- return `${baseURL}&${params.toString()}`;
275
- }
276
-
277
- return `${baseURL}&transaction_id=&${params.toString()}`;
278
- }
279
- ```
280
-
281
- ## 🧪 Testes
282
-
283
- ### Executando Testes
284
-
285
- ```bash
286
- # Todos os testes
287
- npm test
288
-
289
- # Teste específico
290
- npm test -- urlUtils.test.ts
291
- npm test -- useWidgetState.test.ts
292
- ```
293
-
294
- ### Cobertura de Testes
295
-
296
- - ✅ Construção de URLs
297
- - ✅ Gerenciamento de eventos
298
- - ✅ Estados do widget
299
- - ✅ Persistência de dados
300
-
301
- ## ⚙️ Configuração
302
-
303
- ### Constantes
304
-
305
- Configurações centralizadas em [`webViewConstants.ts`](src/constants/webViewConstants.ts):
306
-
307
- ```typescript
308
- export const BASE_URL = 'https://survey-link.solucx.com.br/link';
309
- export const STORAGE_KEY = '@solucxWidgetLog';
310
- export const MIN_HEIGHT = 200;
311
- export const FIXED_Z_INDEX = 9999;
312
- export const MODAL_Z_INDEX = 10000;
313
- ```
314
-
315
- ### JavaScript Injection
316
-
317
- Listener para comunicação WebView:
318
-
319
- ```typescript
320
- export const WEB_VIEW_MESSAGE_LISTENER = `
321
- window.addEventListener('message', function(event) {
322
- window.ReactNativeWebView.postMessage(event.data);
323
- });
324
- `;
325
- ```
326
-
327
- ## 🚨 Considerações Importantes
328
-
329
- ### 1. **Comportamento de Posicionamento**
330
-
331
- ⚠️ **Ponto Crítico**: A posição no JSX **não determina** onde widgets `top`, `bottom` e `modal` aparecem:
332
-
333
- ```tsx
334
- // ❌ Isso NÃO faz o widget aparecer no meio
335
- <Text>Conteúdo antes</Text>
336
- <SoluCXWidget type="bottom" {...props} /> {/* Sempre aparece embaixo */}
337
- <Text>Conteúdo depois</Text>
338
-
339
- // ✅ Apenas o tipo "inline" respeita a posição no código
340
- <Text>Conteúdo antes</Text>
341
- <SoluCXWidget type="inline" {...props} /> {/* Aparece aqui */}
342
- <Text>Conteúdo depois</Text>
343
- ```
344
-
345
- ### 2. **Performance**
346
-
347
- - Widget usa WebView interna (overhead de performance)
348
- - Carregamento lazy das pesquisas
349
- - Cache automático via AsyncStorage
350
- - JavaScript injection para comunicação
351
-
352
- ### 3. **Segurança**
353
-
354
- - **Chaves API**: Nunca hardcode em produção
355
- - **Dados sensíveis**: Não são logados automaticamente
356
- - **URLs**: Validadas antes do carregamento
357
- - **HTTPS**: Obrigatório para comunicação
358
-
359
- ### 4. **Limitações Técnicas**
360
-
361
- - Requer conexão com internet
362
- - Dependente do WebView do sistema
363
- - Eventos assíncronos (não bloqueantes)
364
- - Storage limitado do dispositivo
365
-
366
- ## 📝 Desenvolvimento
367
-
368
- ### Adicionando Novos Tipos
369
-
370
- 1. **Estenda o tipo WidgetType**:
371
-
372
- ```typescript
373
- // modules/solucx_widget/src/interfaces/index.ts
374
- export type WidgetType = 'bottom' | 'top' | 'inline' | 'modal' | 'novo_tipo';
375
- ```
376
-
377
- 2. **Adicione estilos correspondentes**:
378
-
379
- ```typescript
380
- // modules/solucx_widget/src/styles/widgetStyles.ts
381
- const styleMap = {
382
- // ... estilos existentes
383
- novo_tipo: { container: styles.novoWrapper, content: styles.novoContent }
384
- };
385
- ```
386
-
387
- 3. **Implemente lógica no componente principal**:
388
-
389
- ```typescript
390
- // modules/solucx_widget/src/SoluCXWidget.tsx
391
- if (type === 'novo_tipo') {
392
- return <NovoTipoWidget>{/* ... */}</NovoTipoWidget>;
393
- }
394
- ```
395
-
396
- ### Adicionando Novos Eventos
397
-
398
- 1. **Estenda EventKey**:
399
-
400
- ```typescript
401
- // modules/solucx_widget/src/interfaces/index.ts
402
- export type EventKey = 'FORM_OPENED' | 'NOVO_EVENTO'; // Novo evento
403
- ```
404
-
405
- 2. **Implemente handler**:
406
-
407
- ```typescript
408
- // modules/solucx_widget/src/services/widgetEventService.ts
409
- private executeEvent(eventKey: EventKey, value: string): WidgetResponse {
410
- const eventHandlers = {
411
- // ... handlers existentes
412
- NOVO_EVENTO: (value: string) => this.handleNovoEvento(value),
413
- };
414
- }
415
- ```
416
-
417
- 3. **Adicione testes**:
418
-
419
- ```typescript
420
- // modules/solucx_widget/src/__tests__/widgetEventService.test.ts
421
- it('should handle NOVO_EVENTO correctly', () => {
422
- const result = service.handleMessage('NOVO_EVENTO-data', true);
423
- expect(result).toEqual({ status: 'success' });
424
- });
425
- ```
426
-
427
- ## 🔍 Troubleshooting
428
-
429
- ### Problemas Comuns
430
-
431
- #### 1. **Widget não aparece**
432
-
433
- ```bash
434
- # Verificações:
435
- ✅ Chave SoluCX válida?
436
- ✅ Conectividade com internet?
437
- ✅ Logs do WebView no console?
438
- ✅ Evento FORM_OPENED sendo disparado?
439
- ```
440
-
441
- #### 2. **Eventos não funcionam**
442
-
443
- ```bash
444
- # Verificações:
445
- ✅ JavaScript listener injetado?
446
- ✅ Formato das mensagens correto?
447
- ✅ WebView carregou completamente?
448
- ✅ postMessage funcionando?
449
- ```
450
-
451
- #### 3. **Layout quebrado**
452
-
453
- ```bash
454
- # Verificações:
455
- ✅ Dimensões adequadas para o dispositivo?
456
- ✅ Estilos corretos para o tipo?
457
- ✅ Z-index conflitando?
458
- ✅ SafeArea configurada?
459
- ```
460
-
461
- #### 4. **Storage não persiste**
462
-
463
- ```bash
464
- # Verificações:
465
- ✅ AsyncStorage instalado?
466
- ✅ Permissões de storage?
467
- ✅ Dados sendo serializados corretamente?
468
- ✅ Chaves de storage únicas?
469
- ```
470
-
471
- ### Debug Avançado
472
-
473
- ```typescript
474
- // Habilitar logs detalhados
475
- console.log('Widget event received:', processedKey, value);
476
-
477
- // Verificar dados persistidos
478
- const data = await storageService.read();
479
- console.log('Stored data:', data);
480
-
481
- // Verificar URL construída
482
- const url = buildWidgetURL(soluCXKey, data);
483
- console.log('Widget URL:', url);
484
- ```
485
-
486
- ## 📚 Dependências
487
-
488
- ### Peer Dependencies
489
-
490
- ```json
491
- {
492
- "react-native-webview": "^13.0.0",
493
- "@react-native-async-storage/async-storage": "^2.0.0",
494
- "react": "^19.0.0",
495
- "react-native": "^0.79.0"
496
- }
497
- ```
498
-
499
- ### Compatibilidade
500
-
501
- - **React Native**: 0.70+
502
- - **Expo**: 50+
503
- - **iOS**: 11+
504
- - **Android**: API 21+
505
- - **Web**: Suporte limitado (WebView)
506
-
507
- ## 📄 Licença
508
-
509
- Este projeto é privado e proprietário da SoluCX.
510
-
511
- ---
512
-
513
- **Desenvolvido com ❤️ pela equipe SoluCX**
1
+ # SoluCX Widget
2
+
3
+ Um widget React Native modular para coleta de feedback e pesquisas, desenvolvido seguindo princípios de Clean Code e arquitetura escalável.
4
+
5
+ ## 📋 Visão Geral
6
+
7
+ O SoluCX Widget oferece quatro modos de renderização flexíveis para integração em aplicações React Native/Expo:
8
+
9
+ - **Top**: Widget fixo no topo da tela
10
+ - **Bottom**: Widget fixo na parte inferior
11
+ - **Modal**: Widget em sobreposição centralizada
12
+ - **Inline**: Widget integrado ao fluxo do layout
13
+
14
+ ## 🏗️ Arquitetura
15
+
16
+ ### Componentes Principais
17
+
18
+ - [`SoluCXWidget.tsx`](src/SoluCXWidget.tsx) - Componente principal
19
+ - [`useWidgetState.ts`](src/hooks/useWidgetState.ts) - Hook para gerenciamento de estado
20
+ - [`useWidgetHeight.ts`](src/hooks/useWidgetHeight.ts) - Hook para gerenciamento de altura (dinâmica/fixa)
21
+ - [`WidgetEventService`](src/services/widgetEventService.ts) - Serviço de eventos
22
+ - [`StorageService`](src/services/storage.ts) - Persistência local
23
+
24
+ ### Estrutura de Arquivos
25
+
26
+ ```
27
+ src/
28
+ ├── SoluCXWidget.tsx # Componente principal
29
+ ├── components/ # Componentes especializados
30
+ │ ├── ModalWidget.tsx # Widget modal
31
+ │ ├── InlineWidget.tsx # Widget inline
32
+ │ └── OverlayWidget.tsx # Widget overlay (top/bottom)
33
+ ├── hooks/ # Hooks personalizados
34
+ │ ├── useWidgetState.ts # Estado do widget
35
+ │ ├── useWidgetHeight.ts # Gerenciamento de altura (dinâmica/fixa)
36
+ │ └── useHeightAnimation.ts # Animação de altura
37
+ ├── services/ # Serviços
38
+ │ ├── widgetEventService.ts # Gerenciamento de eventos
39
+ │ └── storage.ts # Persistência de dados
40
+ ├── interfaces/ # Tipos TypeScript
41
+ │ ├── index.ts # Exports centralizados
42
+ │ ├── WidgetData.ts # Dados do widget
43
+ │ ├── WidgetOptions.ts # Opções de configuração
44
+ │ ├── WidgetResponse.ts # Respostas e erros
45
+ │ └── WidgetSamplerLog.ts # Log de tentativas
46
+ ├── styles/ # Estilos
47
+ │ └── widgetStyles.ts # Estilos por tipo
48
+ ├── utils/ # Utilitários
49
+ │ └── urlUtils.ts # Construção de URLs
50
+ ├── constants/ # Constantes
51
+ │ └── webViewConstants.ts # URLs e configurações
52
+ └── __tests__/ # Testes
53
+ ├── urlUtils.test.ts
54
+ └── useWidgetState.test.ts
55
+ ```
56
+
57
+ ## 🚀 Instalação e Uso
58
+
59
+ ### Instalação
60
+
61
+ ```bash
62
+ # Como dependência local (monorepo)
63
+ npm install solucx_widget@file:./modules/solucx_widget
64
+
65
+ # Dependências peer necessárias
66
+ npm install react-native-webview @react-native-async-storage/async-storage
67
+ ```
68
+
69
+ ### Uso Básico
70
+
71
+ ```tsx
72
+ import React from 'react';
73
+ import { SoluCXWidget } from 'solucx_widget';
74
+
75
+ export default function MyComponent() {
76
+ return (
77
+ <SoluCXWidget
78
+ soluCXKey='sua-chave-solucx'
79
+ type='bottom' // 'top' | 'bottom' | 'modal' | 'inline'
80
+ data={{
81
+ journey: 'nome_da_jornada',
82
+ name: 'Nome do Cliente',
83
+ email: 'cliente@email.com',
84
+ phone: '11999999999',
85
+ store_id: '1',
86
+ employee_id: '1',
87
+ amount: 100,
88
+ param_REGIAO: 'SUDESTE'
89
+ }}
90
+ options={{
91
+ height: 400
92
+ }}
93
+ />
94
+ );
95
+ }
96
+ ```
97
+
98
+ ## 🎛️ API do Componente
99
+
100
+ ### Props do SoluCXWidget
101
+
102
+ ```typescript
103
+ interface SoluCXWidgetProps {
104
+ soluCXKey: string; // Chave de autenticação SoluCX
105
+ type: WidgetType; // Modo de renderização
106
+ data: WidgetData; // Dados do cliente/transação
107
+ options: WidgetOptions; // Configurações do widget
108
+ }
109
+ ```
110
+
111
+ ### Tipos de Widget
112
+
113
+ ```typescript
114
+ type WidgetType = 'bottom' | 'top' | 'inline' | 'modal';
115
+ ```
116
+
117
+ ### Dados do Widget
118
+
119
+ ```typescript
120
+ interface WidgetData {
121
+ // Identificadores
122
+ transaction_id?: string;
123
+ customer_id?: string;
124
+
125
+ // Dados do cliente
126
+ name?: string;
127
+ email?: string;
128
+ phone?: string;
129
+ birth_date?: string;
130
+ document?: string;
131
+
132
+ // Dados da transação
133
+ store_id?: string;
134
+ store_name?: string;
135
+ employee_id?: string;
136
+ employee_name?: string;
137
+ amount?: number;
138
+ score?: number;
139
+
140
+ // Parâmetros customizados
141
+ journey?: string;
142
+ [key: string]: string | number | undefined;
143
+ }
144
+ ```
145
+
146
+ ### Opções de Configuração
147
+
148
+ ```typescript
149
+ interface WidgetOptions {
150
+ height?: number; // Altura fixa em pontos (não pixels)
151
+ // Se não fornecido: altura dinâmica baseada em eventos de resize
152
+ // Se fornecido: altura fixa para todos os tipos de widget
153
+ retry?: {
154
+ // Configuração de retry
155
+ attempts?: number; // Número de tentativas
156
+ interval?: number; // Intervalo entre tentativas (ms)
157
+ };
158
+ waitDelayAfterRating?: number; // Delay após avaliação (ms)
159
+ }
160
+ ```
161
+
162
+ **⚙️ Gerenciamento de Altura:**
163
+
164
+ O widget possui dois modos de altura:
165
+
166
+ 1. **Altura Dinâmica (padrão)**: Quando `height` não é fornecido, o widget se ajusta automaticamente através de eventos `FORM_RESIZE` do conteúdo. Funciona para todos os tipos (`bottom`, `top`, `inline`, `modal`).
167
+
168
+ 2. **Altura Fixa**: Quando `height` é especificado, o valor é fixo e eventos de resize são ignorados. Funciona para todos os tipos de widget.
169
+
170
+ ```tsx
171
+ // Altura dinâmica - se adapta ao conteúdo
172
+ <SoluCXWidget
173
+ type="bottom"
174
+ options={{}} // height não especificado
175
+ {...props}
176
+ />
177
+
178
+ // Altura fixa de 400 pontos
179
+ <SoluCXWidget
180
+ type="modal"
181
+ options={{ height: 400 }} // altura fixa
182
+ {...props}
183
+ />
184
+ ```
185
+
186
+ **⚠️ Importante**: O valor de `height` é sempre em **pontos** (points), não pixels, seguindo o padrão do React e React Native. O sistema operacional converte automaticamente para pixels considerando a densidade da tela do dispositivo.
187
+
188
+ ## 🔄 Sistema de Eventos
189
+
190
+ O widget comunica através de mensagens WebView bidirecionais:
191
+
192
+ ### Eventos Suportados
193
+
194
+ ```typescript
195
+ type EventKey =
196
+ | 'FORM_OPENED' // Widget foi aberto
197
+ | 'FORM_CLOSE' // Usuário fechou o widget
198
+ | 'FORM_ERROR' // Erro no carregamento
199
+ | 'FORM_RESIZE' // Widget redimensionado
200
+ | 'FORM_PAGECHANGED' // Mudança de página
201
+ | 'QUESTION_ANSWERED' // Pergunta respondida
202
+ | 'FORM_COMPLETED' // Formulário concluído
203
+ | 'FORM_PARTIALCOMPLETED'; // Completado parcialmente
204
+ ```
205
+
206
+ ### Tratamento de Eventos
207
+
208
+ Os eventos são processados automaticamente pelo [`WidgetEventService`](src/services/widgetEventService.ts):
209
+
210
+ ```typescript
211
+ const eventService = new WidgetEventService(
212
+ setIsWidgetVisible, // Função para controlar visibilidade
213
+ resize, // Função para redimensionar
214
+ open // Função para abrir widget
215
+ );
216
+ ```
217
+
218
+ ## 💾 Persistência de Dados
219
+
220
+ O widget utiliza [`AsyncStorage`](@react-native-async-storage/async-storage) para persistir:
221
+
222
+ - **Histórico de tentativas**: Controla quantas vezes o widget foi exibido
223
+ - **Última avaliação**: Data da última interação
224
+ - **Controle de frequência**: Evita spam de widgets
225
+
226
+ ### Estrutura dos Dados
227
+
228
+ ```typescript
229
+ interface WidgetSamplerLog {
230
+ attempts: number; // Número de tentativas
231
+ lastAttempt: number; // Timestamp da última tentativa
232
+ lastRating: number; // Timestamp da última avaliação
233
+ lastParcial: number; // Timestamp do último parcial
234
+ }
235
+ ```
236
+
237
+ ## 🎨 Customização de Estilos
238
+
239
+ ### Estilos por Tipo
240
+
241
+ Cada tipo de widget possui estilos específicos definidos em [`widgetStyles.ts`](src/styles/widgetStyles.ts):
242
+
243
+ ```typescript
244
+ export const getWidgetStyles = (type: WidgetType) => {
245
+ const styleMap = {
246
+ bottom: { container: styles.wrapper, content: styles.bottom },
247
+ top: { container: styles.wrapper, content: styles.top },
248
+ inline: { container: styles.inlineWrapper, content: styles.inline },
249
+ modal: { container: styles.wrapper, content: styles.inline }
250
+ };
251
+
252
+ return styleMap[type] || styleMap.bottom;
253
+ };
254
+ ```
255
+
256
+ ### Características Visuais
257
+
258
+ - **Bottom/Top**: Position absolute com z-index elevado
259
+ - **Modal**: Overlay com background semitransparente
260
+ - **Inline**: Integrado ao fluxo normal do layout
261
+
262
+ ## 🔧 Utilitários
263
+
264
+ ### Construção de URLs
265
+
266
+ O [`urlUtils.ts`](src/utils/urlUtils.ts) gerencia a construção de URLs da pesquisa:
267
+
268
+ ```typescript
269
+ export function buildWidgetURL(key: string, data: WidgetData): string {
270
+ const params = new URLSearchParams(data as Record<string, string>);
271
+ const baseURL = `${BASE_URL}/${key}/?mode=widget`;
272
+
273
+ if (data.transaction_id) {
274
+ return `${baseURL}&${params.toString()}`;
275
+ }
276
+
277
+ return `${baseURL}&transaction_id=&${params.toString()}`;
278
+ }
279
+ ```
280
+
281
+ ## 🧪 Testes
282
+
283
+ ### Executando Testes
284
+
285
+ ```bash
286
+ # Todos os testes
287
+ npm test
288
+
289
+ # Teste específico
290
+ npm test -- urlUtils.test.ts
291
+ npm test -- useWidgetState.test.ts
292
+ ```
293
+
294
+ ### Cobertura de Testes
295
+
296
+ - ✅ Construção de URLs
297
+ - ✅ Gerenciamento de eventos
298
+ - ✅ Estados do widget
299
+ - ✅ Persistência de dados
300
+
301
+ ## ⚙️ Configuração
302
+
303
+ ### Constantes
304
+
305
+ Configurações centralizadas em [`webViewConstants.ts`](src/constants/webViewConstants.ts):
306
+
307
+ ```typescript
308
+ export const BASE_URL = 'https://survey-link.solucx.com.br/link';
309
+ export const STORAGE_KEY = '@solucxWidgetLog';
310
+ export const MIN_HEIGHT = 200;
311
+ export const FIXED_Z_INDEX = 9999;
312
+ export const MODAL_Z_INDEX = 10000;
313
+ ```
314
+
315
+ ### JavaScript Injection
316
+
317
+ Listener para comunicação WebView:
318
+
319
+ ```typescript
320
+ export const WEB_VIEW_MESSAGE_LISTENER = `
321
+ window.addEventListener('message', function(event) {
322
+ window.ReactNativeWebView.postMessage(event.data);
323
+ });
324
+ `;
325
+ ```
326
+
327
+ ## 🚨 Considerações Importantes
328
+
329
+ ### 1. **Comportamento de Posicionamento**
330
+
331
+ ⚠️ **Ponto Crítico**: A posição no JSX **não determina** onde widgets `top`, `bottom` e `modal` aparecem:
332
+
333
+ ```tsx
334
+ // ❌ Isso NÃO faz o widget aparecer no meio
335
+ <Text>Conteúdo antes</Text>
336
+ <SoluCXWidget type="bottom" {...props} /> {/* Sempre aparece embaixo */}
337
+ <Text>Conteúdo depois</Text>
338
+
339
+ // ✅ Apenas o tipo "inline" respeita a posição no código
340
+ <Text>Conteúdo antes</Text>
341
+ <SoluCXWidget type="inline" {...props} /> {/* Aparece aqui */}
342
+ <Text>Conteúdo depois</Text>
343
+ ```
344
+
345
+ ### 2. **Performance**
346
+
347
+ - Widget usa WebView interna (overhead de performance)
348
+ - Carregamento lazy das pesquisas
349
+ - Cache automático via AsyncStorage
350
+ - JavaScript injection para comunicação
351
+
352
+ ### 3. **Segurança**
353
+
354
+ - **Chaves API**: Nunca hardcode em produção
355
+ - **Dados sensíveis**: Não são logados automaticamente
356
+ - **URLs**: Validadas antes do carregamento
357
+ - **HTTPS**: Obrigatório para comunicação
358
+
359
+ ### 4. **Limitações Técnicas**
360
+
361
+ - Requer conexão com internet
362
+ - Dependente do WebView do sistema
363
+ - Eventos assíncronos (não bloqueantes)
364
+ - Storage limitado do dispositivo
365
+
366
+ ## 📝 Desenvolvimento
367
+
368
+ ### Adicionando Novos Tipos
369
+
370
+ 1. **Estenda o tipo WidgetType**:
371
+
372
+ ```typescript
373
+ // modules/solucx_widget/src/interfaces/index.ts
374
+ export type WidgetType = 'bottom' | 'top' | 'inline' | 'modal' | 'novo_tipo';
375
+ ```
376
+
377
+ 2. **Adicione estilos correspondentes**:
378
+
379
+ ```typescript
380
+ // modules/solucx_widget/src/styles/widgetStyles.ts
381
+ const styleMap = {
382
+ // ... estilos existentes
383
+ novo_tipo: { container: styles.novoWrapper, content: styles.novoContent }
384
+ };
385
+ ```
386
+
387
+ 3. **Implemente lógica no componente principal**:
388
+
389
+ ```typescript
390
+ // modules/solucx_widget/src/SoluCXWidget.tsx
391
+ if (type === 'novo_tipo') {
392
+ return <NovoTipoWidget>{/* ... */}</NovoTipoWidget>;
393
+ }
394
+ ```
395
+
396
+ ### Adicionando Novos Eventos
397
+
398
+ 1. **Estenda EventKey**:
399
+
400
+ ```typescript
401
+ // modules/solucx_widget/src/interfaces/index.ts
402
+ export type EventKey = 'FORM_OPENED' | 'NOVO_EVENTO'; // Novo evento
403
+ ```
404
+
405
+ 2. **Implemente handler**:
406
+
407
+ ```typescript
408
+ // modules/solucx_widget/src/services/widgetEventService.ts
409
+ private executeEvent(eventKey: EventKey, value: string): WidgetResponse {
410
+ const eventHandlers = {
411
+ // ... handlers existentes
412
+ NOVO_EVENTO: (value: string) => this.handleNovoEvento(value),
413
+ };
414
+ }
415
+ ```
416
+
417
+ 3. **Adicione testes**:
418
+
419
+ ```typescript
420
+ // modules/solucx_widget/src/__tests__/widgetEventService.test.ts
421
+ it('should handle NOVO_EVENTO correctly', () => {
422
+ const result = service.handleMessage('NOVO_EVENTO-data', true);
423
+ expect(result).toEqual({ status: 'success' });
424
+ });
425
+ ```
426
+
427
+ ## 🔍 Troubleshooting
428
+
429
+ ### Problemas Comuns
430
+
431
+ #### 1. **Widget não aparece**
432
+
433
+ ```bash
434
+ # Verificações:
435
+ ✅ Chave SoluCX válida?
436
+ ✅ Conectividade com internet?
437
+ ✅ Logs do WebView no console?
438
+ ✅ Evento FORM_OPENED sendo disparado?
439
+ ```
440
+
441
+ #### 2. **Eventos não funcionam**
442
+
443
+ ```bash
444
+ # Verificações:
445
+ ✅ JavaScript listener injetado?
446
+ ✅ Formato das mensagens correto?
447
+ ✅ WebView carregou completamente?
448
+ ✅ postMessage funcionando?
449
+ ```
450
+
451
+ #### 3. **Layout quebrado**
452
+
453
+ ```bash
454
+ # Verificações:
455
+ ✅ Dimensões adequadas para o dispositivo?
456
+ ✅ Estilos corretos para o tipo?
457
+ ✅ Z-index conflitando?
458
+ ✅ SafeArea configurada?
459
+ ```
460
+
461
+ #### 4. **Storage não persiste**
462
+
463
+ ```bash
464
+ # Verificações:
465
+ ✅ AsyncStorage instalado?
466
+ ✅ Permissões de storage?
467
+ ✅ Dados sendo serializados corretamente?
468
+ ✅ Chaves de storage únicas?
469
+ ```
470
+
471
+ ### Debug Avançado
472
+
473
+ ```typescript
474
+ // Habilitar logs detalhados
475
+ console.log('Widget event received:', processedKey, value);
476
+
477
+ // Verificar dados persistidos
478
+ const data = await storageService.read();
479
+ console.log('Stored data:', data);
480
+
481
+ // Verificar URL construída
482
+ const url = buildWidgetURL(soluCXKey, data);
483
+ console.log('Widget URL:', url);
484
+ ```
485
+
486
+ ## 📚 Dependências
487
+
488
+ ### Peer Dependencies
489
+
490
+ ```json
491
+ {
492
+ "react-native-webview": "^13.0.0",
493
+ "@react-native-async-storage/async-storage": "^2.0.0",
494
+ "react": "^19.0.0",
495
+ "react-native": "^0.79.0"
496
+ }
497
+ ```
498
+
499
+ ### Compatibilidade
500
+
501
+ - **React Native**: 0.70+
502
+ - **Expo**: 50+
503
+ - **iOS**: 11+
504
+ - **Android**: API 21+
505
+ - **Web**: Suporte limitado (WebView)
506
+
507
+ ## 📄 Licença
508
+
509
+ Este projeto é privado e proprietário da SoluCX.
510
+
511
+ ---
512
+
513
+ **Desenvolvido com ❤️ pela equipe SoluCX**