@uxbertlabs/reportly 1.0.16 → 1.0.18

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.
@@ -21,6 +21,7 @@ declare class Reportly {
21
21
  private finishAnnotation;
22
22
  private retakeScreenshot;
23
23
  private handleIssueSubmit;
24
+ private handleN8nSubmit;
24
25
  private handleModalClose;
25
26
  open(): void;
26
27
  close(): void;
@@ -28,6 +29,12 @@ declare class Reportly {
28
29
  getSavedIssues(): SavedIssue[];
29
30
  clearSavedIssues(): void;
30
31
  exportAllIssues(): void;
32
+ configureN8n(webhookUrl: string, enabled?: boolean, options?: {
33
+ headers?: Record<string, string>;
34
+ timeout?: number;
35
+ }): void;
36
+ enableN8n(enabled: boolean): void;
37
+ testN8nConnection(): Promise<any>;
31
38
  on(event: string, callback: EventCallback): void;
32
39
  destroy(): void;
33
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/core/init.ts"],"names":[],"mappings":"AAUA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAA4B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEpG,cAAM,QAAQ;IACZ,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,aAAa,CAAU;;IAe/B,IAAI,CAAC,UAAU,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,IAAI;IAwDpD,OAAO,CAAC,sBAAsB;YAUhB,iBAAiB;YAUjB,eAAe;IAyB7B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;YASR,gBAAgB;YA2BhB,gBAAgB;IAwB9B,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,gBAAgB;IAQxB,IAAI,IAAI,IAAI;IAIZ,KAAK,IAAI,IAAI;IAKb,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAMtD,cAAc,IAAI,UAAU,EAAE;IAI9B,gBAAgB,IAAI,IAAI;IAIxB,eAAe,IAAI,IAAI;IAIvB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIhD,OAAO,IAAI,IAAI;CAOhB;AAGD,QAAA,MAAM,QAAQ,UAAiB,CAAC;AAGhC,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/core/init.ts"],"names":[],"mappings":"AAUA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAA4B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEpG,cAAM,QAAQ;IACZ,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,aAAa,CAAU;;IAe/B,IAAI,CAAC,UAAU,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,IAAI;IAiEpD,OAAO,CAAC,sBAAsB;YAUhB,iBAAiB;YAUjB,eAAe;IAyB7B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;YASR,gBAAgB;YA2BhB,gBAAgB;IAwB9B,OAAO,CAAC,iBAAiB;YA4BX,eAAe;IAsD7B,OAAO,CAAC,gBAAgB;IAQxB,IAAI,IAAI,IAAI;IAIZ,KAAK,IAAI,IAAI;IAKb,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAMtD,cAAc,IAAI,UAAU,EAAE;IAI9B,gBAAgB,IAAI,IAAI;IAIxB,eAAe,IAAI,IAAI;IAKvB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,OAAc,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAYjI,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKjC,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC;IAIjC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIhD,OAAO,IAAI,IAAI;CAOhB;AAGD,QAAA,MAAM,QAAQ,UAAiB,CAAC;AAGhC,eAAe,QAAQ,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import type { CompleteIssue, SavedIssue } from '../types';
2
+ import { type N8nConfig, type N8nResponse } from './n8n-integration';
2
3
  declare class Export {
3
4
  private savedIssues;
4
- constructor();
5
+ private n8nIntegration;
6
+ constructor(n8nConfig?: N8nConfig);
5
7
  exportToJSON(issueData: CompleteIssue): string;
6
8
  private saveIssue;
7
9
  private loadSavedIssues;
@@ -9,6 +11,34 @@ declare class Export {
9
11
  deleteSavedIssue(issueId: number): void;
10
12
  clearSavedIssues(): void;
11
13
  exportAllIssues(): void;
14
+ /**
15
+ * Send issue to n8n webhook
16
+ */
17
+ sendToN8n(issueData: CompleteIssue): Promise<N8nResponse>;
18
+ /**
19
+ * Configure n8n integration
20
+ */
21
+ configureN8n(config: N8nConfig): void;
22
+ /**
23
+ * Check if n8n is enabled
24
+ */
25
+ isN8nEnabled(): boolean;
26
+ /**
27
+ * Test n8n connection
28
+ */
29
+ testN8nConnection(): Promise<N8nResponse>;
30
+ /**
31
+ * Get n8n configuration
32
+ */
33
+ getN8nConfig(): N8nConfig | null;
34
+ /**
35
+ * Enable/disable n8n integration
36
+ */
37
+ setN8nEnabled(enabled: boolean): void;
38
+ /**
39
+ * Set n8n webhook URL
40
+ */
41
+ setN8nWebhookUrl(url: string): void;
12
42
  }
13
43
  export default Export;
14
44
  //# sourceMappingURL=export.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/features/export.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE1D,cAAM,MAAM;IACV,OAAO,CAAC,WAAW,CAAe;;IAMlC,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM;IAsB9C,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,eAAe;IAUvB,cAAc,IAAI,UAAU,EAAE;IAI9B,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKvC,gBAAgB,IAAI,IAAI;IAKxB,eAAe,IAAI,IAAI;CAcxB;AAED,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/features/export.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAuB,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErF,cAAM,MAAM;IACV,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,cAAc,CAAiB;gBAE3B,SAAS,CAAC,EAAE,SAAS;IAKjC,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM;IAsB9C,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,eAAe;IAUvB,cAAc,IAAI,UAAU,EAAE;IAI9B,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKvC,gBAAgB,IAAI,IAAI;IAKxB,eAAe,IAAI,IAAI;IAiBvB;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;IAW/D;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAIrC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC;IAI/C;;OAEG;IACH,YAAY,IAAI,SAAS,GAAG,IAAI;IAIhC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIrC;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAGpC;AAED,eAAe,MAAM,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { CompleteIssue } from '../types';
2
+ export interface N8nConfig {
3
+ webhookUrl: string;
4
+ enabled: boolean;
5
+ headers?: Record<string, string>;
6
+ timeout?: number;
7
+ }
8
+ export interface N8nResponse {
9
+ success: boolean;
10
+ message: string;
11
+ workflowId?: string;
12
+ error?: string;
13
+ }
14
+ declare class N8nIntegration {
15
+ private config;
16
+ constructor(config?: N8nConfig);
17
+ /**
18
+ * Configure n8n integration
19
+ */
20
+ configure(config: N8nConfig): void;
21
+ /**
22
+ * Check if n8n integration is configured and enabled
23
+ */
24
+ isEnabled(): boolean;
25
+ /**
26
+ * Send issue data to n8n webhook
27
+ */
28
+ sendToN8n(issueData: CompleteIssue): Promise<N8nResponse>;
29
+ /**
30
+ * Prepare payload for n8n webhook
31
+ * This formats the data in a structure that's easy to work with in n8n
32
+ */
33
+ private preparePayload;
34
+ /**
35
+ * Test connection to n8n webhook
36
+ */
37
+ testConnection(): Promise<N8nResponse>;
38
+ /**
39
+ * Get current configuration
40
+ */
41
+ getConfig(): N8nConfig | null;
42
+ /**
43
+ * Update webhook URL
44
+ */
45
+ setWebhookUrl(url: string): void;
46
+ /**
47
+ * Enable/disable n8n integration
48
+ */
49
+ setEnabled(enabled: boolean): void;
50
+ }
51
+ export default N8nIntegration;
52
+ //# sourceMappingURL=n8n-integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"n8n-integration.d.ts","sourceRoot":"","sources":["../../src/features/n8n-integration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,MAAM,CAAmB;gBAErB,MAAM,CAAC,EAAE,SAAS;IAI9B;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAIlC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;IA4F/D;;;OAGG;IACH,OAAO,CAAC,cAAc;IAyCtB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAiC5C;;OAEG;IACH,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAWhC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;CAKnC;AAED,eAAe,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/features/screenshot.ts"],"names":[],"mappings":"AAGA,cAAM,UAAU;IACd,OAAO,CAAC,iBAAiB,CAAgB;;IAKzC,OAAO,CAAC,IAAI;IAGN,OAAO,CAAC,IAAI,GAAE,UAAU,GAAG,UAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IA2H1E,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IA6DzB,OAAO,CAAC,iBAAiB;IAOzB,aAAa,IAAI,MAAM,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;CAGd;AAED,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/features/screenshot.ts"],"names":[],"mappings":"AAGA,cAAM,UAAU;IACd,OAAO,CAAC,iBAAiB,CAAgB;;IAKzC,OAAO,CAAC,IAAI;IAGN,OAAO,CAAC,IAAI,GAAE,UAAU,GAAG,UAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IA8H1E,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IA6DzB,OAAO,CAAC,iBAAiB;IAOzB,aAAa,IAAI,MAAM,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;CAGd;AAED,eAAe,UAAU,CAAC"}
@@ -263,6 +263,9 @@ class Modal {
263
263
  <button type="button" class="uxbert-btn uxbert-btn-secondary" id="uxbert-cancel-btn">
264
264
  Cancel
265
265
  </button>
266
+ <button type="button" class="uxbert-btn uxbert-btn-success" id="uxbert-n8n-btn" style="display: none;">
267
+ 🚀 Send to n8n
268
+ </button>
266
269
  <button type="submit" class="uxbert-btn uxbert-btn-primary" id="uxbert-submit-btn">
267
270
  📥 Download JSON
268
271
  </button>
@@ -317,6 +320,33 @@ class Modal {
317
320
  this.callbacks.onRetake();
318
321
  }
319
322
  });
323
+ // n8n button
324
+ const n8nBtn = this.modal.querySelector('#uxbert-n8n-btn');
325
+ n8nBtn.addEventListener('click', () => {
326
+ this.handleN8nSubmit();
327
+ });
328
+ }
329
+ handleN8nSubmit() {
330
+ if (!this.modal)
331
+ return;
332
+ const titleInput = this.modal.querySelector('#uxbert-title');
333
+ const descriptionInput = this.modal.querySelector('#uxbert-description');
334
+ const prioritySelect = this.modal.querySelector('#uxbert-priority');
335
+ const title = titleInput.value;
336
+ const description = descriptionInput.value;
337
+ const priority = prioritySelect.value;
338
+ if (!title.trim()) {
339
+ alert('Please enter an issue title');
340
+ return;
341
+ }
342
+ const issueData = {
343
+ title: title.trim(),
344
+ description: description.trim(),
345
+ priority
346
+ };
347
+ if (this.callbacks.onSendToN8n) {
348
+ this.callbacks.onSendToN8n(issueData);
349
+ }
320
350
  }
321
351
  handleSubmit() {
322
352
  if (!this.modal)
@@ -375,6 +405,14 @@ class Modal {
375
405
  const viewportRadio = this.modal.querySelector('#uxbert-capture-viewport');
376
406
  return viewportRadio.checked ? 'viewport' : 'fullpage';
377
407
  }
408
+ setN8nButtonVisible(visible) {
409
+ if (!this.modal)
410
+ return;
411
+ const n8nBtn = this.modal.querySelector('#uxbert-n8n-btn');
412
+ if (n8nBtn) {
413
+ n8nBtn.style.display = visible ? 'inline-block' : 'none';
414
+ }
415
+ }
378
416
  reset() {
379
417
  if (!this.modal)
380
418
  return;
@@ -9454,8 +9492,6 @@ class Screenshot {
9454
9492
  }
9455
9493
  async capture(mode = "fullpage") {
9456
9494
  try {
9457
- // Show loading screen
9458
- this.showLoadingScreen();
9459
9495
  // Hide UXbert UI elements before capturing
9460
9496
  this.hideUXbertElements();
9461
9497
  const originalScrollY = window.scrollY;
@@ -9474,6 +9510,8 @@ class Screenshot {
9474
9510
  x: window.scrollX,
9475
9511
  y: window.scrollY,
9476
9512
  onclone: async () => {
9513
+ // Show loading screen after clone (won't appear in screenshot)
9514
+ this.showLoadingScreen();
9477
9515
  // Wait 1 second after cloning to let animations complete
9478
9516
  await this.wait(1000);
9479
9517
  },
@@ -9514,6 +9552,10 @@ class Screenshot {
9514
9552
  logging: false,
9515
9553
  width: fullPageWidth,
9516
9554
  height: fullPageHeight,
9555
+ onclone: async () => {
9556
+ // Show loading screen after clone (won't appear in screenshot)
9557
+ this.showLoadingScreen();
9558
+ },
9517
9559
  ignoreElements: (element) => {
9518
9560
  // Skip cross-origin images that might cause issues
9519
9561
  if (element.tagName === "IMG") {
@@ -10157,9 +10199,213 @@ class DeviceInfo {
10157
10199
  }
10158
10200
  }
10159
10201
 
10202
+ class N8nIntegration {
10203
+ constructor(config) {
10204
+ this.config = config || null;
10205
+ }
10206
+ /**
10207
+ * Configure n8n integration
10208
+ */
10209
+ configure(config) {
10210
+ this.config = config;
10211
+ }
10212
+ /**
10213
+ * Check if n8n integration is configured and enabled
10214
+ */
10215
+ isEnabled() {
10216
+ return !!(this.config && this.config.enabled && this.config.webhookUrl);
10217
+ }
10218
+ /**
10219
+ * Send issue data to n8n webhook
10220
+ */
10221
+ async sendToN8n(issueData) {
10222
+ if (!this.config || !this.config.webhookUrl) {
10223
+ return {
10224
+ success: false,
10225
+ message: 'n8n webhook URL is not configured',
10226
+ error: 'MISSING_CONFIG'
10227
+ };
10228
+ }
10229
+ if (!this.config.enabled) {
10230
+ return {
10231
+ success: false,
10232
+ message: 'n8n integration is disabled',
10233
+ error: 'DISABLED'
10234
+ };
10235
+ }
10236
+ try {
10237
+ // Prepare payload
10238
+ const payload = this.preparePayload(issueData);
10239
+ // Setup request headers
10240
+ const headers = {
10241
+ 'Content-Type': 'application/json',
10242
+ ...this.config.headers
10243
+ };
10244
+ // Setup timeout
10245
+ const timeout = this.config.timeout || 30000; // 30 seconds default
10246
+ const controller = new AbortController();
10247
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
10248
+ // Send request
10249
+ const response = await fetch(this.config.webhookUrl, {
10250
+ method: 'POST',
10251
+ headers,
10252
+ body: JSON.stringify(payload),
10253
+ signal: controller.signal
10254
+ });
10255
+ clearTimeout(timeoutId);
10256
+ // Check response
10257
+ if (!response.ok) {
10258
+ const errorText = await response.text().catch(() => 'Unknown error');
10259
+ return {
10260
+ success: false,
10261
+ message: `n8n webhook request failed: ${response.status} ${response.statusText}`,
10262
+ error: errorText
10263
+ };
10264
+ }
10265
+ // Parse response
10266
+ let responseData;
10267
+ try {
10268
+ responseData = await response.json();
10269
+ }
10270
+ catch (e) {
10271
+ // Some webhooks don't return JSON
10272
+ responseData = { message: 'Success' };
10273
+ }
10274
+ return {
10275
+ success: true,
10276
+ message: 'Issue sent to n8n successfully',
10277
+ workflowId: responseData.workflowId || responseData.executionId
10278
+ };
10279
+ }
10280
+ catch (error) {
10281
+ if (error instanceof Error) {
10282
+ if (error.name === 'AbortError') {
10283
+ return {
10284
+ success: false,
10285
+ message: 'Request to n8n webhook timed out',
10286
+ error: 'TIMEOUT'
10287
+ };
10288
+ }
10289
+ return {
10290
+ success: false,
10291
+ message: `Failed to send issue to n8n: ${error.message}`,
10292
+ error: error.message
10293
+ };
10294
+ }
10295
+ return {
10296
+ success: false,
10297
+ message: 'Unknown error occurred while sending to n8n',
10298
+ error: 'UNKNOWN'
10299
+ };
10300
+ }
10301
+ }
10302
+ /**
10303
+ * Prepare payload for n8n webhook
10304
+ * This formats the data in a structure that's easy to work with in n8n
10305
+ */
10306
+ preparePayload(issueData) {
10307
+ return {
10308
+ // Main issue data
10309
+ issue: {
10310
+ title: issueData.title,
10311
+ description: issueData.description,
10312
+ priority: issueData.priority,
10313
+ createdAt: issueData.createdAt
10314
+ },
10315
+ // Screenshot data (base64)
10316
+ screenshot: {
10317
+ data: issueData.screenshot,
10318
+ format: 'base64',
10319
+ mimeType: 'image/png'
10320
+ },
10321
+ // Device and browser information
10322
+ context: {
10323
+ browser: issueData.deviceInfo.browser,
10324
+ os: issueData.deviceInfo.os,
10325
+ screenResolution: issueData.deviceInfo.screenResolution,
10326
+ viewport: issueData.deviceInfo.viewport,
10327
+ url: issueData.deviceInfo.url,
10328
+ userAgent: issueData.deviceInfo.userAgent,
10329
+ language: issueData.deviceInfo.language,
10330
+ platform: issueData.deviceInfo.platform,
10331
+ cookiesEnabled: issueData.deviceInfo.cookiesEnabled,
10332
+ onLine: issueData.deviceInfo.onLine,
10333
+ timestamp: issueData.deviceInfo.timestamp
10334
+ },
10335
+ // Metadata for tracking
10336
+ metadata: {
10337
+ source: 'uxbert-reportly',
10338
+ version: '1.0.0',
10339
+ timestamp: new Date().toISOString()
10340
+ }
10341
+ };
10342
+ }
10343
+ /**
10344
+ * Test connection to n8n webhook
10345
+ */
10346
+ async testConnection() {
10347
+ if (!this.config || !this.config.webhookUrl) {
10348
+ return {
10349
+ success: false,
10350
+ message: 'n8n webhook URL is not configured',
10351
+ error: 'MISSING_CONFIG'
10352
+ };
10353
+ }
10354
+ try {
10355
+ const controller = new AbortController();
10356
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout for test
10357
+ await fetch(this.config.webhookUrl, {
10358
+ method: 'HEAD',
10359
+ signal: controller.signal
10360
+ });
10361
+ clearTimeout(timeoutId);
10362
+ return {
10363
+ success: true,
10364
+ message: 'n8n webhook is reachable'
10365
+ };
10366
+ }
10367
+ catch (error) {
10368
+ return {
10369
+ success: false,
10370
+ message: 'Failed to reach n8n webhook',
10371
+ error: error instanceof Error ? error.message : 'UNKNOWN'
10372
+ };
10373
+ }
10374
+ }
10375
+ /**
10376
+ * Get current configuration
10377
+ */
10378
+ getConfig() {
10379
+ return this.config;
10380
+ }
10381
+ /**
10382
+ * Update webhook URL
10383
+ */
10384
+ setWebhookUrl(url) {
10385
+ if (!this.config) {
10386
+ this.config = {
10387
+ webhookUrl: url,
10388
+ enabled: true
10389
+ };
10390
+ }
10391
+ else {
10392
+ this.config.webhookUrl = url;
10393
+ }
10394
+ }
10395
+ /**
10396
+ * Enable/disable n8n integration
10397
+ */
10398
+ setEnabled(enabled) {
10399
+ if (this.config) {
10400
+ this.config.enabled = enabled;
10401
+ }
10402
+ }
10403
+ }
10404
+
10160
10405
  class Export {
10161
- constructor() {
10406
+ constructor(n8nConfig) {
10162
10407
  this.savedIssues = this.loadSavedIssues();
10408
+ this.n8nIntegration = new N8nIntegration(n8nConfig);
10163
10409
  }
10164
10410
  exportToJSON(issueData) {
10165
10411
  const json = JSON.stringify(issueData, null, 2);
@@ -10228,6 +10474,54 @@ class Export {
10228
10474
  document.body.removeChild(link);
10229
10475
  URL.revokeObjectURL(url);
10230
10476
  }
10477
+ // n8n Integration Methods
10478
+ /**
10479
+ * Send issue to n8n webhook
10480
+ */
10481
+ async sendToN8n(issueData) {
10482
+ const response = await this.n8nIntegration.sendToN8n(issueData);
10483
+ // Save to localStorage if n8n send was successful
10484
+ if (response.success) {
10485
+ this.saveIssue(issueData);
10486
+ }
10487
+ return response;
10488
+ }
10489
+ /**
10490
+ * Configure n8n integration
10491
+ */
10492
+ configureN8n(config) {
10493
+ this.n8nIntegration.configure(config);
10494
+ }
10495
+ /**
10496
+ * Check if n8n is enabled
10497
+ */
10498
+ isN8nEnabled() {
10499
+ return this.n8nIntegration.isEnabled();
10500
+ }
10501
+ /**
10502
+ * Test n8n connection
10503
+ */
10504
+ async testN8nConnection() {
10505
+ return this.n8nIntegration.testConnection();
10506
+ }
10507
+ /**
10508
+ * Get n8n configuration
10509
+ */
10510
+ getN8nConfig() {
10511
+ return this.n8nIntegration.getConfig();
10512
+ }
10513
+ /**
10514
+ * Enable/disable n8n integration
10515
+ */
10516
+ setN8nEnabled(enabled) {
10517
+ this.n8nIntegration.setEnabled(enabled);
10518
+ }
10519
+ /**
10520
+ * Set n8n webhook URL
10521
+ */
10522
+ setN8nWebhookUrl(url) {
10523
+ this.n8nIntegration.setWebhookUrl(url);
10524
+ }
10231
10525
  }
10232
10526
 
10233
10527
  function styleInject(css, ref) {
@@ -10257,7 +10551,7 @@ function styleInject(css, ref) {
10257
10551
  }
10258
10552
  }
10259
10553
 
10260
- 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}}";
10554
+ 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-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}}";
10261
10555
  styleInject(css_248z);
10262
10556
 
10263
10557
  // Main initialization file
@@ -10289,7 +10583,9 @@ class Reportly {
10289
10583
  this.state = new State();
10290
10584
  this.deviceInfo = new DeviceInfo();
10291
10585
  this.screenshot = new Screenshot();
10292
- this.export = new Export();
10586
+ // Initialize export with n8n config if provided
10587
+ const n8nConfig = userConfig.integrations?.n8n;
10588
+ this.export = new Export(n8nConfig);
10293
10589
  // Initialize UI components
10294
10590
  this.button = new Button(this.config, {
10295
10591
  onClick: () => this.handleButtonClick(),
@@ -10300,6 +10596,7 @@ class Reportly {
10300
10596
  onAnnotate: () => this.startAnnotation(),
10301
10597
  onRetake: () => this.retakeScreenshot(),
10302
10598
  onCapture: () => this.captureWithMode(),
10599
+ onSendToN8n: (issueData) => this.handleN8nSubmit(issueData),
10303
10600
  });
10304
10601
  this.toolbar = new Toolbar({
10305
10602
  onToolChange: (tool) => this.annotation?.setTool(tool),
@@ -10315,6 +10612,10 @@ class Reportly {
10315
10612
  this.modal.create();
10316
10613
  this.toolbar.create();
10317
10614
  this.annotation.createCanvas();
10615
+ // Show/hide n8n button based on configuration
10616
+ if (this.export.isN8nEnabled()) {
10617
+ this.modal.setN8nButtonVisible(true);
10618
+ }
10318
10619
  // Setup keyboard shortcuts
10319
10620
  this.setupKeyboardShortcuts();
10320
10621
  this.isInitialized = true;
@@ -10442,6 +10743,54 @@ class Reportly {
10442
10743
  alert("Failed to export issue. Please try again.");
10443
10744
  }
10444
10745
  }
10746
+ async handleN8nSubmit(issueData) {
10747
+ try {
10748
+ // Create complete issue package
10749
+ const completeIssue = {
10750
+ ...issueData,
10751
+ screenshot: this.state?.getScreenshot() || "",
10752
+ deviceInfo: this.deviceInfo.get(),
10753
+ createdAt: new Date().toISOString(),
10754
+ };
10755
+ // Show loading state
10756
+ const n8nBtn = document.querySelector('#uxbert-n8n-btn');
10757
+ if (n8nBtn) {
10758
+ n8nBtn.disabled = true;
10759
+ n8nBtn.textContent = '⏳ Sending...';
10760
+ }
10761
+ // Send to n8n
10762
+ const response = await this.export?.sendToN8n(completeIssue);
10763
+ if (response?.success) {
10764
+ // Emit event
10765
+ this.state?.emit("issue:sent-to-n8n", completeIssue);
10766
+ // Reset state
10767
+ this.state?.reset();
10768
+ this.annotation?.clear();
10769
+ this.modal?.close();
10770
+ alert("Issue sent to n8n successfully!");
10771
+ console.log("Issue sent to n8n successfully");
10772
+ }
10773
+ else {
10774
+ alert(`Failed to send to n8n: ${response?.message || 'Unknown error'}`);
10775
+ console.error("Failed to send to n8n:", response?.error);
10776
+ }
10777
+ // Reset button state
10778
+ if (n8nBtn) {
10779
+ n8nBtn.disabled = false;
10780
+ n8nBtn.textContent = '🚀 Send to n8n';
10781
+ }
10782
+ }
10783
+ catch (error) {
10784
+ console.error("Failed to send to n8n:", error);
10785
+ alert("Failed to send to n8n. Please try again.");
10786
+ // Reset button state
10787
+ const n8nBtn = document.querySelector('#uxbert-n8n-btn');
10788
+ if (n8nBtn) {
10789
+ n8nBtn.disabled = false;
10790
+ n8nBtn.textContent = '🚀 Send to n8n';
10791
+ }
10792
+ }
10793
+ }
10445
10794
  handleModalClose() {
10446
10795
  this.annotation?.hide();
10447
10796
  this.toolbar?.hide();
@@ -10470,6 +10819,24 @@ class Reportly {
10470
10819
  exportAllIssues() {
10471
10820
  this.export?.exportAllIssues();
10472
10821
  }
10822
+ // n8n Integration API methods
10823
+ configureN8n(webhookUrl, enabled = true, options) {
10824
+ this.export?.configureN8n({
10825
+ webhookUrl,
10826
+ enabled,
10827
+ headers: options?.headers,
10828
+ timeout: options?.timeout
10829
+ });
10830
+ // Update button visibility
10831
+ this.modal?.setN8nButtonVisible(enabled);
10832
+ }
10833
+ enableN8n(enabled) {
10834
+ this.export?.setN8nEnabled(enabled);
10835
+ this.modal?.setN8nButtonVisible(enabled);
10836
+ }
10837
+ testN8nConnection() {
10838
+ return this.export?.testN8nConnection() || Promise.resolve({ success: false, message: 'Not initialized' });
10839
+ }
10473
10840
  on(event, callback) {
10474
10841
  this.state?.on(event, callback);
10475
10842
  }