@product7/feedback-sdk 1.1.0 → 1.1.4

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.
@@ -79,19 +79,45 @@
79
79
  }
80
80
  }
81
81
 
82
+ const MOCK_CONFIG = {
83
+ primaryColor: '#155EEF',
84
+ backgroundColor: '#ffffff',
85
+ textColor: '#1F2937',
86
+ boardId: 'feature-requests',
87
+ size: 'medium',
88
+ displayMode: 'modal',
89
+ };
90
+
91
+ // Environment URLs
92
+ const ENV_URLS = {
93
+ production: {
94
+ base: 'https://api.product7.io/api/v1',
95
+ withWorkspace: (workspace) => `https://${workspace}.api.product7.io/api/v1`,
96
+ },
97
+ staging: {
98
+ base: 'https://staging.api.product7.io/api/v1',
99
+ withWorkspace: (workspace) => `https://${workspace}.staging.api.product7.io/api/v1`,
100
+ },
101
+ };
102
+
82
103
  class APIService {
83
104
  constructor(config = {}) {
84
105
  this.workspace = config.workspace;
85
106
  this.sessionToken = null;
86
107
  this.sessionExpiry = null;
87
108
  this.userContext = config.userContext || null;
109
+ this.mock = config.mock || false;
110
+ this.env = config.env || 'production'; // 'production' or 'staging'
88
111
 
89
112
  if (config.apiUrl) {
113
+ // Custom API URL takes precedence
90
114
  this.baseURL = config.apiUrl;
91
- } else if (this.workspace) {
92
- this.baseURL = `https://${this.workspace}.staging.api.product7.io/api/v1`;
93
115
  } else {
94
- this.baseURL = 'https://staging.api.product7.io/api/v1';
116
+ // Use environment-based URL
117
+ const envConfig = ENV_URLS[this.env] || ENV_URLS.production;
118
+ this.baseURL = this.workspace
119
+ ? envConfig.withWorkspace(this.workspace)
120
+ : envConfig.base;
95
121
  }
96
122
 
97
123
  this._loadStoredSession();
@@ -111,6 +137,18 @@
111
137
  throw new APIError(400, error);
112
138
  }
113
139
 
140
+ // Mock mode - return fake session
141
+ if (this.mock) {
142
+ this.sessionToken = 'mock_session_' + Date.now();
143
+ this.sessionExpiry = new Date(Date.now() + 3600 * 1000);
144
+ this._storeSession();
145
+ return {
146
+ sessionToken: this.sessionToken,
147
+ config: MOCK_CONFIG,
148
+ expiresIn: 3600,
149
+ };
150
+ }
151
+
114
152
  const payload = {
115
153
  workspace: this.workspace,
116
154
  user: this.userContext,
@@ -152,6 +190,20 @@
152
190
  throw new APIError(401, 'No valid session token available');
153
191
  }
154
192
 
193
+ // Mock mode - simulate success
194
+ if (this.mock) {
195
+ await new Promise(resolve => setTimeout(resolve, 500));
196
+ return {
197
+ success: true,
198
+ data: {
199
+ id: 'mock_post_' + Date.now(),
200
+ title: feedbackData.title,
201
+ content: feedbackData.content,
202
+ },
203
+ message: 'Feedback submitted successfully!',
204
+ };
205
+ }
206
+
155
207
  const payload = {
156
208
  board: feedbackData.board_id || feedbackData.board || feedbackData.boardId,
157
209
  title: feedbackData.title,
@@ -588,8 +640,12 @@
588
640
  this.options = {
589
641
  container: null,
590
642
  position: this.sdk.config.position,
591
- theme: this.sdk.config.theme,
592
643
  boardId: this.sdk.config.boardId,
644
+ displayMode: options.displayMode || 'panel', // 'panel' or 'modal'
645
+ size: options.size || 'medium', // 'small', 'medium', 'large'
646
+ primaryColor: options.primaryColor || '#155EEF',
647
+ backgroundColor: options.backgroundColor || '#ffffff',
648
+ textColor: options.textColor || '#1F2937',
593
649
  autoShow: false,
594
650
  showBackdrop: true,
595
651
  customStyles: {},
@@ -604,6 +660,7 @@
604
660
 
605
661
  this.state = {
606
662
  isOpen: false,
663
+ isLoading: false,
607
664
  isSubmitting: false,
608
665
  title: '',
609
666
  content: '',
@@ -658,18 +715,65 @@
658
715
 
659
716
  openPanel() {
660
717
  this.state.isOpen = true;
661
- this._renderPanel();
662
-
718
+
719
+ if (this.options.displayMode === 'modal') {
720
+ this._showLoadingModal();
721
+ // Simulate loading delay then show form
722
+ setTimeout(() => {
723
+ this._hideLoadingModal();
724
+ this._renderPanel();
725
+ requestAnimationFrame(() => {
726
+ if (this.panelElement) {
727
+ this.panelElement.classList.add('open');
728
+ }
729
+ if (this.backdropElement) {
730
+ this.backdropElement.classList.add('show');
731
+ }
732
+ });
733
+ }, 600);
734
+ } else {
735
+ this._renderPanel();
736
+ requestAnimationFrame(() => {
737
+ if (this.panelElement) {
738
+ this.panelElement.classList.add('open');
739
+ }
740
+ if (this.backdropElement) {
741
+ this.backdropElement.classList.add('show');
742
+ }
743
+ });
744
+ }
745
+ }
746
+
747
+ _showLoadingModal() {
748
+ this.state.isLoading = true;
749
+
750
+ // Create backdrop
751
+ this.backdropElement = document.createElement('div');
752
+ this.backdropElement.className = 'feedback-panel-backdrop';
753
+ document.body.appendChild(this.backdropElement);
754
+
755
+ // Create loading modal
756
+ this.loadingElement = document.createElement('div');
757
+ this.loadingElement.className = `feedback-loading-modal theme-${this.options.theme}`;
758
+ this.loadingElement.innerHTML = `
759
+ <div class="feedback-loading-spinner"></div>
760
+ `;
761
+ document.body.appendChild(this.loadingElement);
762
+
663
763
  requestAnimationFrame(() => {
664
- if (this.panelElement) {
665
- this.panelElement.classList.add('open');
666
- }
667
- if (this.backdropElement) {
668
- this.backdropElement.classList.add('show');
669
- }
764
+ this.backdropElement.classList.add('show');
765
+ this.loadingElement.classList.add('show');
670
766
  });
671
767
  }
672
768
 
769
+ _hideLoadingModal() {
770
+ this.state.isLoading = false;
771
+ if (this.loadingElement) {
772
+ this.loadingElement.remove();
773
+ this.loadingElement = null;
774
+ }
775
+ }
776
+
673
777
  closePanel() {
674
778
  if (this.panelElement) {
675
779
  this.panelElement.classList.remove('open');
@@ -774,7 +878,7 @@
774
878
  _renderPanel() {
775
879
  if (this.panelElement) return;
776
880
 
777
- if (this.options.showBackdrop) {
881
+ if (this.options.showBackdrop && !this.backdropElement) {
778
882
  this.backdropElement = document.createElement('div');
779
883
  this.backdropElement.className = 'feedback-panel-backdrop';
780
884
  document.body.appendChild(this.backdropElement);
@@ -782,8 +886,13 @@
782
886
  this.backdropElement.addEventListener('click', this.closePanel);
783
887
  }
784
888
 
889
+ const modeClass = this.options.displayMode === 'modal' ? 'feedback-modal' : 'feedback-panel';
890
+ const sizeClass = `size-${this.options.size}`;
785
891
  this.panelElement = document.createElement('div');
786
- this.panelElement.className = `feedback-panel theme-${this.options.theme}`;
892
+ this.panelElement.className = `${modeClass} ${sizeClass}`;
893
+ this.panelElement.style.setProperty('--primary-color', this.options.primaryColor);
894
+ this.panelElement.style.setProperty('--bg-color', this.options.backgroundColor);
895
+ this.panelElement.style.setProperty('--text-color', this.options.textColor);
787
896
  this.panelElement.innerHTML = this._getPanelHTML();
788
897
 
789
898
  document.body.appendChild(this.panelElement);
@@ -1158,6 +1267,509 @@
1158
1267
  }
1159
1268
  }
1160
1269
 
1270
+ class SurveyWidget extends BaseWidget {
1271
+ constructor(options) {
1272
+ super({ ...options, type: 'survey' });
1273
+
1274
+ this.surveyOptions = {
1275
+ surveyType: options.surveyType || 'nps', // 'nps', 'csat', 'ces', 'custom'
1276
+ position: options.position || 'bottom-right', // 'bottom-right', 'bottom-left', 'center', 'bottom'
1277
+ title: options.title || null,
1278
+ description: options.description || null,
1279
+ lowLabel: options.lowLabel || null,
1280
+ highLabel: options.highLabel || null,
1281
+ customQuestions: options.customQuestions || [],
1282
+ theme: options.theme || 'light',
1283
+ onSubmit: options.onSubmit || null,
1284
+ onDismiss: options.onDismiss || null,
1285
+ };
1286
+
1287
+ this.surveyState = {
1288
+ score: null,
1289
+ feedback: '',
1290
+ customAnswers: {},
1291
+ isVisible: false,
1292
+ };
1293
+ }
1294
+
1295
+ _render() {
1296
+ // Survey widget doesn't render a trigger button, it's shown programmatically
1297
+ const container = document.createElement('div');
1298
+ container.className = 'feedback-survey-container';
1299
+ container.style.display = 'none';
1300
+ return container;
1301
+ }
1302
+
1303
+ _attachEvents() {
1304
+ // Events are attached when survey is shown
1305
+ }
1306
+
1307
+ show() {
1308
+ this._renderSurvey();
1309
+ this.surveyState.isVisible = true;
1310
+ this.sdk.eventBus.emit('survey:shown', { widget: this, type: this.surveyOptions.surveyType });
1311
+ return this;
1312
+ }
1313
+
1314
+ hide() {
1315
+ this._closeSurvey();
1316
+ return this;
1317
+ }
1318
+
1319
+ _renderSurvey() {
1320
+ // Remove any existing survey
1321
+ this._closeSurvey();
1322
+
1323
+ const config = this._getSurveyConfig();
1324
+ const positionStyles = this._getPositionStyles();
1325
+ const themeStyles = this.surveyOptions.theme === 'dark'
1326
+ ? 'background: #1a1a1a; color: #fff;'
1327
+ : 'background: #fff; color: #1d1d1f;';
1328
+
1329
+ // Create backdrop for center position
1330
+ if (this.surveyOptions.position === 'center') {
1331
+ this.backdropElement = document.createElement('div');
1332
+ this.backdropElement.className = 'feedback-survey-backdrop';
1333
+ document.body.appendChild(this.backdropElement);
1334
+ this.backdropElement.addEventListener('click', () => this._handleDismiss());
1335
+ }
1336
+
1337
+ this.surveyElement = document.createElement('div');
1338
+ this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position} theme-${this.surveyOptions.theme}`;
1339
+ this.surveyElement.style.cssText = `position: fixed; ${positionStyles} z-index: 10000; ${themeStyles} border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); padding: 24px; min-width: 320px; max-width: 400px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;`;
1340
+
1341
+ this.surveyElement.innerHTML = `
1342
+ <button class="feedback-survey-close" style="position: absolute; top: 12px; right: 12px; background: none; border: none; font-size: 20px; cursor: pointer; color: ${this.surveyOptions.theme === 'dark' ? '#888' : '#86868b'}; line-height: 1;">&times;</button>
1343
+ <h3 class="feedback-survey-title" style="margin: 0 0 8px 0; font-size: 18px; font-weight: 600; padding-right: 24px;">${config.title}</h3>
1344
+ <p class="feedback-survey-description" style="color: ${this.surveyOptions.theme === 'dark' ? '#aaa' : '#86868b'}; margin: 0 0 20px 0; font-size: 14px;">${config.description}</p>
1345
+ <div class="feedback-survey-content">${config.html}</div>
1346
+ <div class="feedback-survey-feedback" style="margin-top: 16px;">
1347
+ <textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)" style="width: 100%; padding: 12px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#333' : '#d2d2d7'}; border-radius: 8px; font-size: 14px; resize: none; height: 80px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#fff'}; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'}; font-family: inherit; box-sizing: border-box;"></textarea>
1348
+ </div>
1349
+ <button class="feedback-survey-submit" style="width: 100%; margin-top: 12px; padding: 12px; background: #007aff; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; font-family: inherit;">Submit</button>
1350
+ `;
1351
+
1352
+ document.body.appendChild(this.surveyElement);
1353
+ this._attachSurveyEvents();
1354
+
1355
+ // Animate in
1356
+ requestAnimationFrame(() => {
1357
+ this.surveyElement.style.opacity = '1';
1358
+ this.surveyElement.style.transform = this.surveyOptions.position === 'center'
1359
+ ? 'translate(-50%, -50%) scale(1)'
1360
+ : 'translateY(0)';
1361
+ });
1362
+ }
1363
+
1364
+ _getSurveyConfig() {
1365
+ const configs = {
1366
+ nps: {
1367
+ title: this.surveyOptions.title || 'How likely are you to recommend us?',
1368
+ description: this.surveyOptions.description || 'On a scale of 0-10, how likely are you to recommend our product to a friend or colleague?',
1369
+ html: `
1370
+ <div class="feedback-survey-nps" style="display: flex; justify-content: space-between; gap: 4px;">
1371
+ ${[...Array(11).keys()].map(n => `
1372
+ <button class="feedback-survey-nps-btn" data-score="${n}" style="width: 28px; height: 36px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 6px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa'}; cursor: pointer; font-size: 12px; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'}; transition: all 0.15s;">${n}</button>
1373
+ `).join('')}
1374
+ </div>
1375
+ <div style="display: flex; justify-content: space-between; margin-top: 8px; font-size: 11px; color: ${this.surveyOptions.theme === 'dark' ? '#888' : '#86868b'};">
1376
+ <span>${this.surveyOptions.lowLabel || 'Not likely'}</span>
1377
+ <span>${this.surveyOptions.highLabel || 'Very likely'}</span>
1378
+ </div>
1379
+ `
1380
+ },
1381
+ csat: {
1382
+ title: this.surveyOptions.title || 'How satisfied are you?',
1383
+ description: this.surveyOptions.description || 'How would you rate your overall satisfaction with our product?',
1384
+ html: `
1385
+ <div class="feedback-survey-csat" style="display: flex; justify-content: center; gap: 16px;">
1386
+ ${['😞', '😕', '😐', '🙂', '😄'].map((emoji, i) => `
1387
+ <button class="feedback-survey-csat-btn" data-score="${i + 1}" style="background: none; border: none; cursor: pointer; font-size: 36px; transition: transform 0.15s; padding: 8px;">${emoji}</button>
1388
+ `).join('')}
1389
+ </div>
1390
+ <div style="display: flex; justify-content: space-between; margin-top: 8px; font-size: 11px; color: ${this.surveyOptions.theme === 'dark' ? '#888' : '#86868b'};">
1391
+ <span>${this.surveyOptions.lowLabel || 'Very dissatisfied'}</span>
1392
+ <span>${this.surveyOptions.highLabel || 'Very satisfied'}</span>
1393
+ </div>
1394
+ `
1395
+ },
1396
+ ces: {
1397
+ title: this.surveyOptions.title || 'How easy was it?',
1398
+ description: this.surveyOptions.description || 'How easy was it to accomplish your task today?',
1399
+ html: `
1400
+ <div class="feedback-survey-ces" style="display: flex; justify-content: space-between; gap: 8px;">
1401
+ ${['Very Difficult', 'Difficult', 'Neutral', 'Easy', 'Very Easy'].map((label, i) => `
1402
+ <button class="feedback-survey-ces-btn" data-score="${i + 1}" style="flex: 1; padding: 12px 8px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 8px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa'}; cursor: pointer; font-size: 11px; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'}; transition: all 0.15s;">${label}</button>
1403
+ `).join('')}
1404
+ </div>
1405
+ `
1406
+ },
1407
+ custom: {
1408
+ title: this.surveyOptions.title || 'Quick Feedback',
1409
+ description: this.surveyOptions.description || 'Help us improve by answering a few questions.',
1410
+ html: this._renderCustomQuestions()
1411
+ }
1412
+ };
1413
+
1414
+ return configs[this.surveyOptions.surveyType] || configs.nps;
1415
+ }
1416
+
1417
+ _renderCustomQuestions() {
1418
+ if (!this.surveyOptions.customQuestions || this.surveyOptions.customQuestions.length === 0) {
1419
+ // Default custom questions
1420
+ return `
1421
+ <div style="margin-bottom: 16px;">
1422
+ <label style="display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500;">What feature do you use most?</label>
1423
+ <select class="feedback-survey-select" data-question="feature" style="width: 100%; padding: 10px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 8px; font-size: 14px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#fff'}; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'};">
1424
+ <option value="">Select a feature</option>
1425
+ <option value="feedback">Feedback Collection</option>
1426
+ <option value="surveys">Surveys</option>
1427
+ <option value="analytics">Analytics</option>
1428
+ <option value="integrations">Integrations</option>
1429
+ </select>
1430
+ </div>
1431
+ <div>
1432
+ <label style="display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500;">How often do you use it?</label>
1433
+ <div class="feedback-survey-frequency" style="display: flex; gap: 8px;">
1434
+ ${['Daily', 'Weekly', 'Monthly', 'Rarely'].map(freq => `
1435
+ <button class="feedback-survey-freq-btn" data-freq="${freq}" style="flex: 1; padding: 10px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 8px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa'}; cursor: pointer; font-size: 12px; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'}; transition: all 0.15s;">${freq}</button>
1436
+ `).join('')}
1437
+ </div>
1438
+ </div>
1439
+ `;
1440
+ }
1441
+
1442
+ return this.surveyOptions.customQuestions.map((q, index) => `
1443
+ <div style="margin-bottom: 16px;">
1444
+ <label style="display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500;">${q.label}</label>
1445
+ ${this._renderQuestionInput(q, index)}
1446
+ </div>
1447
+ `).join('');
1448
+ }
1449
+
1450
+ _renderQuestionInput(question, index) {
1451
+ switch (question.type) {
1452
+ case 'select':
1453
+ return `
1454
+ <select class="feedback-survey-select" data-question="${question.id || index}" style="width: 100%; padding: 10px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 8px; font-size: 14px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#fff'}; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'};">
1455
+ <option value="">${question.placeholder || 'Select an option'}</option>
1456
+ ${question.options.map(opt => `<option value="${opt.value}">${opt.label}</option>`).join('')}
1457
+ </select>
1458
+ `;
1459
+ case 'text':
1460
+ return `
1461
+ <input type="text" class="feedback-survey-input" data-question="${question.id || index}" placeholder="${question.placeholder || ''}" style="width: 100%; padding: 10px; border: 1px solid ${this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7'}; border-radius: 8px; font-size: 14px; background: ${this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#fff'}; color: ${this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f'}; box-sizing: border-box;">
1462
+ `;
1463
+ default:
1464
+ return '';
1465
+ }
1466
+ }
1467
+
1468
+ _getPositionStyles() {
1469
+ const positions = {
1470
+ 'bottom-right': 'bottom: 24px; right: 24px; opacity: 0; transform: translateY(20px); transition: all 0.3s ease;',
1471
+ 'bottom-left': 'bottom: 24px; left: 24px; opacity: 0; transform: translateY(20px); transition: all 0.3s ease;',
1472
+ 'center': 'top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); opacity: 0; transition: all 0.3s ease;',
1473
+ 'bottom': 'bottom: 0; left: 0; right: 0; border-radius: 16px 16px 0 0; max-width: none; opacity: 0; transform: translateY(20px); transition: all 0.3s ease;'
1474
+ };
1475
+ return positions[this.surveyOptions.position] || positions['bottom-right'];
1476
+ }
1477
+
1478
+ _attachSurveyEvents() {
1479
+ if (!this.surveyElement) return;
1480
+
1481
+ // Close button
1482
+ const closeBtn = this.surveyElement.querySelector('.feedback-survey-close');
1483
+ closeBtn.addEventListener('click', () => this._handleDismiss());
1484
+
1485
+ // Submit button
1486
+ const submitBtn = this.surveyElement.querySelector('.feedback-survey-submit');
1487
+ submitBtn.addEventListener('click', () => this._handleSubmit());
1488
+
1489
+ // Feedback textarea
1490
+ const textarea = this.surveyElement.querySelector('.feedback-survey-textarea');
1491
+ textarea.addEventListener('input', (e) => {
1492
+ this.surveyState.feedback = e.target.value;
1493
+ });
1494
+
1495
+ // Survey type specific events
1496
+ this._attachTypeSpecificEvents();
1497
+
1498
+ // Escape key
1499
+ this._escapeHandler = (e) => {
1500
+ if (e.key === 'Escape') {
1501
+ this._handleDismiss();
1502
+ }
1503
+ };
1504
+ document.addEventListener('keydown', this._escapeHandler);
1505
+ }
1506
+
1507
+ _attachTypeSpecificEvents() {
1508
+ const type = this.surveyOptions.surveyType;
1509
+
1510
+ if (type === 'nps') {
1511
+ this.surveyElement.querySelectorAll('.feedback-survey-nps-btn').forEach(btn => {
1512
+ btn.addEventListener('click', () => this._selectNPS(parseInt(btn.dataset.score)));
1513
+ btn.addEventListener('mouseenter', () => {
1514
+ if (this.surveyState.score !== parseInt(btn.dataset.score)) {
1515
+ btn.style.borderColor = '#007aff';
1516
+ }
1517
+ });
1518
+ btn.addEventListener('mouseleave', () => {
1519
+ if (this.surveyState.score !== parseInt(btn.dataset.score)) {
1520
+ btn.style.borderColor = this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7';
1521
+ }
1522
+ });
1523
+ });
1524
+ }
1525
+
1526
+ if (type === 'csat') {
1527
+ this.surveyElement.querySelectorAll('.feedback-survey-csat-btn').forEach(btn => {
1528
+ btn.addEventListener('click', () => this._selectCSAT(parseInt(btn.dataset.score)));
1529
+ btn.addEventListener('mouseenter', () => {
1530
+ btn.style.transform = 'scale(1.1)';
1531
+ });
1532
+ btn.addEventListener('mouseleave', () => {
1533
+ if (this.surveyState.score !== parseInt(btn.dataset.score)) {
1534
+ btn.style.transform = 'scale(1)';
1535
+ }
1536
+ });
1537
+ });
1538
+ }
1539
+
1540
+ if (type === 'ces') {
1541
+ this.surveyElement.querySelectorAll('.feedback-survey-ces-btn').forEach(btn => {
1542
+ btn.addEventListener('click', () => this._selectCES(parseInt(btn.dataset.score)));
1543
+ btn.addEventListener('mouseenter', () => {
1544
+ if (this.surveyState.score !== parseInt(btn.dataset.score)) {
1545
+ btn.style.borderColor = '#007aff';
1546
+ }
1547
+ });
1548
+ btn.addEventListener('mouseleave', () => {
1549
+ if (this.surveyState.score !== parseInt(btn.dataset.score)) {
1550
+ btn.style.borderColor = this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7';
1551
+ }
1552
+ });
1553
+ });
1554
+ }
1555
+
1556
+ if (type === 'custom') {
1557
+ // Frequency buttons
1558
+ this.surveyElement.querySelectorAll('.feedback-survey-freq-btn').forEach(btn => {
1559
+ btn.addEventListener('click', () => this._selectFrequency(btn.dataset.freq));
1560
+ });
1561
+
1562
+ // Select inputs
1563
+ this.surveyElement.querySelectorAll('.feedback-survey-select').forEach(select => {
1564
+ select.addEventListener('change', (e) => {
1565
+ this.surveyState.customAnswers[select.dataset.question] = e.target.value;
1566
+ });
1567
+ });
1568
+
1569
+ // Text inputs
1570
+ this.surveyElement.querySelectorAll('.feedback-survey-input').forEach(input => {
1571
+ input.addEventListener('input', (e) => {
1572
+ this.surveyState.customAnswers[input.dataset.question] = e.target.value;
1573
+ });
1574
+ });
1575
+ }
1576
+ }
1577
+
1578
+ _selectNPS(score) {
1579
+ this.surveyState.score = score;
1580
+ this.surveyElement.querySelectorAll('.feedback-survey-nps-btn').forEach(btn => {
1581
+ const btnScore = parseInt(btn.dataset.score);
1582
+ if (btnScore === score) {
1583
+ btn.style.background = '#007aff';
1584
+ btn.style.borderColor = '#007aff';
1585
+ btn.style.color = '#fff';
1586
+ } else {
1587
+ btn.style.background = this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa';
1588
+ btn.style.borderColor = this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7';
1589
+ btn.style.color = this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f';
1590
+ }
1591
+ });
1592
+ }
1593
+
1594
+ _selectCSAT(score) {
1595
+ this.surveyState.score = score;
1596
+ this.surveyElement.querySelectorAll('.feedback-survey-csat-btn').forEach(btn => {
1597
+ const btnScore = parseInt(btn.dataset.score);
1598
+ btn.style.transform = btnScore === score ? 'scale(1.2)' : 'scale(1)';
1599
+ });
1600
+ }
1601
+
1602
+ _selectCES(score) {
1603
+ this.surveyState.score = score;
1604
+ this.surveyElement.querySelectorAll('.feedback-survey-ces-btn').forEach(btn => {
1605
+ const btnScore = parseInt(btn.dataset.score);
1606
+ if (btnScore === score) {
1607
+ btn.style.background = '#007aff';
1608
+ btn.style.borderColor = '#007aff';
1609
+ btn.style.color = '#fff';
1610
+ } else {
1611
+ btn.style.background = this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa';
1612
+ btn.style.borderColor = this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7';
1613
+ btn.style.color = this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f';
1614
+ }
1615
+ });
1616
+ }
1617
+
1618
+ _selectFrequency(freq) {
1619
+ this.surveyState.customAnswers.frequency = freq;
1620
+ this.surveyElement.querySelectorAll('.feedback-survey-freq-btn').forEach(btn => {
1621
+ if (btn.dataset.freq === freq) {
1622
+ btn.style.background = '#007aff';
1623
+ btn.style.borderColor = '#007aff';
1624
+ btn.style.color = '#fff';
1625
+ } else {
1626
+ btn.style.background = this.surveyOptions.theme === 'dark' ? '#2a2a2a' : '#f8f9fa';
1627
+ btn.style.borderColor = this.surveyOptions.theme === 'dark' ? '#444' : '#d2d2d7';
1628
+ btn.style.color = this.surveyOptions.theme === 'dark' ? '#fff' : '#1d1d1f';
1629
+ }
1630
+ });
1631
+ }
1632
+
1633
+ async _handleSubmit() {
1634
+ const type = this.surveyOptions.surveyType;
1635
+
1636
+ // Validate
1637
+ if ((type === 'nps' || type === 'csat' || type === 'ces') && this.surveyState.score === null) {
1638
+ this._showError('Please select a rating');
1639
+ return;
1640
+ }
1641
+
1642
+ const response = {
1643
+ type: type,
1644
+ score: this.surveyState.score,
1645
+ feedback: this.surveyState.feedback,
1646
+ customAnswers: this.surveyState.customAnswers,
1647
+ timestamp: new Date().toISOString(),
1648
+ };
1649
+
1650
+ // Call onSubmit callback if provided
1651
+ if (this.surveyOptions.onSubmit) {
1652
+ this.surveyOptions.onSubmit(response);
1653
+ }
1654
+
1655
+ // Submit to API if not in mock mode
1656
+ if (!this.sdk.config.mock) {
1657
+ try {
1658
+ await this.apiService.submitSurveyResponse(response);
1659
+ } catch (error) {
1660
+ console.error('[SurveyWidget] Failed to submit survey:', error);
1661
+ }
1662
+ }
1663
+
1664
+ this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
1665
+
1666
+ this._closeSurvey();
1667
+ this._showSuccessNotification();
1668
+ }
1669
+
1670
+ _handleDismiss() {
1671
+ if (this.surveyOptions.onDismiss) {
1672
+ this.surveyOptions.onDismiss();
1673
+ }
1674
+ this.sdk.eventBus.emit('survey:dismissed', { widget: this });
1675
+ this._closeSurvey();
1676
+ }
1677
+
1678
+ _showError(message) {
1679
+ // Simple error display - could be enhanced
1680
+ const existing = this.surveyElement.querySelector('.feedback-survey-error');
1681
+ if (existing) existing.remove();
1682
+
1683
+ const error = document.createElement('div');
1684
+ error.className = 'feedback-survey-error';
1685
+ error.style.cssText = 'color: #ef4444; font-size: 13px; margin-top: 8px; text-align: center;';
1686
+ error.textContent = message;
1687
+
1688
+ const submitBtn = this.surveyElement.querySelector('.feedback-survey-submit');
1689
+ submitBtn.parentNode.insertBefore(error, submitBtn);
1690
+
1691
+ setTimeout(() => error.remove(), 3000);
1692
+ }
1693
+
1694
+ _showSuccessNotification() {
1695
+ const notification = document.createElement('div');
1696
+ notification.className = 'feedback-survey-success';
1697
+ notification.style.cssText = `
1698
+ position: fixed;
1699
+ top: 24px;
1700
+ right: 24px;
1701
+ background: #10b981;
1702
+ color: white;
1703
+ padding: 16px 24px;
1704
+ border-radius: 12px;
1705
+ font-size: 14px;
1706
+ font-weight: 500;
1707
+ z-index: 10001;
1708
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2);
1709
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
1710
+ `;
1711
+ notification.innerHTML = `
1712
+ <div style="display: flex; align-items: center; gap: 8px;">
1713
+ <span style="font-size: 18px;">✓</span>
1714
+ <span>Thank you for your feedback!</span>
1715
+ </div>
1716
+ `;
1717
+ document.body.appendChild(notification);
1718
+
1719
+ setTimeout(() => {
1720
+ notification.style.opacity = '0';
1721
+ notification.style.transition = 'opacity 0.3s ease';
1722
+ setTimeout(() => notification.remove(), 300);
1723
+ }, 3000);
1724
+ }
1725
+
1726
+ _closeSurvey() {
1727
+ if (this._escapeHandler) {
1728
+ document.removeEventListener('keydown', this._escapeHandler);
1729
+ this._escapeHandler = null;
1730
+ }
1731
+
1732
+ // Capture references to avoid race conditions with setTimeout
1733
+ const surveyEl = this.surveyElement;
1734
+ const backdropEl = this.backdropElement;
1735
+
1736
+ if (surveyEl) {
1737
+ surveyEl.style.opacity = '0';
1738
+ surveyEl.style.transform = this.surveyOptions.position === 'center'
1739
+ ? 'translate(-50%, -50%) scale(0.95)'
1740
+ : 'translateY(20px)';
1741
+ setTimeout(() => {
1742
+ if (surveyEl && surveyEl.parentNode) {
1743
+ surveyEl.parentNode.removeChild(surveyEl);
1744
+ }
1745
+ }, 300);
1746
+ this.surveyElement = null;
1747
+ }
1748
+
1749
+ if (backdropEl) {
1750
+ backdropEl.style.opacity = '0';
1751
+ setTimeout(() => {
1752
+ if (backdropEl && backdropEl.parentNode) {
1753
+ backdropEl.parentNode.removeChild(backdropEl);
1754
+ }
1755
+ }, 300);
1756
+ this.backdropElement = null;
1757
+ }
1758
+
1759
+ this.surveyState = {
1760
+ score: null,
1761
+ feedback: '',
1762
+ customAnswers: {},
1763
+ isVisible: false,
1764
+ };
1765
+ }
1766
+
1767
+ destroy() {
1768
+ this._closeSurvey();
1769
+ super.destroy();
1770
+ }
1771
+ }
1772
+
1161
1773
  class TabWidget extends BaseWidget {
1162
1774
  constructor(options) {
1163
1775
  super({ ...options, type: 'tab' });
@@ -1227,6 +1839,7 @@
1227
1839
  ['button', ButtonWidget],
1228
1840
  ['tab', TabWidget],
1229
1841
  ['inline', InlineWidget],
1842
+ ['survey', SurveyWidget],
1230
1843
  ]);
1231
1844
 
1232
1845
  static register(type, WidgetClass) {
@@ -1293,6 +1906,8 @@
1293
1906
  apiUrl: this.config.apiUrl,
1294
1907
  workspace: this.config.workspace,
1295
1908
  userContext: this.config.userContext,
1909
+ mock: this.config.mock,
1910
+ env: this.config.env,
1296
1911
  });
1297
1912
 
1298
1913
  this._bindMethods();
@@ -1306,8 +1921,9 @@
1306
1921
  try {
1307
1922
  const initData = await this.apiService.init(this.config.userContext);
1308
1923
 
1924
+ // Merge backend config as base, local config overrides
1309
1925
  if (initData.config) {
1310
- this.config = deepMerge(this.config, initData.config);
1926
+ this.config = deepMerge(initData.config, this.config);
1311
1927
  }
1312
1928
 
1313
1929
  this.initialized = true;
@@ -1356,6 +1972,44 @@
1356
1972
  return this.widgets.get(id);
1357
1973
  }
1358
1974
 
1975
+ /**
1976
+ * Show a survey widget
1977
+ * @param {Object} options - Survey options
1978
+ * @param {string} options.surveyType - Type of survey: 'nps', 'csat', 'ces', 'custom'
1979
+ * @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'center', 'bottom'
1980
+ * @param {string} options.theme - Theme: 'light', 'dark'
1981
+ * @param {string} options.title - Custom title
1982
+ * @param {string} options.description - Custom description
1983
+ * @param {string} options.lowLabel - Low end label
1984
+ * @param {string} options.highLabel - High end label
1985
+ * @param {Function} options.onSubmit - Callback when survey is submitted
1986
+ * @param {Function} options.onDismiss - Callback when survey is dismissed
1987
+ * @returns {SurveyWidget} The survey widget instance
1988
+ */
1989
+ showSurvey(options = {}) {
1990
+ if (!this.initialized) {
1991
+ throw new SDKError('SDK must be initialized before showing surveys. Call init() first.');
1992
+ }
1993
+
1994
+ const surveyWidget = this.createWidget('survey', {
1995
+ surveyType: options.surveyType || options.type || 'nps',
1996
+ position: options.position || 'bottom-right',
1997
+ theme: options.theme || this.config.theme || 'light',
1998
+ title: options.title,
1999
+ description: options.description,
2000
+ lowLabel: options.lowLabel,
2001
+ highLabel: options.highLabel,
2002
+ customQuestions: options.customQuestions,
2003
+ onSubmit: options.onSubmit,
2004
+ onDismiss: options.onDismiss,
2005
+ });
2006
+
2007
+ surveyWidget.mount();
2008
+ surveyWidget.show();
2009
+
2010
+ return surveyWidget;
2011
+ }
2012
+
1359
2013
  getAllWidgets() {
1360
2014
  return Array.from(this.widgets.values());
1361
2015
  }
@@ -1454,6 +2108,8 @@
1454
2108
  boardId: 'general',
1455
2109
  autoShow: true,
1456
2110
  debug: false,
2111
+ mock: false,
2112
+ env: 'production', // 'production' or 'staging'
1457
2113
  };
1458
2114
 
1459
2115
  const mergedConfig = deepMerge(deepMerge(defaultConfig, existingConfig), newConfig);
@@ -1566,6 +2222,24 @@
1566
2222
  left: 20px;
1567
2223
  }
1568
2224
 
2225
+ .feedback-widget-button.position-bottom-center {
2226
+ bottom: 20px;
2227
+ left: 50%;
2228
+ transform: translateX(-50%);
2229
+ }
2230
+
2231
+ .feedback-widget-button.position-top-center {
2232
+ top: 20px;
2233
+ left: 50%;
2234
+ transform: translateX(-50%);
2235
+ }
2236
+
2237
+ .feedback-widget-button.position-center {
2238
+ top: 50%;
2239
+ left: 50%;
2240
+ transform: translate(-50%, -50%);
2241
+ }
2242
+
1569
2243
  /* Circular button design with white bg and blue border */
1570
2244
  .feedback-trigger-btn {
1571
2245
  width: 56px;
@@ -1609,6 +2283,99 @@
1609
2283
  color: #ffffff;
1610
2284
  }
1611
2285
 
2286
+ /* Loading Modal */
2287
+ .feedback-loading-modal {
2288
+ position: fixed;
2289
+ top: 50%;
2290
+ left: 50%;
2291
+ transform: translate(-50%, -50%) scale(0.9);
2292
+ z-index: 1000001;
2293
+ opacity: 0;
2294
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2295
+ }
2296
+
2297
+ .feedback-loading-modal.show {
2298
+ opacity: 1;
2299
+ transform: translate(-50%, -50%) scale(1);
2300
+ }
2301
+
2302
+ .feedback-loading-spinner {
2303
+ width: 48px;
2304
+ height: 48px;
2305
+ border: 4px solid rgba(255, 255, 255, 0.3);
2306
+ border-top-color: #155EEF;
2307
+ border-radius: 50%;
2308
+ animation: spin 0.8s linear infinite;
2309
+ }
2310
+
2311
+ .theme-dark .feedback-loading-spinner {
2312
+ border-color: rgba(255, 255, 255, 0.2);
2313
+ border-top-color: #155EEF;
2314
+ }
2315
+
2316
+ @keyframes spin {
2317
+ to { transform: rotate(360deg); }
2318
+ }
2319
+
2320
+ /* Modal Styles (centered) */
2321
+ .feedback-modal {
2322
+ position: fixed;
2323
+ top: 50%;
2324
+ left: 50%;
2325
+ transform: translate(-50%, -50%) scale(0.9);
2326
+ width: 480px;
2327
+ max-width: 90vw;
2328
+ max-height: 85vh;
2329
+ z-index: 1000000;
2330
+ opacity: 0;
2331
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2332
+ font-family: inherit;
2333
+ }
2334
+
2335
+ .feedback-modal.open {
2336
+ opacity: 1;
2337
+ transform: translate(-50%, -50%) scale(1);
2338
+ }
2339
+
2340
+ .feedback-modal .feedback-panel-content {
2341
+ max-height: 85vh;
2342
+ overflow-y: auto;
2343
+ }
2344
+
2345
+ /* Size variants */
2346
+ .feedback-modal.size-small {
2347
+ width: 360px;
2348
+ }
2349
+
2350
+ .feedback-modal.size-medium {
2351
+ width: 480px;
2352
+ }
2353
+
2354
+ .feedback-modal.size-large {
2355
+ width: 600px;
2356
+ }
2357
+
2358
+ .feedback-panel.size-small {
2359
+ width: 320px;
2360
+ }
2361
+
2362
+ .feedback-panel.size-medium {
2363
+ width: 420px;
2364
+ }
2365
+
2366
+ .feedback-panel.size-large {
2367
+ width: 520px;
2368
+ }
2369
+
2370
+ /* Adjust textarea height for sizes */
2371
+ .size-small .feedback-form-group textarea {
2372
+ min-height: 120px;
2373
+ }
2374
+
2375
+ .size-large .feedback-form-group textarea {
2376
+ min-height: 280px;
2377
+ }
2378
+
1612
2379
  /* Side Panel Styles */
1613
2380
  .feedback-panel {
1614
2381
  position: fixed;
@@ -1632,7 +2399,7 @@
1632
2399
  left: 0;
1633
2400
  right: 0;
1634
2401
  bottom: 0;
1635
- background: rgba(0, 0, 0, 0.1);
2402
+ background: rgba(0, 0, 0, 0.5);
1636
2403
  opacity: 0;
1637
2404
  transition: opacity 0.3s ease;
1638
2405
  pointer-events: none;
@@ -1645,43 +2412,31 @@
1645
2412
  }
1646
2413
 
1647
2414
  .feedback-panel-content {
1648
- background: white;
2415
+ background: var(--bg-color, #ffffff);
2416
+ color: var(--text-color, #1F2937);
1649
2417
  height: 100%;
1650
2418
  display: flex;
1651
2419
  flex-direction: column;
1652
2420
  border-radius: 16px;
1653
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
2421
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
1654
2422
  0 10px 10px -5px rgba(0, 0, 0, 0.04),
1655
2423
  0 0 0 1px rgba(0, 0, 0, 0.05);
1656
2424
  }
1657
2425
 
1658
- .feedback-panel.theme-dark .feedback-panel-content {
1659
- background: #1F2937;
1660
- color: white;
1661
- }
1662
-
1663
2426
  .feedback-panel-header {
1664
2427
  display: flex;
1665
2428
  align-items: center;
1666
2429
  justify-content: space-between;
1667
- padding: 24px;
1668
- border-bottom: 1px solid #E5E7EB;
2430
+ padding: 16px 20px;
2431
+ border-bottom: 1px solid rgba(128, 128, 128, 0.2);
1669
2432
  flex-shrink: 0;
1670
2433
  }
1671
2434
 
1672
- .feedback-panel.theme-dark .feedback-panel-header {
1673
- border-bottom-color: #374151;
1674
- }
1675
-
1676
2435
  .feedback-panel-header h3 {
1677
2436
  margin: 0;
1678
2437
  font-size: 18px;
1679
2438
  font-weight: 600;
1680
- color: #111827;
1681
- }
1682
-
1683
- .feedback-panel.theme-dark .feedback-panel-header h3 {
1684
- color: white;
2439
+ color: var(--text-color, #111827);
1685
2440
  }
1686
2441
 
1687
2442
  .feedback-panel-close {
@@ -1689,7 +2444,8 @@
1689
2444
  border: none;
1690
2445
  font-size: 24px;
1691
2446
  cursor: pointer;
1692
- color: #6B7280;
2447
+ color: var(--text-color, #6B7280);
2448
+ opacity: 0.6;
1693
2449
  padding: 4px;
1694
2450
  width: 32px;
1695
2451
  height: 32px;
@@ -1701,28 +2457,19 @@
1701
2457
  }
1702
2458
 
1703
2459
  .feedback-panel-close:hover {
1704
- background: #F3F4F6;
1705
- color: #111827;
2460
+ opacity: 1;
2461
+ background: rgba(128, 128, 128, 0.1);
1706
2462
  }
1707
2463
 
1708
2464
  .feedback-panel-close:focus-visible {
1709
- outline: 2px solid #155EEF;
2465
+ outline: 2px solid var(--primary-color, #155EEF);
1710
2466
  outline-offset: 2px;
1711
2467
  }
1712
2468
 
1713
- .feedback-panel.theme-dark .feedback-panel-close {
1714
- color: #9CA3AF;
1715
- }
1716
-
1717
- .feedback-panel.theme-dark .feedback-panel-close:hover {
1718
- background: #374151;
1719
- color: white;
1720
- }
1721
-
1722
2469
  .feedback-panel-body {
1723
2470
  flex: 1;
1724
2471
  overflow-y: auto;
1725
- padding: 24px;
2472
+ padding: 16px 20px;
1726
2473
  }
1727
2474
 
1728
2475
  .feedback-form {
@@ -1746,23 +2493,21 @@
1746
2493
  font-size: 14px;
1747
2494
  font-weight: 500;
1748
2495
  line-height: 1.25;
1749
- color: #374151;
1750
- }
1751
-
1752
- .feedback-panel.theme-dark .feedback-form-group label {
1753
- color: #D1D5DB;
2496
+ color: var(--text-color, #374151);
2497
+ opacity: 0.8;
1754
2498
  }
1755
2499
 
1756
2500
  .feedback-form-group input {
1757
2501
  height: 44px;
1758
2502
  width: 100%;
1759
2503
  border-radius: 8px;
1760
- border: 1px solid #D1D5DB;
2504
+ border: 1px solid rgba(128, 128, 128, 0.3);
2505
+ background: rgba(128, 128, 128, 0.05);
1761
2506
  padding: 10px 14px;
1762
2507
  font-size: 15px;
1763
2508
  font-weight: 400;
1764
2509
  line-height: 1.5;
1765
- color: #1F2937;
2510
+ color: var(--text-color, #1F2937);
1766
2511
  font-family: inherit;
1767
2512
  outline: none;
1768
2513
  transition: all 0.2s ease;
@@ -1770,11 +2515,12 @@
1770
2515
 
1771
2516
  .feedback-form-group input::placeholder {
1772
2517
  font-size: 15px;
1773
- color: #9CA3AF;
2518
+ color: var(--text-color, #9CA3AF);
2519
+ opacity: 0.5;
1774
2520
  }
1775
2521
 
1776
2522
  .feedback-form-group input:focus {
1777
- border-color: #155EEF;
2523
+ border-color: var(--primary-color, #155EEF);
1778
2524
  box-shadow: 0 0 0 3px rgba(21, 94, 239, 0.1);
1779
2525
  }
1780
2526
 
@@ -1787,12 +2533,13 @@
1787
2533
  width: 100%;
1788
2534
  resize: vertical;
1789
2535
  border-radius: 8px;
1790
- border: 1px solid #D1D5DB;
2536
+ border: 1px solid rgba(128, 128, 128, 0.3);
2537
+ background: rgba(128, 128, 128, 0.05);
1791
2538
  padding: 10px 14px;
1792
2539
  font-size: 15px;
1793
2540
  font-weight: 400;
1794
2541
  line-height: 1.5;
1795
- color: #1F2937;
2542
+ color: var(--text-color, #1F2937);
1796
2543
  font-family: inherit;
1797
2544
  outline: none;
1798
2545
  transition: all 0.2s ease;
@@ -1800,11 +2547,12 @@
1800
2547
 
1801
2548
  .feedback-form-group textarea::placeholder {
1802
2549
  font-size: 15px;
1803
- color: #9CA3AF;
2550
+ color: var(--text-color, #9CA3AF);
2551
+ opacity: 0.5;
1804
2552
  }
1805
2553
 
1806
2554
  .feedback-form-group textarea:focus {
1807
- border-color: #155EEF;
2555
+ border-color: var(--primary-color, #155EEF);
1808
2556
  box-shadow: 0 0 0 3px rgba(21, 94, 239, 0.1);
1809
2557
  }
1810
2558
 
@@ -1812,18 +2560,6 @@
1812
2560
  outline: none;
1813
2561
  }
1814
2562
 
1815
- .feedback-panel.theme-dark .feedback-form-group input,
1816
- .feedback-panel.theme-dark .feedback-form-group textarea {
1817
- background: #374151;
1818
- border-color: #4B5563;
1819
- color: white;
1820
- }
1821
-
1822
- .feedback-panel.theme-dark .feedback-form-group input::placeholder,
1823
- .feedback-panel.theme-dark .feedback-form-group textarea::placeholder {
1824
- color: #6B7280;
1825
- }
1826
-
1827
2563
  .feedback-btn {
1828
2564
  position: relative;
1829
2565
  display: inline-flex;
@@ -1852,38 +2588,29 @@
1852
2588
  }
1853
2589
 
1854
2590
  .feedback-btn-submit {
1855
- background: #155EEF;
2591
+ background: var(--primary-color, #155EEF);
1856
2592
  color: white;
1857
2593
  width: 100%;
1858
2594
  }
1859
2595
 
1860
2596
  .feedback-btn-submit:hover:not(:disabled) {
1861
- background: #1A56DB;
2597
+ background: var(--primary-color, #155EEF);
2598
+ filter: brightness(0.9);
1862
2599
  }
1863
2600
 
1864
2601
  .feedback-btn-submit:active:not(:disabled) {
1865
- background: #1E429F;
2602
+ background: var(--primary-color, #155EEF);
2603
+ filter: brightness(0.8);
1866
2604
  }
1867
2605
 
1868
2606
  .feedback-btn-cancel {
1869
2607
  background: transparent;
1870
- color: #6B7280;
1871
- border: 1px solid #D1D5DB;
2608
+ color: var(--text-color, #6B7280);
2609
+ border: 1px solid rgba(128, 128, 128, 0.3);
1872
2610
  }
1873
2611
 
1874
2612
  .feedback-btn-cancel:hover:not(:disabled) {
1875
- background: #F9FAFB;
1876
- border-color: #9CA3AF;
1877
- color: #374151;
1878
- }
1879
-
1880
- .feedback-panel.theme-dark .feedback-btn-cancel {
1881
- color: #D1D5DB;
1882
- border-color: #4B5563;
1883
- }
1884
-
1885
- .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
1886
- background: #374151;
2613
+ background: rgba(128, 128, 128, 0.1);
1887
2614
  }
1888
2615
 
1889
2616
  .feedback-form-actions {
@@ -2090,6 +2817,70 @@
2090
2817
  display: none !important;
2091
2818
  }
2092
2819
  }
2820
+
2821
+ /* Survey Widget Styles */
2822
+ .feedback-survey-backdrop {
2823
+ position: fixed;
2824
+ top: 0;
2825
+ left: 0;
2826
+ right: 0;
2827
+ bottom: 0;
2828
+ background: rgba(0, 0, 0, 0.5);
2829
+ z-index: 9999;
2830
+ opacity: 0;
2831
+ transition: opacity 0.3s ease;
2832
+ }
2833
+
2834
+ .feedback-survey-backdrop.show {
2835
+ opacity: 1;
2836
+ }
2837
+
2838
+ .feedback-survey {
2839
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2840
+ }
2841
+
2842
+ .feedback-survey-csat-btn:hover {
2843
+ transform: scale(1.1) !important;
2844
+ }
2845
+
2846
+ .feedback-survey-nps-btn:hover,
2847
+ .feedback-survey-ces-btn:hover,
2848
+ .feedback-survey-freq-btn:hover {
2849
+ border-color: #007aff !important;
2850
+ }
2851
+
2852
+ @media (max-width: 768px) {
2853
+ .feedback-survey {
2854
+ left: 16px !important;
2855
+ right: 16px !important;
2856
+ max-width: none !important;
2857
+ min-width: auto !important;
2858
+ }
2859
+
2860
+ .feedback-survey.feedback-survey-center {
2861
+ width: calc(100% - 32px) !important;
2862
+ }
2863
+
2864
+ .feedback-survey-nps {
2865
+ flex-wrap: wrap;
2866
+ justify-content: center !important;
2867
+ }
2868
+
2869
+ .feedback-survey-nps-btn {
2870
+ width: 32px !important;
2871
+ height: 32px !important;
2872
+ font-size: 11px !important;
2873
+ }
2874
+
2875
+ .feedback-survey-ces {
2876
+ flex-direction: column !important;
2877
+ }
2878
+
2879
+ .feedback-survey-ces-btn {
2880
+ flex: none !important;
2881
+ width: 100% !important;
2882
+ }
2883
+ }
2093
2884
  `;
2094
2885
 
2095
2886
  function injectStyles() {
@@ -2167,6 +2958,7 @@
2167
2958
  ButtonWidget,
2168
2959
  TabWidget,
2169
2960
  InlineWidget,
2961
+ SurveyWidget,
2170
2962
  WidgetFactory,
2171
2963
  EventBus,
2172
2964
  APIService,
@@ -2250,6 +3042,7 @@
2250
3042
  exports.FeedbackSDK = FeedbackSDK;
2251
3043
  exports.InlineWidget = InlineWidget;
2252
3044
  exports.SDKError = SDKError;
3045
+ exports.SurveyWidget = SurveyWidget;
2253
3046
  exports.TabWidget = TabWidget;
2254
3047
  exports.ValidationError = ValidationError;
2255
3048
  exports.WidgetError = WidgetError;