@product7/feedback-sdk 1.2.8 → 1.3.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.
@@ -4,91 +4,6 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FeedbackSDK = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- class SDKError extends Error {
8
- constructor(message, cause) {
9
- super(message);
10
- this.name = 'SDKError';
11
- this.cause = cause;
12
-
13
- if (Error.captureStackTrace) {
14
- Error.captureStackTrace(this, SDKError);
15
- }
16
- }
17
- }
18
-
19
- class APIError extends Error {
20
- constructor(status, message, response) {
21
- super(message);
22
- this.name = 'APIError';
23
- this.status = status;
24
- this.response = response;
25
-
26
- if (Error.captureStackTrace) {
27
- Error.captureStackTrace(this, APIError);
28
- }
29
- }
30
-
31
- isNetworkError() {
32
- return this.status === 0;
33
- }
34
-
35
- isClientError() {
36
- return this.status >= 400 && this.status < 500;
37
- }
38
-
39
- isServerError() {
40
- return this.status >= 500 && this.status < 600;
41
- }
42
- }
43
-
44
- class WidgetError extends Error {
45
- constructor(message, widgetType, widgetId) {
46
- super(message);
47
- this.name = 'WidgetError';
48
- this.widgetType = widgetType;
49
- this.widgetId = widgetId;
50
-
51
- if (Error.captureStackTrace) {
52
- Error.captureStackTrace(this, WidgetError);
53
- }
54
- }
55
- }
56
-
57
- class ConfigError extends Error {
58
- constructor(message, configKey) {
59
- super(message);
60
- this.name = 'ConfigError';
61
- this.configKey = configKey;
62
-
63
- if (Error.captureStackTrace) {
64
- Error.captureStackTrace(this, ConfigError);
65
- }
66
- }
67
- }
68
-
69
- class ValidationError extends Error {
70
- constructor(message, field, value) {
71
- super(message);
72
- this.name = 'ValidationError';
73
- this.field = field;
74
- this.value = value;
75
-
76
- if (Error.captureStackTrace) {
77
- Error.captureStackTrace(this, ValidationError);
78
- }
79
- }
80
- }
81
-
82
- const MOCK_CONFIG = {
83
- primaryColor: '#21244A',
84
- backgroundColor: '#ffffff',
85
- textColor: '#1F2937',
86
- boardId: 'feature-requests',
87
- size: 'medium',
88
- displayMode: 'modal',
89
- };
90
-
91
- // Mock changelogs for development
92
7
  const MOCK_CHANGELOGS = [
93
8
  {
94
9
  id: 'changelog_1',
@@ -139,7 +54,6 @@
139
54
  },
140
55
  ];
141
56
 
142
- // Mock conversations for development
143
57
  const MOCK_CONVERSATIONS = [
144
58
  {
145
59
  id: 'conv_1',
@@ -171,7 +85,6 @@
171
85
  },
172
86
  ];
173
87
 
