@solucx/react-native-solucx-widget 0.1.14 → 0.1.16
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.intern.md +95 -55
- package/README.md +57 -29
- package/package.json +4 -4
- package/src/components/InlineWidget.tsx +12 -8
- package/src/components/ModalWidget.tsx +22 -6
- package/src/components/OverlayWidget.tsx +15 -8
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useHeightAnimation.ts +22 -0
- package/src/hooks/useWidgetHeight.ts +38 -0
- package/src/hooks/useWidgetState.ts +86 -62
- package/src/services/widgetEventService.ts +121 -106
- package/src/styles/widgetStyles.ts +0 -25
package/README.intern.md
CHANGED
|
@@ -17,6 +17,7 @@ O SoluCX Widget oferece quatro modos de renderização flexíveis para integraç
|
|
|
17
17
|
|
|
18
18
|
- [`SoluCXWidget.tsx`](src/SoluCXWidget.tsx) - Componente principal
|
|
19
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)
|
|
20
21
|
- [`WidgetEventService`](src/services/widgetEventService.ts) - Serviço de eventos
|
|
21
22
|
- [`StorageService`](src/services/storage.ts) - Persistência local
|
|
22
23
|
|
|
@@ -30,7 +31,9 @@ src/
|
|
|
30
31
|
│ ├── InlineWidget.tsx # Widget inline
|
|
31
32
|
│ └── OverlayWidget.tsx # Widget overlay (top/bottom)
|
|
32
33
|
├── hooks/ # Hooks personalizados
|
|
33
|
-
│
|
|
34
|
+
│ ├── useWidgetState.ts # Estado do widget
|
|
35
|
+
│ ├── useWidgetHeight.ts # Gerenciamento de altura (dinâmica/fixa)
|
|
36
|
+
│ └── useHeightAnimation.ts # Animação de altura
|
|
34
37
|
├── services/ # Serviços
|
|
35
38
|
│ ├── widgetEventService.ts # Gerenciamento de eventos
|
|
36
39
|
│ └── storage.ts # Persistência de dados
|
|
@@ -72,20 +75,20 @@ import { SoluCXWidget } from 'solucx_widget';
|
|
|
72
75
|
export default function MyComponent() {
|
|
73
76
|
return (
|
|
74
77
|
<SoluCXWidget
|
|
75
|
-
soluCXKey=
|
|
76
|
-
type=
|
|
78
|
+
soluCXKey='sua-chave-solucx'
|
|
79
|
+
type='bottom' // 'top' | 'bottom' | 'modal' | 'inline'
|
|
77
80
|
data={{
|
|
78
|
-
journey:
|
|
79
|
-
name:
|
|
80
|
-
email:
|
|
81
|
-
phone:
|
|
82
|
-
store_id:
|
|
83
|
-
employee_id:
|
|
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',
|
|
84
87
|
amount: 100,
|
|
85
|
-
param_REGIAO:
|
|
88
|
+
param_REGIAO: 'SUDESTE'
|
|
86
89
|
}}
|
|
87
|
-
options={{
|
|
88
|
-
height: 400
|
|
90
|
+
options={{
|
|
91
|
+
height: 400
|
|
89
92
|
}}
|
|
90
93
|
/>
|
|
91
94
|
);
|
|
@@ -98,17 +101,17 @@ export default function MyComponent() {
|
|
|
98
101
|
|
|
99
102
|
```typescript
|
|
100
103
|
interface SoluCXWidgetProps {
|
|
101
|
-
soluCXKey: string;
|
|
102
|
-
type: WidgetType;
|
|
103
|
-
data: WidgetData;
|
|
104
|
-
options: WidgetOptions;
|
|
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
|
|
105
108
|
}
|
|
106
109
|
```
|
|
107
110
|
|
|
108
111
|
### Tipos de Widget
|
|
109
112
|
|
|
110
113
|
```typescript
|
|
111
|
-
type WidgetType =
|
|
114
|
+
type WidgetType = 'bottom' | 'top' | 'inline' | 'modal';
|
|
112
115
|
```
|
|
113
116
|
|
|
114
117
|
### Dados do Widget
|
|
@@ -118,14 +121,14 @@ interface WidgetData {
|
|
|
118
121
|
// Identificadores
|
|
119
122
|
transaction_id?: string;
|
|
120
123
|
customer_id?: string;
|
|
121
|
-
|
|
124
|
+
|
|
122
125
|
// Dados do cliente
|
|
123
126
|
name?: string;
|
|
124
127
|
email?: string;
|
|
125
128
|
phone?: string;
|
|
126
129
|
birth_date?: string;
|
|
127
130
|
document?: string;
|
|
128
|
-
|
|
131
|
+
|
|
129
132
|
// Dados da transação
|
|
130
133
|
store_id?: string;
|
|
131
134
|
store_name?: string;
|
|
@@ -133,7 +136,7 @@ interface WidgetData {
|
|
|
133
136
|
employee_name?: string;
|
|
134
137
|
amount?: number;
|
|
135
138
|
score?: number;
|
|
136
|
-
|
|
139
|
+
|
|
137
140
|
// Parâmetros customizados
|
|
138
141
|
journey?: string;
|
|
139
142
|
[key: string]: string | number | undefined;
|
|
@@ -144,15 +147,44 @@ interface WidgetData {
|
|
|
144
147
|
|
|
145
148
|
```typescript
|
|
146
149
|
interface WidgetOptions {
|
|
147
|
-
height?: number;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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)
|
|
151
157
|
};
|
|
152
|
-
waitDelayAfterRating?: number; // Delay após avaliação
|
|
158
|
+
waitDelayAfterRating?: number; // Delay após avaliação (ms)
|
|
153
159
|
}
|
|
154
160
|
```
|
|
155
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
|
+
|
|
156
188
|
## 🔄 Sistema de Eventos
|
|
157
189
|
|
|
158
190
|
O widget comunica através de mensagens WebView bidirecionais:
|
|
@@ -160,15 +192,15 @@ O widget comunica através de mensagens WebView bidirecionais:
|
|
|
160
192
|
### Eventos Suportados
|
|
161
193
|
|
|
162
194
|
```typescript
|
|
163
|
-
type EventKey =
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
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
|
|
172
204
|
```
|
|
173
205
|
|
|
174
206
|
### Tratamento de Eventos
|
|
@@ -177,9 +209,9 @@ Os eventos são processados automaticamente pelo [`WidgetEventService`](src/serv
|
|
|
177
209
|
|
|
178
210
|
```typescript
|
|
179
211
|
const eventService = new WidgetEventService(
|
|
180
|
-
setIsWidgetVisible,
|
|
181
|
-
resize,
|
|
182
|
-
open
|
|
212
|
+
setIsWidgetVisible, // Função para controlar visibilidade
|
|
213
|
+
resize, // Função para redimensionar
|
|
214
|
+
open // Função para abrir widget
|
|
183
215
|
);
|
|
184
216
|
```
|
|
185
217
|
|
|
@@ -195,10 +227,10 @@ O widget utiliza [`AsyncStorage`](@react-native-async-storage/async-storage) par
|
|
|
195
227
|
|
|
196
228
|
```typescript
|
|
197
229
|
interface WidgetSamplerLog {
|
|
198
|
-
attempts: number;
|
|
199
|
-
lastAttempt: number;
|
|
200
|
-
lastRating: number;
|
|
201
|
-
lastParcial: number;
|
|
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
|
|
202
234
|
}
|
|
203
235
|
```
|
|
204
236
|
|
|
@@ -211,10 +243,10 @@ Cada tipo de widget possui estilos específicos definidos em [`widgetStyles.ts`]
|
|
|
211
243
|
```typescript
|
|
212
244
|
export const getWidgetStyles = (type: WidgetType) => {
|
|
213
245
|
const styleMap = {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 }
|
|
218
250
|
};
|
|
219
251
|
|
|
220
252
|
return styleMap[type] || styleMap.bottom;
|
|
@@ -237,11 +269,11 @@ O [`urlUtils.ts`](src/utils/urlUtils.ts) gerencia a construção de URLs da pesq
|
|
|
237
269
|
export function buildWidgetURL(key: string, data: WidgetData): string {
|
|
238
270
|
const params = new URLSearchParams(data as Record<string, string>);
|
|
239
271
|
const baseURL = `${BASE_URL}/${key}/?mode=widget`;
|
|
240
|
-
|
|
272
|
+
|
|
241
273
|
if (data.transaction_id) {
|
|
242
274
|
return `${baseURL}&${params.toString()}`;
|
|
243
275
|
}
|
|
244
|
-
|
|
276
|
+
|
|
245
277
|
return `${baseURL}&transaction_id=&${params.toString()}`;
|
|
246
278
|
}
|
|
247
279
|
```
|
|
@@ -336,21 +368,24 @@ export const WEB_VIEW_MESSAGE_LISTENER = `
|
|
|
336
368
|
### Adicionando Novos Tipos
|
|
337
369
|
|
|
338
370
|
1. **Estenda o tipo WidgetType**:
|
|
371
|
+
|
|
339
372
|
```typescript
|
|
340
373
|
// modules/solucx_widget/src/interfaces/index.ts
|
|
341
|
-
export type WidgetType =
|
|
374
|
+
export type WidgetType = 'bottom' | 'top' | 'inline' | 'modal' | 'novo_tipo';
|
|
342
375
|
```
|
|
343
376
|
|
|
344
377
|
2. **Adicione estilos correspondentes**:
|
|
378
|
+
|
|
345
379
|
```typescript
|
|
346
380
|
// modules/solucx_widget/src/styles/widgetStyles.ts
|
|
347
381
|
const styleMap = {
|
|
348
382
|
// ... estilos existentes
|
|
349
|
-
|
|
383
|
+
novo_tipo: { container: styles.novoWrapper, content: styles.novoContent }
|
|
350
384
|
};
|
|
351
385
|
```
|
|
352
386
|
|
|
353
387
|
3. **Implemente lógica no componente principal**:
|
|
388
|
+
|
|
354
389
|
```typescript
|
|
355
390
|
// modules/solucx_widget/src/SoluCXWidget.tsx
|
|
356
391
|
if (type === 'novo_tipo') {
|
|
@@ -361,14 +396,14 @@ if (type === 'novo_tipo') {
|
|
|
361
396
|
### Adicionando Novos Eventos
|
|
362
397
|
|
|
363
398
|
1. **Estenda EventKey**:
|
|
399
|
+
|
|
364
400
|
```typescript
|
|
365
401
|
// modules/solucx_widget/src/interfaces/index.ts
|
|
366
|
-
export type EventKey =
|
|
367
|
-
| "FORM_OPENED"
|
|
368
|
-
| "NOVO_EVENTO" // Novo evento
|
|
402
|
+
export type EventKey = 'FORM_OPENED' | 'NOVO_EVENTO'; // Novo evento
|
|
369
403
|
```
|
|
370
404
|
|
|
371
405
|
2. **Implemente handler**:
|
|
406
|
+
|
|
372
407
|
```typescript
|
|
373
408
|
// modules/solucx_widget/src/services/widgetEventService.ts
|
|
374
409
|
private executeEvent(eventKey: EventKey, value: string): WidgetResponse {
|
|
@@ -380,6 +415,7 @@ private executeEvent(eventKey: EventKey, value: string): WidgetResponse {
|
|
|
380
415
|
```
|
|
381
416
|
|
|
382
417
|
3. **Adicione testes**:
|
|
418
|
+
|
|
383
419
|
```typescript
|
|
384
420
|
// modules/solucx_widget/src/__tests__/widgetEventService.test.ts
|
|
385
421
|
it('should handle NOVO_EVENTO correctly', () => {
|
|
@@ -393,6 +429,7 @@ it('should handle NOVO_EVENTO correctly', () => {
|
|
|
393
429
|
### Problemas Comuns
|
|
394
430
|
|
|
395
431
|
#### 1. **Widget não aparece**
|
|
432
|
+
|
|
396
433
|
```bash
|
|
397
434
|
# Verificações:
|
|
398
435
|
✅ Chave SoluCX válida?
|
|
@@ -402,6 +439,7 @@ it('should handle NOVO_EVENTO correctly', () => {
|
|
|
402
439
|
```
|
|
403
440
|
|
|
404
441
|
#### 2. **Eventos não funcionam**
|
|
442
|
+
|
|
405
443
|
```bash
|
|
406
444
|
# Verificações:
|
|
407
445
|
✅ JavaScript listener injetado?
|
|
@@ -411,6 +449,7 @@ it('should handle NOVO_EVENTO correctly', () => {
|
|
|
411
449
|
```
|
|
412
450
|
|
|
413
451
|
#### 3. **Layout quebrado**
|
|
452
|
+
|
|
414
453
|
```bash
|
|
415
454
|
# Verificações:
|
|
416
455
|
✅ Dimensões adequadas para o dispositivo?
|
|
@@ -420,6 +459,7 @@ it('should handle NOVO_EVENTO correctly', () => {
|
|
|
420
459
|
```
|
|
421
460
|
|
|
422
461
|
#### 4. **Storage não persiste**
|
|
462
|
+
|
|
423
463
|
```bash
|
|
424
464
|
# Verificações:
|
|
425
465
|
✅ AsyncStorage instalado?
|
|
@@ -432,15 +472,15 @@ it('should handle NOVO_EVENTO correctly', () => {
|
|
|
432
472
|
|
|
433
473
|
```typescript
|
|
434
474
|
// Habilitar logs detalhados
|
|
435
|
-
console.log(
|
|
475
|
+
console.log('Widget event received:', processedKey, value);
|
|
436
476
|
|
|
437
477
|
// Verificar dados persistidos
|
|
438
478
|
const data = await storageService.read();
|
|
439
|
-
console.log(
|
|
479
|
+
console.log('Stored data:', data);
|
|
440
480
|
|
|
441
481
|
// Verificar URL construída
|
|
442
482
|
const url = buildWidgetURL(soluCXKey, data);
|
|
443
|
-
console.log(
|
|
483
|
+
console.log('Widget URL:', url);
|
|
444
484
|
```
|
|
445
485
|
|
|
446
486
|
## 📚 Dependências
|
package/README.md
CHANGED
|
@@ -45,17 +45,17 @@ import { SoluCXWidget } from '@solucx/react-native-solucx-widget';
|
|
|
45
45
|
export default function MyScreen() {
|
|
46
46
|
return (
|
|
47
47
|
<SoluCXWidget
|
|
48
|
-
soluCXKey=
|
|
49
|
-
type=
|
|
48
|
+
soluCXKey='sua-chave-solucx'
|
|
49
|
+
type='bottom'
|
|
50
50
|
data={{
|
|
51
|
-
journey:
|
|
52
|
-
name:
|
|
53
|
-
email:
|
|
54
|
-
store_id:
|
|
55
|
-
amount: 150.
|
|
51
|
+
journey: 'checkout_flow',
|
|
52
|
+
name: 'João Silva',
|
|
53
|
+
email: 'joao@email.com',
|
|
54
|
+
store_id: 'loja_01',
|
|
55
|
+
amount: 150.0
|
|
56
56
|
}}
|
|
57
57
|
options={{
|
|
58
|
-
height: 400
|
|
58
|
+
height: 400
|
|
59
59
|
}}
|
|
60
60
|
/>
|
|
61
61
|
);
|
|
@@ -65,43 +65,47 @@ export default function MyScreen() {
|
|
|
65
65
|
## 📱 Modos de Renderização
|
|
66
66
|
|
|
67
67
|
### Bottom (Padrão)
|
|
68
|
+
|
|
68
69
|
Widget fixo na parte inferior da tela, ideal para feedback não intrusivo.
|
|
69
70
|
|
|
70
71
|
```tsx
|
|
71
|
-
<SoluCXWidget type=
|
|
72
|
+
<SoluCXWidget type='bottom' {...props} />
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
### Top
|
|
76
|
+
|
|
75
77
|
Widget fixo no topo da tela, perfeito para notificações importantes.
|
|
76
78
|
|
|
77
79
|
```tsx
|
|
78
|
-
<SoluCXWidget type=
|
|
80
|
+
<SoluCXWidget type='top' {...props} />
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
### Modal
|
|
84
|
+
|
|
82
85
|
Sobreposição centralizada que bloqueia interação com o fundo.
|
|
83
86
|
|
|
84
87
|
```tsx
|
|
85
|
-
<SoluCXWidget type=
|
|
88
|
+
<SoluCXWidget type='modal' {...props} />
|
|
86
89
|
```
|
|
87
90
|
|
|
88
91
|
### Inline
|
|
92
|
+
|
|
89
93
|
Integrado ao fluxo normal do layout, respeitando a posição no código.
|
|
90
94
|
|
|
91
95
|
```tsx
|
|
92
|
-
<SoluCXWidget type=
|
|
96
|
+
<SoluCXWidget type='inline' {...props} />
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
## 🔧 API Completa
|
|
96
100
|
|
|
97
101
|
### Props
|
|
98
102
|
|
|
99
|
-
| Propriedade | Tipo
|
|
100
|
-
|
|
101
|
-
| `soluCXKey` | `string`
|
|
102
|
-
| `type`
|
|
103
|
-
| `data`
|
|
104
|
-
| `options`
|
|
103
|
+
| Propriedade | Tipo | Obrigatório | Descrição |
|
|
104
|
+
| ----------- | --------------- | ----------- | ---------------------------- |
|
|
105
|
+
| `soluCXKey` | `string` | ✅ | Chave de autenticação SoluCX |
|
|
106
|
+
| `type` | `WidgetType` | ✅ | Modo de renderização |
|
|
107
|
+
| `data` | `WidgetData` | ✅ | Dados do cliente/transação |
|
|
108
|
+
| `options` | `WidgetOptions` | ✅ | Configurações do widget |
|
|
105
109
|
|
|
106
110
|
### WidgetData
|
|
107
111
|
|
|
@@ -110,14 +114,14 @@ interface WidgetData {
|
|
|
110
114
|
// Identificadores
|
|
111
115
|
transaction_id?: string;
|
|
112
116
|
customer_id?: string;
|
|
113
|
-
|
|
117
|
+
|
|
114
118
|
// Dados do cliente
|
|
115
119
|
name?: string;
|
|
116
120
|
email?: string;
|
|
117
121
|
phone?: string;
|
|
118
|
-
birth_date?: string;
|
|
122
|
+
birth_date?: string; // Formato: YYYY-MM-DD
|
|
119
123
|
document?: string;
|
|
120
|
-
|
|
124
|
+
|
|
121
125
|
// Contexto da transação
|
|
122
126
|
store_id?: string;
|
|
123
127
|
store_name?: string;
|
|
@@ -125,8 +129,8 @@ interface WidgetData {
|
|
|
125
129
|
employee_name?: string;
|
|
126
130
|
amount?: number;
|
|
127
131
|
score?: number;
|
|
128
|
-
journey?: string;
|
|
129
|
-
|
|
132
|
+
journey?: string; // Nome da jornada/fluxo
|
|
133
|
+
|
|
130
134
|
// Parâmetros customizados (prefixo param_)
|
|
131
135
|
param_REGIAO?: string;
|
|
132
136
|
[key: string]: string | number | undefined;
|
|
@@ -137,19 +141,43 @@ interface WidgetData {
|
|
|
137
141
|
|
|
138
142
|
```typescript
|
|
139
143
|
interface WidgetOptions {
|
|
140
|
-
height?: number;
|
|
144
|
+
height?: number; // Altura fixa em pontos (points, não pixels)
|
|
145
|
+
// Se não fornecido, será dinâmica baseada em eventos de resize
|
|
146
|
+
// Se fornecido, será fixa independente do tipo de widget
|
|
141
147
|
retry?: {
|
|
142
|
-
attempts?: number;
|
|
143
|
-
interval?: number;
|
|
148
|
+
attempts?: number; // Tentativas (padrão: 3)
|
|
149
|
+
interval?: number; // Intervalo em ms (padrão: 1000)
|
|
144
150
|
};
|
|
145
151
|
waitDelayAfterRating?: number; // Delay após avaliação
|
|
146
152
|
}
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
**Comportamento da Altura (height):**
|
|
156
|
+
|
|
157
|
+
- **Altura Dinâmica (padrão)**: Quando `height` não é fornecido, o widget se ajusta automaticamente baseado em eventos de resize do conteúdo. Isso funciona para todos os tipos: `bottom`, `top`, `inline` e `modal`.
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// Altura dinâmica - se adapta ao conteúdo
|
|
161
|
+
<SoluCXWidget
|
|
162
|
+
type='bottom'
|
|
163
|
+
options={{}} // ou options={{ retry: { attempts: 3 } }}
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- **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 fixa de 400 pontos
|
|
172
|
+
<SoluCXWidget type='modal' options={{ height: 400 }} {...props} />
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**⚠️ 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.
|
|
176
|
+
|
|
149
177
|
### WidgetType
|
|
150
178
|
|
|
151
179
|
```typescript
|
|
152
|
-
type WidgetType =
|
|
180
|
+
type WidgetType = 'bottom' | 'top' | 'inline' | 'modal';
|
|
153
181
|
```
|
|
154
182
|
|
|
155
183
|
## 🔄 Sistema de Eventos
|
|
@@ -247,8 +275,8 @@ const options = {
|
|
|
247
275
|
## 📚 Compatibilidade
|
|
248
276
|
|
|
249
277
|
| Versão | React Native | Expo | iOS | Android |
|
|
250
|
-
|
|
251
|
-
| 1.0.x
|
|
278
|
+
| ------ | ------------ | ---- | --- | ------- |
|
|
279
|
+
| 1.0.x | 0.70+ | 50+ | 11+ | API 21+ |
|
|
252
280
|
|
|
253
281
|
## 📄 Licença
|
|
254
282
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solucx/react-native-solucx-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "The React Native SDK for Solucx Widget",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"author": " <> ()",
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"peerDependencies": {
|
|
17
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
17
18
|
"react": ">=18.0.0",
|
|
18
19
|
"react-native": ">=0.72.0",
|
|
19
|
-
"
|
|
20
|
-
"react-native-
|
|
21
|
-
"react-native-webview": "^13.16.0"
|
|
20
|
+
"react-native-webview": "^13.16.0",
|
|
21
|
+
"react-native-safe-area-context": "^5.6.1"
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
|
-
import { styles, getWidgetVisibility
|
|
3
|
+
import { styles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
4
4
|
import { CloseButton } from './CloseButton';
|
|
5
|
-
import Animated from 'react-native
|
|
5
|
+
import { Animated } from 'react-native';
|
|
6
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
6
7
|
|
|
7
8
|
interface InlineWidgetProps {
|
|
8
9
|
visible: boolean;
|
|
@@ -11,7 +12,12 @@ interface InlineWidgetProps {
|
|
|
11
12
|
onClose?: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const InlineWidget: React.FC<InlineWidgetProps> = ({
|
|
15
|
+
export const InlineWidget: React.FC<InlineWidgetProps> = ({
|
|
16
|
+
visible,
|
|
17
|
+
height,
|
|
18
|
+
children,
|
|
19
|
+
onClose,
|
|
20
|
+
}) => {
|
|
15
21
|
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
16
22
|
|
|
17
23
|
useEffect(() => {
|
|
@@ -20,12 +26,10 @@ export const InlineWidget: React.FC<InlineWidgetProps> = ({ visible, height, chi
|
|
|
20
26
|
|
|
21
27
|
return (
|
|
22
28
|
<View style={[styles.inlineWrapper, getWidgetVisibility(visible)]}>
|
|
23
|
-
<Animated.View
|
|
29
|
+
<Animated.View
|
|
30
|
+
style={[styles.inline, animatedHeightStyle, getWidgetVisibility(visible)]}>
|
|
24
31
|
{children}
|
|
25
|
-
<CloseButton
|
|
26
|
-
visible={visible}
|
|
27
|
-
onPress={onClose || (() => { })}
|
|
28
|
-
/>
|
|
32
|
+
<CloseButton visible={visible} onPress={onClose || (() => { })} />
|
|
29
33
|
</Animated.View>
|
|
30
34
|
</View>
|
|
31
35
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Modal, SafeAreaView, View } from 'react-native';
|
|
3
|
-
import { styles, getWidgetVisibility
|
|
2
|
+
import { Modal, SafeAreaView, View, Animated } from 'react-native';
|
|
3
|
+
import { styles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
4
4
|
import { CloseButton } from './CloseButton';
|
|
5
|
-
import
|
|
5
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
6
6
|
|
|
7
7
|
interface ModalWidgetProps {
|
|
8
8
|
visible: boolean;
|
|
@@ -11,7 +11,12 @@ interface ModalWidgetProps {
|
|
|
11
11
|
onClose?: () => void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export const ModalWidget: React.FC<ModalWidgetProps> = ({
|
|
14
|
+
export const ModalWidget: React.FC<ModalWidgetProps> = ({
|
|
15
|
+
visible,
|
|
16
|
+
height,
|
|
17
|
+
children,
|
|
18
|
+
onClose,
|
|
19
|
+
}) => {
|
|
15
20
|
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(true);
|
|
16
21
|
|
|
17
22
|
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
@@ -22,9 +27,20 @@ export const ModalWidget: React.FC<ModalWidgetProps> = ({ visible, height, child
|
|
|
22
27
|
|
|
23
28
|
return (
|
|
24
29
|
<SafeAreaView>
|
|
25
|
-
<Modal
|
|
30
|
+
<Modal
|
|
31
|
+
transparent
|
|
32
|
+
visible={isWidgetVisible}
|
|
33
|
+
animationType="slide"
|
|
34
|
+
hardwareAccelerated
|
|
35
|
+
>
|
|
26
36
|
<View style={[styles.modalOverlay, getWidgetVisibility(visible)]}>
|
|
27
|
-
<Animated.View
|
|
37
|
+
<Animated.View
|
|
38
|
+
style={[
|
|
39
|
+
styles.modalContent,
|
|
40
|
+
getWidgetVisibility(visible),
|
|
41
|
+
animatedHeightStyle,
|
|
42
|
+
]}
|
|
43
|
+
>
|
|
28
44
|
{children}
|
|
29
45
|
<CloseButton
|
|
30
46
|
visible={visible}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { View, ViewStyle } from 'react-native';
|
|
2
|
+
import { View, ViewStyle, Animated } from 'react-native';
|
|
3
3
|
import { initialWindowMetrics } from 'react-native-safe-area-context';
|
|
4
|
-
import { getWidgetStyles, getWidgetVisibility
|
|
4
|
+
import { getWidgetStyles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
5
5
|
import { FIXED_Z_INDEX } from '../constants/webViewConstants';
|
|
6
6
|
import { CloseButton } from './CloseButton';
|
|
7
|
-
import
|
|
7
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
8
8
|
|
|
9
9
|
interface OverlayWidgetProps {
|
|
10
10
|
visible: boolean;
|
|
@@ -23,7 +23,8 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
|
|
|
23
23
|
children,
|
|
24
24
|
onClose,
|
|
25
25
|
}) => {
|
|
26
|
-
const insets =
|
|
26
|
+
const insets =
|
|
27
|
+
initialWindowMetrics?.insets ?? { top: 0, bottom: 0, left: 0, right: 0 };
|
|
27
28
|
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(true);
|
|
28
29
|
|
|
29
30
|
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
@@ -41,8 +42,8 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
|
|
|
41
42
|
width: '100%',
|
|
42
43
|
height: '100%',
|
|
43
44
|
zIndex: FIXED_Z_INDEX,
|
|
44
|
-
pointerEvents: 'box-none'
|
|
45
|
-
}
|
|
45
|
+
pointerEvents: 'box-none',
|
|
46
|
+
};
|
|
46
47
|
|
|
47
48
|
const contentStyle = [
|
|
48
49
|
getWidgetStyles(position).content,
|
|
@@ -55,14 +56,20 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
|
|
|
55
56
|
...(position === 'bottom' && {
|
|
56
57
|
bottom: insets.bottom,
|
|
57
58
|
}),
|
|
58
|
-
}
|
|
59
|
+
},
|
|
59
60
|
];
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
63
|
<>
|
|
63
64
|
{isWidgetVisible && (
|
|
64
65
|
<View style={[containerStyle, getWidgetVisibility(visible)]}>
|
|
65
|
-
<Animated.View
|
|
66
|
+
<Animated.View
|
|
67
|
+
style={[
|
|
68
|
+
contentStyle,
|
|
69
|
+
animatedHeightStyle,
|
|
70
|
+
getWidgetVisibility(visible),
|
|
71
|
+
]}
|
|
72
|
+
>
|
|
66
73
|
{children}
|
|
67
74
|
<CloseButton
|
|
68
75
|
visible={visible}
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// hooks/useHeightAnimation.ts
|
|
2
|
+
import { useRef, useCallback } from 'react';
|
|
3
|
+
import { Animated } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function useHeightAnimation(initialHeight = 0, duration = 300) {
|
|
6
|
+
const height = useRef(new Animated.Value(initialHeight)).current;
|
|
7
|
+
|
|
8
|
+
const updateHeight = useCallback(
|
|
9
|
+
(toValue: number) => {
|
|
10
|
+
Animated.timing(height, {
|
|
11
|
+
toValue,
|
|
12
|
+
duration,
|
|
13
|
+
useNativeDriver: false,
|
|
14
|
+
}).start();
|
|
15
|
+
},
|
|
16
|
+
[height, duration],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const animatedHeightStyle = { height };
|
|
20
|
+
|
|
21
|
+
return { animatedHeightStyle, updateHeight, height };
|
|
22
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import type { WidgetOptions } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
interface UseWidgetHeightProps {
|
|
5
|
+
options?: WidgetOptions;
|
|
6
|
+
initialHeight?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UseWidgetHeightReturn {
|
|
10
|
+
height: number;
|
|
11
|
+
isFixedHeight: boolean;
|
|
12
|
+
handleResize: (newHeight: number) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useWidgetHeight = ({
|
|
16
|
+
options,
|
|
17
|
+
initialHeight = 300
|
|
18
|
+
}: UseWidgetHeightProps): UseWidgetHeightReturn => {
|
|
19
|
+
const [dynamicHeight, setDynamicHeight] = useState<number>(initialHeight);
|
|
20
|
+
const hasFixedHeight = typeof options?.height === 'number';
|
|
21
|
+
|
|
22
|
+
const height = hasFixedHeight ? options!.height! : dynamicHeight;
|
|
23
|
+
|
|
24
|
+
const handleResize = useCallback(
|
|
25
|
+
(newHeight: number) => {
|
|
26
|
+
if (!hasFixedHeight && newHeight > 0) {
|
|
27
|
+
setDynamicHeight(newHeight);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
[hasFixedHeight]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
height,
|
|
35
|
+
isFixedHeight: hasFixedHeight,
|
|
36
|
+
handleResize
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -1,77 +1,101 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
2
|
import { Dimensions } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
WidgetData,
|
|
5
|
+
WidgetOptions,
|
|
6
|
+
WidgetType,
|
|
7
|
+
WidgetSamplerLog
|
|
8
|
+
} from '../interfaces';
|
|
4
9
|
import { StorageService } from '../services/storage';
|
|
5
10
|
|
|
6
11
|
function getUserId(widgetData: WidgetData): string {
|
|
7
|
-
|
|
12
|
+
return (
|
|
13
|
+
widgetData.customer_id ??
|
|
14
|
+
widgetData.document ??
|
|
15
|
+
widgetData.email ??
|
|
16
|
+
'default_user'
|
|
17
|
+
);
|
|
8
18
|
}
|
|
9
19
|
|
|
10
|
-
export const useWidgetState = (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
export const useWidgetState = (
|
|
21
|
+
data: WidgetData,
|
|
22
|
+
options?: WidgetOptions,
|
|
23
|
+
type?: WidgetType
|
|
24
|
+
) => {
|
|
25
|
+
const [savedData, setSavedData] = useState<WidgetSamplerLog | null>(null);
|
|
26
|
+
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(false);
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
const storageService = new StorageService(userId);
|
|
17
|
-
const screenHeight = Dimensions.get('screen').height;
|
|
18
|
-
const height = options?.height ? Number(options.height) : undefined;
|
|
28
|
+
const userId = getUserId(data);
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const jsonValue = await storageService.read();
|
|
23
|
-
setSavedData(jsonValue);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('Error loading storage data:', error);
|
|
26
|
-
}
|
|
27
|
-
}, [storageService]);
|
|
30
|
+
const storageService = useMemo(() => new StorageService(userId), [userId]);
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await storageService.write(data);
|
|
32
|
-
setSavedData(data);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Error saving storage data:', error);
|
|
35
|
-
}
|
|
36
|
-
}, [storageService]);
|
|
32
|
+
const screenHeight = Dimensions.get('screen').height;
|
|
33
|
+
const height = options?.height ? Number(options.height) : undefined;
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
const userLogs = await storageService.read();
|
|
40
|
-
userLogs.attempts++;
|
|
41
|
-
userLogs.lastAttempt = Date.now();
|
|
42
|
-
try {
|
|
43
|
-
await storageService.write(userLogs);
|
|
44
|
-
setSavedData(userLogs);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Error saving storage data:', error);
|
|
47
|
-
}
|
|
48
|
-
setIsWidgetVisible(true);
|
|
49
|
-
}, []);
|
|
35
|
+
const [widgetHeight, setWidgetHeight] = useState<number>(height ?? 300);
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
const loadSavedData = useCallback(async () => {
|
|
38
|
+
try {
|
|
39
|
+
const jsonValue = await storageService.read();
|
|
40
|
+
setSavedData(jsonValue);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error loading storage data:', error);
|
|
43
|
+
}
|
|
44
|
+
}, [storageService]);
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
const saveData = useCallback(
|
|
47
|
+
async (data: WidgetSamplerLog) => {
|
|
48
|
+
try {
|
|
49
|
+
await storageService.write(data);
|
|
50
|
+
setSavedData(data);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error saving storage data:', error);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
[storageService]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const open = useCallback(async () => {
|
|
59
|
+
const userLogs = await storageService.read();
|
|
60
|
+
userLogs.attempts = (userLogs.attempts || 0) + 1;
|
|
61
|
+
userLogs.lastAttempt = Date.now();
|
|
62
|
+
try {
|
|
63
|
+
await storageService.write(userLogs);
|
|
64
|
+
setSavedData(userLogs);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error saving storage data:', error);
|
|
67
|
+
}
|
|
68
|
+
setIsWidgetVisible(true);
|
|
69
|
+
}, [storageService]);
|
|
70
|
+
|
|
71
|
+
const close = useCallback(() => {
|
|
72
|
+
setIsWidgetVisible(false);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const resize = useCallback(
|
|
76
|
+
(value: string) => {
|
|
77
|
+
const receivedHeight = Number(value);
|
|
78
|
+
|
|
79
|
+
if (height !== undefined) {
|
|
80
|
+
setWidgetHeight(height);
|
|
81
|
+
} else if (receivedHeight > 0) {
|
|
61
82
|
setWidgetHeight(receivedHeight);
|
|
62
|
-
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[height]
|
|
86
|
+
);
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
return {
|
|
89
|
+
savedData,
|
|
90
|
+
widgetHeight,
|
|
91
|
+
isWidgetVisible,
|
|
92
|
+
setIsWidgetVisible,
|
|
93
|
+
loadSavedData,
|
|
94
|
+
saveData,
|
|
95
|
+
open,
|
|
96
|
+
close,
|
|
97
|
+
resize,
|
|
98
|
+
userId,
|
|
99
|
+
screenHeight
|
|
100
|
+
};
|
|
77
101
|
};
|
|
@@ -1,111 +1,126 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
EventKey,
|
|
3
|
+
SurveyEventKey,
|
|
4
|
+
WidgetResponse,
|
|
5
|
+
WidgetOptions,
|
|
6
|
+
} from "../interfaces";
|
|
7
|
+
import { WidgetValidationService } from "./widgetValidationService";
|
|
3
8
|
|
|
4
9
|
export class WidgetEventService {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
private setIsWidgetVisible: (visible: boolean) => void;
|
|
11
|
+
private resize: (value: string) => void;
|
|
12
|
+
private open: () => void;
|
|
13
|
+
private validationService: WidgetValidationService;
|
|
14
|
+
private widgetOptions: WidgetOptions;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
setIsWidgetVisible: (visible: boolean) => void,
|
|
18
|
+
resize: (value: string) => void,
|
|
19
|
+
open: () => void,
|
|
20
|
+
userId: string,
|
|
21
|
+
widgetOptions: WidgetOptions,
|
|
22
|
+
) {
|
|
23
|
+
this.setIsWidgetVisible = setIsWidgetVisible;
|
|
24
|
+
this.resize = resize;
|
|
25
|
+
this.open = open;
|
|
26
|
+
this.validationService = new WidgetValidationService(userId);
|
|
27
|
+
this.widgetOptions = widgetOptions;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handleMessage(
|
|
31
|
+
message: string,
|
|
32
|
+
isForm: boolean,
|
|
33
|
+
): Promise<WidgetResponse> {
|
|
34
|
+
const [eventKey, value = ""] = message.split("-");
|
|
35
|
+
const processedKey = isForm
|
|
36
|
+
? (eventKey as EventKey)
|
|
37
|
+
: this.adaptSurveyKeyToWidgetKey(eventKey as SurveyEventKey);
|
|
38
|
+
|
|
39
|
+
return await this.executeEvent(processedKey, value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async executeEvent(
|
|
43
|
+
eventKey: EventKey,
|
|
44
|
+
value: string,
|
|
45
|
+
): Promise<WidgetResponse> {
|
|
46
|
+
const eventHandlers = {
|
|
47
|
+
FORM_OPENED: () => this.handleFormOpened(),
|
|
48
|
+
FORM_CLOSE: () => this.handleFormClose(),
|
|
49
|
+
FORM_ERROR: (value: string) => this.handleFormError(value),
|
|
50
|
+
FORM_PAGECHANGED: (value: string) => this.handlePageChanged(value),
|
|
51
|
+
QUESTION_ANSWERED: () => this.handleQuestionAnswered(),
|
|
52
|
+
FORM_COMPLETED: () => this.handleFormCompleted(),
|
|
53
|
+
FORM_PARTIALCOMPLETED: () => this.handlePartialCompleted(),
|
|
54
|
+
FORM_RESIZE: (value: string) => this.handleResize(value),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handler = eventHandlers[eventKey];
|
|
58
|
+
return await (handler?.(value) || {
|
|
59
|
+
status: "error",
|
|
60
|
+
message: "Unknown event",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async handleFormOpened(): Promise<WidgetResponse> {
|
|
65
|
+
const canDisplay = await this.validationService.shouldDisplayWidget(
|
|
66
|
+
this.widgetOptions,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!canDisplay) {
|
|
70
|
+
return { status: "error", message: "Widget not allowed" };
|
|
23
71
|
}
|
|
24
72
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.log("Question answered");
|
|
79
|
-
return { status: "success" };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private handleFormCompleted(): WidgetResponse {
|
|
83
|
-
// TODO: Implement completion logic
|
|
84
|
-
return { status: "success" };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private handlePartialCompleted(): WidgetResponse {
|
|
88
|
-
// TODO: Implement partial completion logic
|
|
89
|
-
return { status: "success" };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private handleResize(value: string): WidgetResponse {
|
|
93
|
-
this.setIsWidgetVisible(true);
|
|
94
|
-
this.resize(value);
|
|
95
|
-
return { status: "success" };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private adaptSurveyKeyToWidgetKey(key: SurveyEventKey): EventKey {
|
|
99
|
-
const keyMapping = {
|
|
100
|
-
closeSoluCXWidget: "FORM_CLOSE",
|
|
101
|
-
dismissSoluCXWidget: "FORM_CLOSE",
|
|
102
|
-
completeSoluCXWidget: "FORM_COMPLETED",
|
|
103
|
-
partialSoluCXWidget: "FORM_PARTIALCOMPLETED",
|
|
104
|
-
resizeSoluCXWidget: "FORM_RESIZE",
|
|
105
|
-
openSoluCXWidget: "FORM_OPENED",
|
|
106
|
-
errorSoluCXWidget: "FORM_ERROR",
|
|
107
|
-
} as const;
|
|
108
|
-
|
|
109
|
-
return keyMapping[key] as EventKey;
|
|
110
|
-
}
|
|
73
|
+
this.open();
|
|
74
|
+
this.setIsWidgetVisible(true);
|
|
75
|
+
return { status: "success" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private handleFormClose(): WidgetResponse {
|
|
79
|
+
this.setIsWidgetVisible(false);
|
|
80
|
+
return { status: "success" };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private handleFormError(value: string): WidgetResponse {
|
|
84
|
+
this.setIsWidgetVisible(false);
|
|
85
|
+
return { status: "error", message: value };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private handlePageChanged(value: string): WidgetResponse {
|
|
89
|
+
console.log("Page changed:", value);
|
|
90
|
+
return { status: "success" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private handleQuestionAnswered(): WidgetResponse {
|
|
94
|
+
console.log("Question answered");
|
|
95
|
+
return { status: "success" };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private handleFormCompleted(): WidgetResponse {
|
|
99
|
+
// TODO: Implement completion logic
|
|
100
|
+
return { status: "success" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private handlePartialCompleted(): WidgetResponse {
|
|
104
|
+
// TODO: Implement partial completion logic
|
|
105
|
+
return { status: "success" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private handleResize(value: string): WidgetResponse {
|
|
109
|
+
this.resize(value);
|
|
110
|
+
return { status: "success" };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private adaptSurveyKeyToWidgetKey(key: SurveyEventKey): EventKey {
|
|
114
|
+
const keyMapping = {
|
|
115
|
+
closeSoluCXWidget: "FORM_CLOSE",
|
|
116
|
+
dismissSoluCXWidget: "FORM_CLOSE",
|
|
117
|
+
completeSoluCXWidget: "FORM_COMPLETED",
|
|
118
|
+
partialSoluCXWidget: "FORM_PARTIALCOMPLETED",
|
|
119
|
+
resizeSoluCXWidget: "FORM_RESIZE",
|
|
120
|
+
openSoluCXWidget: "FORM_OPENED",
|
|
121
|
+
errorSoluCXWidget: "FORM_ERROR",
|
|
122
|
+
} as const;
|
|
123
|
+
|
|
124
|
+
return keyMapping[key] as EventKey;
|
|
125
|
+
}
|
|
111
126
|
}
|
|
@@ -1,30 +1,5 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
2
|
import { WidgetType } from '../interfaces';
|
|
3
|
-
import {
|
|
4
|
-
useSharedValue,
|
|
5
|
-
withTiming,
|
|
6
|
-
useAnimatedStyle,
|
|
7
|
-
Easing,
|
|
8
|
-
} from 'react-native-reanimated';
|
|
9
|
-
|
|
10
|
-
export const useHeightAnimation = (height: number) => {
|
|
11
|
-
const animatedHeight = useSharedValue(height);
|
|
12
|
-
|
|
13
|
-
const animatedHeightStyle = useAnimatedStyle(() => {
|
|
14
|
-
return {
|
|
15
|
-
height: animatedHeight.value,
|
|
16
|
-
};
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const updateHeight = (newHeight: number) => {
|
|
20
|
-
animatedHeight.value = withTiming(newHeight, {
|
|
21
|
-
duration: 300,
|
|
22
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
return { animatedHeightStyle, updateHeight };
|
|
27
|
-
};
|
|
28
3
|
|
|
29
4
|
export const styles = StyleSheet.create({
|
|
30
5
|
wrapper: {
|