@product7/feedback-sdk 1.2.7 → 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();
@@ -2630,11 +2409,6 @@
2630
2409
  }
2631
2410
  </div>
2632
2411
  </div>
2633
- <div class="changelog-list-item-arrow">
2634
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2635
- <path d="M9 18l6-6-6-6"/>
2636
- </svg>
2637
- </div>
2638
2412
  </div>
2639
2413
  `;
2640
2414
  }
@@ -3508,15 +3282,12 @@
3508
3282
  }
3509
3283
  }
3510
3284
 
3511
- /**
3512
- * MessengerLauncher - Floating trigger button for messenger
3513
- */
3514
3285
  class MessengerLauncher {
3515
3286
  constructor(state, options = {}) {
3516
3287
  this.state = state;
3517
3288
  this.options = {
3518
3289
  position: options.position || 'bottom-right',
3519
- primaryColor: options.primaryColor || '#1c1c1e',
3290
+ primaryColor: options.primaryColor || '#155eff',
3520
3291
  ...options,
3521
3292
  };
3522
3293
  this.element = null;
@@ -3530,7 +3301,6 @@
3530
3301
  this._updateContent();
3531
3302
  this._attachEvents();
3532
3303
 
3533
- // Subscribe to state changes
3534
3304
  this._unsubscribe = this.state.subscribe((type, data) => {
3535
3305
  if (type === 'openChange') {
3536
3306
  this._updateIcon();
@@ -3552,10 +3322,14 @@
3552
3322
  this.element.innerHTML = `
3553
3323
  <button class="messenger-launcher-btn" aria-label="Open messenger" style="background: ${this.options.primaryColor};">
3554
3324
  <span class="messenger-launcher-icon messenger-launcher-icon-chat">
3555
- <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>
3556
3328
  </span>
3557
3329
  <span class="messenger-launcher-icon messenger-launcher-icon-close" style="display: none;">
3558
- <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>
3559
3333
  </span>
3560
3334
  ${badgeHtml}
3561
3335
  </button>
@@ -3738,19 +3512,31 @@
3738
3512
  }
3739
3513
 
3740
3514
  _getHomeIcon() {
3741
- 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>`;
3742
3519
  }
3743
3520
 
3744
3521
  _getMessagesIcon() {
3745
- 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>`;
3746
3526
  }
3747
3527
 
3748
3528
  _getHelpIcon() {
3749
- 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>`;
3750
3533
  }
3751
3534
 
3752
3535
  _getChangelogIcon() {
3753
- 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>`;
3754
3540
  }
3755
3541
 
3756
3542
  destroy() {
@@ -3893,9 +3679,6 @@
3893
3679
  }
3894
3680
  }
3895
3681
 
