@uxbertlabs/reportly 1.0.17 → 1.0.19

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/dist/reportly.js CHANGED
@@ -185,11 +185,11 @@ var Reportly = (function () {
185
185
  }
186
186
  create() {
187
187
  // Create overlay
188
- this.overlay = document.createElement('div');
189
- this.overlay.className = 'uxbert-overlay';
188
+ this.overlay = document.createElement("div");
189
+ this.overlay.className = "uxbert-overlay";
190
190
  // Create modal
191
- this.modal = document.createElement('div');
192
- this.modal.className = 'uxbert-modal';
191
+ this.modal = document.createElement("div");
192
+ this.modal.className = "uxbert-modal";
193
193
  // Modal HTML
194
194
  this.modal.innerHTML = `
195
195
  <div class="uxbert-modal-header">
@@ -234,12 +234,13 @@ var Reportly = (function () {
234
234
 
235
235
  <div class="uxbert-form-group">
236
236
  <label class="uxbert-form-label" for="uxbert-priority">Priority</label>
237
- <select id="uxbert-priority" class="uxbert-form-select">
238
- <option value="Low">Low</option>
239
- <option value="Medium" selected>Medium</option>
240
- <option value="High">High</option>
241
- <option value="Critical">Critical</option>
242
- </select>
237
+ <select id="uxbert-priority" class="uxbert-form-select">
238
+ <option value="5" selected>Lowest</option>
239
+ <option value="4">Low</option>
240
+ <option value="3" selected>Medium</option>
241
+ <option value="2">High</option>
242
+ <option value="1">Highest</option>
243
+ </select>
243
244
  </div>
244
245
 
245
246
  <div class="uxbert-capture-action" id="uxbert-capture-action">
@@ -264,6 +265,9 @@ var Reportly = (function () {
264
265
  <button type="button" class="uxbert-btn uxbert-btn-secondary" id="uxbert-cancel-btn">
265
266
  Cancel
266
267
  </button>
268
+ <button type="button" class="uxbert-btn uxbert-btn-success" id="uxbert-n8n-btn" style="display: none;">
269
+ 🚀 Send to n8n
270
+ </button>
267
271
  <button type="submit" class="uxbert-btn uxbert-btn-primary" id="uxbert-submit-btn">
268
272
  📥 Download JSON
269
273
  </button>
@@ -280,72 +284,103 @@ var Reportly = (function () {
280
284
  if (!this.modal)
281
285
  return;
282
286
  // Close button
283
- const closeBtn = this.modal.querySelector('.uxbert-modal-close');
284
- closeBtn.addEventListener('click', () => this.close());
287
+ const closeBtn = this.modal.querySelector(".uxbert-modal-close");
288
+ closeBtn.addEventListener("click", () => this.close());
285
289
  // Cancel button
286
- const cancelBtn = this.modal.querySelector('#uxbert-cancel-btn');
287
- cancelBtn.addEventListener('click', () => this.close());
290
+ const cancelBtn = this.modal.querySelector("#uxbert-cancel-btn");
291
+ cancelBtn.addEventListener("click", () => this.close());
288
292
  // Overlay click to close
289
- this.overlay?.addEventListener('click', (e) => {
293
+ this.overlay?.addEventListener("click", (e) => {
290
294
  if (e.target === this.overlay) {
291
295
  this.close();
292
296
  }
293
297
  });
294
298
  // Form submit
295
- const form = this.modal.querySelector('#uxbert-issue-form');
296
- form.addEventListener('submit', (e) => {
299
+ const form = this.modal.querySelector("#uxbert-issue-form");
300
+ form.addEventListener("submit", (e) => {
297
301
  e.preventDefault();
298
302
  this.handleSubmit();
299
303
  });
300
304
  // Capture button
301
- const captureBtn = this.modal.querySelector('#uxbert-capture-btn');
302
- captureBtn.addEventListener('click', () => {
305
+ const captureBtn = this.modal.querySelector("#uxbert-capture-btn");
306
+ captureBtn.addEventListener("click", () => {
303
307
  if (this.callbacks.onCapture) {
304
308
  this.callbacks.onCapture();
305
309
  }
306
310
  });
307
311
  // Annotate button
308
- const annotateBtn = this.modal.querySelector('#uxbert-annotate-btn');
309
- annotateBtn.addEventListener('click', () => {
312
+ const annotateBtn = this.modal.querySelector("#uxbert-annotate-btn");
313
+ annotateBtn.addEventListener("click", () => {
310
314
  if (this.callbacks.onAnnotate) {
311
315
  this.callbacks.onAnnotate();
312
316
  }
313
317
  });
314
318
  // Retake button
315
- const retakeBtn = this.modal.querySelector('#uxbert-retake-btn');
316
- retakeBtn.addEventListener('click', () => {
319
+ const retakeBtn = this.modal.querySelector("#uxbert-retake-btn");
320
+ retakeBtn.addEventListener("click", () => {
317
321
  if (this.callbacks.onRetake) {
318
322
  this.callbacks.onRetake();
319
323
  }
320
324
  });
325
+ // n8n button
326
+ const n8nBtn = this.modal.querySelector("#uxbert-n8n-btn");
327
+ n8nBtn.addEventListener("click", () => {
328
+ this.handleN8nSubmit();
329
+ });
330
+ }
331
+ handleN8nSubmit() {
332
+ if (!this.modal)
333
+ return;
334
+ const titleInput = this.modal.querySelector("#uxbert-title");
335
+ const descriptionInput = this.modal.querySelector("#uxbert-description");
336
+ const prioritySelect = this.modal.querySelector("#uxbert-priority");
337
+ const title = titleInput.value;
338
+ const description = descriptionInput.value;
339
+ const priority = prioritySelect.value;
340
+ if (!title.trim()) {
341
+ alert("Please enter an issue title");
342
+ return;
343
+ }
344
+ const issueData = {
345
+ title: title.trim(),
346
+ description: description.trim(),
347
+ priority,
348
+ };
349
+ if (this.callbacks.onSendToN8n) {
350
+ this.callbacks.onSendToN8n(issueData);
351
+ }
321
352
  }
322
353
  handleSubmit() {
323
354
  if (!this.modal)
324
355
  return;
325
- const titleInput = this.modal.querySelector('#uxbert-title');
326
- const descriptionInput = this.modal.querySelector('#uxbert-description');
327
- const prioritySelect = this.modal.querySelector('#uxbert-priority');
356
+ const titleInput = this.modal.querySelector("#uxbert-title");
357
+ const descriptionInput = this.modal.querySelector("#uxbert-description");
358
+ const prioritySelect = this.modal.querySelector("#uxbert-priority");
328
359
  const title = titleInput.value;
329
360
  const description = descriptionInput.value;
330
361
  const priority = prioritySelect.value;
331
362
  if (!title.trim()) {
332
- alert('Please enter an issue title');
363
+ alert("Please enter an issue title");
333
364
  return;
334
365
  }
335
366
  const issueData = {
336
367
  title: title.trim(),
337
368
  description: description.trim(),
338
- priority
369
+ priority,
339
370
  };
340
371
  if (this.callbacks.onSubmit) {
341
372
  this.callbacks.onSubmit(issueData);
342
373
  }
343
374
  }
344
375
  open() {
345
- this.overlay?.classList.add('active');
376
+ this.overlay?.classList.add("active");
377
+ // Prevent body scroll when modal is open
378
+ document.body.style.overflow = "hidden";
346
379
  }
347
380
  close() {
348
- this.overlay?.classList.remove('active');
381
+ this.overlay?.classList.remove("active");
382
+ // Restore body scroll when modal is closed
383
+ document.body.style.overflow = "";
349
384
  this.reset();
350
385
  if (this.callbacks.onClose) {
351
386
  this.callbacks.onClose();
@@ -354,39 +389,49 @@ var Reportly = (function () {
354
389
  setScreenshot(screenshot) {
355
390
  if (!this.modal)
356
391
  return;
357
- const container = this.modal.querySelector('#uxbert-screenshot-container');
358
- const img = this.modal.querySelector('#uxbert-screenshot-img');
359
- const captureAction = this.modal.querySelector('#uxbert-capture-action');
360
- const submitBtn = this.modal.querySelector('#uxbert-submit-btn');
392
+ const container = this.modal.querySelector("#uxbert-screenshot-container");
393
+ const img = this.modal.querySelector("#uxbert-screenshot-img");
394
+ const captureAction = this.modal.querySelector("#uxbert-capture-action");
395
+ const submitBtn = this.modal.querySelector("#uxbert-submit-btn");
361
396
  if (screenshot) {
362
397
  img.src = screenshot;
363
- container.style.display = 'block';
364
- captureAction.style.display = 'none';
398
+ container.style.display = "block";
399
+ captureAction.style.display = "none";
365
400
  submitBtn.disabled = false;
366
401
  }
367
402
  else {
368
- container.style.display = 'none';
369
- captureAction.style.display = 'block';
403
+ container.style.display = "none";
404
+ captureAction.style.display = "block";
370
405
  submitBtn.disabled = true;
371
406
  }
372
407
  }
373
408
  getCaptureMode() {
374
409
  if (!this.modal)
375
- return 'viewport';
376
- const viewportRadio = this.modal.querySelector('#uxbert-capture-viewport');
377
- return viewportRadio.checked ? 'viewport' : 'fullpage';
410
+ return "viewport";
411
+ const viewportRadio = this.modal.querySelector("#uxbert-capture-viewport");
412
+ return viewportRadio.checked ? "viewport" : "fullpage";
413
+ }
414
+ setN8nButtonVisible(visible) {
415
+ if (!this.modal)
416
+ return;
417
+ const n8nBtn = this.modal.querySelector("#uxbert-n8n-btn");
418
+ if (n8nBtn) {
419
+ n8nBtn.style.display = visible ? "inline-block" : "none";
420
+ }
378
421
  }
379
422
  reset() {
380
423
  if (!this.modal)
381
424
  return;
382
- this.modal.querySelector('#uxbert-title').value = '';
383
- this.modal.querySelector('#uxbert-description').value = '';
384
- this.modal.querySelector('#uxbert-priority').value = 'Medium';
425
+ this.modal.querySelector("#uxbert-title").value = "";
426
+ this.modal.querySelector("#uxbert-description").value = "";
427
+ this.modal.querySelector("#uxbert-priority").value = "Medium";
385
428
  this.setScreenshot(null);
386
429
  }
387
430
  destroy() {
388
431
  if (this.overlay && this.overlay.parentNode) {
389
432
  this.overlay.parentNode.removeChild(this.overlay);
433
+ // Restore body scroll when destroying modal
434
+ document.body.style.overflow = "";
390
435
  this.overlay = null;
391
436
  this.modal = null;
392
437
  }
@@ -10162,9 +10207,223 @@ var Reportly = (function () {
10162
10207
  }
10163
10208
  }
10164
10209
 
10210
+ class N8nIntegration {
10211
+ constructor(config) {
10212
+ this.config = config || null;
10213
+ }
10214
+ /**
10215
+ * Configure n8n integration
10216
+ */
10217
+ configure(config) {
10218
+ this.config = config;
10219
+ }
10220
+ /**
10221
+ * Check if n8n integration is configured and enabled
10222
+ */
10223
+ isEnabled() {
10224
+ return !!(this.config && this.config.enabled && this.config.webhookUrl);
10225
+ }
10226
+ /**
10227
+ * Send issue data to n8n webhook
10228
+ */
10229
+ async sendToN8n(issueData) {
10230
+ if (!this.config || !this.config.webhookUrl) {
10231
+ return {
10232
+ success: false,
10233
+ message: "n8n webhook URL is not configured",
10234
+ error: "MISSING_CONFIG",
10235
+ };
10236
+ }
10237
+ if (!this.config.enabled) {
10238
+ return {
10239
+ success: false,
10240
+ message: "n8n integration is disabled",
10241
+ error: "DISABLED",
10242
+ };
10243
+ }
10244
+ try {
10245
+ // Prepare FormData with binary screenshot
10246
+ const formData = await this.prepareFormData(issueData);
10247
+ // Setup request headers (don't set Content-Type - browser will set it with boundary for FormData)
10248
+ const headers = {
10249
+ ...this.config.headers,
10250
+ };
10251
+ // Setup timeout
10252
+ const timeout = this.config.timeout || 30000; // 30 seconds default
10253
+ const controller = new AbortController();
10254
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
10255
+ // Send request
10256
+ const response = await fetch(this.config.webhookUrl, {
10257
+ method: "POST",
10258
+ headers,
10259
+ body: formData,
10260
+ signal: controller.signal,
10261
+ });
10262
+ clearTimeout(timeoutId);
10263
+ // Check response
10264
+ if (!response.ok) {
10265
+ const errorText = await response.text().catch(() => "Unknown error");
10266
+ return {
10267
+ success: false,
10268
+ message: `n8n webhook request failed: ${response.status} ${response.statusText}`,
10269
+ error: errorText,
10270
+ };
10271
+ }
10272
+ // Parse response
10273
+ let responseData;
10274
+ try {
10275
+ responseData = await response.json();
10276
+ }
10277
+ catch (e) {
10278
+ // Some webhooks don't return JSON
10279
+ responseData = { message: "Success" };
10280
+ }
10281
+ return {
10282
+ success: true,
10283
+ message: "Issue sent to n8n successfully",
10284
+ workflowId: responseData.workflowId || responseData.executionId,
10285
+ };
10286
+ }
10287
+ catch (error) {
10288
+ if (error instanceof Error) {
10289
+ if (error.name === "AbortError") {
10290
+ return {
10291
+ success: false,
10292
+ message: "Request to n8n webhook timed out",
10293
+ error: "TIMEOUT",
10294
+ };
10295
+ }
10296
+ return {
10297
+ success: false,
10298
+ message: `Failed to send issue to n8n: ${error.message}`,
10299
+ error: error.message,
10300
+ };
10301
+ }
10302
+ return {
10303
+ success: false,
10304
+ message: "Unknown error occurred while sending to n8n",
10305
+ error: "UNKNOWN",
10306
+ };
10307
+ }
10308
+ }
10309
+ /**
10310
+ * Convert base64 data URL to Blob
10311
+ */
10312
+ base64ToBlob(base64Data) {
10313
+ // Extract the base64 string and mime type
10314
+ const matches = base64Data.match(/^data:([^;]+);base64,(.+)$/);
10315
+ if (!matches) {
10316
+ throw new Error("Invalid base64 data URL");
10317
+ }
10318
+ const mimeType = matches[1];
10319
+ const base64String = matches[2];
10320
+ // Decode base64
10321
+ const byteCharacters = atob(base64String);
10322
+ const byteNumbers = new Array(byteCharacters.length);
10323
+ for (let i = 0; i < byteCharacters.length; i++) {
10324
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
10325
+ }
10326
+ const byteArray = new Uint8Array(byteNumbers);
10327
+ return new Blob([byteArray], { type: mimeType });
10328
+ }
10329
+ /**
10330
+ * Prepare FormData payload for n8n webhook
10331
+ * This sends the screenshot as a binary file instead of base64 JSON
10332
+ */
10333
+ async prepareFormData(issueData) {
10334
+ const formData = new FormData();
10335
+ // Add issue data as JSON string
10336
+ formData.append("title", issueData.title);
10337
+ formData.append("description", issueData.description);
10338
+ formData.append("priority", issueData.priority);
10339
+ formData.append("createdAt", issueData.createdAt);
10340
+ // Convert base64 screenshot to binary Blob and add as file
10341
+ const screenshotBlob = this.base64ToBlob(issueData.screenshot);
10342
+ const filename = `screenshot-${Date.now()}.png`;
10343
+ formData.append("screenshot", screenshotBlob, filename);
10344
+ // Add device info as individual fields
10345
+ formData.append("browser", issueData.deviceInfo.browser);
10346
+ formData.append("os", issueData.deviceInfo.os);
10347
+ formData.append("screenResolution", issueData.deviceInfo.screenResolution);
10348
+ formData.append("viewport", issueData.deviceInfo.viewport);
10349
+ formData.append("url", issueData.deviceInfo.url);
10350
+ formData.append("userAgent", issueData.deviceInfo.userAgent);
10351
+ formData.append("language", issueData.deviceInfo.language || "");
10352
+ formData.append("platform", issueData.deviceInfo.platform);
10353
+ formData.append("cookiesEnabled", String(issueData.deviceInfo.cookiesEnabled));
10354
+ formData.append("onLine", String(issueData.deviceInfo.onLine));
10355
+ formData.append("timestamp", issueData.deviceInfo.timestamp);
10356
+ // Add metadata
10357
+ formData.append("source", "uxbert-reportly");
10358
+ formData.append("version", "1.0.0");
10359
+ return formData;
10360
+ }
10361
+ /**
10362
+ * Test connection to n8n webhook
10363
+ */
10364
+ async testConnection() {
10365
+ if (!this.config || !this.config.webhookUrl) {
10366
+ return {
10367
+ success: false,
10368
+ message: "n8n webhook URL is not configured",
10369
+ error: "MISSING_CONFIG",
10370
+ };
10371
+ }
10372
+ try {
10373
+ const controller = new AbortController();
10374
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout for test
10375
+ await fetch(this.config.webhookUrl, {
10376
+ method: "HEAD",
10377
+ signal: controller.signal,
10378
+ });
10379
+ clearTimeout(timeoutId);
10380
+ return {
10381
+ success: true,
10382
+ message: "n8n webhook is reachable",
10383
+ };
10384
+ }
10385
+ catch (error) {
10386
+ return {
10387
+ success: false,
10388
+ message: "Failed to reach n8n webhook",
10389
+ error: error instanceof Error ? error.message : "UNKNOWN",
10390
+ };
10391
+ }
10392
+ }
10393
+ /**
10394
+ * Get current configuration
10395
+ */
10396
+ getConfig() {
10397
+ return this.config;
10398
+ }
10399
+ /**
10400
+ * Update webhook URL
10401
+ */
10402
+ setWebhookUrl(url) {
10403
+ if (!this.config) {
10404
+ this.config = {
10405
+ webhookUrl: url,
10406
+ enabled: true,
10407
+ };
10408
+ }
10409
+ else {
10410
+ this.config.webhookUrl = url;
10411
+ }
10412
+ }
10413
+ /**
10414
+ * Enable/disable n8n integration
10415
+ */
10416
+ setEnabled(enabled) {
10417
+ if (this.config) {
10418
+ this.config.enabled = enabled;
10419
+ }
10420
+ }
10421
+ }
10422
+
10165
10423
  class Export {
10166
- constructor() {
10424
+ constructor(n8nConfig) {
10167
10425
  this.savedIssues = this.loadSavedIssues();
10426
+ this.n8nIntegration = new N8nIntegration(n8nConfig);
10168
10427
  }
10169
10428
  exportToJSON(issueData) {
10170
10429
  const json = JSON.stringify(issueData, null, 2);
@@ -10233,6 +10492,54 @@ var Reportly = (function () {
10233
10492
  document.body.removeChild(link);
10234
10493
  URL.revokeObjectURL(url);
10235
10494
  }
10495
+ // n8n Integration Methods
10496
+ /**
10497
+ * Send issue to n8n webhook
10498
+ */
10499
+ async sendToN8n(issueData) {
10500
+ const response = await this.n8nIntegration.sendToN8n(issueData);
10501
+ // Save to localStorage if n8n send was successful
10502
+ if (response.success) {
10503
+ this.saveIssue(issueData);
10504
+ }
10505
+ return response;
10506
+ }
10507
+ /**
10508
+ * Configure n8n integration
10509
+ */
10510
+ configureN8n(config) {
10511
+ this.n8nIntegration.configure(config);
10512
+ }
10513
+ /**
10514
+ * Check if n8n is enabled
10515
+ */
10516
+ isN8nEnabled() {
10517
+ return this.n8nIntegration.isEnabled();
10518
+ }
10519
+ /**
10520
+ * Test n8n connection
10521
+ */
10522
+ async testN8nConnection() {
10523
+ return this.n8nIntegration.testConnection();
10524
+ }
10525
+ /**
10526
+ * Get n8n configuration
10527
+ */
10528
+ getN8nConfig() {
10529
+ return this.n8nIntegration.getConfig();
10530
+ }
10531
+ /**
10532
+ * Enable/disable n8n integration
10533
+ */
10534
+ setN8nEnabled(enabled) {
10535
+ this.n8nIntegration.setEnabled(enabled);
10536
+ }
10537
+ /**
10538
+ * Set n8n webhook URL
10539
+ */
10540
+ setN8nWebhookUrl(url) {
10541
+ this.n8nIntegration.setWebhookUrl(url);
10542
+ }
10236
10543
  }
10237
10544
 
10238
10545
  function styleInject(css, ref) {
@@ -10262,7 +10569,7 @@ var Reportly = (function () {
10262
10569
  }
10263
10570
  }
10264
10571
 
10265
- var css_248z = ":root{--uxbert-primary:#4f46e5;--uxbert-primary-hover:#4338ca;--uxbert-danger:#ef4444;--uxbert-success:#10b981;--uxbert-bg:#fff;--uxbert-text:#1f2937;--uxbert-border:#e5e7eb;--uxbert-shadow:0 10px 25px rgba(0,0,0,.1);--uxbert-z-button:999999;--uxbert-z-modal:1000000;--uxbert-z-canvas:1000001;--uxbert-z-toolbar:1000002}[data-theme=dark]{--uxbert-bg:#1f2937;--uxbert-text:#f9fafb;--uxbert-border:#374151}.uxbert-fab{align-items:center;background:var(--uxbert-primary);border:none;border-radius:50%;box-shadow:var(--uxbert-shadow);color:#fff;cursor:pointer;display:flex;font-size:24px;height:56px;justify-content:center;position:fixed;transition:all .3s ease;width:56px;z-index:var(--uxbert-z-button)}.uxbert-fab:hover{background:var(--uxbert-primary-hover);transform:scale(1.1)}.uxbert-fab.bottom-right{bottom:24px;right:24px}.uxbert-fab.bottom-left{bottom:24px;left:24px}.uxbert-fab.top-right{right:24px;top:24px}.uxbert-fab.top-left{left:24px;top:24px}.uxbert-overlay{align-items:center;animation:fadeIn .3s ease;background:rgba(0,0,0,.5);display:none;height:100%;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:var(--uxbert-z-modal)}.uxbert-overlay.active{display:flex}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.uxbert-modal{animation:slideUp .3s ease;background:var(--uxbert-bg);border-radius:12px;box-shadow:var(--uxbert-shadow);color:var(--uxbert-text);max-height:90vh;max-width:600px;overflow-y:auto;padding:24px;width:90%}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.uxbert-modal-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.uxbert-modal-title{font-size:20px;font-weight:600;margin:0}.uxbert-modal-close{align-items:center;background:none;border:none;border-radius:4px;color:var(--uxbert-text);cursor:pointer;display:flex;font-size:24px;height:32px;justify-content:center;padding:0;width:32px}.uxbert-modal-close:hover{background:var(--uxbert-border)}.uxbert-form-group{margin-bottom:16px}.uxbert-form-label{display:block;font-size:14px;font-weight:500;margin-bottom:8px}.uxbert-capture-mode{display:flex;flex-wrap:wrap;gap:12px}.uxbert-radio-label{align-items:center;border:2px solid var(--uxbert-border);border-radius:8px;cursor:pointer;display:flex;flex:1;gap:8px;min-width:140px;padding:10px 16px;transition:all .2s ease}.uxbert-radio-label:hover{background:rgba(79,70,229,.05);border-color:var(--uxbert-primary)}.uxbert-radio-label input[type=radio]{cursor:pointer;height:18px;margin:0;width:18px}.uxbert-radio-label input[type=radio]:checked{accent-color:var(--uxbert-primary)}.uxbert-radio-label:has(input[type=radio]:checked){background:rgba(79,70,229,.1);border-color:var(--uxbert-primary)}.uxbert-radio-label span{flex:1;font-size:14px;font-weight:500}.uxbert-form-input,.uxbert-form-select,.uxbert-form-textarea{background:var(--uxbert-bg);border:1px solid var(--uxbert-border);border-radius:6px;box-sizing:border-box;color:var(--uxbert-text);font-family:inherit;font-size:14px;padding:10px;width:100%}.uxbert-form-textarea{min-height:100px;resize:vertical}.uxbert-form-input:focus,.uxbert-form-select:focus,.uxbert-form-textarea:focus{border-color:var(--uxbert-primary);outline:none}.uxbert-capture-action{margin:16px 0;text-align:center}.uxbert-capture-action .uxbert-btn{min-width:200px}.uxbert-screenshot-preview{border:1px solid var(--uxbert-border);border-radius:8px;margin:16px 0;overflow:hidden}.uxbert-screenshot-preview img{display:block;width:100%}.uxbert-screenshot-actions{background:var(--uxbert-border);display:flex;gap:8px;padding:12px}.uxbert-btn{align-items:center;border:none;border-radius:6px;cursor:pointer;display:inline-flex;font-size:14px;font-weight:500;gap:8px;padding:10px 20px;transition:all .2s ease}.uxbert-btn-primary{background:var(--uxbert-primary);color:#fff}.uxbert-btn-primary:hover{background:var(--uxbert-primary-hover)}.uxbert-btn-secondary{background:var(--uxbert-border);color:var(--uxbert-text)}.uxbert-btn-secondary:hover{background:#d1d5db}.uxbert-btn-danger{background:var(--uxbert-danger);color:#fff}.uxbert-btn-danger:hover{background:#dc2626}.uxbert-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.uxbert-toolbar-toggle{align-items:center;background:var(--uxbert-primary);border:none;border-radius:50%;bottom:24px;box-shadow:var(--uxbert-shadow);color:#fff;cursor:pointer;display:none;font-size:24px;height:56px;justify-content:center;position:fixed;right:24px;transition:all .3s ease;width:56px;z-index:var(--uxbert-z-toolbar)}.uxbert-toolbar-toggle.active{display:flex}.uxbert-toolbar-toggle.hidden{opacity:0;pointer-events:none;transform:scale(.8)}.uxbert-toolbar-toggle:hover{background:var(--uxbert-primary-hover);transform:scale(1.1)}.uxbert-toolbar{background:var(--uxbert-bg);border-radius:12px;bottom:24px;box-shadow:var(--uxbert-shadow);display:none;flex-direction:column;gap:8px;max-height:80vh;opacity:0;overflow-y:auto;padding:16px;pointer-events:none;position:fixed;right:24px;transform:translateY(20px);transition:all .3s ease;z-index:var(--uxbert-z-toolbar)}.uxbert-toolbar.active{display:flex}.uxbert-toolbar.expanded{opacity:1;pointer-events:all;transform:translateY(0)}.uxbert-toolbar-header{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-bottom:8px}.uxbert-toolbar-title{flex:1;font-size:14px;font-weight:600}.uxbert-toolbar-exit,.uxbert-toolbar-minimize{align-items:center;background:var(--uxbert-border);border:none;border-radius:6px;color:var(--uxbert-text);cursor:pointer;display:flex;font-size:20px;font-weight:700;height:32px;justify-content:center;transition:all .2s ease;width:32px}.uxbert-toolbar-exit{background:var(--uxbert-danger);color:#fff}.uxbert-toolbar-minimize:hover{background:#d1d5db;transform:scale(1.1)}.uxbert-toolbar-exit:hover{background:#dc2626;transform:scale(1.1)}.uxbert-toolbar-tools{display:flex;flex-wrap:wrap;gap:8px}.uxbert-tool-btn{align-items:center;background:var(--uxbert-bg);border:2px solid var(--uxbert-border);border-radius:6px;cursor:pointer;display:flex;font-family:Arial,sans-serif;font-size:18px;font-weight:700;height:40px;justify-content:center;transition:all .2s ease;width:40px}.uxbert-tool-btn.active,.uxbert-tool-btn:hover{background:var(--uxbert-primary);border-color:var(--uxbert-primary);color:#fff}.uxbert-color-picker{display:flex;gap:6px;margin:8px 0}.uxbert-color-option{border:2px solid var(--uxbert-border);border-radius:50%;cursor:pointer;height:32px;transition:transform .2s ease;width:32px}.uxbert-color-option:hover{transform:scale(1.1)}.uxbert-color-option.active{border:3px solid var(--uxbert-text)}.uxbert-canvas-overlay{cursor:crosshair;display:none;left:0;position:absolute;top:0;z-index:var(--uxbert-z-canvas)}.uxbert-canvas-overlay.active{display:block}.uxbert-canvas-overlay.viewport-mode{left:0;position:fixed;top:0}.uxbert-text-input{background:hsla(0,0%,100%,.95);border:2px solid var(--uxbert-primary);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:var(--uxbert-text);font-family:Arial,sans-serif;font-size:16px;font-weight:700;min-width:200px;outline:none;padding:8px 12px;z-index:var(--uxbert-z-toolbar)}.uxbert-text-input:focus{border-color:var(--uxbert-primary-hover);box-shadow:0 4px 16px rgba(79,70,229,.3)}.uxbert-text-input::placeholder{color:#9ca3af;font-weight:400}@media (max-width:768px){.uxbert-modal{max-height:95vh;padding:16px;width:95%}.uxbert-toolbar{bottom:90px;left:16px;max-height:60vh;right:16px;width:auto}.uxbert-toolbar-toggle{bottom:16px;height:56px;right:16px;width:56px}.uxbert-fab{font-size:20px;height:48px;width:48px}.uxbert-color-picker,.uxbert-toolbar-tools{justify-content:center}}";
10572
+ var css_248z = ":root{--uxbert-primary:#4f46e5;--uxbert-primary-hover:#4338ca;--uxbert-danger:#ef4444;--uxbert-success:#10b981;--uxbert-bg:#fff;--uxbert-text:#1f2937;--uxbert-border:#e5e7eb;--uxbert-shadow:0 10px 25px rgba(0,0,0,.1);--uxbert-z-button:999999;--uxbert-z-modal:1000000;--uxbert-z-canvas:1000001;--uxbert-z-toolbar:1000002}[data-theme=dark]{--uxbert-bg:#1f2937;--uxbert-text:#f9fafb;--uxbert-border:#374151}.uxbert-fab{align-items:center;background:var(--uxbert-primary);border:none;border-radius:50%;box-shadow:var(--uxbert-shadow);color:#fff;cursor:pointer;display:flex;font-size:24px;height:56px;justify-content:center;position:fixed;transition:all .3s ease;width:56px;z-index:var(--uxbert-z-button)}.uxbert-fab:hover{background:var(--uxbert-primary-hover);transform:scale(1.1)}.uxbert-fab.bottom-right{bottom:24px;right:24px}.uxbert-fab.bottom-left{bottom:24px;left:24px}.uxbert-fab.top-right{right:24px;top:24px}.uxbert-fab.top-left{left:24px;top:24px}.uxbert-overlay{align-items:center;animation:fadeIn .3s ease;background:rgba(0,0,0,.5);display:none;height:100%;justify-content:center;left:0;overflow:hidden;position:fixed;top:0;width:100%;z-index:var(--uxbert-z-modal)}.uxbert-overlay.active{display:flex}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.uxbert-modal{animation:slideUp .3s ease;background:var(--uxbert-bg);border-radius:12px;box-shadow:var(--uxbert-shadow);color:var(--uxbert-text);max-height:90vh;max-width:600px;overflow-y:auto;padding:24px;width:90%}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.uxbert-modal-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.uxbert-modal-title{font-size:20px;font-weight:600;margin:0}.uxbert-modal-close{align-items:center;background:none;border:none;border-radius:4px;color:var(--uxbert-text);cursor:pointer;display:flex;font-size:24px;height:32px;justify-content:center;padding:0;width:32px}.uxbert-modal-close:hover{background:var(--uxbert-border)}.uxbert-form-group{margin-bottom:16px}.uxbert-form-label{display:block;font-size:14px;font-weight:500;margin-bottom:8px}.uxbert-capture-mode{display:flex;flex-wrap:wrap;gap:12px}.uxbert-radio-label{align-items:center;border:2px solid var(--uxbert-border);border-radius:8px;cursor:pointer;display:flex;flex:1;gap:8px;min-width:140px;padding:10px 16px;transition:all .2s ease}.uxbert-radio-label:hover{background:rgba(79,70,229,.05);border-color:var(--uxbert-primary)}.uxbert-radio-label input[type=radio]{cursor:pointer;height:18px;margin:0;width:18px}.uxbert-radio-label input[type=radio]:checked{accent-color:var(--uxbert-primary)}.uxbert-radio-label:has(input[type=radio]:checked){background:rgba(79,70,229,.1);border-color:var(--uxbert-primary)}.uxbert-radio-label span{flex:1;font-size:14px;font-weight:500}.uxbert-form-input,.uxbert-form-select,.uxbert-form-textarea{background:var(--uxbert-bg);border:1px solid var(--uxbert-border);border-radius:6px;box-sizing:border-box;color:var(--uxbert-text);font-family:inherit;font-size:14px;padding:10px;width:100%}.uxbert-form-textarea{min-height:100px;resize:vertical}.uxbert-form-input:focus,.uxbert-form-select:focus,.uxbert-form-textarea:focus{border-color:var(--uxbert-primary);outline:none}.uxbert-capture-action{margin:16px 0;text-align:center}.uxbert-capture-action .uxbert-btn{min-width:200px}.uxbert-screenshot-preview{border:1px solid var(--uxbert-border);border-radius:8px;margin:16px 0;overflow:hidden}.uxbert-screenshot-preview img{display:block;width:100%}.uxbert-screenshot-actions{background:var(--uxbert-border);display:flex;gap:8px;padding:12px}.uxbert-btn{align-items:center;border:none;border-radius:6px;cursor:pointer;display:inline-flex;font-size:14px;font-weight:500;gap:8px;padding:10px 20px;transition:all .2s ease}.uxbert-btn-primary{background:var(--uxbert-primary);color:#fff}.uxbert-btn-primary:hover{background:var(--uxbert-primary-hover)}.uxbert-btn-secondary{background:var(--uxbert-border);color:var(--uxbert-text)}.uxbert-btn-secondary:hover{background:#d1d5db}.uxbert-btn-success{background:var(--uxbert-success);color:#fff}.uxbert-btn-success:hover{background:#059669}.uxbert-btn-danger{background:var(--uxbert-danger);color:#fff}.uxbert-btn-danger:hover{background:#dc2626}.uxbert-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.uxbert-toolbar-toggle{align-items:center;background:var(--uxbert-primary);border:none;border-radius:50%;bottom:24px;box-shadow:var(--uxbert-shadow);color:#fff;cursor:pointer;display:none;font-size:24px;height:56px;justify-content:center;position:fixed;right:24px;transition:all .3s ease;width:56px;z-index:var(--uxbert-z-toolbar)}.uxbert-toolbar-toggle.active{display:flex}.uxbert-toolbar-toggle.hidden{opacity:0;pointer-events:none;transform:scale(.8)}.uxbert-toolbar-toggle:hover{background:var(--uxbert-primary-hover);transform:scale(1.1)}.uxbert-toolbar{background:var(--uxbert-bg);border-radius:12px;bottom:24px;box-shadow:var(--uxbert-shadow);display:none;flex-direction:column;gap:8px;max-height:80vh;opacity:0;overflow-y:auto;padding:16px;pointer-events:none;position:fixed;right:24px;transform:translateY(20px);transition:all .3s ease;z-index:var(--uxbert-z-toolbar)}.uxbert-toolbar.active{display:flex}.uxbert-toolbar.expanded{opacity:1;pointer-events:all;transform:translateY(0)}.uxbert-toolbar-header{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-bottom:8px}.uxbert-toolbar-title{flex:1;font-size:14px;font-weight:600}.uxbert-toolbar-exit,.uxbert-toolbar-minimize{align-items:center;background:var(--uxbert-border);border:none;border-radius:6px;color:var(--uxbert-text);cursor:pointer;display:flex;font-size:20px;font-weight:700;height:32px;justify-content:center;transition:all .2s ease;width:32px}.uxbert-toolbar-exit{background:var(--uxbert-danger);color:#fff}.uxbert-toolbar-minimize:hover{background:#d1d5db;transform:scale(1.1)}.uxbert-toolbar-exit:hover{background:#dc2626;transform:scale(1.1)}.uxbert-toolbar-tools{display:flex;flex-wrap:wrap;gap:8px}.uxbert-tool-btn{align-items:center;background:var(--uxbert-bg);border:2px solid var(--uxbert-border);border-radius:6px;cursor:pointer;display:flex;font-family:Arial,sans-serif;font-size:18px;font-weight:700;height:40px;justify-content:center;transition:all .2s ease;width:40px}.uxbert-tool-btn.active,.uxbert-tool-btn:hover{background:var(--uxbert-primary);border-color:var(--uxbert-primary);color:#fff}.uxbert-color-picker{display:flex;gap:6px;margin:8px 0}.uxbert-color-option{border:2px solid var(--uxbert-border);border-radius:50%;cursor:pointer;height:32px;transition:transform .2s ease;width:32px}.uxbert-color-option:hover{transform:scale(1.1)}.uxbert-color-option.active{border:3px solid var(--uxbert-text)}.uxbert-canvas-overlay{cursor:crosshair;display:none;left:0;position:absolute;top:0;z-index:var(--uxbert-z-canvas)}.uxbert-canvas-overlay.active{display:block}.uxbert-canvas-overlay.viewport-mode{left:0;position:fixed;top:0}.uxbert-text-input{background:hsla(0,0%,100%,.95);border:2px solid var(--uxbert-primary);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:var(--uxbert-text);font-family:Arial,sans-serif;font-size:16px;font-weight:700;min-width:200px;outline:none;padding:8px 12px;z-index:var(--uxbert-z-toolbar)}.uxbert-text-input:focus{border-color:var(--uxbert-primary-hover);box-shadow:0 4px 16px rgba(79,70,229,.3)}.uxbert-text-input::placeholder{color:#9ca3af;font-weight:400}@media (max-width:768px){.uxbert-modal{max-height:95vh;padding:16px;width:95%}.uxbert-toolbar{bottom:90px;left:16px;max-height:60vh;right:16px;width:auto}.uxbert-toolbar-toggle{bottom:16px;height:56px;right:16px;width:56px}.uxbert-fab{font-size:20px;height:48px;width:48px}.uxbert-color-picker,.uxbert-toolbar-tools{justify-content:center}}";
10266
10573
  styleInject(css_248z);
10267
10574
 
10268
10575
  // Main initialization file
@@ -10294,7 +10601,9 @@ var Reportly = (function () {
10294
10601
  this.state = new State();
10295
10602
  this.deviceInfo = new DeviceInfo();
10296
10603
  this.screenshot = new Screenshot();
10297
- this.export = new Export();
10604
+ // Initialize export with n8n config if provided
10605
+ const n8nConfig = userConfig.integrations?.n8n;
10606
+ this.export = new Export(n8nConfig);
10298
10607
  // Initialize UI components
10299
10608
  this.button = new Button(this.config, {
10300
10609
  onClick: () => this.handleButtonClick(),
@@ -10305,6 +10614,7 @@ var Reportly = (function () {
10305
10614
  onAnnotate: () => this.startAnnotation(),
10306
10615
  onRetake: () => this.retakeScreenshot(),
10307
10616
  onCapture: () => this.captureWithMode(),
10617
+ onSendToN8n: (issueData) => this.handleN8nSubmit(issueData),
10308
10618
  });
10309
10619
  this.toolbar = new Toolbar({
10310
10620
  onToolChange: (tool) => this.annotation?.setTool(tool),
@@ -10320,6 +10630,10 @@ var Reportly = (function () {
10320
10630
  this.modal.create();
10321
10631
  this.toolbar.create();
10322
10632
  this.annotation.createCanvas();
10633
+ // Show/hide n8n button based on configuration
10634
+ if (this.export.isN8nEnabled()) {
10635
+ this.modal.setN8nButtonVisible(true);
10636
+ }
10323
10637
  // Setup keyboard shortcuts
10324
10638
  this.setupKeyboardShortcuts();
10325
10639
  this.isInitialized = true;
@@ -10447,6 +10761,54 @@ var Reportly = (function () {
10447
10761
  alert("Failed to export issue. Please try again.");
10448
10762
  }
10449
10763
  }
10764
+ async handleN8nSubmit(issueData) {
10765
+ try {
10766
+ // Create complete issue package
10767
+ const completeIssue = {
10768
+ ...issueData,
10769
+ screenshot: this.state?.getScreenshot() || "",
10770
+ deviceInfo: this.deviceInfo.get(),
10771
+ createdAt: new Date().toISOString(),
10772
+ };
10773
+ // Show loading state
10774
+ const n8nBtn = document.querySelector('#uxbert-n8n-btn');
10775
+ if (n8nBtn) {
10776
+ n8nBtn.disabled = true;
10777
+ n8nBtn.textContent = '⏳ Sending...';
10778
+ }
10779
+ // Send to n8n
10780
+ const response = await this.export?.sendToN8n(completeIssue);
10781
+ if (response?.success) {
10782
+ // Emit event
10783
+ this.state?.emit("issue:sent-to-n8n", completeIssue);
10784
+ // Reset state
10785
+ this.state?.reset();
10786
+ this.annotation?.clear();
10787
+ this.modal?.close();
10788
+ alert("Issue sent to n8n successfully!");
10789
+ console.log("Issue sent to n8n successfully");
10790
+ }
10791
+ else {
10792
+ alert(`Failed to send to n8n: ${response?.message || 'Unknown error'}`);
10793
+ console.error("Failed to send to n8n:", response?.error);
10794
+ }
10795
+ // Reset button state
10796
+ if (n8nBtn) {
10797
+ n8nBtn.disabled = false;
10798
+ n8nBtn.textContent = '🚀 Send to n8n';
10799
+ }
10800
+ }
10801
+ catch (error) {
10802
+ console.error("Failed to send to n8n:", error);
10803
+ alert("Failed to send to n8n. Please try again.");
10804
+ // Reset button state
10805
+ const n8nBtn = document.querySelector('#uxbert-n8n-btn');
10806
+ if (n8nBtn) {
10807
+ n8nBtn.disabled = false;
10808
+ n8nBtn.textContent = '🚀 Send to n8n';
10809
+ }
10810
+ }
10811
+ }
10450
10812
  handleModalClose() {
10451
10813
  this.annotation?.hide();
10452
10814
  this.toolbar?.hide();
@@ -10475,6 +10837,24 @@ var Reportly = (function () {
10475
10837
  exportAllIssues() {
10476
10838
  this.export?.exportAllIssues();
10477
10839
  }
10840
+ // n8n Integration API methods
10841
+ configureN8n(webhookUrl, enabled = true, options) {
10842
+ this.export?.configureN8n({
10843
+ webhookUrl,
10844
+ enabled,
10845
+ headers: options?.headers,
10846
+ timeout: options?.timeout
10847
+ });
10848
+ // Update button visibility
10849
+ this.modal?.setN8nButtonVisible(enabled);
10850
+ }
10851
+ enableN8n(enabled) {
10852
+ this.export?.setN8nEnabled(enabled);
10853
+ this.modal?.setN8nButtonVisible(enabled);
10854
+ }
10855
+ testN8nConnection() {
10856
+ return this.export?.testN8nConnection() || Promise.resolve({ success: false, message: 'Not initialized' });
10857
+ }
10478
10858
  on(event, callback) {
10479
10859
  this.state?.on(event, callback);
10480
10860
  }