@hgateam/chat-widget 1.0.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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @ticktur/chat-widget
2
+
3
+ Widget de chat embebible con WebSocket en tiempo real para eventos de Ticktur.
4
+
5
+ ## Instalación
6
+
7
+ ### Opción 1: Desde GitHub (Desarrollo)
8
+
9
+ ```bash
10
+ npm install git+https://github.com/ticktur/events-front.git#dev-adrian:packages/chat-widget
11
+ ```
12
+
13
+ ### Opción 2: Uso Directo (CDN)
14
+
15
+ No requiere instalación. Solo incluye el script en tu HTML:
16
+
17
+ ```html
18
+ <!-- Widget Integration: Solo configurar el data-event-id y data-widget-hash -->
19
+ <script
20
+ src="http://localhost:5173/widget.js"
21
+ data-event-id="1"
22
+ data-widget-hash="f51c8b6939c6a86adf4df6f847e78ec1">
23
+ </script>
24
+ ```
25
+
26
+ **Producción:**
27
+ ```html
28
+ <script
29
+ src="https://cdn.ticktur.com/widget.js"
30
+ data-event-id="TU_EVENT_ID"
31
+ data-widget-hash="TU_HASH_WIDGET">
32
+ </script>
33
+ ```
34
+
35
+ ## Uso con NPM
36
+
37
+ Si instalaste desde npm, necesitas servir el archivo `widget.js`:
38
+
39
+ ```javascript
40
+ // En tu proyecto Node.js/Express
41
+ app.use('/widget', express.static('node_modules/@ticktur/chat-widget/public'));
42
+ ```
43
+
44
+ Luego en tu HTML:
45
+ ```html
46
+ <script
47
+ src="/widget/widget.js"
48
+ data-event-id="1"
49
+ data-widget-hash="tu-hash-aqui">
50
+ </script>
51
+ ```
52
+
53
+ ## Características
54
+
55
+ - Widget flotante en esquina inferior derecha
56
+ - Chat en tiempo real con WebSocket (Laravel Reverb)
57
+ - Totalmente aislado del CSS/JS del sitio host
58
+ - Responsive (móvil y desktop)
59
+ - Framework-agnostic (funciona en cualquier web)
60
+
61
+ ## Configuración
62
+
63
+ El widget requiere 2 atributos:
64
+
65
+ - `data-event-id`: ID del evento en Ticktur
66
+ - `data-widget-hash`: Hash de autorización del widget (obtenido desde el dashboard)
67
+
68
+ ## API Backend
69
+
70
+ El widget se conecta a:
71
+ - API de validación: `GET /api/widgets/validate/{hash}`
72
+ - WebSocket: Laravel Reverb en puerto 8080
73
+ - Canal público: `widget.{hash}`
74
+
75
+ ## Eventos WebSocket
76
+
77
+ El widget escucha:
78
+ - `WidgetStatusChanged`: Cuando se activa/desactiva el widget
79
+ - `WidgetMessageReceived`: Nuevos mensajes del soporte
80
+
81
+ ## Soporte
82
+
83
+ Para más información: https://github.com/ticktur/events-front
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@hgateam/chat-widget",
3
+ "version": "1.0.0",
4
+ "description": "Widget de chat embebible con WebSocket para eventos de Ticktur",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
7
+ "types": "types/index.d.ts",
8
+ "files": [
9
+ "src/",
10
+ "public/widget.js",
11
+ "types/",
12
+ "README.md",
13
+ "INSTALL.md"
14
+ ],
15
+ "scripts": {
16
+ "test": "echo \"No tests configured\" && exit 0"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/ticktur/events-front.git",
21
+ "directory": "packages/chat-widget"
22
+ },
23
+ "keywords": [
24
+ "chat",
25
+ "widget",
26
+ "embeddable",
27
+ "websocket",
28
+ "ticktur",
29
+ "vue",
30
+ "react",
31
+ "webpack",
32
+ "vite"
33
+ ],
34
+ "author": "Ticktur",
35
+ "license": "MIT",
36
+ "peerDependencies": {
37
+ "pusher-js": "^8.2.0",
38
+ "laravel-echo": "^1.15.3"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/ticktur/events-front/issues"
42
+ },
43
+ "homepage": "https://github.com/ticktur/events-front/tree/dev-adrian/packages/chat-widget#readme"
44
+ }
@@ -0,0 +1,535 @@
1
+ /**
2
+ * Ticktur Chat Widget - Versión Embebible
3
+ *
4
+ * Uso:
5
+ * <script src="https://chat.ticktur.com/widget.js" data-event-id="1"></script>
6
+ *
7
+ * Funcionalidad:
8
+ * - Crea un botón flotante verde (#10b981) en la esquina inferior derecha
9
+ * - Al hacer clic, abre una ventana de chat en iFrame
10
+ * - Totalmente aislado del CSS/JS del sitio host
11
+ * - Framework-agnostic (funciona en cualquier web)
12
+ *
13
+ * Futuro (Fase 2):
14
+ * - Autenticación con API key usando MD5
15
+ * - data-api-key="tu-clave-md5"
16
+ */
17
+
18
+ (function () {
19
+ 'use strict';
20
+
21
+ // Configuración
22
+ const CONFIG = {
23
+ BASE_URL: 'http://localhost:5173', // Cambiar en producción a https://chat.ticktur.com
24
+ BUTTON_COLOR: '#10b981',
25
+ BUTTON_HOVER_COLOR: '#059669',
26
+ WIDGET_WIDTH: '400px',
27
+ WIDGET_HEIGHT: '600px',
28
+ WIDGET_WIDTH_MOBILE: '100%',
29
+ WIDGET_HEIGHT_MOBILE: '100%',
30
+ Z_INDEX: '9999'
31
+ };
32
+
33
+ // Obtener el script tag actual
34
+ const currentScript = document.currentScript || document.querySelector('script[data-event-id]');
35
+
36
+ if (!currentScript) {
37
+ console.error('[Ticktur Widget] No se encontró el script tag con data-event-id');
38
+ return;
39
+ }
40
+
41
+ // Extraer configuración del script tag
42
+ const eventId = currentScript.getAttribute('data-event-id');
43
+ const widgetHash = currentScript.getAttribute('data-widget-hash');
44
+
45
+ if (!eventId) {
46
+ console.error('[Ticktur Widget] Falta el atributo data-event-id en el script tag');
47
+ return;
48
+ }
49
+
50
+ if (!widgetHash) {
51
+ console.error('[Ticktur Widget] Falta el atributo data-widget-hash en el script tag');
52
+ return;
53
+ }
54
+
55
+ console.log('[Ticktur Widget] Inicializando widget...');
56
+ console.log('[Ticktur Widget] Hash:', widgetHash);
57
+ console.log('[Ticktur Widget] Evento:', eventId);
58
+
59
+ // Variable para almacenar datos del widget validado
60
+ let widgetData = null;
61
+
62
+ // Validar widget contra la base de datos usando endpoint de autorización
63
+ async function validateWidget() {
64
+ try {
65
+ const apiUrl = CONFIG.BASE_URL.replace(':5173', ':8000');
66
+
67
+ const response = await fetch(`${apiUrl}/api/widgets/validate/${widgetHash}`);
68
+
69
+ if (!response.ok) {
70
+ console.error('[Ticktur Widget] Autorización denegada. Status:', response.status);
71
+ return false;
72
+ }
73
+
74
+ const data = await response.json();
75
+
76
+ if (!data.success) {
77
+ console.error('[Ticktur Widget] Widget no autorizado');
78
+ return false;
79
+ }
80
+
81
+ widgetData = data.data;
82
+
83
+ console.log('[Ticktur Widget] Widget autorizado correctamente');
84
+ console.log('[Ticktur Widget] Nombre:', widgetData.widget_name);
85
+ console.log('[Ticktur Widget] Evento ID:', widgetData.event_id);
86
+ console.log('[Ticktur Widget] Estado:', widgetData.is_active ? 'Activo' : 'Inactivo');
87
+ return true;
88
+ } catch (error) {
89
+ console.error('[Ticktur Widget] Error al validar widget:', error);
90
+ return false;
91
+ }
92
+ }
93
+
94
+ // Estado del widget
95
+ let widgetState = {
96
+ isOpen: false,
97
+ isMinimized: false,
98
+ hasUnreadMessages: false
99
+ };
100
+
101
+ // Crear contenedor principal del widget
102
+ function createWidgetContainer() {
103
+ const container = document.createElement('div');
104
+ container.id = 'ticktur-widget-container';
105
+ container.style.cssText = `
106
+ position: fixed;
107
+ bottom: 20px;
108
+ right: 20px;
109
+ z-index: ${CONFIG.Z_INDEX};
110
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
111
+ `;
112
+ return container;
113
+ }
114
+
115
+ // Crear botón flotante
116
+ function createFloatingButton() {
117
+ const button = document.createElement('button');
118
+ button.id = 'ticktur-widget-button';
119
+ button.setAttribute('aria-label', 'Abrir chat de soporte');
120
+ button.innerHTML = `
121
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
122
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
123
+ </svg>
124
+ `;
125
+ button.style.cssText = `
126
+ width: 60px;
127
+ height: 60px;
128
+ border-radius: 50%;
129
+ background: linear-gradient(135deg, ${CONFIG.BUTTON_COLOR} 0%, ${CONFIG.BUTTON_HOVER_COLOR} 100%);
130
+ border: none;
131
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
132
+ cursor: pointer;
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ color: white;
137
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
138
+ outline: none;
139
+ position: relative;
140
+ `;
141
+
142
+ // Badge de notificación (sin usar inicialmente)
143
+ const badge = document.createElement('span');
144
+ badge.id = 'ticktur-widget-badge';
145
+ badge.style.cssText = `
146
+ position: absolute;
147
+ top: -2px;
148
+ right: -2px;
149
+ width: 20px;
150
+ height: 20px;
151
+ background: #ef4444;
152
+ border-radius: 50%;
153
+ border: 2px solid white;
154
+ font-size: 11px;
155
+ font-weight: bold;
156
+ display: none;
157
+ align-items: center;
158
+ justify-content: center;
159
+ color: white;
160
+ `;
161
+ badge.textContent = '1';
162
+ button.appendChild(badge);
163
+
164
+ // Hover effect
165
+ button.addEventListener('mouseenter', () => {
166
+ button.style.transform = 'scale(1.1)';
167
+ button.style.boxShadow = '0 6px 20px rgba(16, 185, 129, 0.5)';
168
+ });
169
+
170
+ button.addEventListener('mouseleave', () => {
171
+ button.style.transform = 'scale(1)';
172
+ button.style.boxShadow = '0 4px 12px rgba(16, 185, 129, 0.4)';
173
+ });
174
+
175
+ // Click event
176
+ button.addEventListener('click', toggleWidget);
177
+
178
+ return button;
179
+ }
180
+
181
+ // Crear ventana de chat (iFrame)
182
+ function createChatWindow() {
183
+ const chatWindow = document.createElement('div');
184
+ chatWindow.id = 'ticktur-widget-window';
185
+ chatWindow.style.cssText = `
186
+ position: fixed;
187
+ bottom: 100px;
188
+ right: 20px;
189
+ width: ${CONFIG.WIDGET_WIDTH};
190
+ height: ${CONFIG.WIDGET_HEIGHT};
191
+ background: white;
192
+ border-radius: 12px;
193
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
194
+ overflow: hidden;
195
+ display: none;
196
+ flex-direction: column;
197
+ z-index: ${CONFIG.Z_INDEX};
198
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
199
+ transform-origin: bottom right;
200
+ `;
201
+
202
+ // iFrame del chat
203
+ const iframe = document.createElement('iframe');
204
+ iframe.id = 'ticktur-widget-iframe';
205
+ iframe.src = `${CONFIG.BASE_URL}/embed/${eventId}`;
206
+ iframe.style.cssText = `
207
+ width: 100%;
208
+ height: 100%;
209
+ border: none;
210
+ display: block;
211
+ `;
212
+ iframe.setAttribute('allow', 'microphone; camera'); // Por si en el futuro se agrega video/audio
213
+ iframe.setAttribute('title', 'Chat de Soporte Ticktur');
214
+
215
+ chatWindow.appendChild(iframe);
216
+
217
+ // Responsive: fullscreen en móviles
218
+ if (window.innerWidth <= 768) {
219
+ chatWindow.style.cssText = `
220
+ position: fixed;
221
+ top: 0;
222
+ left: 0;
223
+ right: 0;
224
+ bottom: 0;
225
+ width: ${CONFIG.WIDGET_WIDTH_MOBILE};
226
+ height: ${CONFIG.WIDGET_HEIGHT_MOBILE};
227
+ border-radius: 0;
228
+ z-index: ${CONFIG.Z_INDEX};
229
+ `;
230
+ }
231
+
232
+ return chatWindow;
233
+ }
234
+
235
+ // Toggle del widget (abrir/cerrar)
236
+ function toggleWidget() {
237
+ const chatWindow = document.getElementById('ticktur-widget-window');
238
+ const button = document.getElementById('ticktur-widget-button');
239
+ const badge = document.getElementById('ticktur-widget-badge');
240
+
241
+ if (!chatWindow) return;
242
+
243
+ widgetState.isOpen = !widgetState.isOpen;
244
+
245
+ if (widgetState.isOpen) {
246
+ // Abrir
247
+ chatWindow.style.display = 'flex';
248
+ chatWindow.style.transform = 'scale(1)';
249
+ chatWindow.style.opacity = '1';
250
+ button.style.transform = 'rotate(90deg) scale(1.1)';
251
+
252
+ // Ocultar badge al abrir
253
+ if (badge) {
254
+ badge.style.display = 'none';
255
+ widgetState.hasUnreadMessages = false;
256
+ }
257
+
258
+ // Cambiar icono a X
259
+ button.innerHTML = `
260
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
261
+ <line x1="18" y1="6" x2="6" y2="18"></line>
262
+ <line x1="6" y1="6" x2="18" y2="18"></line>
263
+ </svg>
264
+ `;
265
+ } else {
266
+ // Cerrar
267
+ chatWindow.style.transform = 'scale(0.8)';
268
+ chatWindow.style.opacity = '0';
269
+ setTimeout(() => {
270
+ chatWindow.style.display = 'none';
271
+ }, 300);
272
+ button.style.transform = 'rotate(0deg) scale(1)';
273
+
274
+ // Restaurar icono de chat
275
+ button.innerHTML = `
276
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
277
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
278
+ </svg>
279
+ `;
280
+ }
281
+ }
282
+
283
+ // Escuchar mensajes del iFrame (para notificaciones)
284
+ function setupMessageListener() {
285
+ window.addEventListener('message', (event) => {
286
+ // Validar origen (en producción debe ser estricto)
287
+ if (event.origin !== CONFIG.BASE_URL && !CONFIG.BASE_URL.includes('localhost')) {
288
+ return;
289
+ }
290
+
291
+ const data = event.data;
292
+
293
+ // Evento: Nuevo mensaje recibido
294
+ if (data.type === 'NEW_MESSAGE' && !widgetState.isOpen) {
295
+ const badge = document.getElementById('ticktur-widget-badge');
296
+ if (badge) {
297
+ badge.style.display = 'flex';
298
+ widgetState.hasUnreadMessages = true;
299
+ }
300
+ }
301
+
302
+ // Evento: Mensaje enviado
303
+ if (data.type === 'MESSAGE_SENT') {
304
+ showNotification('Mensaje enviado correctamente');
305
+ }
306
+
307
+ // Evento: Chat cerrado/resuelto
308
+ if (data.type === 'CHAT_ENDED') {
309
+ console.log('[Ticktur Widget] Conversación finalizada');
310
+ }
311
+
312
+ // Evento: Solicitud de cerrar widget
313
+ if (data.type === 'CLOSE_WIDGET') {
314
+ if (widgetState.isOpen) {
315
+ toggleWidget();
316
+ }
317
+ }
318
+ });
319
+ }
320
+
321
+ // Manejar resize de la ventana
322
+ function handleResize() {
323
+ const chatWindow = document.getElementById('ticktur-widget-window');
324
+ if (!chatWindow) return;
325
+
326
+ if (window.innerWidth <= 768) {
327
+ chatWindow.style.cssText = `
328
+ position: fixed;
329
+ top: 0;
330
+ left: 0;
331
+ right: 0;
332
+ bottom: 0;
333
+ width: 100%;
334
+ height: 100%;
335
+ border-radius: 0;
336
+ z-index: ${CONFIG.Z_INDEX};
337
+ ${widgetState.isOpen ? 'display: flex;' : 'display: none;'}
338
+ `;
339
+ } else {
340
+ chatWindow.style.cssText = `
341
+ position: fixed;
342
+ bottom: 100px;
343
+ right: 20px;
344
+ width: ${CONFIG.WIDGET_WIDTH};
345
+ height: ${CONFIG.WIDGET_HEIGHT};
346
+ background: white;
347
+ border-radius: 12px;
348
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
349
+ overflow: hidden;
350
+ z-index: ${CONFIG.Z_INDEX};
351
+ ${widgetState.isOpen ? 'display: flex;' : 'display: none;'}
352
+ `;
353
+ }
354
+ }
355
+
356
+ // WebSocket: Cargar dependencias y conectar
357
+ function initializeWebSocket() {
358
+ // Reverb usa Pusher JS como cliente
359
+ if (!window.Pusher) {
360
+ const pusherScript = document.createElement('script');
361
+ pusherScript.src = 'https://js.pusher.com/8.2.0/pusher.min.js';
362
+ pusherScript.onload = () => {
363
+ loadLaravelEcho();
364
+ };
365
+ pusherScript.onerror = () => {
366
+ console.warn('[Ticktur Widget] Error al cargar Pusher (cliente de Reverb)');
367
+ };
368
+ document.head.appendChild(pusherScript);
369
+ } else {
370
+ loadLaravelEcho();
371
+ }
372
+ }
373
+
374
+ // Cargar Laravel Echo
375
+ function loadLaravelEcho() {
376
+ if (!window.Echo) {
377
+ const echoScript = document.createElement('script');
378
+ echoScript.src = 'https://cdn.jsdelivr.net/npm/laravel-echo@1.15.3/dist/echo.iife.js';
379
+ echoScript.onload = () => {
380
+ setupEcho();
381
+ };
382
+ echoScript.onerror = () => {
383
+ console.error('[Ticktur Widget] Error al cargar Laravel Echo');
384
+ };
385
+ document.head.appendChild(echoScript);
386
+ } else {
387
+ setupEcho();
388
+ }
389
+ }
390
+
391
+ // Configurar Echo con Reverb (usa pusher como broadcaster)
392
+ function setupEcho() {
393
+ try {
394
+ const echo = new Echo({
395
+ broadcaster: 'pusher',
396
+ key: 'juunjgece3udni3rfg4j',
397
+ wsHost: '127.0.0.1',
398
+ wsPort: 8080,
399
+ wssPort: 8080,
400
+ forceTLS: false,
401
+ enabledTransports: ['ws', 'wss'],
402
+ disableStats: true,
403
+ cluster: '',
404
+ encrypted: false
405
+ });
406
+
407
+ console.log('[Ticktur Widget] WebSocket conectado');
408
+
409
+ // Suscribirse al canal público del widget
410
+ if (widgetData && widgetData.hash) {
411
+ try {
412
+ echo.channel(`widget.${widgetData.hash}`)
413
+ .listen('WidgetStatusChanged', (data) => {
414
+ console.log('[Ticktur Widget] Estado actualizado:', data);
415
+ if (!data.is_active) {
416
+ showNotification('El chat ha sido desactivado');
417
+ // Opcional: cerrar el widget
418
+ }
419
+ })
420
+ .listen('WidgetMessageReceived', (data) => {
421
+ console.log('[Ticktur Widget] Nuevo mensaje recibido:', data);
422
+ showNotification('Nuevo mensaje');
423
+ });
424
+
425
+ console.log('[Ticktur Widget] Suscrito al canal: widget.' + widgetData.hash);
426
+ } catch (channelError) {
427
+ console.warn('[Ticktur Widget] No se pudo suscribir al canal:', channelError);
428
+ }
429
+ }
430
+
431
+ // Guardar instancia para limpieza futura
432
+ window.tickturEcho = echo;
433
+ } catch (error) {
434
+ console.warn('[Ticktur Widget] WebSocket no disponible:', error.message);
435
+ console.log('[Ticktur Widget] El widget funcionará sin actualizaciones en tiempo real');
436
+ }
437
+ }
438
+
439
+ // Mostrar notificación visual
440
+ function showNotification(message) {
441
+ const badge = document.getElementById('ticktur-widget-badge');
442
+ if (badge && !widgetState.isOpen) {
443
+ badge.style.display = 'flex';
444
+ badge.textContent = '!';
445
+ widgetState.hasUnreadMessages = true;
446
+ }
447
+ console.log(`[Ticktur Widget] ${message}`);
448
+ }
449
+ // Actualizar información del widget en el HTML de la página
450
+ function updateWidgetInfo() {
451
+ if (!widgetData) return;
452
+
453
+ // Actualizar event ID
454
+ const eventIdElement = document.getElementById('ticktur-event-id');
455
+ if (eventIdElement) {
456
+ eventIdElement.textContent = widgetData.event_id || eventId;
457
+ }
458
+
459
+ // Actualizar estado del widget
460
+ const statusElements = document.querySelectorAll('[id^="ticktur-widget-status"]');
461
+ statusElements.forEach((element) => {
462
+ if (widgetData.is_active) {
463
+ element.textContent = 'Widget Activo';
464
+ element.style.color = '#10b981';
465
+ } else {
466
+ element.textContent = 'Widget Inactivo';
467
+ element.style.color = '#ef4444';
468
+ }
469
+ });
470
+
471
+ // Actualizar indicador visual de estado
472
+ const statusIndicators = document.querySelectorAll('.ticktur-status-indicator');
473
+ statusIndicators.forEach((indicator) => {
474
+ indicator.textContent = widgetData.is_active ? '✓' : '✕';
475
+ indicator.style.color = widgetData.is_active ? '#10b981' : '#ef4444';
476
+ });
477
+
478
+ console.log('[Ticktur Widget] Información actualizada en la página');
479
+ }
480
+ // Inicializar widget
481
+ async function initWidget() {
482
+ // Verificar que el DOM esté listo
483
+ if (document.readyState === 'loading') {
484
+ document.addEventListener('DOMContentLoaded', initWidget);
485
+ return;
486
+ }
487
+
488
+ console.log('[Ticktur Widget] DOM listo, validando widget...');
489
+
490
+ // Validar widget antes de continuar
491
+ const isValid = await validateWidget();
492
+
493
+ if (!isValid) {
494
+ console.error('[Ticktur Widget] Widget no autorizado. Verifica:');
495
+ console.error(' 1. Que el evento exista');
496
+ console.error(' 2. Que el hash sea correcto');
497
+ console.error(' 3. Que el widget esté activo (is_active = true)');
498
+ return;
499
+ }
500
+
501
+ console.log('[Ticktur Widget] Montando componentes...');
502
+
503
+ // Actualizar información en el HTML
504
+ updateWidgetInfo();
505
+
506
+ // Crear elementos
507
+ const container = createWidgetContainer();
508
+ const button = createFloatingButton();
509
+ const chatWindow = createChatWindow();
510
+
511
+ // Montar en el DOM
512
+ container.appendChild(button);
513
+ document.body.appendChild(container);
514
+ document.body.appendChild(chatWindow);
515
+
516
+ // Setup listeners
517
+ setupMessageListener();
518
+ window.addEventListener('resize', handleResize);
519
+
520
+ // Inicializar WebSocket
521
+ initializeWebSocket();
522
+
523
+ console.log('[Ticktur Widget] Widget inicializado correctamente');
524
+ }
525
+
526
+ // Limpieza al cerrar/recargar
527
+ window.addEventListener('beforeunload', () => {
528
+ if (window.tickturEcho) {
529
+ window.tickturEcho.disconnect();
530
+ }
531
+ });
532
+
533
+ // Auto-inicializar
534
+ initWidget();
535
+ })();
package/src/index.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Ticktur Chat Widget - Entry Point
3
+ * Exporta función para inicialización programática
4
+ */
5
+
6
+ class TickturWidget {
7
+ constructor(config = {}) {
8
+ this.config = {
9
+ eventId: config.eventId || null,
10
+ widgetHash: config.widgetHash || null,
11
+ baseUrl: config.baseUrl || 'http://localhost:5173',
12
+ buttonColor: config.buttonColor || '#10b981',
13
+ buttonHoverColor: config.buttonHoverColor || '#059669',
14
+ widgetWidth: config.widgetWidth || '400px',
15
+ widgetHeight: config.widgetHeight || '600px',
16
+ zIndex: config.zIndex || '9999',
17
+ ...config
18
+ };
19
+
20
+ this.widgetState = {
21
+ isOpen: false,
22
+ isMinimized: false,
23
+ hasUnreadMessages: false
24
+ };
25
+
26
+ this.elements = {};
27
+ }
28
+
29
+ /**
30
+ * Inicializa el widget
31
+ */
32
+ async init() {
33
+ if (!this.config.eventId || !this.config.widgetHash) {
34
+ throw new Error('[Ticktur Widget] eventId y widgetHash son obligatorios');
35
+ }
36
+
37
+ console.log('[Ticktur Widget] Inicializando...', this.config);
38
+
39
+ // Validar widget
40
+ const isValid = await this.validateWidget();
41
+ if (!isValid) {
42
+ throw new Error('[Ticktur Widget] Autorización denegada');
43
+ }
44
+
45
+ // Crear elementos del widget
46
+ this.createButton();
47
+ this.createWidgetContainer();
48
+ this.attachEventListeners();
49
+
50
+ console.log('[Ticktur Widget] Inicializado correctamente');
51
+ }
52
+
53
+ /**
54
+ * Valida el widget contra la API
55
+ */
56
+ async validateWidget() {
57
+ try {
58
+ const apiUrl = this.config.baseUrl.replace(':5173', ':8000');
59
+ const response = await fetch(`${apiUrl}/api/widgets/validate/${this.config.widgetHash}`);
60
+
61
+ if (!response.ok) {
62
+ console.error('[Ticktur Widget] Autorización denegada. Status:', response.status);
63
+ return false;
64
+ }
65
+
66
+ const data = await response.json();
67
+
68
+ if (!data.success) {
69
+ console.error('[Ticktur Widget] Widget no autorizado');
70
+ return false;
71
+ }
72
+
73
+ this.widgetData = data.data;
74
+ console.log('[Ticktur Widget] Widget autorizado:', this.widgetData.widget_name);
75
+ return true;
76
+ } catch (error) {
77
+ console.error('[Ticktur Widget] Error al validar:', error);
78
+ return false;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Crea el botón flotante
84
+ */
85
+ createButton() {
86
+ const button = document.createElement('button');
87
+ button.id = 'ticktur-chat-button';
88
+ button.innerHTML = `
89
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
90
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
91
+ </svg>
92
+ `;
93
+
94
+ button.style.cssText = `
95
+ position: fixed;
96
+ bottom: 24px;
97
+ right: 24px;
98
+ width: 56px;
99
+ height: 56px;
100
+ border-radius: 50%;
101
+ background-color: ${this.config.buttonColor};
102
+ color: white;
103
+ border: none;
104
+ cursor: pointer;
105
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
106
+ z-index: ${this.config.zIndex};
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ transition: all 0.3s ease;
111
+ `;
112
+
113
+ button.onmouseover = () => {
114
+ button.style.backgroundColor = this.config.buttonHoverColor;
115
+ button.style.transform = 'scale(1.1)';
116
+ };
117
+
118
+ button.onmouseout = () => {
119
+ button.style.backgroundColor = this.config.buttonColor;
120
+ button.style.transform = 'scale(1)';
121
+ };
122
+
123
+ document.body.appendChild(button);
124
+ this.elements.button = button;
125
+ }
126
+
127
+ /**
128
+ * Crea el contenedor del widget
129
+ */
130
+ createWidgetContainer() {
131
+ const container = document.createElement('div');
132
+ container.id = 'ticktur-widget-container';
133
+ container.style.cssText = `
134
+ position: fixed;
135
+ bottom: 96px;
136
+ right: 24px;
137
+ width: ${this.config.widgetWidth};
138
+ height: ${this.config.widgetHeight};
139
+ background: white;
140
+ border-radius: 12px;
141
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
142
+ z-index: ${this.config.zIndex};
143
+ display: none;
144
+ overflow: hidden;
145
+ `;
146
+
147
+ const iframe = document.createElement('iframe');
148
+ iframe.src = `${this.config.baseUrl}/?eventId=${this.config.eventId}&hash=${this.config.widgetHash}`;
149
+ iframe.style.cssText = `
150
+ width: 100%;
151
+ height: 100%;
152
+ border: none;
153
+ `;
154
+
155
+ container.appendChild(iframe);
156
+ document.body.appendChild(container);
157
+ this.elements.container = container;
158
+ this.elements.iframe = iframe;
159
+ }
160
+
161
+ /**
162
+ * Adjunta event listeners
163
+ */
164
+ attachEventListeners() {
165
+ this.elements.button.addEventListener('click', () => {
166
+ this.toggle();
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Alterna visibilidad del widget
172
+ */
173
+ toggle() {
174
+ this.widgetState.isOpen = !this.widgetState.isOpen;
175
+ this.elements.container.style.display = this.widgetState.isOpen ? 'block' : 'none';
176
+ }
177
+
178
+ /**
179
+ * Abre el widget
180
+ */
181
+ open() {
182
+ this.widgetState.isOpen = true;
183
+ this.elements.container.style.display = 'block';
184
+ }
185
+
186
+ /**
187
+ * Cierra el widget
188
+ */
189
+ close() {
190
+ this.widgetState.isOpen = false;
191
+ this.elements.container.style.display = 'none';
192
+ }
193
+
194
+ /**
195
+ * Destruye el widget
196
+ */
197
+ destroy() {
198
+ if (this.elements.button) {
199
+ this.elements.button.remove();
200
+ }
201
+ if (this.elements.container) {
202
+ this.elements.container.remove();
203
+ }
204
+ this.elements = {};
205
+ this.widgetState = { isOpen: false, isMinimized: false, hasUnreadMessages: false };
206
+ }
207
+ }
208
+
209
+ // Exportar para uso como módulo
210
+ export default TickturWidget;
211
+
212
+ // También exportar función helper
213
+ export function createWidget(config) {
214
+ const widget = new TickturWidget(config);
215
+ widget.init();
216
+ return widget;
217
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Configuración del widget de Ticktur
3
+ */
4
+ export interface TickturWidgetConfig {
5
+ /** ID del evento (obligatorio) */
6
+ eventId: number | string;
7
+ /** Hash MD5 de autorización (obligatorio) */
8
+ widgetHash: string;
9
+ /** URL base de la API (default: http://localhost:5173) */
10
+ baseUrl?: string;
11
+ /** Color del botón (default: #10b981) */
12
+ buttonColor?: string;
13
+ /** Color del botón al hover (default: #059669) */
14
+ buttonHoverColor?: string;
15
+ /** Ancho del widget (default: 400px) */
16
+ widgetWidth?: string;
17
+ /** Alto del widget (default: 600px) */
18
+ widgetHeight?: string;
19
+ /** Z-index del widget (default: 9999) */
20
+ zIndex?: string;
21
+ }
22
+
23
+ /**
24
+ * Estado del widget
25
+ */
26
+ export interface WidgetState {
27
+ isOpen: boolean;
28
+ isMinimized: boolean;
29
+ hasUnreadMessages: boolean;
30
+ }
31
+
32
+ /**
33
+ * Datos del widget validado
34
+ */
35
+ export interface WidgetData {
36
+ widget_name: string;
37
+ event_id: number;
38
+ is_active: boolean;
39
+ }
40
+
41
+ /**
42
+ * Clase principal del widget de Ticktur
43
+ */
44
+ export default class TickturWidget {
45
+ config: TickturWidgetConfig;
46
+ widgetState: WidgetState;
47
+ widgetData?: WidgetData;
48
+ elements: {
49
+ button?: HTMLButtonElement;
50
+ container?: HTMLDivElement;
51
+ iframe?: HTMLIFrameElement;
52
+ };
53
+
54
+ constructor(config: TickturWidgetConfig);
55
+
56
+ /**
57
+ * Inicializa el widget
58
+ */
59
+ init(): Promise<void>;
60
+
61
+ /**
62
+ * Valida el widget contra la API
63
+ */
64
+ validateWidget(): Promise<boolean>;
65
+
66
+ /**
67
+ * Abre el widget
68
+ */
69
+ open(): void;
70
+
71
+ /**
72
+ * Cierra el widget
73
+ */
74
+ close(): void;
75
+
76
+ /**
77
+ * Alterna la visibilidad del widget
78
+ */
79
+ toggle(): void;
80
+
81
+ /**
82
+ * Destruye el widget y limpia el DOM
83
+ */
84
+ destroy(): void;
85
+ }
86
+
87
+ /**
88
+ * Función helper para crear e inicializar el widget
89
+ */
90
+ export function createWidget(config: TickturWidgetConfig): TickturWidget;