3896
- /**
3897
- * ChangelogView - Changelog and announcements
3898
- */
3899
3682
  class ChangelogView {
3900
3683
  constructor(state, options = {}) {
3901
3684
  this.state = state;
@@ -3910,7 +3693,6 @@
3910
3693
 
3911
3694
  this._updateContent();
3912
3695
 
3913
- // Subscribe to state changes
3914
3696
  this._unsubscribe = this.state.subscribe((type) => {
3915
3697
  if (type === 'changelogUpdate') {
3916
3698
  this._updateChangelogList();
@@ -3927,7 +3709,9 @@
3927
3709
  <div class="messenger-changelog-header">
3928
3710
  <h2>Changelog</h2>
3929
3711
  <button class="messenger-close-btn" aria-label="Close">
3930
- <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>
3931
3715
  </button>
3932
3716
  </div>
3933
3717
 
@@ -3963,7 +3747,6 @@
3963
3747
  .map((item) => this._renderChangelogCard(item))
3964
3748
  .join('');
3965
3749
 
3966
- // Attach click events
3967
3750
  this._attachChangelogEvents();
3968
3751
  }
3969
3752
 
@@ -3994,7 +3777,9 @@
3994
3777
  ${item.description ? `<p class="messenger-changelog-description">${this._truncateText(item.description, 100)}</p>` : ''}
3995
3778
  <div class="messenger-changelog-meta">
3996
3779
  <span class="messenger-changelog-date">${dateStr}</span>
3997
- <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>
3998
3783
  </div>
3999
3784
  </div>
4000
3785
  </div>
@@ -4029,7 +3814,9 @@
4029
3814
  return `
4030
3815
  <div class="messenger-changelog-empty">
4031
3816
  <div class="messenger-changelog-empty-icon">
4032
- <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>
4033
3820
  </div>
4034
3821
  <h3>No changelog yet</h3>
4035
3822
  <p>Check back later for updates</p>
@@ -4054,7 +3841,6 @@
4054
3841
  }
4055
3842
 
4056
3843
  _attachEvents() {
4057
- // Close button
4058
3844
  this.element
4059
3845
  .querySelector('.messenger-close-btn')
4060
3846
  .addEventListener('click', () => {
@@ -4092,9 +3878,6 @@
4092
3878
  }
4093
3879
  }
4094
3880
 
4095
- /**
4096
- * ChatView - Individual conversation chat
4097
- */
4098
3881
  class ChatView {
4099
3882
  constructor(state, options = {}) {
4100
3883
  this.state = state;
@@ -4112,7 +3895,6 @@
4112
3895
 
4113
3896
  this._updateContent();
4114
3897
 
4115
- // Subscribe to state changes
4116
3898
  this._unsubscribe = this.state.subscribe((type, data) => {
4117
3899
  if (
4118
3900
  type === 'messageAdded' &&
@@ -4163,14 +3945,18 @@
4163
3945
  this.element.innerHTML = `
4164
3946
  <div class="messenger-chat-header">
4165
3947
  <button class="messenger-back-btn" aria-label="Back">
4166
- <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>
4167
3951
  </button>
4168
3952
  <div class="messenger-chat-header-info">
4169
3953
  ${avatarHtml}
4170
3954
  <span class="messenger-chat-title">${title}</span>
4171
3955
  </div>
4172
3956
  <button class="messenger-close-btn" aria-label="Close">
4173
- <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>
4174
3960
  </button>
4175
3961
  </div>
4176
3962
 
@@ -4189,7 +3975,9 @@
4189
3975
  <textarea class="messenger-compose-input" placeholder="${placeholder}" rows="1"></textarea>
4190
3976
  </div>
4191
3977
  <button class="messenger-compose-send" aria-label="Send" disabled>
4192
- <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>
4193
3981
  </button>
4194
3982
  </div>
4195
3983
  `;
@@ -4309,7 +4097,6 @@
4309
4097
 
4310
4098
  _formatMessageContent(content) {
4311
4099
  if (!content) return '';
4312
- // Basic HTML escaping and line breaks
4313
4100
  return content
4314
4101
  .replace(/&/g, '&amp;')
4315
4102
  .replace(/</g, '&lt;')
@@ -4344,33 +4131,27 @@
4344
4131
  }
4345
4132
 
4346
4133
  _attachEvents() {
4347
- // Back button
4348
4134
  this.element
4349
4135
  .querySelector('.messenger-back-btn')
4350
4136
  .addEventListener('click', () => {
4351
4137
  this.state.setView('messages');
4352
4138
  });
4353
4139
 
4354
- // Close button
4355
4140
  this.element
4356
4141
  .querySelector('.messenger-close-btn')
4357
4142
  .addEventListener('click', () => {
4358
4143
  this.state.setOpen(false);
4359
4144
  });
4360
4145
 
4361
- // Compose input
4362
4146
  const input = this.element.querySelector('.messenger-compose-input');
4363
4147
  const sendBtn = this.element.querySelector('.messenger-compose-send');
4364
4148
 
4365
4149
  input.addEventListener('input', () => {
4366
- // Auto-resize textarea
4367
4150
  input.style.height = 'auto';
4368
4151
  input.style.height = Math.min(input.scrollHeight, 120) + 'px';
4369
4152
 
4370
- // Enable/disable send button
4371
4153
  sendBtn.disabled = !input.value.trim();
4372
4154
 
4373
- // Send typing indicator
4374
4155
  if (input.value.trim()) {
4375
4156
  this._startTyping();
4376
4157
  }
@@ -4394,18 +4175,15 @@
4394
4175
 
4395
4176
  if (!content) return;
4396
4177
 
4397
- // Stop typing indicator
4398
4178
  this._stopTyping();
4399
4179
 
4400
4180
  const isNewConversation = !this.state.activeConversationId;
4401
4181
 
4402
4182
  if (isNewConversation) {
4403
- // Start a new conversation
4404
4183
  if (this.options.onStartConversation) {
4405
4184
  this.options.onStartConversation(content);
4406
4185
  }
4407
4186
  } else {
4408
- // Add message to existing conversation
4409
4187
  const message = {
4410
4188
  id: 'msg_' + Date.now(),
4411
4189
  content: content,
@@ -4415,13 +4193,11 @@
4415
4193
 
4416
4194
  this.state.addMessage(this.state.activeConversationId, message);
4417
4195
 
4418
- // Emit event for API integration
4419
4196
  if (this.options.onSendMessage) {
4420
4197
  this.options.onSendMessage(this.state.activeConversationId, message);
4421
4198
  }
4422
4199
  }
4423
4200
 
4424
- // Clear input
4425
4201
  input.value = '';
4426
4202
  input.style.height = 'auto';
4427
4203
  this.element.querySelector('.messenger-compose-send').disabled = true;
@@ -4435,7 +4211,6 @@
4435
4211
  }
4436
4212
  }
4437
4213
 
4438
- // Reset typing timeout
4439
4214
  if (this._typingTimeout) {
4440
4215
  clearTimeout(this._typingTimeout);
4441
4216
  }
@@ -4490,9 +4265,6 @@
4490
4265
  }
4491
4266
  }
4492
4267
 
4493
- /**
4494
- * ConversationsView - Message thread list
4495
- */
4496
4268
  class ConversationsView {
4497
4269
  constructor(state, options = {}) {
4498
4270
  this.state = state;
@@ -4508,7 +4280,6 @@
4508
4280
  this._updateContent();
4509
4281
  this._attachEvents();
4510
4282
 
4511
- // Subscribe to state changes
4512
4283
  this._unsubscribe = this.state.subscribe((type) => {
4513
4284
  if (
4514
4285
  type === 'conversationsUpdate' ||
@@ -4531,7 +4302,9 @@
4531
4302
  conversationsHtml = `
4532
4303
  <div class="messenger-conversations-empty">
4533
4304
  <div class="messenger-conversations-empty-icon">
4534
- <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>
4535
4308
  </div>
4536
4309
  <h3>No conversations yet</h3>
4537
4310
  <p>Start a new conversation with our team</p>
@@ -4549,7 +4322,9 @@
4549
4322
  <div class="messenger-conversations-header">
4550
4323
  <h2>Messages</h2>
4551
4324
  <button class="messenger-close-btn" aria-label="Close">
4552
- <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>
4553
4328
  </button>
4554
4329
  </div>
4555
4330
 
@@ -4561,7 +4336,9 @@
4561
4336
  <button class="messenger-new-message-btn">
4562
4337
  <div class="messenger-new-message-avatars">${avatarsHtml}</div>
4563
4338
  <span>Send us a message</span>
4564
- <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>
4565
4342
  </button>
4566
4343
  </div>
4567
4344
  `;
@@ -4660,7 +4437,6 @@
4660
4437
  }
4661
4438
 
4662
4439
  _attachEvents() {
4663
- // Close button
4664
4440
  const closeBtn = this.element.querySelector('.messenger-close-btn');
4665
4441
  if (closeBtn) {
4666
4442
  closeBtn.addEventListener('click', () => {
@@ -4668,7 +4444,6 @@
4668
4444
  });
4669
4445
  }
4670
4446
 
4671
- // Conversation items
4672
4447
  this.element
4673
4448
  .querySelectorAll('.messenger-conversation-item')
4674
4449
  .forEach((item) => {
@@ -4678,14 +4453,12 @@
4678
4453
  this.state.markAsRead(convId);
4679
4454
  this.state.setView('chat');
4680
4455
 
4681
- // Notify widget to fetch messages
4682
4456
  if (this.options.onSelectConversation) {
4683
4457
  this.options.onSelectConversation(convId);
4684
4458
  }
4685
4459
  });
4686
4460
  });
4687
4461
 
4688
- // New message button
4689
4462
  const newMsgBtn = this.element.querySelector('.messenger-new-message-btn');
4690
4463
  if (newMsgBtn) {
4691
4464
  newMsgBtn.addEventListener('click', () => {
@@ -4695,11 +4468,9 @@
4695
4468
  }
4696
4469
 
4697
4470
  _startNewConversation() {
4698
- // Set view to chat with no active conversation (new conversation mode)
4699
4471
  this.state.setActiveConversation(null);
4700
4472
  this.state.setView('chat');
4701
4473
 
4702
- // Notify widget to handle new conversation flow
4703
4474
  if (this.options.onStartNewConversation) {
4704
4475
  this.options.onStartNewConversation();
4705
4476
  }
@@ -4715,9 +4486,6 @@
4715
4486
  }
4716
4487
  }
4717
4488
 
4718
- /**
4719
- * HelpView - Help collections browse with Intercom-style design
4720
- */
4721
4489
  class HelpView {
4722
4490
  constructor(state, options = {}) {
4723
4491
  this.state = state;
@@ -4732,7 +4500,6 @@
4732
4500
 
4733
4501
  this._updateContent();
4734
4502
 
4735
- // Subscribe to state changes
4736
4503
  this._unsubscribe = this.state.subscribe((type) => {
4737
4504
  if (type === 'helpArticlesUpdate' || type === 'helpSearchChange') {
4738
4505
  this._updateCollectionsList();
@@ -4751,7 +4518,9 @@
4751
4518
  <div class="messenger-help-header">
4752
4519
  <h2>Help</h2>
4753
4520
  <button class="messenger-close-btn" aria-label="Close">
4754
- <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>
4755
4524
  </button>
4756
4525
  </div>
4757
4526
 
@@ -4763,7 +4532,9 @@
4763
4532
  placeholder="Search for help"
4764
4533
  value="${searchQuery}"
4765
4534
  />
4766
- <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>
4767
4538
  </div>
4768
4539
  </div>
4769
4540
 
@@ -4786,7 +4557,6 @@
4786
4557
  const collections = this.state.helpArticles || [];
4787
4558
  const searchQuery = (this.state.helpSearchQuery || '').toLowerCase();
4788
4559
 
4789
- // Filter collections by search
4790
4560
  const filteredCollections = searchQuery
4791
4561
  ? collections.filter(
4792
4562
  (c) =>
@@ -4795,7 +4565,6 @@
4795
4565
  )
4796
4566
  : collections;
4797
4567
 
4798
- // Update collection count
4799
4568
  const headerEl = this.element.querySelector(
4800
4569
  '.messenger-help-collections-header'
4801
4570
  );
@@ -4812,7 +4581,6 @@
4812
4581
  .map((collection) => this._renderCollectionItem(collection))
4813
4582
  .join('');
4814
4583
 
4815
- // Attach click events
4816
4584
  this._attachCollectionEvents();
4817
4585
  }
4818
4586
 
@@ -4825,7 +4593,9 @@
4825
4593
  <p class="messenger-help-collection-desc">${collection.description || ''}</p>
4826
4594
  <span class="messenger-help-collection-count">${articleCount} articles</span>
4827
4595
  </div>
4828
- <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>
4829
4599
  </div>
4830
4600
  `;
4831
4601
  }
@@ -4837,7 +4607,9 @@
4837
4607
  return `
4838
4608
  <div class="messenger-help-empty">
4839
4609
  <div class="messenger-help-empty-icon">
4840
- <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>
4841
4613
  </div>
4842
4614
  <h3>No results found</h3>
4843
4615
  <p>Try a different search term</p>
@@ -4848,7 +4620,9 @@
4848
4620
  return `
4849
4621
  <div class="messenger-help-empty">
4850
4622
  <div class="messenger-help-empty-icon">
4851
- <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>
4852
4626
  </div>
4853
4627
  <h3>Help collections</h3>
4854
4628
  <p>No collections available yet</p>
@@ -4857,14 +4631,12 @@
4857
4631
  }
4858
4632
 
4859
4633
  _attachEvents() {
4860
- // Close button
4861
4634
  this.element
4862
4635
  .querySelector('.messenger-close-btn')
4863
4636
  .addEventListener('click', () => {
4864
4637
  this.state.setOpen(false);
4865
4638
  });
4866
4639
 
4867
- // Search input
4868
4640
  const searchInput = this.element.querySelector(
4869
4641
  '.messenger-help-search-input'
4870
4642
  );
@@ -4907,9 +4679,6 @@
4907
4679
  }
4908
4680
  }
4909
4681
 
4910
- /**
4911
- * HomeView - Welcome screen with team info and quick actions
4912
- */
4913
4682
  class HomeView {
4914
4683
  constructor(state, options = {}) {
4915
4684
  this.state = state;
@@ -4924,7 +4693,6 @@
4924
4693
 
4925
4694
  this._updateContent();
4926
4695
 
4927
- // Subscribe to state changes to re-render when data loads
4928
4696
  this._unsubscribe = this.state.subscribe((type) => {
4929
4697
  if (
4930
4698
  type === 'homeChangelogUpdate' ||
@@ -4950,7 +4718,9 @@
4950
4718
  </div>
4951
4719
  <div class="messenger-home-avatars">${avatarsHtml}</div>
4952
4720
  <button class="messenger-close-btn" aria-label="Close">
4953
- <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>
4954
4724
  </button>
4955
4725
  </div>
4956
4726
  <div class="messenger-home-welcome">
@@ -4963,7 +4733,9 @@
4963
4733
  <div class="messenger-home-body">
4964
4734
  <button class="messenger-home-message-btn">
4965
4735
  <span>Send us a message</span>
4966
- <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>
4967
4739
  </button>
4968
4740
 
4969
4741
  ${this._renderFeaturedCard()}
@@ -4978,7 +4750,6 @@
4978
4750
  _renderAvatarStack() {
4979
4751
  const avatars = this.state.teamAvatars;
4980
4752
  if (!avatars || avatars.length === 0) {
4981
- // Default avatars with initials
4982
4753
  return `
4983
4754
  <div class="messenger-avatar-stack">
4984
4755
  <div class="messenger-avatar" style="background: #5856d6;">S</div>
@@ -5028,7 +4799,6 @@
5028
4799
  }
5029
4800
 
5030
4801
  _renderFeaturedCard() {
5031
- // Only show if there's featured content configured
5032
4802
  if (!this.options.featuredContent) {
5033
4803
  return '';
5034
4804
  }
@@ -5049,7 +4819,6 @@
5049
4819
  }
5050
4820
 
5051
4821
  _renderRecentChangelog() {
5052
- // Show recent changelog preview as cards with images
5053
4822
  const changelogItems = this.state.homeChangelogItems;
5054
4823
  if (changelogItems.length === 0) {
5055
4824
  return '';
@@ -5098,31 +4867,26 @@
5098
4867
  }
5099
4868
 
5100
4869
  _attachEvents() {
5101
- // Close button
5102
4870
  this.element
5103
4871
  .querySelector('.messenger-close-btn')
5104
4872
  .addEventListener('click', () => {
5105
4873
  this.state.setOpen(false);
5106
4874
  });
5107
4875
 
5108
- // Send message button
5109
4876
  this.element
5110
4877
  .querySelector('.messenger-home-message-btn')
5111
4878
  .addEventListener('click', () => {
5112
4879
  this.state.setView('messages');
5113
4880
  });
5114
4881
 
5115
- // Changelog items
5116
4882
  this.element
5117
4883
  .querySelectorAll('.messenger-home-changelog-item')
5118
4884
  .forEach((item) => {
5119
4885
  item.addEventListener('click', () => {
5120
- // Navigate to changelog view with specific item selected
5121
4886
  this.state.setView('changelog');
5122
4887
  });
5123
4888
  });
5124
4889
 
5125
- // See all changelog
5126
4890
  const seeAllBtn = this.element.querySelector(
5127
4891
  '.messenger-home-changelog-all'
5128
4892
  );
@@ -5132,7 +4896,6 @@
5132
4896
  });
5133
4897
  }
5134
4898
 
5135
- // Featured card action
5136
4899
  const featuredBtn = this.element.querySelector(
5137
4900
  '.messenger-home-featured-btn'
5138
4901
  );
@@ -5175,7 +4938,7 @@
5175
4938
  welcomeMessage: options.welcomeMessage || 'How can we help?',
5176
4939
  enableHelp: options.enableHelp !== false,
5177
4940
  enableChangelog: options.enableChangelog !== false,
5178
- logoUrl: options.logoUrl || 'https://feedback-sdk.product7.io/p7.png',
4941
+ logoUrl: options.logoUrl || 'https://product7.io/p7logo.svg',
5179
4942
  featuredContent: options.featuredContent || null,
5180
4943
  primaryColor: options.primaryColor || '#1c1c1e',
5181
4944
  // Callbacks
@@ -5740,7 +5503,6 @@
5740
5503
  }
5741
5504
 
5742
5505
  async _fetchChangelog() {
5743
- // Mock data for now - simulating changelog API response
5744
5506
  if (this.apiService?.mock) {
5745
5507
  return {
5746
5508
  homeItems: [
@@ -6658,7 +6420,6 @@
6658
6420
  try {
6659
6421
  const initData = await this.apiService.init(this.config.userContext);
6660
6422
 
6661
- // Merge backend config as base, local config overrides
6662
6423
  if (initData.config) {
6663
6424
  this.config = deepMerge(initData.config, this.config);
6664
6425
  }
@@ -6711,13 +6472,6 @@
6711
6472
  return this.widgets.get(id);
6712
6473
  }
6713
6474
 
6714
- /**
6715
- * Fetch active surveys from the backend
6716
- * @param {Object} context - Optional context for targeting
6717
- * @param {string} context.page - Current page/route
6718
- * @param {string} context.event - Event trigger name
6719
- * @returns {Promise<Array>} Array of active survey configurations
6720
- */
6721
6475
  async getActiveSurveys(context = {}) {
6722
6476
  if (!this.initialized) {
6723
6477
  throw new SDKError(
@@ -6737,17 +6491,6 @@
6737
6491
  }
6738
6492
  }
6739
6493
 
6740
- /**
6741
- * Show a survey by its backend ID
6742
- * Fetches survey configuration from the backend and displays it
6743
- * @param {string} surveyId - The backend survey ID
6744
- * @param {Object} options - Additional display options
6745
- * @param {string} options.position - Position override
6746
- * @param {string} options.theme - Theme override
6747
- * @param {Function} options.onSubmit - Callback when survey is submitted
6748
- * @param {Function} options.onDismiss - Callback when survey is dismissed
6749
- * @returns {Promise<SurveyWidget>} The survey widget instance
6750
- */
6751
6494
  async showSurveyById(surveyId, options = {}) {
6752
6495
  if (!this.initialized) {
6753
6496
  throw new SDKError(
@@ -6755,7 +6498,6 @@
6755
6498
  );
6756
6499
  }
6757
6500
 
6758
- // Fetch active surveys to find the one with matching ID
6759
6501
  const surveys = await this.getActiveSurveys();
6760
6502
  const surveyConfig = surveys.find((s) => s.id === surveyId);
6761
6503
 
@@ -6777,22 +6519,6 @@
6777
6519
  });
6778
6520
  }
6779
6521
 
6780
- /**
6781
- * Show a survey widget (local/manual mode)
6782
- * For backend-driven surveys, use showSurveyById() instead
6783
- * @param {Object} options - Survey options
6784
- * @param {string} options.surveyId - Backend survey ID (for API tracking)
6785
- * @param {string} options.surveyType - Type of survey: 'nps', 'csat', 'ces', 'custom'
6786
- * @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'center', 'bottom'
6787
- * @param {string} options.theme - Theme: 'light', 'dark'
6788
- * @param {string} options.title - Custom title
6789
- * @param {string} options.description - Custom description
6790
- * @param {string} options.lowLabel - Low end label
6791
- * @param {string} options.highLabel - High end label
6792
- * @param {Function} options.onSubmit - Callback when survey is submitted
6793
- * @param {Function} options.onDismiss - Callback when survey is dismissed
6794
- * @returns {SurveyWidget} The survey widget instance
6795
- */
6796
6522
  showSurvey(options = {}) {
6797
6523
  if (!this.initialized) {
6798
6524
  throw new SDKError(
@@ -6820,20 +6546,6 @@
6820
6546
  return surveyWidget;
6821
6547
  }
6822
6548
 
6823
- /**
6824
- * Show a changelog widget with sidebar
6825
- * @param {Object} options - Changelog widget options
6826
- * @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'top-right', 'top-left'
6827
- * @param {string} options.theme - Theme: 'light', 'dark'
6828
- * @param {string} options.title - Sidebar title
6829
- * @param {string} options.triggerText - Text on the trigger button
6830
- * @param {boolean} options.showBadge - Show notification badge
6831
- * @param {string} options.viewButtonText - Text for the view update button
6832
- * @param {string} options.changelogBaseUrl - Base URL for changelog links
6833
- * @param {boolean} options.openInNewTab - Open changelog links in new tab (default: true)
6834
- * @param {Function} options.onViewUpdate - Callback when user clicks view update
6835
- * @returns {ChangelogWidget} The changelog widget instance
6836
- */
6837
6549
  showChangelog(options = {}) {
6838
6550
  if (!this.initialized) {
6839
6551
  throw new SDKError(
@@ -6859,13 +6571,6 @@
6859
6571
  return changelogWidget;
6860
6572
  }
6861
6573
 
6862
- /**
6863
- * Get changelogs from the backend
6864
- * @param {Object} options - Optional query parameters
6865
- * @param {number} options.limit - Number of changelogs to fetch
6866
- * @param {number} options.offset - Offset for pagination
6867
- * @returns {Promise<Array>} Array of changelog entries
6868
- */
6869
6574
  async getChangelogs(options = {}) {
6870
6575
  if (!this.initialized) {
6871
6576
  throw new SDKError(
@@ -6973,17 +6678,10 @@
6973
6678
  this.eventBus.emit('sdk:destroyed');
6974
6679
  }
6975
6680
 
6976
- /**
6977
- * Check if feedback was recently submitted for this workspace
6978
- * Uses backend tracking (preferred) with localStorage as fallback
6979
- * @param {number} cooldownDays - Days to consider as "recently" (default: 30)
6980
- * @returns {boolean} true if feedback was submitted within the cooldown period
6981
- */
6982
6681
  hasFeedbackBeenSubmitted(cooldownDays = 30) {
6983
6682
  const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
6984
6683
  const now = Date.now();
6985
6684
 
6986
- // Check backend tracking first (from init response)
6987
6685
  if (this.config.last_feedback_at) {
6988
6686
  try {
6989
6687
  const backendTimestamp = new Date(
@@ -6997,7 +6695,6 @@
6997
6695
  }
6998
6696
  }
6999
6697
 
7000
- // Fallback to localStorage
7001
6698
  try {
7002
6699
  const storageKey = `feedback_submitted_${this.config.workspace}`;
7003
6700
  const stored = localStorage.getItem(storageKey);
@@ -7010,9 +6707,6 @@
7010
6707
  }
7011
6708
  }
7012
6709
 
7013
- /**
7014
- * Clear the feedback submission tracking (allow showing widgets again)
7015
- */
7016
6710
  clearFeedbackSubmissionTracking() {
7017
6711
  try {
7018
6712
  const storageKey = `feedback_submitted_${this.config.workspace}`;
@@ -8898,7 +8592,7 @@
8898
8592
  }
8899
8593
  `;
8900
8594
 
8901
- const CSS_STYLES = `
8595
+ const baseStyles = `
8902
8596
  .feedback-widget {
8903
8597
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
8904
8598
  font-size: 14px;
@@ -8913,32 +8607,94 @@
8913
8607
  box-sizing: border-box;
8914
8608
  }
8915
8609
 
8916
- .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 {
8917
8673
  position: fixed;
8918
8674
  z-index: 999999;
8919
8675
  }
8920
8676
 
8921
- .feedback-widget-button.position-bottom-right {
8677
+ .changelog-widget.position-bottom-right {
8922
8678
  bottom: 20px;
8923
8679
  right: 20px;
8924
8680
  }
8925
8681
 
8926
- .feedback-widget-button.position-bottom-left {
8682
+ .changelog-widget.position-bottom-left {
8927
8683
  bottom: 20px;
8928
8684
  left: 20px;
8929
8685
  }
8930
8686
 
8931
- .feedback-widget-button.position-top-right {
8687
+ .changelog-widget.position-top-right {
8932
8688
  top: 20px;
8933
8689
  right: 20px;
8934
8690
  }
8935
8691
 
8936
- .feedback-widget-button.position-top-left {
8692
+ .changelog-widget.position-top-left {
8937
8693
  top: 20px;
8938
8694
  left: 20px;
8939
8695
  }
8940
8696
 
8941
- .feedback-trigger-btn {
8697
+ .changelog-trigger-btn {
8942
8698
  position: relative;
8943
8699
  display: flex;
8944
8700
  align-items: center;
@@ -8960,1326 +8716,1234 @@
8960
8716
  width: fit-content;
8961
8717
  }
8962
8718
 
8963
- .feedback-trigger-btn:hover:not(:disabled) {
8719
+ .changelog-trigger-btn:hover {
8964
8720
  transform: translateY(-2px);
8965
8721
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
8966
8722
  }
8967
8723
 
8968
- .feedback-trigger-btn:disabled {
8969
- opacity: 0.7;
8970
- cursor: not-allowed;
8971
- }
8972
-
8973
- .feedback-trigger-btn:focus-visible {
8724
+ .changelog-trigger-btn:focus-visible {
8974
8725
  outline: 2px solid #155EEF;
8975
8726
  outline-offset: 2px;
8976
8727
  }
8977
8728
 
8978
- .feedback-icon {
8729
+ .changelog-icon {
8979
8730
  flex-shrink: 0;
8980
8731
  }
8981
8732
 
8982
- .feedback-minimize-icon,
8983
- .feedback-expand-icon {
8984
- position: absolute;
8985
- top: -6px;
8986
- right: -6px;
8987
- width: 24px;
8988
- height: 24px;
8989
- padding: 4px;
8990
- display: flex;
8991
- align-items: center;
8992
- justify-content: center;
8993
- background: white;
8994
- border-radius: 50%;
8995
- opacity: 0;
8996
- transition: opacity 0.2s ease;
8997
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
8998
- cursor: pointer;
8999
- display: flex;
9000
- align-items: center;
9001
- justify-content: center;
8733
+ .changelog-confetti-emoji {
8734
+ font-size: 14px;
8735
+ margin-left: 2px;
9002
8736
  }
9003
8737
 
9004
- .feedback-minimize-icon svg,
9005
- .feedback-expand-icon svg {
9006
- width: 16px;
9007
- height: 16px;
9008
- display: block;
9009
- fill: #155EEF;
9010
- }
9011
-
9012
- /* Show minimize icon on hover when expanded */
9013
- .feedback-widget-button:not(.minimized) .feedback-trigger-btn:hover .feedback-minimize-icon {
9014
- opacity: 1;
9015
- }
9016
-
9017
- /* Minimized state - just icon */
9018
- .feedback-widget-button.minimized .feedback-trigger-btn {
9019
- padding: 12px;
9020
- width: 48px;
9021
- height: 48px;
9022
- justify-content: center;
9023
- }
9024
-
9025
- .feedback-widget-button.minimized .feedback-text {
9026
- display: none;
9027
- }
9028
-
9029
- .feedback-widget-button.minimized .feedback-minimize-icon {
9030
- display: none;
9031
- }
9032
-
9033
- /* Show expand icon on hover when minimized */
9034
- .feedback-widget-button.minimized .feedback-trigger-btn:hover .feedback-expand-icon {
9035
- 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;
9036
8747
  }
9037
8748
 
9038
- /* Side Panel Styles */
9039
- .feedback-panel {
8749
+ .changelog-confetti-container {
9040
8750
  position: fixed;
9041
- bottom: 80px;
9042
- right: 24px;
9043
- width: 420px;
9044
- max-height: 500px;
9045
- z-index: 1000000;
9046
- transform: translateX(calc(100% + 24px));
9047
- transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
9048
- 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;
9049
8758
  }
9050
8759
 
9051
- .feedback-panel.open {
9052
- transform: translateX(0);
8760
+ .changelog-confetti {
8761
+ position: absolute;
8762
+ top: -20px;
8763
+ opacity: 0;
8764
+ animation: confettiFall 2s ease-out forwards;
9053
8765
  }
9054
8766
 
9055
- .feedback-panel-backdrop {
8767
+ .changelog-modal-backdrop {
9056
8768
  position: fixed;
9057
8769
  top: 0;
9058
8770
  left: 0;
9059
8771
  right: 0;
9060
8772
  bottom: 0;
9061
- background: rgba(0, 0, 0, 0.1);
8773
+ background: rgba(0, 0, 0, 0.5);
9062
8774
  opacity: 0;
9063
8775
  transition: opacity 0.3s ease;
9064
8776
  pointer-events: none;
9065
- z-index: 999999;
8777
+ z-index: 999998;
8778
+ display: flex;
8779
+ align-items: center;
8780
+ justify-content: center;
9066
8781
  }
9067
8782
 
9068
- .feedback-panel-backdrop.show {
8783
+ .changelog-modal-backdrop.show {
9069
8784
  opacity: 1;
9070
8785
  pointer-events: auto;
9071
8786
  }
9072
8787
 
9073
- .feedback-panel-content {
9074
- background: white;
9075
- height: 100%;
8788
+ .changelog-modal {
8789
+ position: fixed;
8790
+ top: 0;
8791
+ left: 0;
8792
+ right: 0;
8793
+ bottom: 0;
8794
+ z-index: 999999;
9076
8795
  display: flex;
9077
- flex-direction: column;
9078
- border-radius: 16px;
9079
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
9080
- 0 10px 10px -5px rgba(0, 0, 0, 0.04),
9081
- 0 0 0 1px rgba(0, 0, 0, 0.05);
9082
- }
9083
-
9084
- .feedback-panel.theme-dark .feedback-panel-content {
9085
- background: #1F2937;
9086
- 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;
9087
8801
  }
9088
8802
 
9089
- .feedback-panel-header {
9090
- display: flex;
9091
- align-items: center;
9092
- justify-content: space-between;
9093
- padding: 24px;
9094
- border-bottom: 1px solid #E5E7EB;
9095
- flex-shrink: 0;
8803
+ .changelog-modal.open {
8804
+ pointer-events: auto;
9096
8805
  }
9097
8806
 
9098
- .feedback-panel.theme-dark .feedback-panel-header {
9099
- 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);
9100
8819
  }
9101
8820
 
9102
- .feedback-panel-header h3 {
9103
- margin: 0;
9104
- font-size: 18px;
9105
- font-weight: 600;
9106
- color: #111827;
8821
+ .changelog-modal.open .changelog-modal-container {
8822
+ transform: scale(1) translateY(0);
8823
+ opacity: 1;
9107
8824
  }
9108
8825
 
9109
- .feedback-panel.theme-dark .feedback-panel-header h3 {
9110
- color: white;
8826
+ .changelog-modal.theme-dark .changelog-modal-container {
8827
+ background: #1E3A5F;
9111
8828
  }
9112
8829
 
9113
- .feedback-panel-close {
9114
- background: none;
8830
+ .changelog-modal-close {
8831
+ position: absolute;
8832
+ top: 16px;
8833
+ right: 16px;
8834
+ background: rgba(255, 255, 255, 0.9);
9115
8835
  border: none;
9116
8836
  font-size: 24px;
9117
8837
  cursor: pointer;
9118
8838
  color: #6B7280;
9119
- padding: 4px;
9120
- width: 32px;
9121
- height: 32px;
8839
+ padding: 0;
8840
+ width: 36px;
8841
+ height: 36px;
9122
8842
  display: flex;
9123
8843
  align-items: center;
9124
8844
  justify-content: center;
9125
- border-radius: 6px;
8845
+ border-radius: 50%;
9126
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);
9127
8850
  }
9128
8851
 
9129
- .feedback-panel-close:hover {
9130
- background: #F3F4F6;
8852
+ .changelog-modal-close:hover {
8853
+ background: white;
9131
8854
  color: #111827;
8855
+ transform: scale(1.05);
9132
8856
  }
9133
8857
 
9134
- .feedback-panel-close:focus-visible {
9135
- outline: 2px solid #155EEF;
9136
- outline-offset: 2px;
9137
- }
9138
-
9139
- .feedback-panel.theme-dark .feedback-panel-close {
9140
- color: #9CA3AF;
8858
+ .changelog-modal.theme-dark .changelog-modal-close {
8859
+ background: rgba(55, 65, 81, 0.9);
8860
+ color: #D1D5DB;
9141
8861
  }
9142
8862
 
9143
- .feedback-panel.theme-dark .feedback-panel-close:hover {
8863
+ .changelog-modal.theme-dark .changelog-modal-close:hover {
9144
8864
  background: #374151;
9145
8865
  color: white;
9146
8866
  }
9147
8867
 
9148
- .feedback-panel-body {
9149
- flex: 1;
8868
+ .changelog-modal-content {
9150
8869
  overflow-y: auto;
9151
- padding: 24px;
8870
+ max-height: 90vh;
9152
8871
  }
9153
8872
 
9154
- .feedback-form {
8873
+ .changelog-loading {
9155
8874
  display: flex;
9156
- flex-direction: column;
9157
- height: 100%;
8875
+ align-items: center;
8876
+ justify-content: center;
8877
+ padding: 80px 20px;
9158
8878
  }
9159
8879
 
9160
- .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 {
9161
8890
  display: flex;
9162
8891
  flex-direction: column;
9163
- gap: 8px;
9164
- margin-bottom: 20px;
8892
+ align-items: center;
8893
+ justify-content: center;
8894
+ padding: 80px 20px;
8895
+ text-align: center;
8896
+ color: #9CA3AF;
9165
8897
  }
9166
8898
 
9167
- .feedback-form-group:last-child {
9168
- margin-bottom: 0;
8899
+ .changelog-empty svg {
8900
+ margin-bottom: 16px;
8901
+ stroke: #D1D5DB;
9169
8902
  }
9170
8903
 
9171
- .feedback-form-group label {
9172
- font-size: 14px;
9173
- font-weight: 500;
9174
- line-height: 1.25;
9175
- color: #374151;
8904
+ .changelog-empty p {
8905
+ margin: 0;
8906
+ font-size: 15px;
9176
8907
  }
9177
8908
 
9178
- .feedback-panel.theme-dark .feedback-form-group label {
9179
- color: #D1D5DB;
8909
+ .changelog-popup-item {
8910
+ display: flex;
8911
+ flex-direction: column;
9180
8912
  }
9181
8913
 
9182
- .feedback-form-group input {
9183
- height: 44px;
8914
+ .changelog-popup-image {
9184
8915
  width: 100%;
9185
- border-radius: 8px;
9186
- border: 1px solid #D1D5DB;
9187
- padding: 10px 14px;
9188
- font-size: 15px;
9189
- font-weight: 400;
9190
- line-height: 1.5;
9191
- color: #1F2937;
9192
- font-family: inherit;
9193
- outline: none;
9194
- transition: all 0.2s ease;
8916
+ padding: 24px 24px 0;
9195
8917
  }
9196
8918
 
9197
- .feedback-form-group input::placeholder {
9198
- font-size: 15px;
9199
- 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);
9200
8927
  }
9201
8928
 
9202
- .feedback-form-group input:focus {
9203
- border-color: #155EEF;
9204
- 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;
9205
8932
  }
9206
8933
 
9207
- .feedback-form-group input:focus-visible {
9208
- 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;
9209
8940
  }
9210
8941
 
9211
- .feedback-form-group textarea {
9212
- min-height: 200px;
9213
- width: 100%;
9214
- resize: vertical;
9215
- border-radius: 8px;
9216
- border: 1px solid #D1D5DB;
9217
- padding: 10px 14px;
9218
- font-size: 15px;
9219
- font-weight: 400;
9220
- line-height: 1.5;
9221
- color: #1F2937;
9222
- font-family: inherit;
9223
- outline: none;
9224
- transition: all 0.2s ease;
9225
- }
9226
-
9227
- .feedback-form-group textarea::placeholder {
9228
- font-size: 15px;
9229
- color: #9CA3AF;
9230
- }
9231
-
9232
- .feedback-form-group textarea:focus {
9233
- border-color: #155EEF;
9234
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
9235
- }
9236
-
9237
- .feedback-form-group textarea:focus-visible {
9238
- outline: none;
8942
+ .changelog-modal.theme-dark .changelog-popup-title {
8943
+ color: white;
9239
8944
  }
9240
8945
 
9241
- .feedback-panel.theme-dark .feedback-form-group input,
9242
- .feedback-panel.theme-dark .feedback-form-group textarea {
9243
- background: #374151;
9244
- border-color: #4B5563;
9245
- color: white;
8946
+ .changelog-popup-description {
8947
+ margin: 0 0 24px;
8948
+ font-size: 17px;
8949
+ line-height: 1.6;
8950
+ color: #4B5563;
9246
8951
  }
9247
8952
 
9248
- .feedback-panel.theme-dark .feedback-form-group input::placeholder,
9249
- .feedback-panel.theme-dark .feedback-form-group textarea::placeholder {
9250
- color: #6B7280;
8953
+ .changelog-modal.theme-dark .changelog-popup-description {
8954
+ color: #D1D5DB;
9251
8955
  }
9252
8956
 
9253
- .feedback-btn {
9254
- position: relative;
8957
+ .changelog-popup-btn {
9255
8958
  display: inline-flex;
9256
8959
  align-items: center;
9257
8960
  justify-content: center;
9258
- overflow: hidden;
9259
- border-radius: 8px;
8961
+ padding: 14px 32px;
8962
+ font-size: 16px;
8963
+ font-weight: 600;
8964
+ color: white;
8965
+ background: #155EEF;
9260
8966
  border: none;
9261
- height: 44px;
9262
- padding: 10px 18px;
9263
- font-size: 15px;
9264
- font-weight: 500;
9265
- font-family: inherit;
8967
+ border-radius: 10px;
9266
8968
  cursor: pointer;
9267
8969
  transition: all 0.2s ease;
9268
8970
  }
9269
8971
 
9270
- .feedback-btn:disabled {
9271
- opacity: 0.6;
9272
- 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);
9273
8976
  }
9274
8977
 
9275
- .feedback-btn:focus-visible {
8978
+ .changelog-popup-btn:focus-visible {
9276
8979
  outline: 2px solid #155EEF;
9277
8980
  outline-offset: 2px;
9278
8981
  }
9279
8982
 
9280
- .feedback-btn-submit {
9281
- background: #155EEF;
9282
- color: white;
9283
- 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;
9284
8989
  }
9285
8990
 
9286
- .feedback-btn-submit:hover:not(:disabled) {
9287
- background: #4338ca;
8991
+ .changelog-popup-dots {
8992
+ display: flex;
8993
+ gap: 8px;
9288
8994
  }
9289
8995
 
9290
- .feedback-btn-submit:active:not(:disabled) {
9291
- 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;
9292
9003
  }
9293
9004
 
9294
- .feedback-btn-cancel {
9295
- background: transparent;
9296
- color: #6B7280;
9297
- border: 1px solid #D1D5DB;
9005
+ .changelog-dot:hover {
9006
+ background: rgba(21, 94, 239, 0.5);
9298
9007
  }
9299
9008
 
9300
- .feedback-btn-cancel:hover:not(:disabled) {
9301
- background: #F9FAFB;
9302
- border-color: #9CA3AF;
9303
- color: #374151;
9009
+ .changelog-dot.active {
9010
+ background: #155EEF;
9011
+ transform: scale(1.2);
9304
9012
  }
9305
9013
 
9306
- .feedback-panel.theme-dark .feedback-btn-cancel {
9307
- color: #D1D5DB;
9308
- 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;
9309
9027
  }
9310
9028
 
9311
- .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
9312
- background: #374151;
9029
+ .changelog-view-all-btn:hover {
9030
+ background: rgba(21, 94, 239, 0.1);
9313
9031
  }
9314
9032
 
9315
- .feedback-form-actions {
9316
- display: flex;
9317
- flex-direction: column;
9318
- gap: 12px;
9319
- margin-top: auto;
9320
- padding-top: 24px;
9033
+ .changelog-view-all-btn svg {
9034
+ transition: transform 0.2s ease;
9321
9035
  }
9322
9036
 
9323
- .feedback-error {
9324
- color: #DC2626;
9325
- font-size: 14px;
9326
- font-weight: 400;
9327
- margin-top: 8px;
9328
- padding: 12px;
9329
- background: #FEE2E2;
9330
- border: 1px solid #FECACA;
9331
- border-radius: 8px;
9332
- display: none;
9037
+ .changelog-view-all-btn:hover svg {
9038
+ transform: translateX(3px);
9333
9039
  }
9334
9040
 
9335
- .feedback-error.show {
9336
- display: block;
9041
+ .changelog-modal.theme-dark .changelog-view-all-btn {
9042
+ color: #60A5FA;
9337
9043
  }
9338
9044
 
9339
- .feedback-panel.theme-dark .feedback-error {
9340
- background: #7F1D1D;
9341
- border-color: #991B1B;
9342
- color: #FCA5A5;
9045
+ .changelog-modal.theme-dark .changelog-view-all-btn:hover {
9046
+ background: rgba(96, 165, 250, 0.1);
9343
9047
  }
9344
9048
 
9345
- .feedback-success-notification {
9049
+ .changelog-list-modal-backdrop {
9346
9050
  position: fixed;
9347
- top: 24px;
9348
- right: 24px;
9349
- z-index: 1000002;
9350
- background: white;
9351
- border: 1px solid #D1FAE5;
9352
- border-radius: 12px;
9353
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
9354
- animation: slideInRight 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9355
- 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;
9356
9060
  }
9357
9061
 
9358
- .feedback-success-content {
9359
- display: flex;
9360
- align-items: center;
9361
- padding: 16px 20px;
9362
- gap: 12px;
9062
+ .changelog-list-modal-backdrop.show {
9063
+ opacity: 1;
9064
+ pointer-events: auto;
9363
9065
  }
9364
9066
 
9365
- .feedback-success-icon {
9366
- width: 20px;
9367
- height: 20px;
9368
- border-radius: 50%;
9369
- background: #10B981;
9370
- 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;
9371
9074
  display: flex;
9372
9075
  align-items: center;
9373
9076
  justify-content: center;
9374
- font-size: 12px;
9375
- font-weight: 600;
9376
- flex-shrink: 0;
9077
+ padding: 20px;
9078
+ pointer-events: none;
9079
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9377
9080
  }
9378
9081
 
9379
- .feedback-success-content span {
9380
- color: #065F46;
9381
- font-weight: 500;
9382
- font-size: 14px;
9383
- flex: 1;
9082
+ .changelog-list-modal.open {
9083
+ pointer-events: auto;
9384
9084
  }
9385
9085
 
9386
- .feedback-success-close {
9387
- background: none;
9388
- border: none;
9389
- color: #6B7280;
9390
- cursor: pointer;
9391
- font-size: 20px;
9392
- padding: 0;
9393
- width: 24px;
9394
- 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);
9395
9098
  display: flex;
9396
- align-items: center;
9397
- justify-content: center;
9398
- transition: all 0.2s ease;
9399
- border-radius: 4px;
9400
- flex-shrink: 0;
9099
+ flex-direction: column;
9401
9100
  }
9402
9101
 
9403
- .feedback-success-close:hover {
9404
- background: #F3F4F6;
9405
- color: #374151;
9102
+ .changelog-list-modal.open .changelog-list-modal-container {
9103
+ transform: scale(1) translateY(0);
9104
+ opacity: 1;
9406
9105
  }
9407
9106
 
9408
- .feedback-success-close:focus-visible {
9409
- outline: 2px solid #155EEF;
9410
- outline-offset: 2px;
9107
+ .changelog-list-modal.theme-dark .changelog-list-modal-container {
9108
+ background: #1F2937;
9109
+ color: white;
9411
9110
  }
9412
9111
 
9413
- @keyframes slideInRight {
9414
- from {
9415
- transform: translateX(400px);
9416
- opacity: 0;
9417
- }
9418
- to {
9419
- transform: translateX(0);
9420
- opacity: 1;
9421
- }
9422
- }
9423
-
9424
- @keyframes fadeIn {
9425
- from { opacity: 0; }
9426
- 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;
9427
9120
  }
9428
9121
 
9429
- .feedback-panel-backdrop {
9430
- animation: fadeIn 0.3s ease;
9122
+ .changelog-list-modal.theme-dark .changelog-list-modal-header {
9123
+ border-bottom-color: #374151;
9124
+ background: #1F2937;
9431
9125
  }
9432
9126
 
9433
- @media (max-width: 768px) {
9434
- .feedback-panel {
9435
- width: 100%;
9436
- top: auto;
9437
- bottom: 0;
9438
- right: 0;
9439
- left: 0;
9440
- height: 85vh;
9441
- max-height: 85vh;
9442
- transform: translateY(100%);
9443
- border-radius: 20px 20px 0 0;
9444
- }
9445
-
9446
- .feedback-panel.open {
9447
- transform: translateY(0);
9448
- }
9449
-
9450
- .feedback-panel-content {
9451
- border-radius: 20px 20px 0 0;
9452
- }
9453
-
9454
- .feedback-panel-header {
9455
- padding: 20px;
9456
- position: relative;
9457
- }
9458
-
9459
- .feedback-panel-header::before {
9460
- content: '';
9461
- position: absolute;
9462
- top: 8px;
9463
- left: 50%;
9464
- transform: translateX(-50%);
9465
- width: 40px;
9466
- height: 4px;
9467
- background: #D1D5DB;
9468
- border-radius: 2px;
9469
- }
9470
-
9471
- .feedback-panel.theme-dark .feedback-panel-header::before {
9472
- background: #4B5563;
9473
- }
9474
-
9475
- .feedback-panel-body {
9476
- padding: 20px;
9477
- }
9478
-
9479
- .feedback-form-group textarea {
9480
- min-height: 150px;
9481
- }
9482
-
9483
- .feedback-widget-button {
9484
- bottom: 16px;
9485
- right: 16px;
9486
- }
9487
-
9488
- .feedback-widget-button.position-bottom-left {
9489
- left: 16px;
9490
- }
9491
-
9492
- .feedback-success-notification {
9493
- top: 16px;
9494
- right: 16px;
9495
- left: 16px;
9496
- min-width: auto;
9497
- }
9498
-
9499
- .feedback-minimize-icon,
9500
- .feedback-expand-icon {
9501
- top: -4px;
9502
- right: -4px;
9503
- width: 20px;
9504
- height: 20px;
9505
- }
9506
-
9507
- .feedback-minimize-icon svg,
9508
- .feedback-expand-icon svg {
9509
- width: 14px;
9510
- height: 14px;
9511
- }
9127
+ .changelog-list-modal-header h2 {
9128
+ margin: 0;
9129
+ font-size: 16px;
9130
+ font-weight: 600;
9131
+ color: #111827;
9512
9132
  }
9513
9133
 
9514
- @media (prefers-reduced-motion: reduce) {
9515
- .feedback-trigger-btn,
9516
- .feedback-btn,
9517
- .feedback-panel,
9518
- .feedback-panel-backdrop,
9519
- .feedback-success-notification,
9520
- .feedback-minimize-icon,
9521
- .feedback-expand-icon {
9522
- transition: none;
9523
- animation: none;
9524
- }
9134
+ .changelog-list-modal.theme-dark .changelog-list-modal-header h2 {
9135
+ color: white;
9525
9136
  }
9526
9137
 
9527
- @media print {
9528
- .feedback-widget,
9529
- .feedback-panel,
9530
- .feedback-panel-backdrop,
9531
- .feedback-success-notification {
9532
- display: none !important;
9533
- }
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;
9534
9153
  }
9535
9154
 
9536
- /* Changelog Widget Styles */
9537
- .changelog-widget {
9538
- position: fixed;
9539
- z-index: 999999;
9155
+ .changelog-list-modal-close:hover {
9156
+ background: #F3F4F6;
9157
+ color: #111827;
9540
9158
  }
9541
9159
 
9542
- .changelog-widget.position-bottom-right {
9543
- bottom: 20px;
9544
- right: 20px;
9160
+ .changelog-list-modal.theme-dark .changelog-list-modal-close {
9161
+ color: #9CA3AF;
9545
9162
  }
9546
9163
 
9547
- .changelog-widget.position-bottom-left {
9548
- bottom: 20px;
9549
- left: 20px;
9164
+ .changelog-list-modal.theme-dark .changelog-list-modal-close:hover {
9165
+ background: #374151;
9166
+ color: white;
9550
9167
  }
9551
9168
 
9552
- .changelog-widget.position-top-right {
9553
- top: 20px;
9554
- right: 20px;
9169
+ .changelog-list-modal-body {
9170
+ flex: 1;
9171
+ overflow-y: auto;
9555
9172
  }
9556
9173
 
9557
- .changelog-widget.position-top-left {
9558
- top: 20px;
9559
- left: 20px;
9174
+ .changelog-list {
9175
+ display: flex;
9176
+ flex-direction: column;
9560
9177
  }
9561
9178
 
9562
- .changelog-trigger-btn {
9563
- position: relative;
9179
+ .changelog-list-item {
9564
9180
  display: flex;
9181
+ flex-direction: row;
9565
9182
  align-items: center;
9566
- justify-content: center;
9567
- gap: 8px;
9568
- height: 48px;
9569
- overflow: visible;
9570
- border-radius: 9999px;
9571
- border: none;
9572
- padding: 12px 20px;
9573
- font-size: 14px;
9574
- font-weight: 500;
9575
- font-family: inherit;
9183
+ padding: 12px 16px;
9184
+ border-bottom: 1px solid #E5E7EB;
9576
9185
  cursor: pointer;
9577
- transition: all 0.3s ease;
9578
- color: white;
9579
- background: #155EEF;
9580
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
9581
- width: fit-content;
9582
- }
9583
-
9584
- .changelog-trigger-btn:hover {
9585
- transform: translateY(-2px);
9586
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
9186
+ transition: background-color 0.2s ease;
9187
+ position: relative;
9587
9188
  }
9588
9189
 
9589
- .changelog-trigger-btn:focus-visible {
9590
- outline: 2px solid #155EEF;
9591
- outline-offset: 2px;
9190
+ .changelog-list-item:hover {
9191
+ background: #F9FAFB;
9592
9192
  }
9593
9193
 
9594
- .changelog-icon {
9595
- flex-shrink: 0;
9194
+ .changelog-list-item:last-child {
9195
+ border-bottom: none;
9596
9196
  }
9597
9197
 
9598
- .changelog-confetti-emoji {
9599
- font-size: 14px;
9600
- margin-left: 2px;
9198
+ .changelog-list-modal.theme-dark .changelog-list-item {
9199
+ border-bottom-color: #374151;
9601
9200
  }
9602
9201
 
9603
- .changelog-badge {
9604
- position: absolute;
9605
- top: -4px;
9606
- right: -4px;
9607
- width: 12px;
9608
- height: 12px;
9609
- background: #EF4444;
9610
- border-radius: 50%;
9611
- border: 2px solid white;
9202
+ .changelog-list-modal.theme-dark .changelog-list-item:hover {
9203
+ background: #374151;
9612
9204
  }
9613
9205
 
9614
- /* Confetti Animation */
9615
- .changelog-confetti-container {
9616
- position: fixed;
9617
- top: 0;
9618
- left: 0;
9206
+ .changelog-list-item-image {
9619
9207
  width: 100%;
9620
- height: 100%;
9621
- pointer-events: none;
9622
- z-index: 1000001;
9208
+ margin-bottom: 8px;
9209
+ border-radius: 6px;
9623
9210
  overflow: hidden;
9211
+ border: 1px solid #E5E7EB;
9624
9212
  }
9625
9213
 
9626
- .changelog-confetti {
9627
- position: absolute;
9628
- top: -20px;
9629
- opacity: 0;
9630
- animation: confettiFall 2s ease-out forwards;
9631
- }
9632
-
9633
- @keyframes confettiFall {
9634
- 0% {
9635
- opacity: 1;
9636
- transform: translateY(0) rotate(0deg) scale(1);
9637
- }
9638
- 10% {
9639
- opacity: 1;
9640
- }
9641
- 100% {
9642
- opacity: 0;
9643
- transform: translateY(100vh) rotate(720deg) scale(0.5);
9644
- }
9645
- }
9646
-
9647
- /* Changelog Modal */
9648
- .changelog-modal-backdrop {
9649
- position: fixed;
9650
- top: 0;
9651
- left: 0;
9652
- right: 0;
9653
- bottom: 0;
9654
- background: rgba(0, 0, 0, 0.5);
9655
- opacity: 0;
9656
- transition: opacity 0.3s ease;
9657
- pointer-events: none;
9658
- z-index: 999998;
9659
- display: flex;
9660
- align-items: center;
9661
- justify-content: center;
9214
+ .changelog-list-item-image img {
9215
+ width: 100%;
9216
+ height: 100px;
9217
+ display: block;
9218
+ object-fit: cover;
9662
9219
  }
9663
9220
 
9664
- .changelog-modal-backdrop.show {
9665
- opacity: 1;
9666
- pointer-events: auto;
9221
+ .changelog-list-item-main {
9222
+ flex: 1;
9223
+ min-width: 0;
9667
9224
  }
9668
9225
 
9669
- .changelog-modal {
9670
- position: fixed;
9671
- top: 0;
9672
- left: 0;
9673
- right: 0;
9674
- bottom: 0;
9675
- z-index: 999999;
9226
+ .changelog-list-item-content {
9676
9227
  display: flex;
9677
- align-items: center;
9678
- justify-content: center;
9679
- padding: 20px;
9680
- pointer-events: none;
9681
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9682
- }
9683
-
9684
- .changelog-modal.open {
9685
- pointer-events: auto;
9686
- }
9687
-
9688
- .changelog-modal-container {
9689
- position: relative;
9690
- width: 100%;
9691
- max-width: 580px;
9692
- max-height: 90vh;
9693
- background: #DBEAFE;
9694
- border-radius: 24px;
9695
- overflow: hidden;
9696
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
9697
- transform: scale(0.9) translateY(20px);
9698
- opacity: 0;
9699
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9228
+ flex-direction: column;
9229
+ gap: 3px;
9700
9230
  }
9701
9231
 
9702
- .changelog-modal.open .changelog-modal-container {
9703
- transform: scale(1) translateY(0);
9704
- opacity: 1;
9232
+ .changelog-list-item-date {
9233
+ font-size: 11px;
9234
+ color: #6B7280;
9235
+ font-weight: 500;
9705
9236
  }
9706
9237
 
9707
- .changelog-modal.theme-dark .changelog-modal-container {
9708
- background: #1E3A5F;
9238
+ .changelog-list-modal.theme-dark .changelog-list-item-date {
9239
+ color: #9CA3AF;
9709
9240
  }
9710
9241
 
9711
- .changelog-modal-close {
9712
- position: absolute;
9713
- top: 16px;
9714
- right: 16px;
9715
- background: rgba(255, 255, 255, 0.9);
9716
- border: none;
9717
- font-size: 24px;
9718
- cursor: pointer;
9719
- color: #6B7280;
9720
- padding: 0;
9721
- width: 36px;
9722
- height: 36px;
9242
+ .changelog-list-item-labels {
9723
9243
  display: flex;
9724
- align-items: center;
9725
- justify-content: center;
9726
- border-radius: 50%;
9727
- transition: all 0.2s ease;
9728
- line-height: 1;
9729
- z-index: 10;
9730
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
9244
+ flex-wrap: wrap;
9245
+ gap: 4px;
9246
+ margin-bottom: 1px;
9731
9247
  }
9732
9248
 
9733
- .changelog-modal-close:hover {
9734
- background: white;
9249
+ .changelog-list-item-title {
9250
+ margin: 0;
9251
+ font-size: 14px;
9252
+ font-weight: 600;
9253
+ line-height: 1.3;
9735
9254
  color: #111827;
9736
- transform: scale(1.05);
9737
9255
  }
9738
9256
 
9739
- .changelog-modal.theme-dark .changelog-modal-close {
9740
- background: rgba(55, 65, 81, 0.9);
9741
- color: #D1D5DB;
9742
- }
9743
-
9744
- .changelog-modal.theme-dark .changelog-modal-close:hover {
9745
- background: #374151;
9257
+ .changelog-list-modal.theme-dark .changelog-list-item-title {
9746
9258
  color: white;
9747
9259
  }
9748
9260
 
9749
- .changelog-modal-content {
9750
- overflow-y: auto;
9751
- max-height: 90vh;
9752
- }
9753
-
9754
- /* Changelog Loading */
9755
- .changelog-loading {
9756
- display: flex;
9757
- align-items: center;
9758
- justify-content: center;
9759
- padding: 80px 20px;
9760
- }
9761
-
9762
- .changelog-loading-spinner {
9763
- width: 32px;
9764
- height: 32px;
9765
- border: 3px solid #E5E7EB;
9766
- border-top-color: #155EEF;
9767
- border-radius: 50%;
9768
- animation: changelogSpin 0.8s linear infinite;
9769
- }
9770
-
9771
- @keyframes changelogSpin {
9772
- to {
9773
- transform: rotate(360deg);
9774
- }
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;
9775
9270
  }
9776
9271
 
9777
- /* Changelog Empty State */
9778
- .changelog-empty {
9779
- display: flex;
9780
- flex-direction: column;
9781
- align-items: center;
9782
- justify-content: center;
9783
- padding: 80px 20px;
9784
- text-align: center;
9272
+ .changelog-list-modal.theme-dark .changelog-list-item-description {
9785
9273
  color: #9CA3AF;
9786
9274
  }
9787
9275
 
9788
- .changelog-empty svg {
9789
- margin-bottom: 16px;
9790
- stroke: #D1D5DB;
9276
+ .changelog-list-item-arrow {
9277
+ flex-shrink: 0;
9278
+ margin-left: 12px;
9279
+ color: #9CA3AF;
9280
+ transition: all 0.2s ease;
9791
9281
  }
9792
9282
 
9793
- .changelog-empty p {
9794
- margin: 0;
9795
- font-size: 15px;
9283
+ .changelog-list-item:hover .changelog-list-item-arrow {
9284
+ color: #155EEF;
9285
+ transform: translateX(3px);
9796
9286
  }
9797
9287
 
9798
- /* Changelog Popup Item */
9799
- .changelog-popup-item {
9800
- display: flex;
9801
- flex-direction: column;
9288
+ .changelog-list-modal.theme-dark .changelog-list-item-arrow {
9289
+ color: #6B7280;
9802
9290
  }
9803
9291
 
9804
- .changelog-popup-image {
9805
- width: 100%;
9806
- padding: 24px 24px 0;
9292
+ .changelog-list-modal.theme-dark .changelog-list-item:hover .changelog-list-item-arrow {
9293
+ color: #60A5FA;
9807
9294
  }
9808
9295
 
9809
- .changelog-popup-image img {
9810
- width: 100%;
9811
- height: auto;
9812
- display: block;
9813
- object-fit: cover;
9814
- border-radius: 12px;
9815
- border: 2px solid #155EEF;
9816
- 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
+ }
9817
9367
  }
9368
+ `;
9818
9369
 
9819
- .changelog-popup-body {
9820
- padding: 24px 32px 32px;
9821
- text-align: center;
9370
+ const feedbackStyles = `
9371
+ .feedback-widget-button {
9372
+ position: fixed;
9373
+ z-index: 999999;
9822
9374
  }
9823
9375
 
9824
- .changelog-popup-title {
9825
- margin: 0 0 16px;
9826
- font-size: 18px;
9827
- font-weight: 600;
9828
- line-height: 1.3;
9829
- color: #111827;
9376
+ .feedback-widget-button.position-bottom-right {
9377
+ bottom: 20px;
9378
+ right: 20px;
9830
9379
  }
9831
9380
 
9832
- .changelog-modal.theme-dark .changelog-popup-title {
9833
- color: white;
9381
+ .feedback-widget-button.position-bottom-left {
9382
+ bottom: 20px;
9383
+ left: 20px;
9834
9384
  }
9835
9385
 
9836
- .changelog-popup-description {
9837
- margin: 0 0 24px;
9838
- font-size: 17px;
9839
- line-height: 1.6;
9840
- color: #4B5563;
9386
+ .feedback-widget-button.position-top-right {
9387
+ top: 20px;
9388
+ right: 20px;
9841
9389
  }
9842
9390
 
9843
- .changelog-modal.theme-dark .changelog-popup-description {
9844
- color: #D1D5DB;
9391
+ .feedback-widget-button.position-top-left {
9392
+ top: 20px;
9393
+ left: 20px;
9845
9394
  }
9846
9395
 
9847
- .changelog-popup-btn {
9848
- display: inline-flex;
9396
+ .feedback-trigger-btn {
9397
+ position: relative;
9398
+ display: flex;
9849
9399
  align-items: center;
9850
9400
  justify-content: center;
9851
- padding: 14px 32px;
9852
- font-size: 16px;
9853
- font-weight: 600;
9854
- color: white;
9855
- background: #155EEF;
9401
+ gap: 8px;
9402
+ height: 48px;
9403
+ overflow: visible;
9404
+ border-radius: 9999px;
9856
9405
  border: none;
9857
- border-radius: 10px;
9406
+ padding: 12px 20px;
9407
+ font-size: 14px;
9408
+ font-weight: 500;
9409
+ font-family: inherit;
9858
9410
  cursor: pointer;
9859
- 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;
9860
9416
  }
9861
9417
 
9862
- .changelog-popup-btn:hover {
9863
- background: #1249CA;
9418
+ .feedback-trigger-btn:hover:not(:disabled) {
9864
9419
  transform: translateY(-2px);
9865
- box-shadow: 0 4px 12px rgba(21, 94, 239, 0.3);
9420
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
9866
9421
  }
9867
9422
 
9868
- .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 {
9869
9429
  outline: 2px solid #155EEF;
9870
9430
  outline-offset: 2px;
9871
9431
  }
9872
9432
 
9873
- /* Popup Footer with Dots and View All */
9874
- .changelog-popup-footer {
9875
- padding: 0 32px 24px;
9876
- display: flex;
9877
- flex-direction: column;
9878
- align-items: center;
9879
- gap: 16px;
9433
+ .feedback-icon {
9434
+ flex-shrink: 0;
9880
9435
  }
9881
9436
 
9882
- .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;
9883
9445
  display: flex;
9884
- gap: 8px;
9885
- }
9886
-
9887
- .changelog-dot {
9888
- width: 10px;
9889
- height: 10px;
9446
+ align-items: center;
9447
+ justify-content: center;
9448
+ background: white;
9890
9449
  border-radius: 50%;
9891
- 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);
9892
9453
  cursor: pointer;
9893
- transition: all 0.2s ease;
9894
9454
  }
9895
9455
 
9896
- .changelog-dot:hover {
9897
- 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;
9898
9462
  }
9899
9463
 
9900
- .changelog-dot.active {
9901
- background: #155EEF;
9902
- transform: scale(1.2);
9464
+ .feedback-widget-button:not(.minimized) .feedback-trigger-btn:hover .feedback-minimize-icon {
9465
+ opacity: 1;
9903
9466
  }
9904
9467
 
9905
- .changelog-view-all-btn {
9906
- display: inline-flex;
9907
- align-items: center;
9908
- gap: 6px;
9909
- background: none;
9910
- border: none;
9911
- color: #155EEF;
9912
- font-size: 14px;
9913
- font-weight: 500;
9914
- cursor: pointer;
9915
- padding: 8px 12px;
9916
- border-radius: 6px;
9917
- 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;
9918
9473
  }
9919
9474
 
9920
- .changelog-view-all-btn:hover {
9921
- background: rgba(21, 94, 239, 0.1);
9475
+ .feedback-widget-button.minimized .feedback-text {
9476
+ display: none;
9922
9477
  }
9923
9478
 
9924
- .changelog-view-all-btn svg {
9925
- transition: transform 0.2s ease;
9479
+ .feedback-widget-button.minimized .feedback-minimize-icon {
9480
+ display: none;
9926
9481
  }
9927
9482
 
9928
- .changelog-view-all-btn:hover svg {
9929
- transform: translateX(3px);
9483
+ .feedback-widget-button.minimized .feedback-trigger-btn:hover .feedback-expand-icon {
9484
+ opacity: 1;
9930
9485
  }
9931
9486
 
9932
- .changelog-modal.theme-dark .changelog-view-all-btn {
9933
- 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;
9934
9497
  }
9935
9498
 
9936
- .changelog-modal.theme-dark .changelog-view-all-btn:hover {
9937
- background: rgba(96, 165, 250, 0.1);
9499
+ .feedback-panel.open {
9500
+ transform: translateX(0);
9938
9501
  }
9939
9502
 
9940
- /* ==================== CHANGELOG LIST MODAL ==================== */
9941
- .changelog-list-modal-backdrop {
9503
+ .feedback-panel-backdrop {
9942
9504
  position: fixed;
9943
9505
  top: 0;
9944
9506
  left: 0;
9945
9507
  right: 0;
9946
9508
  bottom: 0;
9947
- background: rgba(0, 0, 0, 0.5);
9509
+ background: rgba(0, 0, 0, 0.1);
9948
9510
  opacity: 0;
9949
9511
  transition: opacity 0.3s ease;
9950
9512
  pointer-events: none;
9951
- z-index: 999998;
9952
- }
9953
-
9954
- .changelog-list-modal-backdrop.show {
9955
- opacity: 1;
9956
- pointer-events: auto;
9957
- }
9958
-
9959
- .changelog-list-modal {
9960
- position: fixed;
9961
- top: 0;
9962
- left: 0;
9963
- right: 0;
9964
- bottom: 0;
9965
9513
  z-index: 999999;
9966
- display: flex;
9967
- align-items: center;
9968
- justify-content: center;
9969
- padding: 20px;
9970
- pointer-events: none;
9971
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', Oxygen, Ubuntu, Cantarell, sans-serif;
9514
+ animation: fadeIn 0.3s ease;
9972
9515
  }
9973
9516
 
9974
- .changelog-list-modal.open {
9517
+ .feedback-panel-backdrop.show {
9518
+ opacity: 1;
9975
9519
  pointer-events: auto;
9976
9520
  }
9977
9521
 
9978
- .changelog-list-modal-container {
9979
- position: relative;
9980
- width: 100%;
9981
- max-width: 460px;
9982
- max-height: 85vh;
9522
+ .feedback-panel-content {
9983
9523
  background: white;
9984
- border-radius: 20px;
9985
- overflow: hidden;
9986
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
9987
- transform: scale(0.9) translateY(20px);
9988
- opacity: 0;
9989
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
9524
+ height: 100%;
9990
9525
  display: flex;
9991
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);
9992
9531
  }
9993
9532
 
9994
- .changelog-list-modal.open .changelog-list-modal-container {
9995
- transform: scale(1) translateY(0);
9996
- opacity: 1;
9997
- }
9998
-
9999
- .changelog-list-modal.theme-dark .changelog-list-modal-container {
9533
+ .feedback-panel.theme-dark .feedback-panel-content {
10000
9534
  background: #1F2937;
10001
9535
  color: white;
10002
9536
  }
10003
9537
 
10004
- .changelog-list-modal-header {
9538
+ .feedback-panel-header {
10005
9539
  display: flex;
10006
9540
  align-items: center;
10007
9541
  justify-content: space-between;
10008
- padding: 14px 20px;
9542
+ padding: 24px;
10009
9543
  border-bottom: 1px solid #E5E7EB;
10010
9544
  flex-shrink: 0;
10011
- background: white;
10012
9545
  }
10013
9546
 
10014
- .changelog-list-modal.theme-dark .changelog-list-modal-header {
9547
+ .feedback-panel.theme-dark .feedback-panel-header {
10015
9548
  border-bottom-color: #374151;
10016
- background: #1F2937;
10017
9549
  }
10018
9550
 
10019
- .changelog-list-modal-header h2 {
9551
+ .feedback-panel-header h3 {
10020
9552
  margin: 0;
10021
- font-size: 16px;
9553
+ font-size: 18px;
10022
9554
  font-weight: 600;
10023
9555
  color: #111827;
10024
9556
  }
10025
9557
 
10026
- .changelog-list-modal.theme-dark .changelog-list-modal-header h2 {
9558
+ .feedback-panel.theme-dark .feedback-panel-header h3 {
10027
9559
  color: white;
10028
9560
  }
10029
9561
 
10030
- .changelog-list-modal-close {
9562
+ .feedback-panel-close {
10031
9563
  background: none;
10032
9564
  border: none;
10033
- font-size: 22px;
9565
+ font-size: 24px;
10034
9566
  cursor: pointer;
10035
9567
  color: #6B7280;
10036
9568
  padding: 4px;
10037
- width: 28px;
10038
- height: 28px;
9569
+ width: 32px;
9570
+ height: 32px;
10039
9571
  display: flex;
10040
9572
  align-items: center;
10041
9573
  justify-content: center;
10042
9574
  border-radius: 6px;
10043
9575
  transition: all 0.2s ease;
10044
- line-height: 1;
10045
9576
  }
10046
9577
 
10047
- .changelog-list-modal-close:hover {
9578
+ .feedback-panel-close:hover {
10048
9579
  background: #F3F4F6;
10049
9580
  color: #111827;
10050
9581
  }
10051
9582
 
10052
- .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 {
10053
9589
  color: #9CA3AF;
10054
9590
  }
10055
9591
 
10056
- .changelog-list-modal.theme-dark .changelog-list-modal-close:hover {
9592
+ .feedback-panel.theme-dark .feedback-panel-close:hover {
10057
9593
  background: #374151;
10058
9594
  color: white;
10059
9595
  }
10060
9596
 
10061
- .changelog-list-modal-body {
9597
+ .feedback-panel-body {
10062
9598
  flex: 1;
10063
9599
  overflow-y: auto;
9600
+ padding: 24px;
10064
9601
  }
10065
9602
 
10066
- /* Changelog List */
10067
- .changelog-list {
9603
+ .feedback-form {
10068
9604
  display: flex;
10069
9605
  flex-direction: column;
9606
+ height: 100%;
10070
9607
  }
10071
9608
 
10072
- .changelog-list-item {
9609
+ .feedback-form-group {
10073
9610
  display: flex;
10074
- flex-direction: row;
10075
- align-items: center;
10076
- padding: 12px 16px;
10077
- border-bottom: 1px solid #E5E7EB;
10078
- cursor: pointer;
10079
- transition: background-color 0.2s ease;
10080
- position: relative;
9611
+ flex-direction: column;
9612
+ gap: 8px;
9613
+ margin-bottom: 20px;
10081
9614
  }
10082
9615
 
10083
- .changelog-list-item:hover {
10084
- background: #F9FAFB;
9616
+ .feedback-form-group:last-child {
9617
+ margin-bottom: 0;
10085
9618
  }
10086
9619
 
10087
- .changelog-list-item:last-child {
10088
- border-bottom: none;
9620
+ .feedback-form-group label {
9621
+ font-size: 14px;
9622
+ font-weight: 500;
9623
+ line-height: 1.25;
9624
+ color: #374151;
10089
9625
  }
10090
9626
 
10091
- .changelog-list-modal.theme-dark .changelog-list-item {
10092
- border-bottom-color: #374151;
9627
+ .feedback-panel.theme-dark .feedback-form-group label {
9628
+ color: #D1D5DB;
10093
9629
  }
10094
9630
 
10095
- .changelog-list-modal.theme-dark .changelog-list-item:hover {
10096
- 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;
10097
9644
  }
10098
9645
 
10099
- .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;
10100
9662
  width: 100%;
10101
- margin-bottom: 8px;
10102
- 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;
10103
9707
  overflow: hidden;
10104
- 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;
10105
9717
  }
10106
9718
 
10107
- .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;
10108
9732
  width: 100%;
10109
- height: 100px;
10110
- display: block;
10111
- object-fit: cover;
10112
9733
  }
10113
9734
 
10114
- .changelog-list-item-main {
10115
- flex: 1;
10116
- 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;
10117
9741
  }
10118
9742
 
10119
- .changelog-list-item-content {
10120
- display: flex;
10121
- flex-direction: column;
10122
- gap: 3px;
9743
+ .feedback-btn-cancel {
9744
+ background: transparent;
9745
+ color: #6B7280;
9746
+ border: 1px solid #D1D5DB;
10123
9747
  }
10124
9748
 
10125
- .changelog-list-item-date {
10126
- font-size: 11px;
10127
- color: #6B7280;
10128
- font-weight: 500;
9749
+ .feedback-btn-cancel:hover:not(:disabled) {
9750
+ background: #F9FAFB;
9751
+ border-color: #9CA3AF;
9752
+ color: #374151;
10129
9753
  }
10130
9754
 
10131
- .changelog-list-modal.theme-dark .changelog-list-item-date {
10132
- color: #9CA3AF;
9755
+ .feedback-panel.theme-dark .feedback-btn-cancel {
9756
+ color: #D1D5DB;
9757
+ border-color: #4B5563;
10133
9758
  }
10134
9759
 
10135
- .changelog-list-item-labels {
9760
+ .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
9761
+ background: #374151;
9762
+ }
9763
+
9764
+ .feedback-form-actions {
10136
9765
  display: flex;
10137
- flex-wrap: wrap;
10138
- gap: 4px;
10139
- margin-bottom: 1px;
9766
+ flex-direction: column;
9767
+ gap: 12px;
9768
+ margin-top: auto;
9769
+ padding-top: 24px;
10140
9770
  }
10141
9771
 
10142
- .changelog-list-item-title {
10143
- margin: 0;
9772
+ .feedback-error {
9773
+ color: #DC2626;
10144
9774
  font-size: 14px;
10145
- font-weight: 600;
10146
- line-height: 1.3;
10147
- 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;
10148
9782
  }
10149
9783
 
10150
- .changelog-list-modal.theme-dark .changelog-list-item-title {
10151
- color: white;
9784
+ .feedback-error.show {
9785
+ display: block;
10152
9786
  }
10153
9787
 
10154
- .changelog-list-item-description {
10155
- margin: 0;
10156
- font-size: 12px;
10157
- line-height: 1.4;
10158
- color: #6B7280;
10159
- display: -webkit-box;
10160
- -webkit-line-clamp: 2;
10161
- -webkit-box-orient: vertical;
10162
- overflow: hidden;
9788
+ .feedback-panel.theme-dark .feedback-error {
9789
+ background: #7F1D1D;
9790
+ border-color: #991B1B;
9791
+ color: #FCA5A5;
10163
9792
  }
10164
9793
 
10165
- .changelog-list-modal.theme-dark .changelog-list-item-description {
10166
- 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;
10167
9806
  }
10168
9807
 
10169
- .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;
10170
9826
  flex-shrink: 0;
10171
- margin-left: 12px;
10172
- color: #9CA3AF;
10173
- transition: all 0.2s ease;
10174
9827
  }
10175
9828
 
10176
- .changelog-list-item:hover .changelog-list-item-arrow {
10177
- color: #155EEF;
10178
- transform: translateX(3px);
9829
+ .feedback-success-content span {
9830
+ color: #065F46;
9831
+ font-weight: 500;
9832
+ font-size: 14px;
9833
+ flex: 1;
10179
9834
  }
10180
9835
 
10181
- .changelog-list-modal.theme-dark .changelog-list-item-arrow {
9836
+ .feedback-success-close {
9837
+ background: none;
9838
+ border: none;
10182
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;
10183
9851
  }
10184
9852
 
10185
- .changelog-list-modal.theme-dark .changelog-list-item:hover .changelog-list-item-arrow {
10186
- color: #60A5FA;
9853
+ .feedback-success-close:hover {
9854
+ background: #F3F4F6;
9855
+ color: #374151;
10187
9856
  }
10188
9857
 
10189
- /* Mobile Responsive */
10190
- @media (max-width: 768px) {
10191
- .changelog-modal {
10192
- padding: 16px;
10193
- }
10194
-
10195
- .changelog-modal-container {
10196
- max-width: 100%;
10197
- border-radius: 20px;
10198
- }
9858
+ .feedback-success-close:focus-visible {
9859
+ outline: 2px solid #155EEF;
9860
+ outline-offset: 2px;
9861
+ }
10199
9862
 
10200
- .changelog-widget {
9863
+ @media (max-width: 768px) {
9864
+ .feedback-widget-button {
10201
9865
  bottom: 16px;
10202
9866
  right: 16px;
10203
9867
  }
10204
-
10205
- .changelog-widget.position-bottom-left {
9868
+
9869
+ .feedback-widget-button.position-bottom-left {
10206
9870
  left: 16px;
10207
9871
  }
10208
-
10209
- .changelog-popup-image {
10210
- padding: 20px 20px 0;
10211
- }
10212
-
10213
- .changelog-popup-body {
10214
- padding: 20px 24px 24px;
10215
- }
10216
-
10217
- .changelog-popup-title {
10218
- font-size: 22px;
9872
+
9873
+ .feedback-minimize-icon,
9874
+ .feedback-expand-icon {
9875
+ top: -4px;
9876
+ right: -4px;
9877
+ width: 20px;
9878
+ height: 20px;
10219
9879
  }
10220
-
10221
- .changelog-popup-description {
10222
- font-size: 15px;
9880
+
9881
+ .feedback-minimize-icon svg,
9882
+ .feedback-expand-icon svg {
9883
+ width: 14px;
9884
+ height: 14px;
10223
9885
  }
10224
9886
 
10225
- .changelog-popup-btn {
10226
- padding: 12px 28px;
10227
- font-size: 15px;
9887
+ .feedback-panel {
10228
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;
10229
9897
  }
10230
-
10231
- .changelog-popup-footer {
10232
- padding: 0 24px 20px;
9898
+
9899
+ .feedback-panel.open {
9900
+ transform: translateY(0);
10233
9901
  }
10234
-
10235
- /* List modal mobile */
10236
- .changelog-list-modal {
10237
- padding: 16px;
9902
+
9903
+ .feedback-panel-content {
9904
+ border-radius: 20px 20px 0 0;
10238
9905
  }
10239
-
10240
- .changelog-list-modal-container {
10241
- max-width: 100%;
10242
- max-height: 90vh;
10243
- border-radius: 16px;
9906
+
9907
+ .feedback-panel-header {
9908
+ padding: 20px;
9909
+ position: relative;
10244
9910
  }
10245
-
10246
- .changelog-list-item {
10247
- 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;
10248
9922
  }
10249
-
10250
- .changelog-list-item-image img {
10251
- height: 80px;
9923
+
9924
+ .feedback-panel.theme-dark .feedback-panel-header::before {
9925
+ background: #4B5563;
10252
9926
  }
10253
-
10254
- .changelog-list-item-title {
10255
- font-size: 13px;
9927
+
9928
+ .feedback-panel-body {
9929
+ padding: 20px;
10256
9930
  }
10257
-
10258
- .changelog-list-item-description {
10259
- font-size: 11px;
9931
+
9932
+ .feedback-form-group textarea {
9933
+ min-height: 150px;
10260
9934
  }
10261
- }
10262
9935
 
10263
- @media (prefers-reduced-motion: reduce) {
10264
- .changelog-modal,
10265
- .changelog-modal-container,
10266
- .changelog-modal-backdrop,
10267
- .changelog-list-modal,
10268
- .changelog-list-modal-container,
10269
- .changelog-list-modal-backdrop,
10270
- .changelog-trigger-btn,
10271
- .changelog-popup-btn,
10272
- .changelog-view-all-btn,
10273
- .changelog-loading-spinner,
10274
- .changelog-dot,
10275
- .changelog-list-item,
10276
- .changelog-list-item-arrow {
10277
- transition: none;
10278
- animation: none;
9936
+ .feedback-success-notification {
9937
+ top: 16px;
9938
+ right: 16px;
9939
+ left: 16px;
9940
+ min-width: auto;
10279
9941
  }
10280
9942
  }
10281
9943
  `;
10282
9944
 
9945
+ const CSS_STYLES = baseStyles + feedbackStyles + changelogStyles;
9946
+
10283
9947
  function injectStyles() {
10284
9948
  if (
10285
9949
  typeof document !== 'undefined' &&