174
- // Mock messages for development
175
88
  const MOCK_MESSAGES = {
176
89
  conv_1: [
177
90
  {
@@ -218,7 +131,6 @@
218
131
  ],
219
132
  };
220
133
 
221
- // Mock help collections for development
222
134
  const MOCK_HELP_COLLECTIONS = [
223
135
  {
224
136
  id: 'collection_1',
@@ -264,7 +176,6 @@
264
176
  },
265
177
  ];
266
178
 
267
- // Mock surveys for development
268
179
  const MOCK_SURVEYS = [
269
180
  {
270
181
  id: 'mock_nps_survey',
@@ -296,110 +207,73 @@
296
207
  },
297
208
  ];
298
209
 
299
- // Environment URLs
300
- const ENV_URLS = {
301
- production: {
302
- base: 'https://api.product7.io/api/v1',
303
- withWorkspace: (workspace) => `https://${workspace}.api.product7.io/api/v1`,
304
- },
305
- staging: {
306
- base: 'https://staging.api.product7.io/api/v1',
307
- withWorkspace: (workspace) =>
308
- `https://${workspace}.staging.api.product7.io/api/v1`,
309
- },
310
- };
210
+ const delay$1 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
311
211
 
312
- class APIService {
313
- constructor(config = {}) {
314
- this.workspace = config.workspace;
315
- this.sessionToken = null;
316
- this.sessionExpiry = null;
317
- this.userContext = config.userContext || null;
318
- this.mock = config.mock || false;
319
- this.env = config.env || 'production';
212
+ function getDeviceInfo() {
213
+ if (typeof navigator === 'undefined') {
214
+ return { device: null, browser: null, os: null };
215
+ }
320
216
 
321
- if (config.apiUrl) {
322
- this.baseURL = config.apiUrl;
323
- } else {
324
- const envConfig = ENV_URLS[this.env] || ENV_URLS.production;
325
- this.baseURL = this.workspace
326
- ? envConfig.withWorkspace(this.workspace)
327
- : envConfig.base;
328
- }
217
+ const ua = navigator.userAgent;
329
218
 
330
- this._loadStoredSession();
219
+ let device = 'desktop';
220
+ if (/Mobi|Android/i.test(ua)) {
221
+ device = /Tablet|iPad/i.test(ua) ? 'tablet' : 'mobile';
331
222
  }
332
223
 
333
- async init(userContext = null) {
334
- if (userContext) {
335
- this.userContext = userContext;
336
- }
337
-
338
- if (this.isSessionValid()) {
339
- return { sessionToken: this.sessionToken };
340
- }
224
+ let browser = 'unknown';
225
+ if (ua.includes('Firefox')) browser = 'firefox';
226
+ else if (ua.includes('Edg')) browser = 'edge';
227
+ else if (ua.includes('Chrome')) browser = 'chrome';
228
+ else if (ua.includes('Safari')) browser = 'safari';
229
+ else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'opera';
341
230
 
342
- if (!this.userContext || !this.workspace) {
343
- const error = `Missing ${!this.workspace ? 'workspace' : 'user context'} for initialization`;
344
- throw new APIError(400, error);
345
- }
231
+ let os = 'unknown';
232
+ if (ua.includes('Windows')) os = 'windows';
233
+ else if (ua.includes('Mac')) os = 'macos';
234
+ else if (ua.includes('Linux')) os = 'linux';
235
+ else if (ua.includes('Android')) os = 'android';
236
+ else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'ios';
346
237
 
347
- // Mock mode - return fake session
348
- if (this.mock) {
349
- this.sessionToken = 'mock_session_' + Date.now();
350
- this.sessionExpiry = new Date(Date.now() + 3600 * 1000);
351
- this._storeSession();
352
- return {
353
- sessionToken: this.sessionToken,
354
- config: MOCK_CONFIG,
355
- expiresIn: 3600,
356
- };
357
- }
238
+ return { device, browser, os };
239
+ }
358
240
 
359
- const payload = {
360
- workspace: this.workspace,
361
- user: this.userContext,
362
- };
241
+ class ChangelogService {
242
+ constructor(baseAPI) {
243
+ this.api = baseAPI;
244
+ }
363
245
 
364
- try {
365
- const response = await this._makeRequest('/widget/init', {
366
- method: 'POST',
367
- body: JSON.stringify(payload),
368
- headers: {
369
- 'Content-Type': 'application/json',
370
- },
371
- });
246
+ async getChangelogs(options = {}) {
247
+ await this.api._ensureSession();
372
248
 
373
- this.sessionToken = response.session_token;
374
- this.sessionExpiry = new Date(Date.now() + response.expires_in * 1000);
375
- this._storeSession();
249
+ if (this.api.mock) {
250
+ await delay$1(300);
251
+ return { success: true, data: MOCK_CHANGELOGS };
252
+ }
376
253
 
377
- return {
378
- sessionToken: this.sessionToken,
379
- config: response.config || {},
380
- expiresIn: response.expires_in,
381
- };
382
- } catch (error) {
383
- throw new APIError(
384
- error.status || 500,
385
- `Failed to initialize widget: ${error.message}`,
386
- error.response
254
+ return this.api._handleAuthRetry(async () => {
255
+ const endpoint = this.api._getEndpointWithParams(
256
+ '/widget/changelogs',
257
+ options
387
258
  );
388
- }
259
+ return await this.api._makeRequest(endpoint, {
260
+ method: 'GET',
261
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
262
+ });
263
+ });
389
264
  }
265
+ }
390
266
 
391
- async submitFeedback(feedbackData) {
392
- if (!this.isSessionValid()) {
393
- await this.init();
394
- }
267
+ class FeedbackService {
268
+ constructor(baseAPI) {
269
+ this.api = baseAPI;
270
+ }
395
271
 
396
- if (!this.sessionToken) {
397
- throw new APIError(401, 'No valid session token available');
398
- }
272
+ async submitFeedback(feedbackData) {
273
+ await this.api._ensureSession();
399
274
 
400
- // Mock mode - simulate success
401
- if (this.mock) {
402
- await new Promise((resolve) => setTimeout(resolve, 500));
275
+ if (this.api.mock) {
276
+ await delay$1(500);
403
277
  return {
404
278
  success: true,
405
279
  data: {
@@ -419,313 +293,98 @@
419
293
  attachments: feedbackData.attachments || [],
420
294
  };
421
295
 
422
- try {
423
- const response = await this._makeRequest('/widget/feedback', {
296
+ return this.api._handleAuthRetry(async () => {
297
+ const response = await this.api._makeRequest('/widget/feedback', {
424
298
  method: 'POST',
425
299
  body: JSON.stringify(payload),
426
300
  headers: {
427
301
  'Content-Type': 'application/json',
428
- Authorization: `Bearer ${this.sessionToken}`,
302
+ Authorization: `Bearer ${this.api.sessionToken}`,
429
303
  },
430
304
  });
431
-
432
305
  return response;
433
- } catch (error) {
434
- if (error.status === 401) {
435
- this.sessionToken = null;
436
- this.sessionExpiry = null;
437
- await this.init();
438
- return this.submitFeedback(feedbackData);
439
- }
440
-
441
- throw new APIError(
442
- error.status || 500,
443
- `Failed to submit feedback: ${error.message}`,
444
- error.response
445
- );
446
- }
306
+ });
447
307
  }
308
+ }
448
309
 
449
- /**
450
- * Get active surveys for the current user/context
451
- * @param {Object} context - Optional context overrides for targeting
452
- * @param {string} context.url - Override current URL
453
- * @param {Object} context.userProperties - Additional user properties
454
- * @returns {Promise<Array>} Array of active surveys matching targeting rules
455
- */
456
- async getActiveSurveys(context = {}) {
457
- if (!this.isSessionValid()) {
458
- await this.init();
459
- }
310
+ class HelpService {
311
+ constructor(baseAPI) {
312
+ this.api = baseAPI;
313
+ }
460
314
 
461
- if (!this.sessionToken) {
462
- throw new APIError(401, 'No valid session token available');
463
- }
315
+ async getHelpCollections(options = {}) {
316
+ await this.api._ensureSession();
464
317
 
465
- // Mock mode - return mock surveys
466
- if (this.mock) {
467
- await new Promise((resolve) => setTimeout(resolve, 200));
468
- return {
469
- success: true,
470
- data: MOCK_SURVEYS,
471
- };
318
+ if (this.api.mock) {
319
+ await delay$1(200);
320
+ return { status: true, data: MOCK_HELP_COLLECTIONS };
472
321
  }
473
322
 
474
- try {
475
- const queryParams = new URLSearchParams();
476
-
477
- // Auto-detect current URL or use provided override
478
- const currentUrl =
479
- context.url ||
480
- (typeof window !== 'undefined' ? window.location.href : '');
481
- if (currentUrl) {
482
- queryParams.append('url', currentUrl);
483
- }
484
-
485
- // Auto-detect device info
486
- const deviceInfo = this._getDeviceInfo();
487
- if (deviceInfo.device) queryParams.append('device', deviceInfo.device);
488
- if (deviceInfo.browser) queryParams.append('browser', deviceInfo.browser);
489
- if (deviceInfo.os) queryParams.append('os', deviceInfo.os);
490
-
491
- // Add user properties if provided
492
- if (context.userProperties) {
493
- queryParams.append(
494
- 'user_properties',
495
- JSON.stringify(context.userProperties)
496
- );
497
- }
498
-
499
- const endpoint = `/widget/surveys/active${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
500
-
501
- const response = await this._makeRequest(endpoint, {
502
- method: 'GET',
503
- headers: {
504
- Authorization: `Bearer ${this.sessionToken}`,
505
- },
506
- });
507
-
508
- return response;
509
- } catch (error) {
510
- if (error.status === 401) {
511
- this.sessionToken = null;
512
- this.sessionExpiry = null;
513
- await this.init();
514
- return this.getActiveSurveys(context);
515
- }
516
-
517
- throw new APIError(
518
- error.status || 500,
519
- `Failed to get active surveys: ${error.message}`,
520
- error.response
521
- );
522
- }
323
+ const endpoint = this.api._getEndpointWithParams(
324
+ '/widget/help/collections',
325
+ options
326
+ );
327
+ return this.api._makeRequest(endpoint, {
328
+ method: 'GET',
329
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
330
+ });
523
331
  }
524
332
 
525
- /**
526
- * Auto-detect device, browser, and OS info
527
- * @returns {Object} Device info object
528
- */
529
- _getDeviceInfo() {
530
- if (typeof navigator === 'undefined') {
531
- return { device: null, browser: null, os: null };
532
- }
533
-
534
- const ua = navigator.userAgent;
333
+ async searchHelpArticles(query, options = {}) {
334
+ await this.api._ensureSession();
535
335
 
536
- // Detect device type
537
- let device = 'desktop';
538
- if (/Mobi|Android/i.test(ua)) {
539
- device = /Tablet|iPad/i.test(ua) ? 'tablet' : 'mobile';
336
+ if (this.api.mock) {
337
+ await delay$1(200);
338
+ const filtered = MOCK_HELP_COLLECTIONS.filter(
339
+ (c) =>
340
+ c.title.toLowerCase().includes(query.toLowerCase()) ||
341
+ c.description.toLowerCase().includes(query.toLowerCase())
342
+ );
343
+ return { status: true, data: filtered };
540
344
  }
541
345
 
542
- // Detect browser
543
- let browser = 'unknown';
544
- if (ua.includes('Firefox')) browser = 'firefox';
545
- else if (ua.includes('Edg')) browser = 'edge';
546
- else if (ua.includes('Chrome')) browser = 'chrome';
547
- else if (ua.includes('Safari')) browser = 'safari';
548
- else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'opera';
549
-
550
- // Detect OS
551
- let os = 'unknown';
552
- if (ua.includes('Windows')) os = 'windows';
553
- else if (ua.includes('Mac')) os = 'macos';
554
- else if (ua.includes('Linux')) os = 'linux';
555
- else if (ua.includes('Android')) os = 'android';
556
- else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'ios';
557
-
558
- return { device, browser, os };
346
+ const params = { q: query, ...options };
347
+ const endpoint = this.api._getEndpointWithParams(
348
+ '/widget/help/search',
349
+ params
350
+ );
351
+ return this.api._makeRequest(endpoint, {
352
+ method: 'GET',
353
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
354
+ });
559
355
  }
356
+ }
560
357
 
561
- /**
562
- * Submit a survey response
563
- * @param {string} surveyId - The survey ID
564
- * @param {Object} responseData - The survey response data
565
- * @param {number|string} responseData.rating - The rating/score value
566
- * @param {string} responseData.feedback - Optional text feedback
567
- * @param {Object} responseData.answers - For custom surveys, key-value pairs
568
- * @returns {Promise<Object>} Submission result
569
- */
570
- async submitSurveyResponse(surveyId, responseData) {
571
- if (!this.isSessionValid()) {
572
- await this.init();
573
- }
574
-
575
- if (!this.sessionToken) {
576
- throw new APIError(401, 'No valid session token available');
577
- }
358
+ class MessengerService {
359
+ constructor(baseAPI) {
360
+ this.api = baseAPI;
361
+ }
578
362
 
579
- if (!surveyId) {
580
- throw new APIError(400, 'Survey ID is required');
581
- }
363
+ async getMessengerSettings() {
364
+ await this.api._ensureSession();
582
365
 
583
- // Mock mode - simulate success
584
- if (this.mock) {
585
- await new Promise((resolve) => setTimeout(resolve, 300));
366
+ if (this.api.mock) {
586
367
  return {
587
- success: true,
368
+ status: true,
588
369
  data: {
589
- id: 'mock_response_' + Date.now(),
590
- survey_id: surveyId,
591
- ...responseData,
370
+ enabled: true,
371
+ greeting_message: 'Hi there! How can we help you today?',
372
+ team_name: 'Support Team',
373
+ response_time: 'Usually replies within a few minutes',
592
374
  },
593
- message: 'Survey response submitted successfully!',
594
375
  };
595
376
  }
596
377
 
597
- const payload = {
598
- rating: responseData.rating,
599
- feedback: responseData.feedback || '',
600
- answers: responseData.answers || {},
601
- };
602
-
603
- try {
604
- const response = await this._makeRequest(
605
- `/widget/surveys/${surveyId}/responses`,
606
- {
607
- method: 'POST',
608
- body: JSON.stringify(payload),
609
- headers: {
610
- 'Content-Type': 'application/json',
611
- Authorization: `Bearer ${this.sessionToken}`,
612
- },
613
- }
614
- );
615
-
616
- return response;
617
- } catch (error) {
618
- if (error.status === 401) {
619
- this.sessionToken = null;
620
- this.sessionExpiry = null;
621
- await this.init();
622
- return this.submitSurveyResponse(surveyId, responseData);
623
- }
624
-
625
- throw new APIError(
626
- error.status || 500,
627
- `Failed to submit survey response: ${error.message}`,
628
- error.response
629
- );
630
- }
631
- }
632
-
633
- /**
634
- * Dismiss a survey (mark as seen but not completed)
635
- * @param {string} surveyId - The survey ID to dismiss
636
- * @returns {Promise<Object>} Dismissal result
637
- */
638
- async dismissSurvey(surveyId) {
639
- if (!this.isSessionValid()) {
640
- await this.init();
641
- }
642
-
643
- if (!this.sessionToken) {
644
- throw new APIError(401, 'No valid session token available');
645
- }
646
-
647
- if (!surveyId) {
648
- throw new APIError(400, 'Survey ID is required');
649
- }
650
-
651
- // Mock mode - simulate success
652
- if (this.mock) {
653
- await new Promise((resolve) => setTimeout(resolve, 100));
654
- return {
655
- success: true,
656
- message: 'Survey dismissed successfully',
657
- };
658
- }
659
-
660
- try {
661
- const response = await this._makeRequest(
662
- `/widget/surveys/${surveyId}/dismiss`,
663
- {
664
- method: 'POST',
665
- headers: {
666
- Authorization: `Bearer ${this.sessionToken}`,
667
- },
668
- }
669
- );
670
-
671
- return response;
672
- } catch (error) {
673
- if (error.status === 401) {
674
- this.sessionToken = null;
675
- this.sessionExpiry = null;
676
- await this.init();
677
- return this.dismissSurvey(surveyId);
678
- }
679
-
680
- throw new APIError(
681
- error.status || 500,
682
- `Failed to dismiss survey: ${error.message}`,
683
- error.response
684
- );
685
- }
686
- }
687
-
688
- // ==========================================
689
- // MESSENGER / CHAT ENDPOINTS
690
- // ==========================================
691
-
692
- /**
693
- * Get messenger settings
694
- * @returns {Promise<Object>} Messenger settings
695
- */
696
- async getMessengerSettings() {
697
- if (!this.isSessionValid()) {
698
- await this.init();
699
- }
700
-
701
- if (this.mock) {
702
- return {
703
- status: true,
704
- data: {
705
- enabled: true,
706
- greeting_message: 'Hi there! How can we help you today?',
707
- team_name: 'Support Team',
708
- response_time: 'Usually replies within a few minutes',
709
- },
710
- };
711
- }
712
-
713
- return this._makeRequest('/widget/messenger/settings', {
378
+ return this.api._makeRequest('/widget/messenger/settings', {
714
379
  method: 'GET',
715
- headers: { Authorization: `Bearer ${this.sessionToken}` },
380
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
716
381
  });
717
382
  }
718
383
 
719
- /**
720
- * Check if agents are online
721
- * @returns {Promise<Object>} Agent availability status
722
- */
723
384
  async checkAgentsOnline() {
724
- if (!this.isSessionValid()) {
725
- await this.init();
726
- }
385
+ await this.api._ensureSession();
727
386
 
728
- if (this.mock) {
387
+ if (this.api.mock) {
729
388
  return {
730
389
  status: true,
731
390
  data: {
@@ -736,26 +395,17 @@
736
395
  };
737
396
  }
738
397
 
739
- return this._makeRequest('/widget/messenger/agents/online', {
398
+ return this.api._makeRequest('/widget/messenger/agents/online', {
740
399
  method: 'GET',
741
- headers: { Authorization: `Bearer ${this.sessionToken}` },
400
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
742
401
  });
743
402
  }
744
403
 
745
- /**
746
- * Get all conversations for the current contact
747
- * @param {Object} options - Query options
748
- * @param {number} options.page - Page number
749
- * @param {number} options.limit - Items per page
750
- * @returns {Promise<Object>} Conversations list
751
- */
752
404
  async getConversations(options = {}) {
753
- if (!this.isSessionValid()) {
754
- await this.init();
755
- }
405
+ await this.api._ensureSession();
756
406
 
757
- if (this.mock) {
758
- await new Promise((resolve) => setTimeout(resolve, 300));
407
+ if (this.api.mock) {
408
+ await delay$1(300);
759
409
  return {
760
410
  status: true,
761
411
  data: MOCK_CONVERSATIONS,
@@ -763,92 +413,42 @@
763
413
  };
764
414
  }
765
415
 
766
- const params = new URLSearchParams();
767
- if (options.page) params.append('page', options.page);
768
- if (options.limit) params.append('limit', options.limit);
769
-
770
- const endpoint = `/widget/messenger/conversations${params.toString() ? '?' + params.toString() : ''}`;
771
- return this._makeRequest(endpoint, {
416
+ const endpoint = this.api._getEndpointWithParams(
417
+ '/widget/messenger/conversations',
418
+ options
419
+ );
420
+ return this.api._makeRequest(endpoint, {
772
421
  method: 'GET',
773
- headers: { Authorization: `Bearer ${this.sessionToken}` },
422
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
774
423
  });
775
424
  }
776
425
 
777
- /**
778
- * Get a single conversation with messages
779
- * @param {string} conversationId - Conversation ID
780
- * @returns {Promise<Object>} Conversation with messages
781
- */
782
426
  async getConversation(conversationId) {
783
- if (!this.isSessionValid()) {
784
- await this.init();
785
- }
427
+ await this.api._ensureSession();
786
428
 
787
- if (this.mock) {
788
- await new Promise((resolve) => setTimeout(resolve, 200));
429
+ if (this.api.mock) {
430
+ await delay$1(200);
789
431
  const conv = MOCK_CONVERSATIONS.find((c) => c.id === conversationId);
790
432
  return {
791
433
  status: true,
792
- data: {
793
- ...conv,
794
- messages: MOCK_MESSAGES[conversationId] || [],
795
- },
434
+ data: { ...conv, messages: MOCK_MESSAGES[conversationId] || [] },
796
435
  };
797
436
  }
798
437
 
799
- return this._makeRequest(
438
+ return this.api._makeRequest(
800
439
  `/widget/messenger/conversations/${conversationId}`,
801
440
  {
802
441
  method: 'GET',
803
- headers: { Authorization: `Bearer ${this.sessionToken}` },
442
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
804
443
  }
805
444
  );
806
445
  }
807
446
 
808
- /**
809
- * Get messages for a conversation
810
- * @param {string} conversationId - Conversation ID
811
- * @param {Object} options - Query options
812
- * @returns {Promise<Object>} Messages list
813
- */
814
- async getMessages(conversationId, options = {}) {
815
- if (!this.isSessionValid()) {
816
- await this.init();
817
- }
818
-
819
- if (this.mock) {
820
- await new Promise((resolve) => setTimeout(resolve, 200));
821
- return {
822
- status: true,
823
- data: MOCK_MESSAGES[conversationId] || [],
824
- };
825
- }
826
-
827
- const params = new URLSearchParams();
828
- if (options.page) params.append('page', options.page);
829
- if (options.limit) params.append('limit', options.limit);
830
-
831
- const endpoint = `/widget/messenger/conversations/${conversationId}/messages${params.toString() ? '?' + params.toString() : ''}`;
832
- return this._makeRequest(endpoint, {
833
- method: 'GET',
834
- headers: { Authorization: `Bearer ${this.sessionToken}` },
835
- });
836
- }
837
-
838
- /**
839
- * Start a new conversation
840
- * @param {Object} data - Conversation data
841
- * @param {string} data.message - Initial message content
842
- * @param {string} data.subject - Optional subject
843
- * @returns {Promise<Object>} Created conversation
844
- */
845
447
  async startConversation(data) {
846
- if (!this.isSessionValid()) {
847
- await this.init();
848
- }
448
+ await this.api._ensureSession();
849
449
 
850
- if (this.mock) {
851
- await new Promise((resolve) => setTimeout(resolve, 300));
450
+ if (this.api.mock) {
451
+ await delay$1(300);
852
452
  const newConv = {
853
453
  id: 'conv_' + Date.now(),
854
454
  subject: data.subject || 'New conversation',
@@ -869,11 +469,11 @@
869
469
  return { status: true, data: newConv };
870
470
  }
871
471
 
872
- return this._makeRequest('/widget/messenger/conversations', {
472
+ return this.api._makeRequest('/widget/messenger/conversations', {
873
473
  method: 'POST',
874
474
  headers: {
875
475
  'Content-Type': 'application/json',
876
- Authorization: `Bearer ${this.sessionToken}`,
476
+ Authorization: `Bearer ${this.api.sessionToken}`,
877
477
  },
878
478
  body: JSON.stringify({
879
479
  message: data.message,
@@ -882,20 +482,11 @@
882
482
  });
883
483
  }
884
484
 
885
- /**
886
- * Send a message in a conversation
887
- * @param {string} conversationId - Conversation ID
888
- * @param {Object} data - Message data
889
- * @param {string} data.content - Message content
890
- * @returns {Promise<Object>} Sent message
891
- */
892
485
  async sendMessage(conversationId, data) {
893
- if (!this.isSessionValid()) {
894
- await this.init();
895
- }
486
+ await this.api._ensureSession();
896
487
 
897
- if (this.mock) {
898
- await new Promise((resolve) => setTimeout(resolve, 200));
488
+ if (this.api.mock) {
489
+ await delay$1(200);
899
490
  const newMessage = {
900
491
  id: 'msg_' + Date.now(),
901
492
  content: data.content,
@@ -909,80 +500,23 @@
909
500
  return { status: true, data: newMessage };
910
501
  }
911
502
 
912
- return this._makeRequest(
503
+ return this.api._makeRequest(
913
504
  `/widget/messenger/conversations/${conversationId}/messages`,
914
505
  {
915
506
  method: 'POST',
916
507
  headers: {
917
508
  'Content-Type': 'application/json',
918
- Authorization: `Bearer ${this.sessionToken}`,
509
+ Authorization: `Bearer ${this.api.sessionToken}`,
919
510
  },
920
511
  body: JSON.stringify({ content: data.content }),
921
512
  }
922
513
  );
923
514
  }
924
515
 
925
- /**
926
- * Send typing indicator
927
- * @param {string} conversationId - Conversation ID
928
- * @param {boolean} isTyping - Whether user is typing
929
- * @returns {Promise<Object>} Response
930
- */
931
- async sendTypingIndicator(conversationId, isTyping) {
932
- if (!this.isSessionValid()) {
933
- await this.init();
934
- }
935
-
936
- if (this.mock) {
937
- return { status: true };
938
- }
939
-
940
- return this._makeRequest(
941
- `/widget/messenger/conversations/${conversationId}/typing`,
942
- {
943
- method: 'POST',
944
- headers: {
945
- 'Content-Type': 'application/json',
946
- Authorization: `Bearer ${this.sessionToken}`,
947
- },
948
- body: JSON.stringify({ is_typing: isTyping }),
949
- }
950
- );
951
- }
952
-
953
- /**
954
- * Mark conversation as read
955
- * @param {string} conversationId - Conversation ID
956
- * @returns {Promise<Object>} Response
957
- */
958
- async markConversationAsRead(conversationId) {
959
- if (!this.isSessionValid()) {
960
- await this.init();
961
- }
962
-
963
- if (this.mock) {
964
- return { status: true };
965
- }
966
-
967
- return this._makeRequest(
968
- `/widget/messenger/conversations/${conversationId}/read`,
969
- {
970
- method: 'POST',
971
- headers: { Authorization: `Bearer ${this.sessionToken}` },
972
- }
973
- );
974
- }
975
-
976
- /**
977
- * Get unread count
978
- * @returns {Promise<Object>} Unread count data
979
- */
980
516
  async getUnreadCount() {
981
- if (!this.isSessionValid()) {
982
- await this.init();
983
- }
517
+ await this.api._ensureSession();
984
518
 
985
- if (this.mock) {
519
+ if (this.api.mock) {
986
520
  const count = MOCK_CONVERSATIONS.reduce(
987
521
  (sum, c) => sum + (c.unread || 0),
988
522
  0
@@ -993,187 +527,325 @@
993
527
  };
994
528
  }
995
529
 
996
- return this._makeRequest('/widget/messenger/unread', {
530
+ return this.api._makeRequest('/widget/messenger/unread', {
997
531
  method: 'GET',
998
- headers: { Authorization: `Bearer ${this.sessionToken}` },
532
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
999
533
  });
1000
534
  }
1001
535
 
1002
- /**
1003
- * Submit conversation rating
1004
- * @param {string} conversationId - Conversation ID
1005
- * @param {Object} data - Rating data
1006
- * @param {number} data.rating - Rating (1-5 or thumbs up/down)
1007
- * @param {string} data.comment - Optional comment
1008
- * @returns {Promise<Object>} Response
1009
- */
1010
- async submitRating(conversationId, data) {
1011
- if (!this.isSessionValid()) {
1012
- await this.init();
1013
- }
536
+ async markConversationAsRead(conversationId) {
537
+ await this.api._ensureSession();
1014
538
 
1015
- if (this.mock) {
1016
- return { status: true, message: 'Thank you for your feedback!' };
539
+ if (this.api.mock) {
540
+ return { status: true };
1017
541
  }
1018
542
 
1019
- return this._makeRequest(
1020
- `/widget/messenger/conversations/${conversationId}/rate`,
543
+ return this.api._makeRequest(
544
+ `/widget/messenger/conversations/${conversationId}/read`,
1021
545
  {
1022
546
  method: 'POST',
1023
- headers: {
1024
- 'Content-Type': 'application/json',
1025
- Authorization: `Bearer ${this.sessionToken}`,
1026
- },
1027
- body: JSON.stringify({
1028
- rating: data.rating,
1029
- comment: data.comment || '',
1030
- }),
547
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
1031
548
  }
1032
549
  );
1033
550
  }
551
+ }
1034
552
 
1035
- /**
1036
- * Identify contact (for logged-in users)
1037
- * @param {Object} data - Contact data
1038
- * @param {string} data.email - Email address
1039
- * @param {string} data.name - Name
1040
- * @returns {Promise<Object>} Response
1041
- */
1042
- async identifyContact(data) {
1043
- if (!this.isSessionValid()) {
1044
- await this.init();
1045
- }
553
+ class SDKError extends Error {
554
+ constructor(message, cause) {
555
+ super(message);
556
+ this.name = 'SDKError';
557
+ this.cause = cause;
1046
558
 
1047
- if (this.mock) {
1048
- return { status: true, message: 'Contact identified' };
559
+ if (Error.captureStackTrace) {
560
+ Error.captureStackTrace(this, SDKError);
1049
561
  }
1050
-
1051
- return this._makeRequest('/widget/messenger/identify', {
1052
- method: 'POST',
1053
- headers: {
1054
- 'Content-Type': 'application/json',
1055
- Authorization: `Bearer ${this.sessionToken}`,
1056
- },
1057
- body: JSON.stringify(data),
1058
- });
1059
562
  }
563
+ }
1060
564
 
1061
- // ==========================================
1062
- // HELP ARTICLES ENDPOINTS
1063
- // ==========================================
565
+ class APIError extends Error {
566
+ constructor(status, message, response) {
567
+ super(message);
568
+ this.name = 'APIError';
569
+ this.status = status;
570
+ this.response = response;
1064
571
 
1065
- /**
1066
- * Get help collections
1067
- * @param {Object} options - Query options
1068
- * @returns {Promise<Object>} Collections list
1069
- */
1070
- async getHelpCollections(options = {}) {
1071
- if (!this.isSessionValid()) {
1072
- await this.init();
572
+ if (Error.captureStackTrace) {
573
+ Error.captureStackTrace(this, APIError);
1073
574
  }
575
+ }
1074
576
 
1075
- if (this.mock) {
1076
- await new Promise((resolve) => setTimeout(resolve, 200));
1077
- return { status: true, data: MOCK_HELP_COLLECTIONS };
1078
- }
577
+ isNetworkError() {
578
+ return this.status === 0;
579
+ }
1079
580
 
1080
- const params = new URLSearchParams();
1081
- if (options.limit) params.append('limit', options.limit);
581
+ isClientError() {
582
+ return this.status >= 400 && this.status < 500;
583
+ }
1082
584
 
1083
- const endpoint = `/widget/help/collections${params.toString() ? '?' + params.toString() : ''}`;
1084
- return this._makeRequest(endpoint, {
1085
- method: 'GET',
1086
- headers: { Authorization: `Bearer ${this.sessionToken}` },
1087
- });
585
+ isServerError() {
586
+ return this.status >= 500 && this.status < 600;
1088
587
  }
588
+ }
1089
589
 
1090
- /**
1091
- * Search help articles
1092
- * @param {string} query - Search query
1093
- * @param {Object} options - Query options
1094
- * @returns {Promise<Object>} Search results
1095
- */
1096
- async searchHelpArticles(query, options = {}) {
1097
- if (!this.isSessionValid()) {
1098
- await this.init();
590
+ class WidgetError extends Error {
591
+ constructor(message, widgetType, widgetId) {
592
+ super(message);
593
+ this.name = 'WidgetError';
594
+ this.widgetType = widgetType;
595
+ this.widgetId = widgetId;
596
+
597
+ if (Error.captureStackTrace) {
598
+ Error.captureStackTrace(this, WidgetError);
1099
599
  }
600
+ }
601
+ }
1100
602
 
1101
- if (this.mock) {
1102
- await new Promise((resolve) => setTimeout(resolve, 200));
1103
- const filtered = MOCK_HELP_COLLECTIONS.filter(
1104
- (c) =>
1105
- c.title.toLowerCase().includes(query.toLowerCase()) ||
1106
- c.description.toLowerCase().includes(query.toLowerCase())
603
+ class ConfigError extends Error {
604
+ constructor(message, configKey) {
605
+ super(message);
606
+ this.name = 'ConfigError';
607
+ this.configKey = configKey;
608
+
609
+ if (Error.captureStackTrace) {
610
+ Error.captureStackTrace(this, ConfigError);
611
+ }
612
+ }
613
+ }
614
+
615
+ class ValidationError extends Error {
616
+ constructor(message, field, value) {
617
+ super(message);
618
+ this.name = 'ValidationError';
619
+ this.field = field;
620
+ this.value = value;
621
+
622
+ if (Error.captureStackTrace) {
623
+ Error.captureStackTrace(this, ValidationError);
624
+ }
625
+ }
626
+ }
627
+
628
+ class SurveyService {
629
+ constructor(baseAPI) {
630
+ this.api = baseAPI;
631
+ }
632
+
633
+ async getActiveSurveys(context = {}) {
634
+ await this.api._ensureSession();
635
+
636
+ if (this.api.mock) {
637
+ await delay$1(200);
638
+ return { success: true, data: MOCK_SURVEYS };
639
+ }
640
+
641
+ const params = {
642
+ url:
643
+ context.url ||
644
+ (typeof window !== 'undefined' ? window.location.href : ''),
645
+ ...getDeviceInfo(),
646
+ ...(context.userProperties && {
647
+ user_properties: context.userProperties,
648
+ }),
649
+ };
650
+
651
+ return this.api._handleAuthRetry(async () => {
652
+ const endpoint = this.api._getEndpointWithParams(
653
+ '/widget/surveys/active',
654
+ params
1107
655
  );
1108
- return { status: true, data: filtered };
656
+ return await this.api._makeRequest(endpoint, {
657
+ method: 'GET',
658
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
659
+ });
660
+ });
661
+ }
662
+
663
+ async submitSurveyResponse(surveyId, responseData) {
664
+ if (!surveyId) throw new APIError(400, 'Survey ID is required');
665
+
666
+ await this.api._ensureSession();
667
+
668
+ if (this.api.mock) {
669
+ await delay$1(300);
670
+ return {
671
+ success: true,
672
+ data: {
673
+ id: 'mock_response_' + Date.now(),
674
+ survey_id: surveyId,
675
+ ...responseData,
676
+ },
677
+ message: 'Survey response submitted successfully!',
678
+ };
1109
679
  }
1110
680
 
1111
- const params = new URLSearchParams({ q: query });
1112
- if (options.limit) params.append('limit', options.limit);
681
+ const payload = {
682
+ rating: responseData.rating,
683
+ feedback: responseData.feedback || '',
684
+ answers: responseData.answers || {},
685
+ };
1113
686
 
1114
- return this._makeRequest(`/widget/help/search?${params.toString()}`, {
1115
- method: 'GET',
1116
- headers: { Authorization: `Bearer ${this.sessionToken}` },
687
+ return this.api._handleAuthRetry(async () => {
688
+ return await this.api._makeRequest(
689
+ `/widget/surveys/${surveyId}/responses`,
690
+ {
691
+ method: 'POST',
692
+ body: JSON.stringify(payload),
693
+ headers: {
694
+ 'Content-Type': 'application/json',
695
+ Authorization: `Bearer ${this.api.sessionToken}`,
696
+ },
697
+ }
698
+ );
1117
699
  });
1118
700
  }
1119
701
 
1120
- // ==========================================
1121
- // CHANGELOG ENDPOINTS
1122
- // ==========================================
702
+ async dismissSurvey(surveyId) {
703
+ if (!surveyId) throw new APIError(400, 'Survey ID is required');
1123
704
 
1124
- /**
1125
- * Get published changelogs
1126
- * @param {Object} options - Optional query parameters
1127
- * @param {number} options.limit - Number of changelogs to fetch
1128
- * @param {number} options.offset - Offset for pagination
1129
- * @returns {Promise<Object>} Changelogs response
1130
- */
1131
- async getChangelogs(options = {}) {
1132
- if (!this.isSessionValid()) {
1133
- await this.init();
705
+ await this.api._ensureSession();
706
+
707
+ if (this.api.mock) {
708
+ await delay$1(100);
709
+ return { success: true, message: 'Survey dismissed successfully' };
1134
710
  }
1135
711
 
1136
- if (!this.sessionToken) {
1137
- throw new APIError(401, 'No valid session token available');
712
+ return this.api._handleAuthRetry(async () => {
713
+ return await this.api._makeRequest(
714
+ `/widget/surveys/${surveyId}/dismiss`,
715
+ {
716
+ method: 'POST',
717
+ headers: { Authorization: `Bearer ${this.api.sessionToken}` },
718
+ }
719
+ );
720
+ });
721
+ }
722
+ }
723
+
724
+ class BaseAPIService {
725
+ constructor(config = {}) {
726
+ this.workspace = config.workspace;
727
+ this.sessionToken = null;
728
+ this.sessionExpiry = null;
729
+ this.userContext = config.userContext || null;
730
+ this.mock = config.mock || false;
731
+ this.env = config.env || 'production';
732
+ this.baseURL = this._getBaseURL(config);
733
+
734
+ this._loadStoredSession();
735
+ }
736
+
737
+ _getBaseURL(config) {
738
+ if (config.apiUrl) return config.apiUrl;
739
+
740
+ const ENV_URLS = {
741
+ production: {
742
+ base: 'https://api.product7.io/api/v1',
743
+ withWorkspace: (ws) => `https://${ws}.api.product7.io/api/v1`,
744
+ },
745
+ staging: {
746
+ base: 'https://staging.api.product7.io/api/v1',
747
+ withWorkspace: (ws) => `https://${ws}.staging.api.product7.io/api/v1`,
748
+ },
749
+ };
750
+
751
+ const envConfig = ENV_URLS[this.env] || ENV_URLS.production;
752
+ return this.workspace
753
+ ? envConfig.withWorkspace(this.workspace)
754
+ : envConfig.base;
755
+ }
756
+
757
+ async init(userContext = null) {
758
+ if (userContext) {
759
+ this.userContext = userContext;
760
+ }
761
+
762
+ if (this.isSessionValid()) {
763
+ return { sessionToken: this.sessionToken };
764
+ }
765
+
766
+ if (!this.workspace || !this.userContext) {
767
+ throw new APIError(
768
+ 400,
769
+ `Missing ${!this.workspace ? 'workspace' : 'user context'} for initialization`
770
+ );
1138
771
  }
1139
772
 
1140
- // Mock mode - return mock changelogs
1141
773
  if (this.mock) {
1142
- await new Promise((resolve) => setTimeout(resolve, 300));
1143
- return {
1144
- success: true,
1145
- data: MOCK_CHANGELOGS,
1146
- };
774
+ return this._initMockSession();
1147
775
  }
1148
776
 
1149
- try {
1150
- const queryParams = new URLSearchParams();
1151
- if (options.limit) queryParams.append('limit', options.limit);
1152
- if (options.offset) queryParams.append('offset', options.offset);
777
+ return this._initRealSession();
778
+ }
779
+
780
+ async _initMockSession() {
781
+ this.sessionToken = 'mock_session_' + Date.now();
782
+ this.sessionExpiry = new Date(Date.now() + 3600 * 1000);
783
+ this._storeSession();
784
+ return {
785
+ sessionToken: this.sessionToken,
786
+ config: {
787
+ primaryColor: '#21244A',
788
+ backgroundColor: '#ffffff',
789
+ textColor: '#1F2937',
790
+ boardId: 'feature-requests',
791
+ size: 'medium',
792
+ displayMode: 'modal',
793
+ },
794
+ expiresIn: 3600,
795
+ };
796
+ }
1153
797
 
1154
- const endpoint = `/widget/changelogs${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
798
+ async _initRealSession() {
799
+ const payload = {
800
+ workspace: this.workspace,
801
+ user: this.userContext,
802
+ };
1155
803
 
1156
- const response = await this._makeRequest(endpoint, {
1157
- method: 'GET',
1158
- headers: {
1159
- Authorization: `Bearer ${this.sessionToken}`,
1160
- },
804
+ try {
805
+ const response = await this._makeRequest('/widget/init', {
806
+ method: 'POST',
807
+ body: JSON.stringify(payload),
808
+ headers: { 'Content-Type': 'application/json' },
1161
809
  });
1162
810
 
1163
- return response;
811
+ this.sessionToken = response.session_token;
812
+ this.sessionExpiry = new Date(Date.now() + response.expires_in * 1000);
813
+ this._storeSession();
814
+
815
+ return {
816
+ sessionToken: this.sessionToken,
817
+ config: response.config || {},
818
+ expiresIn: response.expires_in,
819
+ };
820
+ } catch (error) {
821
+ throw new APIError(
822
+ error.status || 500,
823
+ `Failed to initialize widget: ${error.message}`,
824
+ error.response
825
+ );
826
+ }
827
+ }
828
+
829
+ async _ensureSession() {
830
+ if (!this.isSessionValid()) {
831
+ await this.init();
832
+ }
833
+ if (!this.sessionToken) {
834
+ throw new APIError(401, 'No valid session token available');
835
+ }
836
+ }
837
+
838
+ async _handleAuthRetry(method, ...args) {
839
+ try {
840
+ return await method.apply(this, args);
1164
841
  } catch (error) {
1165
842
  if (error.status === 401) {
1166
843
  this.sessionToken = null;
1167
844
  this.sessionExpiry = null;
1168
845
  await this.init();
1169
- return this.getChangelogs(options);
846
+ return await method.apply(this, args);
1170
847
  }
1171
-
1172
- throw new APIError(
1173
- error.status || 500,
1174
- `Failed to get changelogs: ${error.message}`,
1175
- error.response
1176
- );
848
+ throw error;
1177
849
  }
1178
850
  }
1179
851
 
@@ -1185,12 +857,7 @@
1185
857
 
1186
858
  setUserContext(userContext) {
1187
859
  this.userContext = userContext;
1188
- if (typeof localStorage !== 'undefined') {
1189
- localStorage.setItem(
1190
- 'feedbackSDK_userContext',
1191
- JSON.stringify(userContext)
1192
- );
1193
- }
860
+ this._storeData('feedbackSDK_userContext', userContext);
1194
861
  }
1195
862
 
1196
863
  getUserContext() {
@@ -1200,30 +867,26 @@
1200
867
  clearSession() {
1201
868
  this.sessionToken = null;
1202
869
  this.sessionExpiry = null;
1203
- if (typeof localStorage !== 'undefined') {
1204
- localStorage.removeItem('feedbackSDK_session');
1205
- localStorage.removeItem('feedbackSDK_userContext');
1206
- }
870
+ this._removeData('feedbackSDK_session');
871
+ this._removeData('feedbackSDK_userContext');
1207
872
  }
1208
873
 
1209
874
  _storeSession() {
1210
875
  if (typeof localStorage === 'undefined') return;
1211
-
1212
876
  try {
1213
877
  const sessionData = {
1214
878
  token: this.sessionToken,
1215
879
  expiry: this.sessionExpiry.toISOString(),
1216
880
  workspace: this.workspace,
1217
881
  };
1218
- localStorage.setItem('feedbackSDK_session', JSON.stringify(sessionData));
882
+ this._storeData('feedbackSDK_session', sessionData);
1219
883
  } catch (error) {
1220
- // Silently fail if localStorage is not available
884
+ // Silent fail
1221
885
  }
1222
886
  }
1223
887
 
1224
888
  _loadStoredSession() {
1225
889
  if (typeof localStorage === 'undefined') return false;
1226
-
1227
890
  try {
1228
891
  const stored = localStorage.getItem('feedbackSDK_session');
1229
892
  if (!stored) return false;
@@ -1238,6 +901,18 @@
1238
901
  }
1239
902
  }
1240
903
 
904
+ _storeData(key, value) {
905
+ if (typeof localStorage !== 'undefined') {
906
+ localStorage.setItem(key, JSON.stringify(value));
907
+ }
908
+ }
909
+
910
+ _removeData(key) {
911
+ if (typeof localStorage !== 'undefined') {
912
+ localStorage.removeItem(key);
913
+ }
914
+ }
915
+
1241
916
  async _makeRequest(endpoint, options = {}) {
1242
917
  const url = `${this.baseURL}${endpoint}`;
1243
918
 
@@ -1266,68 +941,172 @@
1266
941
 
1267
942
  return await response.text();
1268
943
  } catch (error) {
1269
- if (error instanceof APIError) {
1270
- throw error;
1271
- }
944
+ if (error instanceof APIError) throw error;
1272
945
  throw new APIError(0, error.message, null);
1273
946
  }
1274
947
  }
1275
- }
1276
948
 
1277
- class EventBus {
1278
- constructor() {
1279
- this.events = new Map();
949
+ _buildQueryParams(params) {
950
+ const queryParams = new URLSearchParams();
951
+ Object.entries(params).forEach(([key, value]) => {
952
+ if (value !== undefined && value !== null) {
953
+ queryParams.append(
954
+ key,
955
+ typeof value === 'object' ? JSON.stringify(value) : value
956
+ );
957
+ }
958
+ });
959
+ return queryParams.toString();
1280
960
  }
1281
961
 
1282
- on(event, callback) {
1283
- if (!this.events.has(event)) {
1284
- this.events.set(event, []);
1285
- }
1286
- this.events.get(event).push(callback);
1287
-
1288
- return () => this.off(event, callback);
962
+ _getEndpointWithParams(endpoint, params) {
963
+ const queryString = this._buildQueryParams(params);
964
+ return `${endpoint}${queryString ? '?' + queryString : ''}`;
1289
965
  }
966
+ }
1290
967
 
1291
- off(event, callback) {
1292
- const callbacks = this.events.get(event);
1293
- if (callbacks) {
1294
- const index = callbacks.indexOf(callback);
1295
- if (index > -1) {
1296
- callbacks.splice(index, 1);
1297
- }
1298
- }
968
+ class APIService extends BaseAPIService {
969
+ constructor(config = {}) {
970
+ super(config);
971
+
972
+ this.feedback = new FeedbackService(this);
973
+ this.survey = new SurveyService(this);
974
+ this.messenger = new MessengerService(this);
975
+ this.help = new HelpService(this);
976
+ this.changelog = new ChangelogService(this);
1299
977
  }
1300
978
 
1301
- emit(event, data) {
1302
- const callbacks = this.events.get(event);
1303
- if (callbacks) {
1304
- callbacks.forEach((callback) => {
1305
- try {
1306
- callback(data);
1307
- } catch (error) {
1308
- console.error('[FeedbackSDK] Event callback error:', error);
1309
- }
1310
- });
1311
- }
979
+ async submitFeedback(data) {
980
+ return this.feedback.submitFeedback(data);
1312
981
  }
1313
982
 
1314
- once(event, callback) {
1315
- const unsubscribe = this.on(event, (data) => {
1316
- callback(data);
1317
- unsubscribe();
1318
- });
1319
- return unsubscribe;
983
+ async getActiveSurveys(context) {
984
+ return this.survey.getActiveSurveys(context);
1320
985
  }
1321
986
 
1322
- clear() {
1323
- this.events.clear();
987
+ async submitSurveyResponse(surveyId, responseData) {
988
+ return this.survey.submitSurveyResponse(surveyId, responseData);
1324
989
  }
1325
990
 
1326
- getListenerCount(event) {
1327
- const callbacks = this.events.get(event);
1328
- return callbacks ? callbacks.length : 0;
991
+ async dismissSurvey(surveyId) {
992
+ return this.survey.dismissSurvey(surveyId);
1329
993
  }
1330
- }
994
+
995
+ async getMessengerSettings() {
996
+ return this.messenger.getMessengerSettings();
997
+ }
998
+
999
+ async checkAgentsOnline() {
1000
+ return this.messenger.checkAgentsOnline();
1001
+ }
1002
+
1003
+ async getConversations(options) {
1004
+ return this.messenger.getConversations(options);
1005
+ }
1006
+
1007
+ async getConversation(conversationId) {
1008
+ return this.messenger.getConversation(conversationId);
1009
+ }
1010
+
1011
+ async getMessages(conversationId, options) {
1012
+ return this.messenger.getMessages(conversationId, options);
1013
+ }
1014
+
1015
+ async startConversation(data) {
1016
+ return this.messenger.startConversation(data);
1017
+ }
1018
+
1019
+ async sendMessage(conversationId, data) {
1020
+ return this.messenger.sendMessage(conversationId, data);
1021
+ }
1022
+
1023
+ async sendTypingIndicator(conversationId, isTyping) {
1024
+ return this.messenger.sendTypingIndicator(conversationId, isTyping);
1025
+ }
1026
+
1027
+ async markConversationAsRead(conversationId) {
1028
+ return this.messenger.markConversationAsRead(conversationId);
1029
+ }
1030
+
1031
+ async getUnreadCount() {
1032
+ return this.messenger.getUnreadCount();
1033
+ }
1034
+
1035
+ async submitRating(conversationId, data) {
1036
+ return this.messenger.submitRating(conversationId, data);
1037
+ }
1038
+
1039
+ async identifyContact(data) {
1040
+ return this.messenger.identifyContact(data);
1041
+ }
1042
+
1043
+ async getHelpCollections(options) {
1044
+ return this.help.getHelpCollections(options);
1045
+ }
1046
+
1047
+ async searchHelpArticles(query, options) {
1048
+ return this.help.searchHelpArticles(query, options);
1049
+ }
1050
+
1051
+ async getChangelogs(options) {
1052
+ return this.changelog.getChangelogs(options);
1053
+ }
1054
+ }
1055
+
1056
+ class EventBus {
1057
+ constructor() {
1058
+ this.events = new Map();
1059
+ }
1060
+
1061
+ on(event, callback) {
1062
+ if (!this.events.has(event)) {
1063
+ this.events.set(event, []);
1064
+ }
1065
+ this.events.get(event).push(callback);
1066
+
1067
+ return () => this.off(event, callback);
1068
+ }
1069
+
1070
+ off(event, callback) {
1071
+ const callbacks = this.events.get(event);
1072
+ if (callbacks) {
1073
+ const index = callbacks.indexOf(callback);
1074
+ if (index > -1) {
1075
+ callbacks.splice(index, 1);
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ emit(event, data) {
1081
+ const callbacks = this.events.get(event);
1082
+ if (callbacks) {
1083
+ callbacks.forEach((callback) => {
1084
+ try {
1085
+ callback(data);
1086
+ } catch (error) {
1087
+ console.error('[FeedbackSDK] Event callback error:', error);
1088
+ }
1089
+ });
1090
+ }
1091
+ }
1092
+
1093
+ once(event, callback) {
1094
+ const unsubscribe = this.on(event, (data) => {
1095
+ callback(data);
1096
+ unsubscribe();
1097
+ });
1098
+ return unsubscribe;
1099
+ }
1100
+
1101
+ clear() {
1102
+ this.events.clear();
1103
+ }
1104
+
1105
+ getListenerCount(event) {
1106
+ const callbacks = this.events.get(event);
1107
+ return callbacks ? callbacks.length : 0;
1108
+ }
1109
+ }
1331
1110
 
1332
1111
  function generateId(prefix = 'feedback') {
1333
1112
  const timestamp = Date.now();
@@ -3503,15 +3282,12 @@
3503
3282
  }
3504
3283
  }
3505
3284
 
3506
- /**
3507
- * MessengerLauncher - Floating trigger button for messenger
3508
- */
3509
3285
  class MessengerLauncher {
3510
3286
  constructor(state, options = {}) {
3511
3287
  this.state = state;
3512
3288
  this.options = {
3513
3289
  position: options.position || 'bottom-right',
3514
- primaryColor: options.primaryColor || '#1c1c1e',
3290
+ primaryColor: options.primaryColor || '#155eff',
3515
3291
  ...options,
3516
3292
  };
3517
3293
  this.element = null;
@@ -3525,7 +3301,6 @@
3525
3301
  this._updateContent();
3526
3302
  this._attachEvents();
3527
3303
 
3528
- // Subscribe to state changes
3529
3304
  this._unsubscribe = this.state.subscribe((type, data) => {
3530
3305
  if (type === 'openChange') {
3531
3306
  this._updateIcon();
@@ -3547,10 +3322,14 @@
3547
3322
  this.element.innerHTML = `
3548
3323
  <button class="messenger-launcher-btn" aria-label="Open messenger" style="background: ${this.options.primaryColor};">
3549
3324
  <span class="messenger-launcher-icon messenger-launcher-icon-chat">
3550
- <i class="ph ph-chat-circle-dots" style="font-size: 24px;"></i>
3325
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#ffffff" viewBox="0 0 256 256">
3326
+ <path d="M144,140a12,12,0,1,1-12-12A12,12,0,0,1,144,140Zm44-12a12,12,0,1,0,12,12A12,12,0,0,0,188,128Zm51.34,83.47a16,16,0,0,1-19.87,19.87l-24.71-7.27A80,80,0,0,1,86.43,183.42a79,79,0,0,1-25.19-7.35l-24.71,7.27a16,16,0,0,1-19.87-19.87l7.27-24.71A80,80,0,1,1,169.58,72.59a80,80,0,0,1,62.49,114.17ZM81.3,166.3a79.94,79.94,0,0,1,70.38-93.87A64,64,0,0,0,39.55,134.19a8,8,0,0,1,.63,6L32,168l27.76-8.17a8,8,0,0,1,6,.63A63.45,63.45,0,0,0,81.3,166.3Zm135.15,15.89a64,64,0,1,0-26.26,26.26,8,8,0,0,1,6-.63L224,216l-8.17-27.76A8,8,0,0,1,216.45,182.19Z"></path>
3327
+ </svg>
3551
3328
  </span>
3552
3329
  <span class="messenger-launcher-icon messenger-launcher-icon-close" style="display: none;">
3553
- <i class="ph ph-x" style="font-size: 24px;"></i>
3330
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#ffffff" viewBox="0 0 256 256">
3331
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
3332
+ </svg>
3554
3333
  </span>
3555
3334
  ${badgeHtml}
3556
3335
  </button>
@@ -3733,19 +3512,31 @@
3733
3512
  }
3734
3513
 
3735
3514
  _getHomeIcon() {
3736
- return `<i class="ph-duotone ph-house" style="font-size: 24px;"></i>`;
3515
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#000000" viewBox="0 0 256 256">
3516
+ <path d="M216,115.54V208a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V160a8,8,0,0,0-8-8H112a8,8,0,0,0-8,8v48a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V115.54a8,8,0,0,1,2.62-5.92l80-75.54a8,8,0,0,1,10.77,0l80,75.54A8,8,0,0,1,216,115.54Z" opacity="0.2"></path>
3517
+ <path d="M218.83,103.77l-80-75.48a1.14,1.14,0,0,1-.11-.11,16,16,0,0,0-21.53,0l-.11.11L37.17,103.77A16,16,0,0,0,32,115.55V208a16,16,0,0,0,16,16H96a16,16,0,0,0,16-16V160h32v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V115.55A16,16,0,0,0,218.83,103.77ZM208,208H160V160a16,16,0,0,0-16-16H112a16,16,0,0,0-16,16v48H48V115.55l.11-.1L128,40l79.9,75.43.11.1Z"></path>
3518
+ </svg>`;
3737
3519
  }
3738
3520
 
3739
3521
  _getMessagesIcon() {
3740
- return `<i class="ph-duotone ph-chat" style="font-size: 24px;"></i>`;
3522
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#000000" viewBox="0 0 256 256">
3523
+ <path d="M224,64V192a8,8,0,0,1-8,8H82.5a8,8,0,0,0-5.15,1.88l-32.2,28.23A8,8,0,0,1,32,224V64a8,8,0,0,1,8-8H216A8,8,0,0,1,224,64Z" opacity="0.2"></path>
3524
+ <path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78.69.69,0,0,0,.13-.11L82.5,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H82.5a16,16,0,0,0-10.3,3.75l-.12.11L40,224V64H216Z"></path>
3525
+ </svg>`;
3741
3526
  }
3742
3527
 
3743
3528
  _getHelpIcon() {
3744
- return `<i class="ph-duotone ph-question" style="font-size: 24px;"></i>`;
3529
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#000000" viewBox="0 0 256 256">
3530
+ <path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"></path>
3531
+ <path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path>
3532
+ </svg>`;
3745
3533
  }
3746
3534
 
3747
3535
  _getChangelogIcon() {
3748
- return `<i class="ph-duotone ph-megaphone" style="font-size: 24px;"></i>`;
3536
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#000000" viewBox="0 0 256 256">
3537
+ <path d="M200,144a32,32,0,0,1-63.5,4.5L85.83,121.25a32.07,32.07,0,0,1-41.54-34l-24.15-21a8,8,0,0,1,10.25-12.29L54.55,75a32,32,0,0,1,59.16,2.5l50.63,27.25a31.88,31.88,0,0,1,35.66,7l26.46-23A8,8,0,1,1,236.71,101l-26.46,23A32,32,0,0,1,200,144Z" opacity="0.2"></path>
3538
+ <path d="M228.54,86.66l-26.46,23.07A40,40,0,0,0,168,72.13L120.89,46.5a40,40,0,0,0-75.44-4l-22-19.2a8,8,0,0,0-10.5,12L35.44,54.77a40,40,0,0,0,50,61.07l47.1,25.64a40,40,0,0,0,75.41,4.07l26.46-23.07a8,8,0,0,0-10.5-12ZM56,96A24,24,0,1,1,77.25,82.75,24,24,0,0,1,56,96Zm144,64a24,24,0,1,1,24-24A24,24,0,0,1,200,160Z"></path>
3539
+ </svg>`;
3749
3540
  }
3750
3541
 
3751
3542
  destroy() {
@@ -3888,9 +3679,6 @@
3888
3679
  }
3889
3680
  }
3890
3681
 
3891
- /**
3892
- * ChangelogView - Changelog and announcements
3893
- */
3894
3682
  class ChangelogView {
3895
3683
  constructor(state, options = {}) {
3896
3684
  this.state = state;
@@ -3905,7 +3693,6 @@
3905
3693
 
3906
3694
  this._updateContent();
3907
3695
 
3908
- // Subscribe to state changes
3909
3696
  this._unsubscribe = this.state.subscribe((type) => {
3910
3697
  if (type === 'changelogUpdate') {
3911
3698
  this._updateChangelogList();
@@ -3922,7 +3709,9 @@
3922
3709
  <div class="messenger-changelog-header">
3923
3710
  <h2>Changelog</h2>
3924
3711
  <button class="messenger-close-btn" aria-label="Close">
3925
- <i class="ph ph-x" style="font-size: 20px;"></i>
3712
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
3713
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
3714
+ </svg>
3926
3715
  </button>
3927
3716
  </div>
3928
3717
 
@@ -3958,7 +3747,6 @@
3958
3747
  .map((item) => this._renderChangelogCard(item))
3959
3748
  .join('');
3960
3749
 
3961
- // Attach click events
3962
3750
  this._attachChangelogEvents();
3963
3751
  }
3964
3752
 
@@ -3989,7 +3777,9 @@
3989
3777
  ${item.description ? `<p class="messenger-changelog-description">${this._truncateText(item.description, 100)}</p>` : ''}
3990
3778
  <div class="messenger-changelog-meta">
3991
3779
  <span class="messenger-changelog-date">${dateStr}</span>
3992
- <i class="ph ph-caret-right messenger-changelog-arrow" style="font-size: 16px;"></i>
3780
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256" class="messenger-changelog-arrow">
3781
+ <path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path>
3782
+ </svg>
3993
3783
  </div>
3994
3784
  </div>
3995
3785
  </div>
@@ -4024,7 +3814,9 @@
4024
3814
  return `
4025
3815
  <div class="messenger-changelog-empty">
4026
3816
  <div class="messenger-changelog-empty-icon">
4027
- <i class="ph ph-megaphone" style="font-size: 48px;"></i>
3817
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="#000000" viewBox="0 0 256 256">
3818
+ <path d="M228.54,86.66l-26.46,23.07A40,40,0,0,0,168,72.13L120.89,46.5a40,40,0,0,0-75.44-4l-22-19.2a8,8,0,0,0-10.5,12L35.44,54.77a40,40,0,0,0,50,61.07l47.1,25.64a40,40,0,0,0,75.41,4.07l26.46-23.07a8,8,0,0,0-10.5-12ZM56,96A24,24,0,1,1,77.25,82.75,24,24,0,0,1,56,96Zm144,64a24,24,0,1,1,24-24A24,24,0,0,1,200,160Z"></path>
3819
+ </svg>
4028
3820
  </div>
4029
3821
  <h3>No changelog yet</h3>
4030
3822
  <p>Check back later for updates</p>
@@ -4049,7 +3841,6 @@
4049
3841
  }
4050
3842
 
4051
3843
  _attachEvents() {
4052
- // Close button
4053
3844
  this.element
4054
3845
  .querySelector('.messenger-close-btn')
4055
3846
  .addEventListener('click', () => {
@@ -4087,9 +3878,6 @@
4087
3878
  }
4088
3879
  }
4089
3880
 
4090
- /**
4091
- * ChatView - Individual conversation chat
4092
- */
4093
3881
  class ChatView {
4094
3882
  constructor(state, options = {}) {
4095
3883
  this.state = state;
@@ -4107,7 +3895,6 @@
4107
3895
 
4108
3896
  this._updateContent();
4109
3897
 
4110
- // Subscribe to state changes
4111
3898
  this._unsubscribe = this.state.subscribe((type, data) => {
4112
3899
  if (
4113
3900
  type === 'messageAdded' &&
@@ -4158,14 +3945,18 @@
4158
3945
  this.element.innerHTML = `
4159
3946
  <div class="messenger-chat-header">
4160
3947
  <button class="messenger-back-btn" aria-label="Back">
4161
- <i class="ph ph-caret-left" style="font-size: 20px;"></i>
3948
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
3949
+ <path d="M165.66,202.34a8,8,0,0,1-11.32,11.32l-80-80a8,8,0,0,1,0-11.32l80-80a8,8,0,0,1,11.32,11.32L91.31,128Z"></path>
3950
+ </svg>
4162
3951
  </button>
4163
3952
  <div class="messenger-chat-header-info">
4164
3953
  ${avatarHtml}
4165
3954
  <span class="messenger-chat-title">${title}</span>
4166
3955
  </div>
4167
3956
  <button class="messenger-close-btn" aria-label="Close">
4168
- <i class="ph ph-x" style="font-size: 20px;"></i>
3957
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
3958
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
3959
+ </svg>
4169
3960
  </button>
4170
3961
  </div>
4171
3962
 
@@ -4184,7 +3975,9 @@
4184
3975
  <textarea class="messenger-compose-input" placeholder="${placeholder}" rows="1"></textarea>
4185
3976
  </div>
4186
3977
  <button class="messenger-compose-send" aria-label="Send" disabled>
4187
- <i class="ph ph-paper-plane-tilt" style="font-size: 20px;"></i>
3978
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
3979
+ <path d="M227.32,28.68a16,16,0,0,0-15.66-4.08l-.15,0L19.57,82.84a16,16,0,0,0-2.49,29.8L102,154l41.3,84.87A15.86,15.86,0,0,0,157.74,248q.69,0,1.38-.06a15.88,15.88,0,0,0,14-11.51l58.2-191.94c0-.05,0-.1,0-.15A16,16,0,0,0,227.32,28.68ZM157.83,231.85l-.05.14L118.42,148.9l47.24-47.25a8,8,0,0,0-11.31-11.31L107.1,137.58,24,98.22l.14,0L216,40Z"></path>
3980
+ </svg>
4188
3981
  </button>
4189
3982
  </div>
4190
3983
  `;
@@ -4304,7 +4097,6 @@
4304
4097
 
4305
4098
  _formatMessageContent(content) {
4306
4099
  if (!content) return '';
4307
- // Basic HTML escaping and line breaks
4308
4100
  return content
4309
4101
  .replace(/&/g, '&amp;')
4310
4102
  .replace(/</g, '&lt;')
@@ -4339,33 +4131,27 @@
4339
4131
  }
4340
4132
 
4341
4133
  _attachEvents() {
4342
- // Back button
4343
4134
  this.element
4344
4135
  .querySelector('.messenger-back-btn')
4345
4136
  .addEventListener('click', () => {
4346
4137
  this.state.setView('messages');
4347
4138
  });
4348
4139
 
4349
- // Close button
4350
4140
  this.element
4351
4141
  .querySelector('.messenger-close-btn')
4352
4142
  .addEventListener('click', () => {
4353
4143
  this.state.setOpen(false);
4354
4144
  });
4355
4145
 
4356
- // Compose input
4357
4146
  const input = this.element.querySelector('.messenger-compose-input');
4358
4147
  const sendBtn = this.element.querySelector('.messenger-compose-send');
4359
4148
 
4360
4149
  input.addEventListener('input', () => {
4361
- // Auto-resize textarea
4362
4150
  input.style.height = 'auto';
4363
4151
  input.style.height = Math.min(input.scrollHeight, 120) + 'px';
4364
4152
 
4365
- // Enable/disable send button
4366
4153
  sendBtn.disabled = !input.value.trim();
4367
4154
 
4368
- // Send typing indicator
4369
4155
  if (input.value.trim()) {
4370
4156
  this._startTyping();
4371
4157
  }
@@ -4389,18 +4175,15 @@
4389
4175
 
4390
4176
  if (!content) return;
4391
4177
 
4392
- // Stop typing indicator
4393
4178
  this._stopTyping();
4394
4179
 
4395
4180
  const isNewConversation = !this.state.activeConversationId;
4396
4181
 
4397
4182
  if (isNewConversation) {
4398
- // Start a new conversation
4399
4183
  if (this.options.onStartConversation) {
4400
4184
  this.options.onStartConversation(content);
4401
4185
  }
4402
4186
  } else {
4403
- // Add message to existing conversation
4404
4187
  const message = {
4405
4188
  id: 'msg_' + Date.now(),
4406
4189
  content: content,
@@ -4410,13 +4193,11 @@
4410
4193
 
4411
4194
  this.state.addMessage(this.state.activeConversationId, message);
4412
4195
 
4413
- // Emit event for API integration
4414
4196
  if (this.options.onSendMessage) {
4415
4197
  this.options.onSendMessage(this.state.activeConversationId, message);
4416
4198
  }
4417
4199
  }
4418
4200
 
4419
- // Clear input
4420
4201
  input.value = '';
4421
4202
  input.style.height = 'auto';
4422
4203
  this.element.querySelector('.messenger-compose-send').disabled = true;
@@ -4430,7 +4211,6 @@
4430
4211
  }
4431
4212
  }
4432
4213
 
4433
- // Reset typing timeout
4434
4214
  if (this._typingTimeout) {
4435
4215
  clearTimeout(this._typingTimeout);
4436
4216
  }
@@ -4485,9 +4265,6 @@
4485
4265
  }
4486
4266
  }
4487
4267
 
4488
- /**
4489
- * ConversationsView - Message thread list
4490
- */
4491
4268
  class ConversationsView {
4492
4269
  constructor(state, options = {}) {
4493
4270
  this.state = state;
@@ -4503,7 +4280,6 @@
4503
4280
  this._updateContent();
4504
4281
  this._attachEvents();
4505
4282
 
4506
- // Subscribe to state changes
4507
4283
  this._unsubscribe = this.state.subscribe((type) => {
4508
4284
  if (
4509
4285
  type === 'conversationsUpdate' ||
@@ -4526,7 +4302,9 @@
4526
4302
  conversationsHtml = `
4527
4303
  <div class="messenger-conversations-empty">
4528
4304
  <div class="messenger-conversations-empty-icon">
4529
- <i class="ph ph-chat" style="font-size: 48px;"></i>
4305
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="#000000" viewBox="0 0 256 256">
4306
+ <path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78.69.69,0,0,0,.13-.11L82.5,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H82.5a16,16,0,0,0-10.3,3.75l-.12.11L40,224V64H216Z"></path>
4307
+ </svg>
4530
4308
  </div>
4531
4309
  <h3>No conversations yet</h3>
4532
4310
  <p>Start a new conversation with our team</p>
@@ -4544,7 +4322,9 @@
4544
4322
  <div class="messenger-conversations-header">
4545
4323
  <h2>Messages</h2>
4546
4324
  <button class="messenger-close-btn" aria-label="Close">
4547
- <i class="ph ph-x" style="font-size: 20px;"></i>
4325
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
4326
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
4327
+ </svg>
4548
4328
  </button>
4549
4329
  </div>
4550
4330
 
@@ -4556,7 +4336,9 @@
4556
4336
  <button class="messenger-new-message-btn">
4557
4337
  <div class="messenger-new-message-avatars">${avatarsHtml}</div>
4558
4338
  <span>Send us a message</span>
4559
- <i class="ph ph-arrow-right" style="font-size: 16px;"></i>
4339
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
4340
+ <path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path>
4341
+ </svg>
4560
4342
  </button>
4561
4343
  </div>
4562
4344
  `;
@@ -4655,7 +4437,6 @@
4655
4437
  }
4656
4438
 
4657
4439
  _attachEvents() {
4658
- // Close button
4659
4440
  const closeBtn = this.element.querySelector('.messenger-close-btn');
4660
4441
  if (closeBtn) {
4661
4442
  closeBtn.addEventListener('click', () => {
@@ -4663,7 +4444,6 @@
4663
4444
  });
4664
4445
  }
4665
4446
 
4666
- // Conversation items
4667
4447
  this.element
4668
4448
  .querySelectorAll('.messenger-conversation-item')
4669
4449
  .forEach((item) => {
@@ -4673,14 +4453,12 @@
4673
4453
  this.state.markAsRead(convId);
4674
4454
  this.state.setView('chat');
4675
4455
 
4676
- // Notify widget to fetch messages
4677
4456
  if (this.options.onSelectConversation) {
4678
4457
  this.options.onSelectConversation(convId);
4679
4458
  }
4680
4459
  });
4681
4460
  });
4682
4461
 
4683
- // New message button
4684
4462
  const newMsgBtn = this.element.querySelector('.messenger-new-message-btn');
4685
4463
  if (newMsgBtn) {
4686
4464
  newMsgBtn.addEventListener('click', () => {
@@ -4690,11 +4468,9 @@
4690
4468
  }
4691
4469
 
4692
4470
  _startNewConversation() {
4693
- // Set view to chat with no active conversation (new conversation mode)
4694
4471
  this.state.setActiveConversation(null);
4695
4472
  this.state.setView('chat');
4696
4473
 
4697
- // Notify widget to handle new conversation flow
4698
4474
  if (this.options.onStartNewConversation) {
4699
4475
  this.options.onStartNewConversation();
4700
4476
  }
@@ -4710,9 +4486,6 @@
4710
4486
  }
4711
4487
  }
4712
4488
 
4713
- /**
4714
- * HelpView - Help collections browse with Intercom-style design
4715
- */
4716
4489
  class HelpView {
4717
4490
  constructor(state, options = {}) {
4718
4491
  this.state = state;
@@ -4727,7 +4500,6 @@
4727
4500
 
4728
4501
  this._updateContent();
4729
4502
 
4730
- // Subscribe to state changes
4731
4503
  this._unsubscribe = this.state.subscribe((type) => {
4732
4504
  if (type === 'helpArticlesUpdate' || type === 'helpSearchChange') {
4733
4505
  this._updateCollectionsList();
@@ -4746,7 +4518,9 @@
4746
4518
  <div class="messenger-help-header">
4747
4519
  <h2>Help</h2>
4748
4520
  <button class="messenger-close-btn" aria-label="Close">
4749
- <i class="ph ph-x" style="font-size: 20px;"></i>
4521
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
4522
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
4523
+ </svg>
4750
4524
  </button>
4751
4525
  </div>
4752
4526
 
@@ -4758,7 +4532,9 @@
4758
4532
  placeholder="Search for help"
4759
4533
  value="${searchQuery}"
4760
4534
  />
4761
- <i class="ph ph-magnifying-glass messenger-help-search-icon" style="font-size: 18px;"></i>
4535
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="#000000" viewBox="0 0 256 256" class="messenger-help-search-icon">
4536
+ <path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path>
4537
+ </svg>
4762
4538
  </div>
4763
4539
  </div>
4764
4540
 
@@ -4781,7 +4557,6 @@
4781
4557
  const collections = this.state.helpArticles || [];
4782
4558
  const searchQuery = (this.state.helpSearchQuery || '').toLowerCase();
4783
4559
 
4784
- // Filter collections by search
4785
4560
  const filteredCollections = searchQuery
4786
4561
  ? collections.filter(
4787
4562
  (c) =>
@@ -4790,7 +4565,6 @@
4790
4565
  )
4791
4566
  : collections;
4792
4567
 
4793
- // Update collection count
4794
4568
  const headerEl = this.element.querySelector(
4795
4569
  '.messenger-help-collections-header'
4796
4570
  );
@@ -4807,7 +4581,6 @@
4807
4581
  .map((collection) => this._renderCollectionItem(collection))
4808
4582
  .join('');
4809
4583
 
4810
- // Attach click events
4811
4584
  this._attachCollectionEvents();
4812
4585
  }
4813
4586
 
@@ -4820,7 +4593,9 @@
4820
4593
  <p class="messenger-help-collection-desc">${collection.description || ''}</p>
4821
4594
  <span class="messenger-help-collection-count">${articleCount} articles</span>
4822
4595
  </div>
4823
- <i class="ph ph-caret-right messenger-help-collection-arrow" style="font-size: 20px;"></i>
4596
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256" class="messenger-help-collection-arrow">
4597
+ <path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path>
4598
+ </svg>
4824
4599
  </div>
4825
4600
  `;
4826
4601
  }
@@ -4832,7 +4607,9 @@
4832
4607
  return `
4833
4608
  <div class="messenger-help-empty">
4834
4609
  <div class="messenger-help-empty-icon">
4835
- <i class="ph ph-magnifying-glass" style="font-size: 48px;"></i>
4610
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="#000000" viewBox="0 0 256 256">
4611
+ <path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path>
4612
+ </svg>
4836
4613
  </div>
4837
4614
  <h3>No results found</h3>
4838
4615
  <p>Try a different search term</p>
@@ -4843,7 +4620,9 @@
4843
4620
  return `
4844
4621
  <div class="messenger-help-empty">
4845
4622
  <div class="messenger-help-empty-icon">
4846
- <i class="ph ph-question" style="font-size: 48px;"></i>
4623
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="#000000" viewBox="0 0 256 256">
4624
+ <path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path>
4625
+ </svg>
4847
4626
  </div>
4848
4627
  <h3>Help collections</h3>
4849
4628
  <p>No collections available yet</p>
@@ -4852,14 +4631,12 @@
4852
4631
  }
4853
4632
 
4854
4633
  _attachEvents() {
4855
- // Close button
4856
4634
  this.element
4857
4635
  .querySelector('.messenger-close-btn')
4858
4636
  .addEventListener('click', () => {
4859
4637
  this.state.setOpen(false);
4860
4638
  });
4861
4639
 
4862
- // Search input
4863
4640
  const searchInput = this.element.querySelector(
4864
4641
  '.messenger-help-search-input'
4865
4642
  );
@@ -4902,9 +4679,6 @@
4902
4679
  }
4903
4680
  }
4904
4681
 
4905
- /**
4906
- * HomeView - Welcome screen with team info and quick actions
4907
- */
4908
4682
  class HomeView {
4909
4683
  constructor(state, options = {}) {
4910
4684
  this.state = state;
@@ -4919,7 +4693,6 @@
4919
4693
 
4920
4694
  this._updateContent();
4921
4695
 
4922
- // Subscribe to state changes to re-render when data loads
4923
4696
  this._unsubscribe = this.state.subscribe((type) => {
4924
4697
  if (
4925
4698
  type === 'homeChangelogUpdate' ||
@@ -4945,7 +4718,9 @@
4945
4718
  </div>
4946
4719
  <div class="messenger-home-avatars">${avatarsHtml}</div>
4947
4720
  <button class="messenger-close-btn" aria-label="Close">
4948
- <i class="ph ph-x" style="font-size: 20px;"></i>
4721
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
4722
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
4723
+ </svg>
4949
4724
  </button>
4950
4725
  </div>
4951
4726
  <div class="messenger-home-welcome">
@@ -4958,7 +4733,9 @@
4958
4733
  <div class="messenger-home-body">
4959
4734
  <button class="messenger-home-message-btn">
4960
4735
  <span>Send us a message</span>
4961
- <i class="ph ph-arrow-right" style="font-size: 16px;"></i>
4736
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
4737
+ <path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path>
4738
+ </svg>
4962
4739
  </button>
4963
4740
 
4964
4741
  ${this._renderFeaturedCard()}
@@ -4973,7 +4750,6 @@
4973
4750
  _renderAvatarStack() {
4974
4751
  const avatars = this.state.teamAvatars;
4975
4752
  if (!avatars || avatars.length === 0) {
4976
- // Default avatars with initials
4977
4753
  return `
4978
4754
  <div class="messenger-avatar-stack">
4979
4755
  <div class="messenger-avatar" style="background: #5856d6;">S</div>
@@ -5023,7 +4799,6 @@
5023
4799
  }
5024
4800
 
5025
4801
  _renderFeaturedCard() {
5026
- // Only show if there's featured content configured
5027
4802
  if (!this.options.featuredContent) {
5028
4803
  return '';
5029
4804
  }
@@ -5044,7 +4819,6 @@
5044
4819
  }
5045
4820
 
5046
4821
  _renderRecentChangelog() {
5047
- // Show recent changelog preview as cards with images
5048
4822
  const changelogItems = this.state.homeChangelogItems;
5049
4823
  if (changelogItems.length === 0) {
5050
4824
  return '';
@@ -5093,31 +4867,26 @@
5093
4867
  }
5094
4868
 
5095
4869
  _attachEvents() {
5096
- // Close button
5097
4870
  this.element
5098
4871
  .querySelector('.messenger-close-btn')
5099
4872
  .addEventListener('click', () => {
5100
4873
  this.state.setOpen(false);
5101
4874
  });
5102
4875
 
5103
- // Send message button
5104
4876
  this.element
5105
4877
  .querySelector('.messenger-home-message-btn')
5106
4878
  .addEventListener('click', () => {
5107
4879
  this.state.setView('messages');
5108
4880
  });
5109
4881
 
5110
- // Changelog items
5111
4882
  this.element
5112
4883
  .querySelectorAll('.messenger-home-changelog-item')
5113
4884
  .forEach((item) => {
5114
4885
  item.addEventListener('click', () => {
5115
- // Navigate to changelog view with specific item selected
5116
4886
  this.state.setView('changelog');
5117
4887
  });
5118
4888
  });
5119
4889
 
5120
- // See all changelog
5121
4890
  const seeAllBtn = this.element.querySelector(
5122
4891
  '.messenger-home-changelog-all'
5123
4892
  );
@@ -5127,7 +4896,6 @@
5127
4896
  });
5128
4897
  }
5129
4898
 
5130
- // Featured card action
5131
4899
  const featuredBtn = this.element.querySelector(
5132
4900
  '.messenger-home-featured-btn'
5133
4901
  );
@@ -5170,7 +4938,7 @@
5170
4938
  welcomeMessage: options.welcomeMessage || 'How can we help?',
5171
4939
  enableHelp: options.enableHelp !== false,
5172
4940
  enableChangelog: options.enableChangelog !== false,
5173
- logoUrl: options.logoUrl || 'https://feedback-sdk.product7.io/p7.png',
4941
+ logoUrl: options.logoUrl || 'https://product7.io/p7logo.svg',
5174
4942
  featuredContent: options.featuredContent || null,
5175
4943
  primaryColor: options.primaryColor || '#1c1c1e',
5176
4944
  // Callbacks
@@ -5735,7 +5503,6 @@
5735
5503
  }
5736
5504
 
5737
5505
  async _fetchChangelog() {
5738
- // Mock data for now - simulating changelog API response
5739
5506
  if (this.apiService?.mock) {
5740
5507
  return {
5741
5508
  homeItems: [
@@ -6653,7 +6420,6 @@
6653
6420
  try {
6654
6421
  const initData = await this.apiService.init(this.config.userContext);
6655
6422
 
6656
- // Merge backend config as base, local config overrides
6657
6423
  if (initData.config) {
6658
6424
  this.config = deepMerge(initData.config, this.config);
6659
6425
  }
@@ -6706,13 +6472,6 @@
6706
6472
  return this.widgets.get(id);
6707
6473
  }
6708
6474
 
6709
- /**
6710
- * Fetch active surveys from the backend
6711
- * @param {Object} context - Optional context for targeting
6712
- * @param {string} context.page - Current page/route
6713
- * @param {string} context.event - Event trigger name
6714
- * @returns {Promise<Array>} Array of active survey configurations
6715
- */
6716
6475
  async getActiveSurveys(context = {}) {
6717
6476
  if (!this.initialized) {
6718
6477
  throw new SDKError(
@@ -6732,17 +6491,6 @@
6732
6491
  }
6733
6492
  }
6734
6493
 
6735
- /**
6736
- * Show a survey by its backend ID
6737
- * Fetches survey configuration from the backend and displays it
6738
- * @param {string} surveyId - The backend survey ID
6739
- * @param {Object} options - Additional display options
6740
- * @param {string} options.position - Position override
6741
- * @param {string} options.theme - Theme override
6742
- * @param {Function} options.onSubmit - Callback when survey is submitted
6743
- * @param {Function} options.onDismiss - Callback when survey is dismissed
6744
- * @returns {Promise<SurveyWidget>} The survey widget instance
6745
- */
6746
6494
  async showSurveyById(surveyId, options = {}) {
6747
6495
  if (!this.initialized) {
6748
6496
  throw new SDKError(
@@ -6750,7 +6498,6 @@
6750
6498
  );
6751
6499
  }
6752
6500
 
6753
- // Fetch active surveys to find the one with matching ID
6754
6501
  const surveys = await this.getActiveSurveys();
6755
6502
  const surveyConfig = surveys.find((s) => s.id === surveyId);
6756
6503
 
@@ -6772,22 +6519,6 @@
6772
6519
  });
6773
6520
  }
6774
6521
 
6775
- /**
6776
- * Show a survey widget (local/manual mode)
6777
- * For backend-driven surveys, use showSurveyById() instead
6778
- * @param {Object} options - Survey options
6779
- * @param {string} options.surveyId - Backend survey ID (for API tracking)
6780
- * @param {string} options.surveyType - Type of survey: 'nps', 'csat', 'ces', 'custom'
6781
- * @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'center', 'bottom'
6782
- * @param {string} options.theme - Theme: 'light', 'dark'
6783
- * @param {string} options.title - Custom title
6784
- * @param {string} options.description - Custom description
6785
- * @param {string} options.lowLabel - Low end label
6786
- * @param {string} options.highLabel - High end label
6787
- * @param {Function} options.onSubmit - Callback when survey is submitted
6788
- * @param {Function} options.onDismiss - Callback when survey is dismissed
6789
- * @returns {SurveyWidget} The survey widget instance
6790
- */
6791
6522
  showSurvey(options = {}) {
6792
6523
  if (!this.initialized) {
6793
6524
  throw new SDKError(
@@ -6815,20 +6546,6 @@
6815
6546
  return surveyWidget;
6816
6547
  }
6817
6548
 
6818
- /**
6819
- * Show a changelog widget with sidebar
6820
- * @param {Object} options - Changelog widget options
6821
- * @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'top-right', 'top-left'
6822
- * @param {string} options.theme - Theme: 'light', 'dark'
6823
- * @param {string} options.title - Sidebar title
6824
- * @param {string} options.triggerText - Text on the trigger button
6825
- * @param {boolean} options.showBadge - Show notification badge
6826
- * @param {string} options.viewButtonText - Text for the view update button
6827
- * @param {string} options.changelogBaseUrl - Base URL for changelog links
6828
- * @param {boolean} options.openInNewTab - Open changelog links in new tab (default: true)
6829
- * @param {Function} options.onViewUpdate - Callback when user clicks view update
6830
- * @returns {ChangelogWidget} The changelog widget instance
6831
- */
6832
6549
  showChangelog(options = {}) {
6833
6550
  if (!this.initialized) {
6834
6551
  throw new SDKError(
@@ -6854,13 +6571,6 @@
6854
6571
  return changelogWidget;
6855
6572
  }
6856
6573
 
6857
- /**
6858
- * Get changelogs from the backend
6859
- * @param {Object} options - Optional query parameters
6860
- * @param {number} options.limit - Number of changelogs to fetch
6861
- * @param {number} options.offset - Offset for pagination
6862
- * @returns {Promise<Array>} Array of changelog entries
6863
- */
6864
6574
  async getChangelogs(options = {}) {
6865
6575
  if (!this.initialized) {
6866
6576
  throw new SDKError(
@@ -6968,17 +6678,10 @@
6968
6678
  this.eventBus.emit('sdk:destroyed');
6969
6679
  }
6970
6680
 
6971
- /**
6972
- * Check if feedback was recently submitted for this workspace
6973
- * Uses backend tracking (preferred) with localStorage as fallback
6974
- * @param {number} cooldownDays - Days to consider as "recently" (default: 30)
6975
- * @returns {boolean} true if feedback was submitted within the cooldown period
6976
- */
6977
6681
  hasFeedbackBeenSubmitted(cooldownDays = 30) {
6978
6682
  const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
6979
6683
  const now = Date.now();
6980
6684
 
6981
- // Check backend tracking first (from init response)
6982
6685
  if (this.config.last_feedback_at) {
6983
6686
  try {
6984
6687
  const backendTimestamp = new Date(
@@ -6992,7 +6695,6 @@
6992
6695
  }
6993
6696
  }
6994
6697
 
6995
- // Fallback to localStorage
6996
6698
  try {
6997
6699
  const storageKey = `feedback_submitted_${this.config.workspace}`;
6998
6700
  const stored = localStorage.getItem(storageKey);
@@ -7005,9 +6707,6 @@
7005
6707
  }
7006
6708
  }
7007
6709
 
7008
- /**
7009
- * Clear the feedback submission tracking (allow showing widgets again)
7010
- */
7011
6710
  clearFeedbackSubmissionTracking() {
7012
6711
  try {
7013
6712
  const storageKey = `feedback_submitted_${this.config.workspace}`;
@@ -8893,7 +8592,7 @@
8893
8592
  }
8894
8593
  `;
8895
8594
 
8896
- const CSS_STYLES = `
8595
+ const baseStyles = `
8897
8596
  .feedback-widget {
8898
8597
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
8899
8598
  font-size: 14px;
@@ -8908,32 +8607,94 @@
8908
8607
  box-sizing: border-box;
8909
8608
  }
8910
8609
 
8911
- .feedback-widget-button {
8610
+ /* Animations */
8611
+ @keyframes fadeIn {
8612
+ from { opacity: 0; }
8613
+ to { opacity: 1; }
8614
+ }
8615
+
8616
+ @keyframes slideInRight {
8617
+ from {
8618
+ transform: translateX(400px);
8619
+ opacity: 0;
8620
+ }
8621
+ to {
8622
+ transform: translateX(0);
8623
+ opacity: 1;
8624
+ }
8625
+ }
8626
+
8627
+ @keyframes confettiFall {
8628
+ 0% {
8629
+ opacity: 1;
8630
+ transform: translateY(0) rotate(0deg) scale(1);
8631
+ }
8632
+ 10% {
8633
+ opacity: 1;
8634
+ }
8635
+ 100% {
8636
+ opacity: 0;
8637
+ transform: translateY(100vh) rotate(720deg) scale(0.5);
8638
+ }
8639
+ }
8640
+
8641
+ @keyframes changelogSpin {
8642
+ to {
8643
+ transform: rotate(360deg);
8644
+ }
8645
+ }
8646
+
8647
+ /* Accessibility */
8648
+ @media (prefers-reduced-motion: reduce) {
8649
+ * {
8650
+ transition: none !important;
8651
+ animation: none !important;
8652
+ }
8653
+ }
8654
+
8655
+ /* Print */
8656
+ @media print {
8657
+ .feedback-widget,
8658
+ .feedback-panel,
8659
+ .feedback-panel-backdrop,
8660
+ .feedback-success-notification,
8661
+ .changelog-widget,
8662
+ .changelog-modal,
8663
+ .changelog-modal-backdrop,
8664
+ .changelog-list-modal,
8665
+ .changelog-list-modal-backdrop {
8666
+ display: none !important;
8667
+ }
8668
+ }
8669
+ `;
8670
+
8671
+ const changelogStyles = `
8672
+ .changelog-widget {
8912
8673
  position: fixed;
8913
8674
  z-index: 999999;
8914
8675
  }
8915
8676
 
8916
- .feedback-widget-button.position-bottom-right {
8677
+ .changelog-widget.position-bottom-right {
8917
8678
  bottom: 20px;
8918
8679
  right: 20px;
8919
8680
  }
8920
8681
 
8921
- .feedback-widget-button.position-bottom-left {
8682
+ .changelog-widget.position-bottom-left {
8922
8683
  bottom: 20px;
8923
8684
  left: 20px;
8924
8685
  }
8925
8686
 
8926
- .feedback-widget-button.position-top-right {
8687
+ .changelog-widget.position-top-right {
8927
8688
  top: 20px;
8928
8689
  right: 20px;
8929
8690
  }
8930
8691
 
8931
- .feedback-widget-button.position-top-left {
8692
+ .changelog-widget.position-top-left {
8932
8693
  top: 20px;
8933
8694
  left: 20px;
8934
8695
  }
8935
8696
 
8936
- .feedback-trigger-btn {
8697
+ .changelog-trigger-btn {
8937
8698
  position: relative;
8938
8699
  display: flex;
8939
8700
  align-items: center;
@@ -8955,1326 +8716,1234 @@
8955
8716
  width: fit-content;
8956
8717
  }
8957
8718
 
8958
- .feedback-trigger-btn:hover:not(:disabled) {
8719
+ .changelog-trigger-btn:hover {
8959
8720
  transform: translateY(-2px);
8960
8721
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
8961
8722
  }
8962
8723
 
8963
- .feedback-trigger-btn:disabled {
8964
- opacity: 0.7;
8965
- cursor: not-allowed;
8966
- }
8967
-
8968
- .feedback-trigger-btn:focus-visible {
8724
+ .changelog-trigger-btn:focus-visible {
8969
8725
  outline: 2px solid #155EEF;
8970
8726
  outline-offset: 2px;
8971
8727
  }
8972
8728
 
8973
- .feedback-icon {
8729
+ .changelog-icon {
8974
8730
  flex-shrink: 0;
8975
8731
  }
8976
8732
 
8977
- .feedback-minimize-icon,
8978
- .feedback-expand-icon {
8979
- position: absolute;
8980
- top: -6px;
8981
- right: -6px;
8982
- width: 24px;
8983
- height: 24px;
8984
- padding: 4px;
8985
- display: flex;
8986
- align-items: center;
8987
- justify-content: center;
8988
- background: white;
8989
- border-radius: 50%;
8990
- opacity: 0;
8991
- transition: opacity 0.2s ease;
8992
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
8993
- cursor: pointer;
8994
- display: flex;
8995
- align-items: center;
8996
- justify-content: center;
8733
+ .changelog-confetti-emoji {
8734
+ font-size: 14px;
8735
+ margin-left: 2px;
8997
8736
  }
8998
8737
 
8999
- .feedback-minimize-icon svg,
9000
- .feedback-expand-icon svg {
9001
- width: 16px;
9002
- height: 16px;
9003
- display: block;
9004
- fill: #155EEF;
9005
- }
9006
-
9007
- /* Show minimize icon on hover when expanded */
9008
- .feedback-widget-button:not(.minimized) .feedback-trigger-btn:hover .feedback-minimize-icon {
9009
- opacity: 1;
9010
- }
9011
-
9012
- /* Minimized state - just icon */
9013
- .feedback-widget-button.minimized .feedback-trigger-btn {
9014
- padding: 12px;
9015
- width: 48px;
9016
- height: 48px;
9017
- justify-content: center;
9018
- }
9019
-
9020
- .feedback-widget-button.minimized .feedback-text {
9021
- display: none;
9022
- }
9023
-
9024
- .feedback-widget-button.minimized .feedback-minimize-icon {
9025
- display: none;
9026
- }
9027
-
9028
- /* Show expand icon on hover when minimized */
9029
- .feedback-widget-button.minimized .feedback-trigger-btn:hover .feedback-expand-icon {
9030
- opacity: 1;
8738
+ .changelog-badge {
8739
+ position: absolute;
8740
+ top: -4px;
8741
+ right: -4px;
8742
+ width: 12px;
8743
+ height: 12px;
8744
+ background: #EF4444;
8745
+ border-radius: 50%;
8746
+ border: 2px solid white;
9031
8747
  }
9032
8748
 
9033
- /* Side Panel Styles */
9034
- .feedback-panel {
8749
+ .changelog-confetti-container {
9035
8750
  position: fixed;
9036
- bottom: 80px;
9037
- right: 24px;
9038
- width: 420px;
9039
- max-height: 500px;
9040
- z-index: 1000000;
9041
- transform: translateX(calc(100% + 24px));
9042
- transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
9043
- font-family: inherit;
8751
+ top: 0;
8752
+ left: 0;
8753
+ width: 100%;
8754
+ height: 100%;
8755
+ pointer-events: none;
8756
+ z-index: 1000001;
8757
+ overflow: hidden;
9044
8758
  }
9045
8759
 
9046
- .feedback-panel.open {
9047
- transform: translateX(0);
8760
+ .changelog-confetti {
8761
+ position: absolute;
8762
+ top: -20px;
8763
+ opacity: 0;
8764
+ animation: confettiFall 2s ease-out forwards;
9048
8765
  }
9049
8766
 
9050
- .feedback-panel-backdrop {
8767
+ .changelog-modal-backdrop {
9051
8768
  position: fixed;
9052
8769
  top: 0;
9053
8770
  left: 0;
9054
8771
  right: 0;
9055
8772
  bottom: 0;
9056
- background: rgba(0, 0, 0, 0.1);
8773
+ background: rgba(0, 0, 0, 0.5);
9057
8774
  opacity: 0;
9058
8775
  transition: opacity 0.3s ease;
9059
8776
  pointer-events: none;
9060
- z-index: 999999;
8777
+ z-index: 999998;
8778
+ display: flex;
8779
+ align-items: center;
8780
+ justify-content: center;
9061
8781
  }
9062
8782
 
9063
- .feedback-panel-backdrop.show {
8783
+ .changelog-modal-backdrop.show {
9064
8784
  opacity: 1;
9065
8785
  pointer-events: auto;
9066
8786
  }
9067
8787
 
9068
- .feedback-panel-content {
9069
- background: white;
9070
- height: 100%;
8788
+ .changelog-modal {
8789
+ position: fixed;
8790
+ top: 0;
8791
+ left: 0;
8792
+ right: 0;
8793
+ bottom: 0;
8794
+ z-index: 999999;
9071
8795
  display: flex;
9072
- flex-direction: column;
9073
- border-radius: 16px;
9074
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
9075
- 0 10px 10px -5px rgba(0, 0, 0, 0.04),
9076
- 0 0 0 1px rgba(0, 0, 0, 0.05);
9077
- }
9078
-
9079
- .feedback-panel.theme-dark .feedback-panel-content {
9080
- background: #1F2937;
9081
- color: white;
8796
+ align-items: center;
8797
+ justify-content: center;
8798
+ padding: 20px;
8799
+ pointer-events: none;
8800
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9082
8801
  }
9083
8802
 
9084
- .feedback-panel-header {
9085
- display: flex;
9086
- align-items: center;
9087
- justify-content: space-between;
9088
- padding: 24px;
9089
- border-bottom: 1px solid #E5E7EB;
9090
- flex-shrink: 0;
8803
+ .changelog-modal.open {
8804
+ pointer-events: auto;
9091
8805
  }
9092
8806
 
9093
- .feedback-panel.theme-dark .feedback-panel-header {
9094
- border-bottom-color: #374151;
8807
+ .changelog-modal-container {
8808
+ position: relative;
8809
+ width: 100%;
8810
+ max-width: 580px;
8811
+ max-height: 90vh;
8812
+ background: #DBEAFE;
8813
+ border-radius: 24px;
8814
+ overflow: hidden;
8815
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
8816
+ transform: scale(0.9) translateY(20px);
8817
+ opacity: 0;
8818
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9095
8819
  }
9096
8820
 
9097
- .feedback-panel-header h3 {
9098
- margin: 0;
9099
- font-size: 18px;
9100
- font-weight: 600;
9101
- color: #111827;
8821
+ .changelog-modal.open .changelog-modal-container {
8822
+ transform: scale(1) translateY(0);
8823
+ opacity: 1;
9102
8824
  }
9103
8825
 
9104
- .feedback-panel.theme-dark .feedback-panel-header h3 {
9105
- color: white;
8826
+ .changelog-modal.theme-dark .changelog-modal-container {
8827
+ background: #1E3A5F;
9106
8828
  }
9107
8829
 
9108
- .feedback-panel-close {
9109
- background: none;
8830
+ .changelog-modal-close {
8831
+ position: absolute;
8832
+ top: 16px;
8833
+ right: 16px;
8834
+ background: rgba(255, 255, 255, 0.9);
9110
8835
  border: none;
9111
8836
  font-size: 24px;
9112
8837
  cursor: pointer;
9113
8838
  color: #6B7280;
9114
- padding: 4px;
9115
- width: 32px;
9116
- height: 32px;
8839
+ padding: 0;
8840
+ width: 36px;
8841
+ height: 36px;
9117
8842
  display: flex;
9118
8843
  align-items: center;
9119
8844
  justify-content: center;
9120
- border-radius: 6px;
8845
+ border-radius: 50%;
9121
8846
  transition: all 0.2s ease;
8847
+ line-height: 1;
8848
+ z-index: 10;
8849
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
9122
8850
  }
9123
8851
 
9124
- .feedback-panel-close:hover {
9125
- background: #F3F4F6;
8852
+ .changelog-modal-close:hover {
8853
+ background: white;
9126
8854
  color: #111827;
8855
+ transform: scale(1.05);
9127
8856
  }
9128
8857
 
9129
- .feedback-panel-close:focus-visible {
9130
- outline: 2px solid #155EEF;
9131
- outline-offset: 2px;
9132
- }
9133
-
9134
- .feedback-panel.theme-dark .feedback-panel-close {
9135
- color: #9CA3AF;
8858
+ .changelog-modal.theme-dark .changelog-modal-close {
8859
+ background: rgba(55, 65, 81, 0.9);
8860
+ color: #D1D5DB;
9136
8861
  }
9137
8862
 
9138
- .feedback-panel.theme-dark .feedback-panel-close:hover {
8863
+ .changelog-modal.theme-dark .changelog-modal-close:hover {
9139
8864
  background: #374151;
9140
8865
  color: white;
9141
8866
  }
9142
8867
 
9143
- .feedback-panel-body {
9144
- flex: 1;
8868
+ .changelog-modal-content {
9145
8869
  overflow-y: auto;
9146
- padding: 24px;
8870
+ max-height: 90vh;
9147
8871
  }
9148
8872
 
9149
- .feedback-form {
8873
+ .changelog-loading {
9150
8874
  display: flex;
9151
- flex-direction: column;
9152
- height: 100%;
8875
+ align-items: center;
8876
+ justify-content: center;
8877
+ padding: 80px 20px;
9153
8878
  }
9154
8879
 
9155
- .feedback-form-group {
8880
+ .changelog-loading-spinner {
8881
+ width: 32px;
8882
+ height: 32px;
8883
+ border: 3px solid #E5E7EB;
8884
+ border-top-color: #155EEF;
8885
+ border-radius: 50%;
8886
+ animation: changelogSpin 0.8s linear infinite;
8887
+ }
8888
+
8889
+ .changelog-empty {
9156
8890
  display: flex;
9157
8891
  flex-direction: column;
9158
- gap: 8px;
9159
- margin-bottom: 20px;
8892
+ align-items: center;
8893
+ justify-content: center;
8894
+ padding: 80px 20px;
8895
+ text-align: center;
8896
+ color: #9CA3AF;
9160
8897
  }
9161
8898
 
9162
- .feedback-form-group:last-child {
9163
- margin-bottom: 0;
8899
+ .changelog-empty svg {
8900
+ margin-bottom: 16px;
8901
+ stroke: #D1D5DB;
9164
8902
  }
9165
8903
 
9166
- .feedback-form-group label {
9167
- font-size: 14px;
9168
- font-weight: 500;
9169
- line-height: 1.25;
9170
- color: #374151;
8904
+ .changelog-empty p {
8905
+ margin: 0;
8906
+ font-size: 15px;
9171
8907
  }
9172
8908
 
9173
- .feedback-panel.theme-dark .feedback-form-group label {
9174
- color: #D1D5DB;
8909
+ .changelog-popup-item {
8910
+ display: flex;
8911
+ flex-direction: column;
9175
8912
  }
9176
8913
 
9177
- .feedback-form-group input {
9178
- height: 44px;
8914
+ .changelog-popup-image {
9179
8915
  width: 100%;
9180
- border-radius: 8px;
9181
- border: 1px solid #D1D5DB;
9182
- padding: 10px 14px;
9183
- font-size: 15px;
9184
- font-weight: 400;
9185
- line-height: 1.5;
9186
- color: #1F2937;
9187
- font-family: inherit;
9188
- outline: none;
9189
- transition: all 0.2s ease;
8916
+ padding: 24px 24px 0;
9190
8917
  }
9191
8918
 
9192
- .feedback-form-group input::placeholder {
9193
- font-size: 15px;
9194
- color: #9CA3AF;
8919
+ .changelog-popup-image img {
8920
+ width: 100%;
8921
+ height: auto;
8922
+ display: block;
8923
+ object-fit: cover;
8924
+ border-radius: 12px;
8925
+ border: 2px solid #155EEF;
8926
+ box-shadow: 0 4px 20px rgba(21, 94, 239, 0.2);
9195
8927
  }
9196
8928
 
9197
- .feedback-form-group input:focus {
9198
- border-color: #155EEF;
9199
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
8929
+ .changelog-popup-body {
8930
+ padding: 24px 32px 32px;
8931
+ text-align: center;
9200
8932
  }
9201
8933
 
9202
- .feedback-form-group input:focus-visible {
9203
- outline: none;
8934
+ .changelog-popup-title {
8935
+ margin: 0 0 16px;
8936
+ font-size: 18px;
8937
+ font-weight: 600;
8938
+ line-height: 1.3;
8939
+ color: #111827;
9204
8940
  }
9205
8941
 
9206
- .feedback-form-group textarea {
9207
- min-height: 200px;
9208
- width: 100%;
9209
- resize: vertical;
9210
- border-radius: 8px;
9211
- border: 1px solid #D1D5DB;
9212
- padding: 10px 14px;
9213
- font-size: 15px;
9214
- font-weight: 400;
9215
- line-height: 1.5;
9216
- color: #1F2937;
9217
- font-family: inherit;
9218
- outline: none;
9219
- transition: all 0.2s ease;
9220
- }
9221
-
9222
- .feedback-form-group textarea::placeholder {
9223
- font-size: 15px;
9224
- color: #9CA3AF;
9225
- }
9226
-
9227
- .feedback-form-group textarea:focus {
9228
- border-color: #155EEF;
9229
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
9230
- }
9231
-
9232
- .feedback-form-group textarea:focus-visible {
9233
- outline: none;
8942
+ .changelog-modal.theme-dark .changelog-popup-title {
8943
+ color: white;
9234
8944
  }
9235
8945
 
9236
- .feedback-panel.theme-dark .feedback-form-group input,
9237
- .feedback-panel.theme-dark .feedback-form-group textarea {
9238
- background: #374151;
9239
- border-color: #4B5563;
9240
- color: white;
8946
+ .changelog-popup-description {
8947
+ margin: 0 0 24px;
8948
+ font-size: 17px;
8949
+ line-height: 1.6;
8950
+ color: #4B5563;
9241
8951
  }
9242
8952
 
9243
- .feedback-panel.theme-dark .feedback-form-group input::placeholder,
9244
- .feedback-panel.theme-dark .feedback-form-group textarea::placeholder {
9245
- color: #6B7280;
8953
+ .changelog-modal.theme-dark .changelog-popup-description {
8954
+ color: #D1D5DB;
9246
8955
  }
9247
8956
 
9248
- .feedback-btn {
9249
- position: relative;
8957
+ .changelog-popup-btn {
9250
8958
  display: inline-flex;
9251
8959
  align-items: center;
9252
8960
  justify-content: center;
9253
- overflow: hidden;
9254
- border-radius: 8px;
8961
+ padding: 14px 32px;
8962
+ font-size: 16px;
8963
+ font-weight: 600;
8964
+ color: white;
8965
+ background: #155EEF;
9255
8966
  border: none;
9256
- height: 44px;
9257
- padding: 10px 18px;
9258
- font-size: 15px;
9259
- font-weight: 500;
9260
- font-family: inherit;
8967
+ border-radius: 10px;
9261
8968
  cursor: pointer;
9262
8969
  transition: all 0.2s ease;
9263
8970
  }
9264
8971
 
9265
- .feedback-btn:disabled {
9266
- opacity: 0.6;
9267
- cursor: not-allowed;
8972
+ .changelog-popup-btn:hover {
8973
+ background: #1249CA;
8974
+ transform: translateY(-2px);
8975
+ box-shadow: 0 4px 12px rgba(21, 94, 239, 0.3);
9268
8976
  }
9269
8977
 
9270
- .feedback-btn:focus-visible {
8978
+ .changelog-popup-btn:focus-visible {
9271
8979
  outline: 2px solid #155EEF;
9272
8980
  outline-offset: 2px;
9273
8981
  }
9274
8982
 
9275
- .feedback-btn-submit {
9276
- background: #155EEF;
9277
- color: white;
9278
- width: 100%;
8983
+ .changelog-popup-footer {
8984
+ padding: 0 32px 24px;
8985
+ display: flex;
8986
+ flex-direction: column;
8987
+ align-items: center;
8988
+ gap: 16px;
9279
8989
  }
9280
8990
 
9281
- .feedback-btn-submit:hover:not(:disabled) {
9282
- background: #4338ca;
8991
+ .changelog-popup-dots {
8992
+ display: flex;
8993
+ gap: 8px;
9283
8994
  }
9284
8995
 
9285
- .feedback-btn-submit:active:not(:disabled) {
9286
- background: #3730a3;
8996
+ .changelog-dot {
8997
+ width: 10px;
8998
+ height: 10px;
8999
+ border-radius: 50%;
9000
+ background: rgba(21, 94, 239, 0.3);
9001
+ cursor: pointer;
9002
+ transition: all 0.2s ease;
9287
9003
  }
9288
9004
 
9289
- .feedback-btn-cancel {
9290
- background: transparent;
9291
- color: #6B7280;
9292
- border: 1px solid #D1D5DB;
9005
+ .changelog-dot:hover {
9006
+ background: rgba(21, 94, 239, 0.5);
9293
9007
  }
9294
9008
 
9295
- .feedback-btn-cancel:hover:not(:disabled) {
9296
- background: #F9FAFB;
9297
- border-color: #9CA3AF;
9298
- color: #374151;
9009
+ .changelog-dot.active {
9010
+ background: #155EEF;
9011
+ transform: scale(1.2);
9299
9012
  }
9300
9013
 
9301
- .feedback-panel.theme-dark .feedback-btn-cancel {
9302
- color: #D1D5DB;
9303
- border-color: #4B5563;
9014
+ .changelog-view-all-btn {
9015
+ display: inline-flex;
9016
+ align-items: center;
9017
+ gap: 6px;
9018
+ background: none;
9019
+ border: none;
9020
+ color: #155EEF;
9021
+ font-size: 14px;
9022
+ font-weight: 500;
9023
+ cursor: pointer;
9024
+ padding: 8px 12px;
9025
+ border-radius: 6px;
9026
+ transition: all 0.2s ease;
9304
9027
  }
9305
9028
 
9306
- .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
9307
- background: #374151;
9029
+ .changelog-view-all-btn:hover {
9030
+ background: rgba(21, 94, 239, 0.1);
9308
9031
  }
9309
9032
 
9310
- .feedback-form-actions {
9311
- display: flex;
9312
- flex-direction: column;
9313
- gap: 12px;
9314
- margin-top: auto;
9315
- padding-top: 24px;
9033
+ .changelog-view-all-btn svg {
9034
+ transition: transform 0.2s ease;
9316
9035
  }
9317
9036
 
9318
- .feedback-error {
9319
- color: #DC2626;
9320
- font-size: 14px;
9321
- font-weight: 400;
9322
- margin-top: 8px;
9323
- padding: 12px;
9324
- background: #FEE2E2;
9325
- border: 1px solid #FECACA;
9326
- border-radius: 8px;
9327
- display: none;
9037
+ .changelog-view-all-btn:hover svg {
9038
+ transform: translateX(3px);
9328
9039
  }
9329
9040
 
9330
- .feedback-error.show {
9331
- display: block;
9041
+ .changelog-modal.theme-dark .changelog-view-all-btn {
9042
+ color: #60A5FA;
9332
9043
  }
9333
9044
 
9334
- .feedback-panel.theme-dark .feedback-error {
9335
- background: #7F1D1D;
9336
- border-color: #991B1B;
9337
- color: #FCA5A5;
9045
+ .changelog-modal.theme-dark .changelog-view-all-btn:hover {
9046
+ background: rgba(96, 165, 250, 0.1);
9338
9047
  }
9339
9048
 
9340
- .feedback-success-notification {
9049
+ .changelog-list-modal-backdrop {
9341
9050
  position: fixed;
9342
- top: 24px;
9343
- right: 24px;
9344
- z-index: 1000002;
9345
- background: white;
9346
- border: 1px solid #D1FAE5;
9347
- border-radius: 12px;
9348
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
9349
- animation: slideInRight 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9350
- min-width: 320px;
9051
+ top: 0;
9052
+ left: 0;
9053
+ right: 0;
9054
+ bottom: 0;
9055
+ background: rgba(0, 0, 0, 0.5);
9056
+ opacity: 0;
9057
+ transition: opacity 0.3s ease;
9058
+ pointer-events: none;
9059
+ z-index: 999998;
9351
9060
  }
9352
9061
 
9353
- .feedback-success-content {
9354
- display: flex;
9355
- align-items: center;
9356
- padding: 16px 20px;
9357
- gap: 12px;
9062
+ .changelog-list-modal-backdrop.show {
9063
+ opacity: 1;
9064
+ pointer-events: auto;
9358
9065
  }
9359
9066
 
9360
- .feedback-success-icon {
9361
- width: 20px;
9362
- height: 20px;
9363
- border-radius: 50%;
9364
- background: #10B981;
9365
- color: white;
9067
+ .changelog-list-modal {
9068
+ position: fixed;
9069
+ top: 0;
9070
+ left: 0;
9071
+ right: 0;
9072
+ bottom: 0;
9073
+ z-index: 999999;
9366
9074
  display: flex;
9367
9075
  align-items: center;
9368
9076
  justify-content: center;
9369
- font-size: 12px;
9370
- font-weight: 600;
9371
- flex-shrink: 0;
9077
+ padding: 20px;
9078
+ pointer-events: none;
9079
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9372
9080
  }
9373
9081
 
9374
- .feedback-success-content span {
9375
- color: #065F46;
9376
- font-weight: 500;
9377
- font-size: 14px;
9378
- flex: 1;
9082
+ .changelog-list-modal.open {
9083
+ pointer-events: auto;
9379
9084
  }
9380
9085
 
9381
- .feedback-success-close {
9382
- background: none;
9383
- border: none;
9384
- color: #6B7280;
9385
- cursor: pointer;
9386
- font-size: 20px;
9387
- padding: 0;
9388
- width: 24px;
9389
- height: 24px;
9086
+ .changelog-list-modal-container {
9087
+ position: relative;
9088
+ width: 100%;
9089
+ max-width: 460px;
9090
+ max-height: 85vh;
9091
+ background: white;
9092
+ border-radius: 20px;
9093
+ overflow: hidden;
9094
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
9095
+ transform: scale(0.9) translateY(20px);
9096
+ opacity: 0;
9097
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9390
9098
  display: flex;
9391
- align-items: center;
9392
- justify-content: center;
9393
- transition: all 0.2s ease;
9394
- border-radius: 4px;
9395
- flex-shrink: 0;
9099
+ flex-direction: column;
9396
9100
  }
9397
9101
 
9398
- .feedback-success-close:hover {
9399
- background: #F3F4F6;
9400
- color: #374151;
9102
+ .changelog-list-modal.open .changelog-list-modal-container {
9103
+ transform: scale(1) translateY(0);
9104
+ opacity: 1;
9401
9105
  }
9402
9106
 
9403
- .feedback-success-close:focus-visible {
9404
- outline: 2px solid #155EEF;
9405
- outline-offset: 2px;
9107
+ .changelog-list-modal.theme-dark .changelog-list-modal-container {
9108
+ background: #1F2937;
9109
+ color: white;
9406
9110
  }
9407
9111
 
9408
- @keyframes slideInRight {
9409
- from {
9410
- transform: translateX(400px);
9411
- opacity: 0;
9412
- }
9413
- to {
9414
- transform: translateX(0);
9415
- opacity: 1;
9416
- }
9417
- }
9418
-
9419
- @keyframes fadeIn {
9420
- from { opacity: 0; }
9421
- to { opacity: 1; }
9112
+ .changelog-list-modal-header {
9113
+ display: flex;
9114
+ align-items: center;
9115
+ justify-content: space-between;
9116
+ padding: 14px 20px;
9117
+ border-bottom: 1px solid #E5E7EB;
9118
+ flex-shrink: 0;
9119
+ background: white;
9422
9120
  }
9423
9121
 
9424
- .feedback-panel-backdrop {
9425
- animation: fadeIn 0.3s ease;
9122
+ .changelog-list-modal.theme-dark .changelog-list-modal-header {
9123
+ border-bottom-color: #374151;
9124
+ background: #1F2937;
9426
9125
  }
9427
9126
 
9428
- @media (max-width: 768px) {
9429
- .feedback-panel {
9430
- width: 100%;
9431
- top: auto;
9432
- bottom: 0;
9433
- right: 0;
9434
- left: 0;
9435
- height: 85vh;
9436
- max-height: 85vh;
9437
- transform: translateY(100%);
9438
- border-radius: 20px 20px 0 0;
9439
- }
9440
-
9441
- .feedback-panel.open {
9442
- transform: translateY(0);
9443
- }
9444
-
9445
- .feedback-panel-content {
9446
- border-radius: 20px 20px 0 0;
9447
- }
9448
-
9449
- .feedback-panel-header {
9450
- padding: 20px;
9451
- position: relative;
9452
- }
9453
-
9454
- .feedback-panel-header::before {
9455
- content: '';
9456
- position: absolute;
9457
- top: 8px;
9458
- left: 50%;
9459
- transform: translateX(-50%);
9460
- width: 40px;
9461
- height: 4px;
9462
- background: #D1D5DB;
9463
- border-radius: 2px;
9464
- }
9465
-
9466
- .feedback-panel.theme-dark .feedback-panel-header::before {
9467
- background: #4B5563;
9468
- }
9469
-
9470
- .feedback-panel-body {
9471
- padding: 20px;
9472
- }
9473
-
9474
- .feedback-form-group textarea {
9475
- min-height: 150px;
9476
- }
9477
-
9478
- .feedback-widget-button {
9479
- bottom: 16px;
9480
- right: 16px;
9481
- }
9482
-
9483
- .feedback-widget-button.position-bottom-left {
9484
- left: 16px;
9485
- }
9486
-
9487
- .feedback-success-notification {
9488
- top: 16px;
9489
- right: 16px;
9490
- left: 16px;
9491
- min-width: auto;
9492
- }
9493
-
9494
- .feedback-minimize-icon,
9495
- .feedback-expand-icon {
9496
- top: -4px;
9497
- right: -4px;
9498
- width: 20px;
9499
- height: 20px;
9500
- }
9501
-
9502
- .feedback-minimize-icon svg,
9503
- .feedback-expand-icon svg {
9504
- width: 14px;
9505
- height: 14px;
9506
- }
9127
+ .changelog-list-modal-header h2 {
9128
+ margin: 0;
9129
+ font-size: 16px;
9130
+ font-weight: 600;
9131
+ color: #111827;
9507
9132
  }
9508
9133
 
9509
- @media (prefers-reduced-motion: reduce) {
9510
- .feedback-trigger-btn,
9511
- .feedback-btn,
9512
- .feedback-panel,
9513
- .feedback-panel-backdrop,
9514
- .feedback-success-notification,
9515
- .feedback-minimize-icon,
9516
- .feedback-expand-icon {
9517
- transition: none;
9518
- animation: none;
9519
- }
9134
+ .changelog-list-modal.theme-dark .changelog-list-modal-header h2 {
9135
+ color: white;
9520
9136
  }
9521
9137
 
9522
- @media print {
9523
- .feedback-widget,
9524
- .feedback-panel,
9525
- .feedback-panel-backdrop,
9526
- .feedback-success-notification {
9527
- display: none !important;
9528
- }
9138
+ .changelog-list-modal-close {
9139
+ background: none;
9140
+ border: none;
9141
+ font-size: 22px;
9142
+ cursor: pointer;
9143
+ color: #6B7280;
9144
+ padding: 4px;
9145
+ width: 28px;
9146
+ height: 28px;
9147
+ display: flex;
9148
+ align-items: center;
9149
+ justify-content: center;
9150
+ border-radius: 6px;
9151
+ transition: all 0.2s ease;
9152
+ line-height: 1;
9529
9153
  }
9530
9154
 
9531
- /* Changelog Widget Styles */
9532
- .changelog-widget {
9533
- position: fixed;
9534
- z-index: 999999;
9155
+ .changelog-list-modal-close:hover {
9156
+ background: #F3F4F6;
9157
+ color: #111827;
9535
9158
  }
9536
9159
 
9537
- .changelog-widget.position-bottom-right {
9538
- bottom: 20px;
9539
- right: 20px;
9160
+ .changelog-list-modal.theme-dark .changelog-list-modal-close {
9161
+ color: #9CA3AF;
9540
9162
  }
9541
9163
 
9542
- .changelog-widget.position-bottom-left {
9543
- bottom: 20px;
9544
- left: 20px;
9164
+ .changelog-list-modal.theme-dark .changelog-list-modal-close:hover {
9165
+ background: #374151;
9166
+ color: white;
9545
9167
  }
9546
9168
 
9547
- .changelog-widget.position-top-right {
9548
- top: 20px;
9549
- right: 20px;
9169
+ .changelog-list-modal-body {
9170
+ flex: 1;
9171
+ overflow-y: auto;
9550
9172
  }
9551
9173
 
9552
- .changelog-widget.position-top-left {
9553
- top: 20px;
9554
- left: 20px;
9174
+ .changelog-list {
9175
+ display: flex;
9176
+ flex-direction: column;
9555
9177
  }
9556
9178
 
9557
- .changelog-trigger-btn {
9558
- position: relative;
9179
+ .changelog-list-item {
9559
9180
  display: flex;
9181
+ flex-direction: row;
9560
9182
  align-items: center;
9561
- justify-content: center;
9562
- gap: 8px;
9563
- height: 48px;
9564
- overflow: visible;
9565
- border-radius: 9999px;
9566
- border: none;
9567
- padding: 12px 20px;
9568
- font-size: 14px;
9569
- font-weight: 500;
9570
- font-family: inherit;
9183
+ padding: 12px 16px;
9184
+ border-bottom: 1px solid #E5E7EB;
9571
9185
  cursor: pointer;
9572
- transition: all 0.3s ease;
9573
- color: white;
9574
- background: #155EEF;
9575
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
9576
- width: fit-content;
9577
- }
9578
-
9579
- .changelog-trigger-btn:hover {
9580
- transform: translateY(-2px);
9581
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
9186
+ transition: background-color 0.2s ease;
9187
+ position: relative;
9582
9188
  }
9583
9189
 
9584
- .changelog-trigger-btn:focus-visible {
9585
- outline: 2px solid #155EEF;
9586
- outline-offset: 2px;
9190
+ .changelog-list-item:hover {
9191
+ background: #F9FAFB;
9587
9192
  }
9588
9193
 
9589
- .changelog-icon {
9590
- flex-shrink: 0;
9194
+ .changelog-list-item:last-child {
9195
+ border-bottom: none;
9591
9196
  }
9592
9197
 
9593
- .changelog-confetti-emoji {
9594
- font-size: 14px;
9595
- margin-left: 2px;
9198
+ .changelog-list-modal.theme-dark .changelog-list-item {
9199
+ border-bottom-color: #374151;
9596
9200
  }
9597
9201
 
9598
- .changelog-badge {
9599
- position: absolute;
9600
- top: -4px;
9601
- right: -4px;
9602
- width: 12px;
9603
- height: 12px;
9604
- background: #EF4444;
9605
- border-radius: 50%;
9606
- border: 2px solid white;
9202
+ .changelog-list-modal.theme-dark .changelog-list-item:hover {
9203
+ background: #374151;
9607
9204
  }
9608
9205
 
9609
- /* Confetti Animation */
9610
- .changelog-confetti-container {
9611
- position: fixed;
9612
- top: 0;
9613
- left: 0;
9206
+ .changelog-list-item-image {
9614
9207
  width: 100%;
9615
- height: 100%;
9616
- pointer-events: none;
9617
- z-index: 1000001;
9208
+ margin-bottom: 8px;
9209
+ border-radius: 6px;
9618
9210
  overflow: hidden;
9211
+ border: 1px solid #E5E7EB;
9619
9212
  }
9620
9213
 
9621
- .changelog-confetti {
9622
- position: absolute;
9623
- top: -20px;
9624
- opacity: 0;
9625
- animation: confettiFall 2s ease-out forwards;
9626
- }
9627
-
9628
- @keyframes confettiFall {
9629
- 0% {
9630
- opacity: 1;
9631
- transform: translateY(0) rotate(0deg) scale(1);
9632
- }
9633
- 10% {
9634
- opacity: 1;
9635
- }
9636
- 100% {
9637
- opacity: 0;
9638
- transform: translateY(100vh) rotate(720deg) scale(0.5);
9639
- }
9640
- }
9641
-
9642
- /* Changelog Modal */
9643
- .changelog-modal-backdrop {
9644
- position: fixed;
9645
- top: 0;
9646
- left: 0;
9647
- right: 0;
9648
- bottom: 0;
9649
- background: rgba(0, 0, 0, 0.5);
9650
- opacity: 0;
9651
- transition: opacity 0.3s ease;
9652
- pointer-events: none;
9653
- z-index: 999998;
9654
- display: flex;
9655
- align-items: center;
9656
- justify-content: center;
9214
+ .changelog-list-item-image img {
9215
+ width: 100%;
9216
+ height: 100px;
9217
+ display: block;
9218
+ object-fit: cover;
9657
9219
  }
9658
9220
 
9659
- .changelog-modal-backdrop.show {
9660
- opacity: 1;
9661
- pointer-events: auto;
9221
+ .changelog-list-item-main {
9222
+ flex: 1;
9223
+ min-width: 0;
9662
9224
  }
9663
9225
 
9664
- .changelog-modal {
9665
- position: fixed;
9666
- top: 0;
9667
- left: 0;
9668
- right: 0;
9669
- bottom: 0;
9670
- z-index: 999999;
9226
+ .changelog-list-item-content {
9671
9227
  display: flex;
9672
- align-items: center;
9673
- justify-content: center;
9674
- padding: 20px;
9675
- pointer-events: none;
9676
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9677
- }
9678
-
9679
- .changelog-modal.open {
9680
- pointer-events: auto;
9681
- }
9682
-
9683
- .changelog-modal-container {
9684
- position: relative;
9685
- width: 100%;
9686
- max-width: 580px;
9687
- max-height: 90vh;
9688
- background: #DBEAFE;
9689
- border-radius: 24px;
9690
- overflow: hidden;
9691
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
9692
- transform: scale(0.9) translateY(20px);
9693
- opacity: 0;
9694
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9228
+ flex-direction: column;
9229
+ gap: 3px;
9695
9230
  }
9696
9231
 
9697
- .changelog-modal.open .changelog-modal-container {
9698
- transform: scale(1) translateY(0);
9699
- opacity: 1;
9232
+ .changelog-list-item-date {
9233
+ font-size: 11px;
9234
+ color: #6B7280;
9235
+ font-weight: 500;
9700
9236
  }
9701
9237
 
9702
- .changelog-modal.theme-dark .changelog-modal-container {
9703
- background: #1E3A5F;
9238
+ .changelog-list-modal.theme-dark .changelog-list-item-date {
9239
+ color: #9CA3AF;
9704
9240
  }
9705
9241
 
9706
- .changelog-modal-close {
9707
- position: absolute;
9708
- top: 16px;
9709
- right: 16px;
9710
- background: rgba(255, 255, 255, 0.9);
9711
- border: none;
9712
- font-size: 24px;
9713
- cursor: pointer;
9714
- color: #6B7280;
9715
- padding: 0;
9716
- width: 36px;
9717
- height: 36px;
9242
+ .changelog-list-item-labels {
9718
9243
  display: flex;
9719
- align-items: center;
9720
- justify-content: center;
9721
- border-radius: 50%;
9722
- transition: all 0.2s ease;
9723
- line-height: 1;
9724
- z-index: 10;
9725
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
9244
+ flex-wrap: wrap;
9245
+ gap: 4px;
9246
+ margin-bottom: 1px;
9726
9247
  }
9727
9248
 
9728
- .changelog-modal-close:hover {
9729
- background: white;
9249
+ .changelog-list-item-title {
9250
+ margin: 0;
9251
+ font-size: 14px;
9252
+ font-weight: 600;
9253
+ line-height: 1.3;
9730
9254
  color: #111827;
9731
- transform: scale(1.05);
9732
9255
  }
9733
9256
 
9734
- .changelog-modal.theme-dark .changelog-modal-close {
9735
- background: rgba(55, 65, 81, 0.9);
9736
- color: #D1D5DB;
9737
- }
9738
-
9739
- .changelog-modal.theme-dark .changelog-modal-close:hover {
9740
- background: #374151;
9257
+ .changelog-list-modal.theme-dark .changelog-list-item-title {
9741
9258
  color: white;
9742
9259
  }
9743
9260
 
9744
- .changelog-modal-content {
9745
- overflow-y: auto;
9746
- max-height: 90vh;
9747
- }
9748
-
9749
- /* Changelog Loading */
9750
- .changelog-loading {
9751
- display: flex;
9752
- align-items: center;
9753
- justify-content: center;
9754
- padding: 80px 20px;
9755
- }
9756
-
9757
- .changelog-loading-spinner {
9758
- width: 32px;
9759
- height: 32px;
9760
- border: 3px solid #E5E7EB;
9761
- border-top-color: #155EEF;
9762
- border-radius: 50%;
9763
- animation: changelogSpin 0.8s linear infinite;
9764
- }
9765
-
9766
- @keyframes changelogSpin {
9767
- to {
9768
- transform: rotate(360deg);
9769
- }
9261
+ .changelog-list-item-description {
9262
+ margin: 0;
9263
+ font-size: 12px;
9264
+ line-height: 1.4;
9265
+ color: #6B7280;
9266
+ display: -webkit-box;
9267
+ -webkit-line-clamp: 2;
9268
+ -webkit-box-orient: vertical;
9269
+ overflow: hidden;
9770
9270
  }
9771
9271
 
9772
- /* Changelog Empty State */
9773
- .changelog-empty {
9774
- display: flex;
9775
- flex-direction: column;
9776
- align-items: center;
9777
- justify-content: center;
9778
- padding: 80px 20px;
9779
- text-align: center;
9272
+ .changelog-list-modal.theme-dark .changelog-list-item-description {
9780
9273
  color: #9CA3AF;
9781
9274
  }
9782
9275
 
9783
- .changelog-empty svg {
9784
- margin-bottom: 16px;
9785
- stroke: #D1D5DB;
9276
+ .changelog-list-item-arrow {
9277
+ flex-shrink: 0;
9278
+ margin-left: 12px;
9279
+ color: #9CA3AF;
9280
+ transition: all 0.2s ease;
9786
9281
  }
9787
9282
 
9788
- .changelog-empty p {
9789
- margin: 0;
9790
- font-size: 15px;
9283
+ .changelog-list-item:hover .changelog-list-item-arrow {
9284
+ color: #155EEF;
9285
+ transform: translateX(3px);
9791
9286
  }
9792
9287
 
9793
- /* Changelog Popup Item */
9794
- .changelog-popup-item {
9795
- display: flex;
9796
- flex-direction: column;
9288
+ .changelog-list-modal.theme-dark .changelog-list-item-arrow {
9289
+ color: #6B7280;
9797
9290
  }
9798
9291
 
9799
- .changelog-popup-image {
9800
- width: 100%;
9801
- padding: 24px 24px 0;
9292
+ .changelog-list-modal.theme-dark .changelog-list-item:hover .changelog-list-item-arrow {
9293
+ color: #60A5FA;
9802
9294
  }
9803
9295
 
9804
- .changelog-popup-image img {
9805
- width: 100%;
9806
- height: auto;
9807
- display: block;
9808
- object-fit: cover;
9809
- border-radius: 12px;
9810
- border: 2px solid #155EEF;
9811
- box-shadow: 0 4px 20px rgba(21, 94, 239, 0.2);
9296
+ /* ==================== MOBILE RESPONSIVE ==================== */
9297
+ @media (max-width: 768px) {
9298
+ .changelog-widget {
9299
+ bottom: 16px;
9300
+ right: 16px;
9301
+ }
9302
+
9303
+ .changelog-widget.position-bottom-left {
9304
+ left: 16px;
9305
+ }
9306
+
9307
+ .changelog-modal {
9308
+ padding: 16px;
9309
+ }
9310
+
9311
+ .changelog-modal-container {
9312
+ max-width: 100%;
9313
+ border-radius: 20px;
9314
+ }
9315
+
9316
+ .changelog-popup-image {
9317
+ padding: 20px 20px 0;
9318
+ }
9319
+
9320
+ .changelog-popup-body {
9321
+ padding: 20px 24px 24px;
9322
+ }
9323
+
9324
+ .changelog-popup-title {
9325
+ font-size: 22px;
9326
+ }
9327
+
9328
+ .changelog-popup-description {
9329
+ font-size: 15px;
9330
+ }
9331
+
9332
+ .changelog-popup-btn {
9333
+ padding: 12px 28px;
9334
+ font-size: 15px;
9335
+ width: 100%;
9336
+ }
9337
+
9338
+ .changelog-popup-footer {
9339
+ padding: 0 24px 20px;
9340
+ }
9341
+
9342
+ .changelog-list-modal {
9343
+ padding: 16px;
9344
+ }
9345
+
9346
+ .changelog-list-modal-container {
9347
+ max-width: 100%;
9348
+ max-height: 90vh;
9349
+ border-radius: 16px;
9350
+ }
9351
+
9352
+ .changelog-list-item {
9353
+ padding: 10px 14px;
9354
+ }
9355
+
9356
+ .changelog-list-item-image img {
9357
+ height: 80px;
9358
+ }
9359
+
9360
+ .changelog-list-item-title {
9361
+ font-size: 13px;
9362
+ }
9363
+
9364
+ .changelog-list-item-description {
9365
+ font-size: 11px;
9366
+ }
9812
9367
  }
9368
+ `;
9813
9369
 
9814
- .changelog-popup-body {
9815
- padding: 24px 32px 32px;
9816
- text-align: center;
9370
+ const feedbackStyles = `
9371
+ .feedback-widget-button {
9372
+ position: fixed;
9373
+ z-index: 999999;
9817
9374
  }
9818
9375
 
9819
- .changelog-popup-title {
9820
- margin: 0 0 16px;
9821
- font-size: 18px;
9822
- font-weight: 600;
9823
- line-height: 1.3;
9824
- color: #111827;
9376
+ .feedback-widget-button.position-bottom-right {
9377
+ bottom: 20px;
9378
+ right: 20px;
9825
9379
  }
9826
9380
 
9827
- .changelog-modal.theme-dark .changelog-popup-title {
9828
- color: white;
9381
+ .feedback-widget-button.position-bottom-left {
9382
+ bottom: 20px;
9383
+ left: 20px;
9829
9384
  }
9830
9385
 
9831
- .changelog-popup-description {
9832
- margin: 0 0 24px;
9833
- font-size: 17px;
9834
- line-height: 1.6;
9835
- color: #4B5563;
9386
+ .feedback-widget-button.position-top-right {
9387
+ top: 20px;
9388
+ right: 20px;
9836
9389
  }
9837
9390
 
9838
- .changelog-modal.theme-dark .changelog-popup-description {
9839
- color: #D1D5DB;
9391
+ .feedback-widget-button.position-top-left {
9392
+ top: 20px;
9393
+ left: 20px;
9840
9394
  }
9841
9395
 
9842
- .changelog-popup-btn {
9843
- display: inline-flex;
9396
+ .feedback-trigger-btn {
9397
+ position: relative;
9398
+ display: flex;
9844
9399
  align-items: center;
9845
9400
  justify-content: center;
9846
- padding: 14px 32px;
9847
- font-size: 16px;
9848
- font-weight: 600;
9849
- color: white;
9850
- background: #155EEF;
9401
+ gap: 8px;
9402
+ height: 48px;
9403
+ overflow: visible;
9404
+ border-radius: 9999px;
9851
9405
  border: none;
9852
- border-radius: 10px;
9406
+ padding: 12px 20px;
9407
+ font-size: 14px;
9408
+ font-weight: 500;
9409
+ font-family: inherit;
9853
9410
  cursor: pointer;
9854
- transition: all 0.2s ease;
9411
+ transition: all 0.3s ease;
9412
+ color: white;
9413
+ background: #155EEF;
9414
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
9415
+ width: fit-content;
9855
9416
  }
9856
9417
 
9857
- .changelog-popup-btn:hover {
9858
- background: #1249CA;
9418
+ .feedback-trigger-btn:hover:not(:disabled) {
9859
9419
  transform: translateY(-2px);
9860
- box-shadow: 0 4px 12px rgba(21, 94, 239, 0.3);
9420
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
9861
9421
  }
9862
9422
 
9863
- .changelog-popup-btn:focus-visible {
9423
+ .feedback-trigger-btn:disabled {
9424
+ opacity: 0.7;
9425
+ cursor: not-allowed;
9426
+ }
9427
+
9428
+ .feedback-trigger-btn:focus-visible {
9864
9429
  outline: 2px solid #155EEF;
9865
9430
  outline-offset: 2px;
9866
9431
  }
9867
9432
 
9868
- /* Popup Footer with Dots and View All */
9869
- .changelog-popup-footer {
9870
- padding: 0 32px 24px;
9871
- display: flex;
9872
- flex-direction: column;
9873
- align-items: center;
9874
- gap: 16px;
9433
+ .feedback-icon {
9434
+ flex-shrink: 0;
9875
9435
  }
9876
9436
 
9877
- .changelog-popup-dots {
9437
+ .feedback-minimize-icon,
9438
+ .feedback-expand-icon {
9439
+ position: absolute;
9440
+ top: -6px;
9441
+ right: -6px;
9442
+ width: 24px;
9443
+ height: 24px;
9444
+ padding: 4px;
9878
9445
  display: flex;
9879
- gap: 8px;
9880
- }
9881
-
9882
- .changelog-dot {
9883
- width: 10px;
9884
- height: 10px;
9446
+ align-items: center;
9447
+ justify-content: center;
9448
+ background: white;
9885
9449
  border-radius: 50%;
9886
- background: rgba(21, 94, 239, 0.3);
9450
+ opacity: 0;
9451
+ transition: opacity 0.2s ease;
9452
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
9887
9453
  cursor: pointer;
9888
- transition: all 0.2s ease;
9889
9454
  }
9890
9455
 
9891
- .changelog-dot:hover {
9892
- background: rgba(21, 94, 239, 0.5);
9456
+ .feedback-minimize-icon svg,
9457
+ .feedback-expand-icon svg {
9458
+ width: 16px;
9459
+ height: 16px;
9460
+ display: block;
9461
+ fill: #155EEF;
9893
9462
  }
9894
9463
 
9895
- .changelog-dot.active {
9896
- background: #155EEF;
9897
- transform: scale(1.2);
9464
+ .feedback-widget-button:not(.minimized) .feedback-trigger-btn:hover .feedback-minimize-icon {
9465
+ opacity: 1;
9898
9466
  }
9899
9467
 
9900
- .changelog-view-all-btn {
9901
- display: inline-flex;
9902
- align-items: center;
9903
- gap: 6px;
9904
- background: none;
9905
- border: none;
9906
- color: #155EEF;
9907
- font-size: 14px;
9908
- font-weight: 500;
9909
- cursor: pointer;
9910
- padding: 8px 12px;
9911
- border-radius: 6px;
9912
- transition: all 0.2s ease;
9468
+ .feedback-widget-button.minimized .feedback-trigger-btn {
9469
+ padding: 12px;
9470
+ width: 48px;
9471
+ height: 48px;
9472
+ justify-content: center;
9913
9473
  }
9914
9474
 
9915
- .changelog-view-all-btn:hover {
9916
- background: rgba(21, 94, 239, 0.1);
9475
+ .feedback-widget-button.minimized .feedback-text {
9476
+ display: none;
9917
9477
  }
9918
9478
 
9919
- .changelog-view-all-btn svg {
9920
- transition: transform 0.2s ease;
9479
+ .feedback-widget-button.minimized .feedback-minimize-icon {
9480
+ display: none;
9921
9481
  }
9922
9482
 
9923
- .changelog-view-all-btn:hover svg {
9924
- transform: translateX(3px);
9483
+ .feedback-widget-button.minimized .feedback-trigger-btn:hover .feedback-expand-icon {
9484
+ opacity: 1;
9925
9485
  }
9926
9486
 
9927
- .changelog-modal.theme-dark .changelog-view-all-btn {
9928
- color: #60A5FA;
9487
+ .feedback-panel {
9488
+ position: fixed;
9489
+ bottom: 80px;
9490
+ right: 24px;
9491
+ width: 420px;
9492
+ max-height: 500px;
9493
+ z-index: 1000000;
9494
+ transform: translateX(calc(100% + 24px));
9495
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
9496
+ font-family: inherit;
9929
9497
  }
9930
9498
 
9931
- .changelog-modal.theme-dark .changelog-view-all-btn:hover {
9932
- background: rgba(96, 165, 250, 0.1);
9499
+ .feedback-panel.open {
9500
+ transform: translateX(0);
9933
9501
  }
9934
9502
 
9935
- /* ==================== CHANGELOG LIST MODAL ==================== */
9936
- .changelog-list-modal-backdrop {
9503
+ .feedback-panel-backdrop {
9937
9504
  position: fixed;
9938
9505
  top: 0;
9939
9506
  left: 0;
9940
9507
  right: 0;
9941
9508
  bottom: 0;
9942
- background: rgba(0, 0, 0, 0.5);
9509
+ background: rgba(0, 0, 0, 0.1);
9943
9510
  opacity: 0;
9944
9511
  transition: opacity 0.3s ease;
9945
9512
  pointer-events: none;
9946
- z-index: 999998;
9947
- }
9948
-
9949
- .changelog-list-modal-backdrop.show {
9950
- opacity: 1;
9951
- pointer-events: auto;
9952
- }
9953
-
9954
- .changelog-list-modal {
9955
- position: fixed;
9956
- top: 0;
9957
- left: 0;
9958
- right: 0;
9959
- bottom: 0;
9960
9513
  z-index: 999999;
9961
- display: flex;
9962
- align-items: center;
9963
- justify-content: center;
9964
- padding: 20px;
9965
- pointer-events: none;
9966
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9514
+ animation: fadeIn 0.3s ease;
9967
9515
  }
9968
9516
 
9969
- .changelog-list-modal.open {
9517
+ .feedback-panel-backdrop.show {
9518
+ opacity: 1;
9970
9519
  pointer-events: auto;
9971
9520
  }
9972
9521
 
9973
- .changelog-list-modal-container {
9974
- position: relative;
9975
- width: 100%;
9976
- max-width: 460px;
9977
- max-height: 85vh;
9522
+ .feedback-panel-content {
9978
9523
  background: white;
9979
- border-radius: 20px;
9980
- overflow: hidden;
9981
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
9982
- transform: scale(0.9) translateY(20px);
9983
- opacity: 0;
9984
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9524
+ height: 100%;
9985
9525
  display: flex;
9986
9526
  flex-direction: column;
9527
+ border-radius: 16px;
9528
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
9529
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04),
9530
+ 0 0 0 1px rgba(0, 0, 0, 0.05);
9987
9531
  }
9988
9532
 
9989
- .changelog-list-modal.open .changelog-list-modal-container {
9990
- transform: scale(1) translateY(0);
9991
- opacity: 1;
9992
- }
9993
-
9994
- .changelog-list-modal.theme-dark .changelog-list-modal-container {
9533
+ .feedback-panel.theme-dark .feedback-panel-content {
9995
9534
  background: #1F2937;
9996
9535
  color: white;
9997
9536
  }
9998
9537
 
9999
- .changelog-list-modal-header {
9538
+ .feedback-panel-header {
10000
9539
  display: flex;
10001
9540
  align-items: center;
10002
9541
  justify-content: space-between;
10003
- padding: 14px 20px;
9542
+ padding: 24px;
10004
9543
  border-bottom: 1px solid #E5E7EB;
10005
9544
  flex-shrink: 0;
10006
- background: white;
10007
9545
  }
10008
9546
 
10009
- .changelog-list-modal.theme-dark .changelog-list-modal-header {
9547
+ .feedback-panel.theme-dark .feedback-panel-header {
10010
9548
  border-bottom-color: #374151;
10011
- background: #1F2937;
10012
9549
  }
10013
9550
 
10014
- .changelog-list-modal-header h2 {
9551
+ .feedback-panel-header h3 {
10015
9552
  margin: 0;
10016
- font-size: 16px;
9553
+ font-size: 18px;
10017
9554
  font-weight: 600;
10018
9555
  color: #111827;
10019
9556
  }
10020
9557
 
10021
- .changelog-list-modal.theme-dark .changelog-list-modal-header h2 {
9558
+ .feedback-panel.theme-dark .feedback-panel-header h3 {
10022
9559
  color: white;
10023
9560
  }
10024
9561
 
10025
- .changelog-list-modal-close {
9562
+ .feedback-panel-close {
10026
9563
  background: none;
10027
9564
  border: none;
10028
- font-size: 22px;
9565
+ font-size: 24px;
10029
9566
  cursor: pointer;
10030
9567
  color: #6B7280;
10031
9568
  padding: 4px;
10032
- width: 28px;
10033
- height: 28px;
9569
+ width: 32px;
9570
+ height: 32px;
10034
9571
  display: flex;
10035
9572
  align-items: center;
10036
9573
  justify-content: center;
10037
9574
  border-radius: 6px;
10038
9575
  transition: all 0.2s ease;
10039
- line-height: 1;
10040
9576
  }
10041
9577
 
10042
- .changelog-list-modal-close:hover {
9578
+ .feedback-panel-close:hover {
10043
9579
  background: #F3F4F6;
10044
9580
  color: #111827;
10045
9581
  }
10046
9582
 
10047
- .changelog-list-modal.theme-dark .changelog-list-modal-close {
9583
+ .feedback-panel-close:focus-visible {
9584
+ outline: 2px solid #155EEF;
9585
+ outline-offset: 2px;
9586
+ }
9587
+
9588
+ .feedback-panel.theme-dark .feedback-panel-close {
10048
9589
  color: #9CA3AF;
10049
9590
  }
10050
9591
 
10051
- .changelog-list-modal.theme-dark .changelog-list-modal-close:hover {
9592
+ .feedback-panel.theme-dark .feedback-panel-close:hover {
10052
9593
  background: #374151;
10053
9594
  color: white;
10054
9595
  }
10055
9596
 
10056
- .changelog-list-modal-body {
9597
+ .feedback-panel-body {
10057
9598
  flex: 1;
10058
9599
  overflow-y: auto;
9600
+ padding: 24px;
10059
9601
  }
10060
9602
 
10061
- /* Changelog List */
10062
- .changelog-list {
9603
+ .feedback-form {
10063
9604
  display: flex;
10064
9605
  flex-direction: column;
9606
+ height: 100%;
10065
9607
  }
10066
9608
 
10067
- .changelog-list-item {
9609
+ .feedback-form-group {
10068
9610
  display: flex;
10069
- flex-direction: row;
10070
- align-items: center;
10071
- padding: 12px 16px;
10072
- border-bottom: 1px solid #E5E7EB;
10073
- cursor: pointer;
10074
- transition: background-color 0.2s ease;
10075
- position: relative;
9611
+ flex-direction: column;
9612
+ gap: 8px;
9613
+ margin-bottom: 20px;
10076
9614
  }
10077
9615
 
10078
- .changelog-list-item:hover {
10079
- background: #F9FAFB;
9616
+ .feedback-form-group:last-child {
9617
+ margin-bottom: 0;
10080
9618
  }
10081
9619
 
10082
- .changelog-list-item:last-child {
10083
- border-bottom: none;
9620
+ .feedback-form-group label {
9621
+ font-size: 14px;
9622
+ font-weight: 500;
9623
+ line-height: 1.25;
9624
+ color: #374151;
10084
9625
  }
10085
9626
 
10086
- .changelog-list-modal.theme-dark .changelog-list-item {
10087
- border-bottom-color: #374151;
9627
+ .feedback-panel.theme-dark .feedback-form-group label {
9628
+ color: #D1D5DB;
10088
9629
  }
10089
9630
 
10090
- .changelog-list-modal.theme-dark .changelog-list-item:hover {
10091
- background: #374151;
9631
+ .feedback-form-group input {
9632
+ height: 44px;
9633
+ width: 100%;
9634
+ border-radius: 8px;
9635
+ border: 1px solid #D1D5DB;
9636
+ padding: 10px 14px;
9637
+ font-size: 15px;
9638
+ font-weight: 400;
9639
+ line-height: 1.5;
9640
+ color: #1F2937;
9641
+ font-family: inherit;
9642
+ outline: none;
9643
+ transition: all 0.2s ease;
10092
9644
  }
10093
9645
 
10094
- .changelog-list-item-image {
9646
+ .feedback-form-group input::placeholder {
9647
+ font-size: 15px;
9648
+ color: #9CA3AF;
9649
+ }
9650
+
9651
+ .feedback-form-group input:focus {
9652
+ border-color: #155EEF;
9653
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
9654
+ }
9655
+
9656
+ .feedback-form-group input:focus-visible {
9657
+ outline: none;
9658
+ }
9659
+
9660
+ .feedback-form-group textarea {
9661
+ min-height: 200px;
10095
9662
  width: 100%;
10096
- margin-bottom: 8px;
10097
- border-radius: 6px;
9663
+ resize: vertical;
9664
+ border-radius: 8px;
9665
+ border: 1px solid #D1D5DB;
9666
+ padding: 10px 14px;
9667
+ font-size: 15px;
9668
+ font-weight: 400;
9669
+ line-height: 1.5;
9670
+ color: #1F2937;
9671
+ font-family: inherit;
9672
+ outline: none;
9673
+ transition: all 0.2s ease;
9674
+ }
9675
+
9676
+ .feedback-form-group textarea::placeholder {
9677
+ font-size: 15px;
9678
+ color: #9CA3AF;
9679
+ }
9680
+
9681
+ .feedback-form-group textarea:focus {
9682
+ border-color: #155EEF;
9683
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
9684
+ }
9685
+
9686
+ .feedback-form-group textarea:focus-visible {
9687
+ outline: none;
9688
+ }
9689
+
9690
+ .feedback-panel.theme-dark .feedback-form-group input,
9691
+ .feedback-panel.theme-dark .feedback-form-group textarea {
9692
+ background: #374151;
9693
+ border-color: #4B5563;
9694
+ color: white;
9695
+ }
9696
+
9697
+ .feedback-panel.theme-dark .feedback-form-group input::placeholder,
9698
+ .feedback-panel.theme-dark .feedback-form-group textarea::placeholder {
9699
+ color: #6B7280;
9700
+ }
9701
+
9702
+ .feedback-btn {
9703
+ position: relative;
9704
+ display: inline-flex;
9705
+ align-items: center;
9706
+ justify-content: center;
10098
9707
  overflow: hidden;
10099
- border: 1px solid #E5E7EB;
9708
+ border-radius: 8px;
9709
+ border: none;
9710
+ height: 44px;
9711
+ padding: 10px 18px;
9712
+ font-size: 15px;
9713
+ font-weight: 500;
9714
+ font-family: inherit;
9715
+ cursor: pointer;
9716
+ transition: all 0.2s ease;
10100
9717
  }
10101
9718
 
10102
- .changelog-list-item-image img {
9719
+ .feedback-btn:disabled {
9720
+ opacity: 0.6;
9721
+ cursor: not-allowed;
9722
+ }
9723
+
9724
+ .feedback-btn:focus-visible {
9725
+ outline: 2px solid #155EEF;
9726
+ outline-offset: 2px;
9727
+ }
9728
+
9729
+ .feedback-btn-submit {
9730
+ background: #155EEF;
9731
+ color: white;
10103
9732
  width: 100%;
10104
- height: 100px;
10105
- display: block;
10106
- object-fit: cover;
10107
9733
  }
10108
9734
 
10109
- .changelog-list-item-main {
10110
- flex: 1;
10111
- min-width: 0;
9735
+ .feedback-btn-submit:hover:not(:disabled) {
9736
+ background: #4338ca;
9737
+ }
9738
+
9739
+ .feedback-btn-submit:active:not(:disabled) {
9740
+ background: #3730a3;
10112
9741
  }
10113
9742
 
10114
- .changelog-list-item-content {
10115
- display: flex;
10116
- flex-direction: column;
10117
- gap: 3px;
9743
+ .feedback-btn-cancel {
9744
+ background: transparent;
9745
+ color: #6B7280;
9746
+ border: 1px solid #D1D5DB;
10118
9747
  }
10119
9748
 
10120
- .changelog-list-item-date {
10121
- font-size: 11px;
10122
- color: #6B7280;
10123
- font-weight: 500;
9749
+ .feedback-btn-cancel:hover:not(:disabled) {
9750
+ background: #F9FAFB;
9751
+ border-color: #9CA3AF;
9752
+ color: #374151;
10124
9753
  }
10125
9754
 
10126
- .changelog-list-modal.theme-dark .changelog-list-item-date {
10127
- color: #9CA3AF;
9755
+ .feedback-panel.theme-dark .feedback-btn-cancel {
9756
+ color: #D1D5DB;
9757
+ border-color: #4B5563;
10128
9758
  }
10129
9759
 
10130
- .changelog-list-item-labels {
9760
+ .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
9761
+ background: #374151;
9762
+ }
9763
+
9764
+ .feedback-form-actions {
10131
9765
  display: flex;
10132
- flex-wrap: wrap;
10133
- gap: 4px;
10134
- margin-bottom: 1px;
9766
+ flex-direction: column;
9767
+ gap: 12px;
9768
+ margin-top: auto;
9769
+ padding-top: 24px;
10135
9770
  }
10136
9771
 
10137
- .changelog-list-item-title {
10138
- margin: 0;
9772
+ .feedback-error {
9773
+ color: #DC2626;
10139
9774
  font-size: 14px;
10140
- font-weight: 600;
10141
- line-height: 1.3;
10142
- color: #111827;
9775
+ font-weight: 400;
9776
+ margin-top: 8px;
9777
+ padding: 12px;
9778
+ background: #FEE2E2;
9779
+ border: 1px solid #FECACA;
9780
+ border-radius: 8px;
9781
+ display: none;
10143
9782
  }
10144
9783
 
10145
- .changelog-list-modal.theme-dark .changelog-list-item-title {
10146
- color: white;
9784
+ .feedback-error.show {
9785
+ display: block;
10147
9786
  }
10148
9787
 
10149
- .changelog-list-item-description {
10150
- margin: 0;
10151
- font-size: 12px;
10152
- line-height: 1.4;
10153
- color: #6B7280;
10154
- display: -webkit-box;
10155
- -webkit-line-clamp: 2;
10156
- -webkit-box-orient: vertical;
10157
- overflow: hidden;
9788
+ .feedback-panel.theme-dark .feedback-error {
9789
+ background: #7F1D1D;
9790
+ border-color: #991B1B;
9791
+ color: #FCA5A5;
10158
9792
  }
10159
9793
 
10160
- .changelog-list-modal.theme-dark .changelog-list-item-description {
10161
- color: #9CA3AF;
9794
+ /* ==================== SUCCESS NOTIFICATION ==================== */
9795
+ .feedback-success-notification {
9796
+ position: fixed;
9797
+ top: 24px;
9798
+ right: 24px;
9799
+ z-index: 1000002;
9800
+ background: white;
9801
+ border: 1px solid #D1FAE5;
9802
+ border-radius: 12px;
9803
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
9804
+ animation: slideInRight 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9805
+ min-width: 320px;
10162
9806
  }
10163
9807
 
10164
- .changelog-list-item-arrow {
9808
+ .feedback-success-content {
9809
+ display: flex;
9810
+ align-items: center;
9811
+ padding: 16px 20px;
9812
+ gap: 12px;
9813
+ }
9814
+
9815
+ .feedback-success-icon {
9816
+ width: 20px;
9817
+ height: 20px;
9818
+ border-radius: 50%;
9819
+ background: #10B981;
9820
+ color: white;
9821
+ display: flex;
9822
+ align-items: center;
9823
+ justify-content: center;
9824
+ font-size: 12px;
9825
+ font-weight: 600;
10165
9826
  flex-shrink: 0;
10166
- margin-left: 12px;
10167
- color: #9CA3AF;
10168
- transition: all 0.2s ease;
10169
9827
  }
10170
9828
 
10171
- .changelog-list-item:hover .changelog-list-item-arrow {
10172
- color: #155EEF;
10173
- transform: translateX(3px);
9829
+ .feedback-success-content span {
9830
+ color: #065F46;
9831
+ font-weight: 500;
9832
+ font-size: 14px;
9833
+ flex: 1;
10174
9834
  }
10175
9835
 
10176
- .changelog-list-modal.theme-dark .changelog-list-item-arrow {
9836
+ .feedback-success-close {
9837
+ background: none;
9838
+ border: none;
10177
9839
  color: #6B7280;
9840
+ cursor: pointer;
9841
+ font-size: 20px;
9842
+ padding: 0;
9843
+ width: 24px;
9844
+ height: 24px;
9845
+ display: flex;
9846
+ align-items: center;
9847
+ justify-content: center;
9848
+ transition: all 0.2s ease;
9849
+ border-radius: 4px;
9850
+ flex-shrink: 0;
10178
9851
  }
10179
9852
 
10180
- .changelog-list-modal.theme-dark .changelog-list-item:hover .changelog-list-item-arrow {
10181
- color: #60A5FA;
9853
+ .feedback-success-close:hover {
9854
+ background: #F3F4F6;
9855
+ color: #374151;
10182
9856
  }
10183
9857
 
10184
- /* Mobile Responsive */
10185
- @media (max-width: 768px) {
10186
- .changelog-modal {
10187
- padding: 16px;
10188
- }
10189
-
10190
- .changelog-modal-container {
10191
- max-width: 100%;
10192
- border-radius: 20px;
10193
- }
9858
+ .feedback-success-close:focus-visible {
9859
+ outline: 2px solid #155EEF;
9860
+ outline-offset: 2px;
9861
+ }
10194
9862
 
10195
- .changelog-widget {
9863
+ @media (max-width: 768px) {
9864
+ .feedback-widget-button {
10196
9865
  bottom: 16px;
10197
9866
  right: 16px;
10198
9867
  }
10199
-
10200
- .changelog-widget.position-bottom-left {
9868
+
9869
+ .feedback-widget-button.position-bottom-left {
10201
9870
  left: 16px;
10202
9871
  }
10203
-
10204
- .changelog-popup-image {
10205
- padding: 20px 20px 0;
10206
- }
10207
-
10208
- .changelog-popup-body {
10209
- padding: 20px 24px 24px;
10210
- }
10211
-
10212
- .changelog-popup-title {
10213
- font-size: 22px;
9872
+
9873
+ .feedback-minimize-icon,
9874
+ .feedback-expand-icon {
9875
+ top: -4px;
9876
+ right: -4px;
9877
+ width: 20px;
9878
+ height: 20px;
10214
9879
  }
10215
-
10216
- .changelog-popup-description {
10217
- font-size: 15px;
9880
+
9881
+ .feedback-minimize-icon svg,
9882
+ .feedback-expand-icon svg {
9883
+ width: 14px;
9884
+ height: 14px;
10218
9885
  }
10219
9886
 
10220
- .changelog-popup-btn {
10221
- padding: 12px 28px;
10222
- font-size: 15px;
9887
+ .feedback-panel {
10223
9888
  width: 100%;
9889
+ top: auto;
9890
+ bottom: 0;
9891
+ right: 0;
9892
+ left: 0;
9893
+ height: 85vh;
9894
+ max-height: 85vh;
9895
+ transform: translateY(100%);
9896
+ border-radius: 20px 20px 0 0;
10224
9897
  }
10225
-
10226
- .changelog-popup-footer {
10227
- padding: 0 24px 20px;
9898
+
9899
+ .feedback-panel.open {
9900
+ transform: translateY(0);
10228
9901
  }
10229
-
10230
- /* List modal mobile */
10231
- .changelog-list-modal {
10232
- padding: 16px;
9902
+
9903
+ .feedback-panel-content {
9904
+ border-radius: 20px 20px 0 0;
10233
9905
  }
10234
-
10235
- .changelog-list-modal-container {
10236
- max-width: 100%;
10237
- max-height: 90vh;
10238
- border-radius: 16px;
9906
+
9907
+ .feedback-panel-header {
9908
+ padding: 20px;
9909
+ position: relative;
10239
9910
  }
10240
-
10241
- .changelog-list-item {
10242
- padding: 10px 14px;
9911
+
9912
+ .feedback-panel-header::before {
9913
+ content: '';
9914
+ position: absolute;
9915
+ top: 8px;
9916
+ left: 50%;
9917
+ transform: translateX(-50%);
9918
+ width: 40px;
9919
+ height: 4px;
9920
+ background: #D1D5DB;
9921
+ border-radius: 2px;
10243
9922
  }
10244
-
10245
- .changelog-list-item-image img {
10246
- height: 80px;
9923
+
9924
+ .feedback-panel.theme-dark .feedback-panel-header::before {
9925
+ background: #4B5563;
10247
9926
  }
10248
-
10249
- .changelog-list-item-title {
10250
- font-size: 13px;
9927
+
9928
+ .feedback-panel-body {
9929
+ padding: 20px;
10251
9930
  }
10252
-
10253
- .changelog-list-item-description {
10254
- font-size: 11px;
9931
+
9932
+ .feedback-form-group textarea {
9933
+ min-height: 150px;
10255
9934
  }
10256
- }
10257
9935
 
10258
- @media (prefers-reduced-motion: reduce) {
10259
- .changelog-modal,
10260
- .changelog-modal-container,
10261
- .changelog-modal-backdrop,
10262
- .changelog-list-modal,
10263
- .changelog-list-modal-container,
10264
- .changelog-list-modal-backdrop,
10265
- .changelog-trigger-btn,
10266
- .changelog-popup-btn,
10267
- .changelog-view-all-btn,
10268
- .changelog-loading-spinner,
10269
- .changelog-dot,
10270
- .changelog-list-item,
10271
- .changelog-list-item-arrow {
10272
- transition: none;
10273
- animation: none;
9936
+ .feedback-success-notification {
9937
+ top: 16px;
9938
+ right: 16px;
9939
+ left: 16px;
9940
+ min-width: auto;
10274
9941
  }
10275
9942
  }
10276
9943
  `;
10277
9944
 
9945
+ const CSS_STYLES = baseStyles + feedbackStyles + changelogStyles;
9946
+
10278
9947
  function injectStyles() {
10279
9948
  if (
10280
9949
  typeof document !== 'undefined' &&