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