@product7/feedback-sdk 1.0.9 → 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,23 +2222,45 @@
1566
2222
  left: 20px;
1567
2223
  }
1568
2224
 
1569
- /* Circular button design */
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
+
2243
+ /* Circular button design with white bg and blue border */
1570
2244
  .feedback-trigger-btn {
1571
2245
  width: 56px;
1572
2246
  height: 56px;
1573
- border-radius: 50%; /* Makes it circular */
1574
- border: none;
2247
+ border-radius: 50%;
2248
+ border: 2px solid #155eff;
2249
+ background-color: #ffffff;
1575
2250
  cursor: pointer;
1576
2251
  display: flex;
1577
2252
  align-items: center;
1578
2253
  justify-content: center;
1579
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2254
+ box-shadow: 0 4px 12px rgba(21, 94, 255, 0.15);
1580
2255
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1581
2256
  padding: 0;
2257
+ color: #155eff; /* Icon color matches border */
1582
2258
  }
1583
2259
 
1584
2260
  .feedback-trigger-btn:hover {
1585
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
2261
+ background-color: #155eff;
2262
+ color: #ffffff;
2263
+ box-shadow: 0 6px 20px rgba(21, 94, 255, 0.3);
1586
2264
  }
1587
2265
 
1588
2266
  .feedback-trigger-btn:active {
@@ -1593,6 +2271,111 @@
1593
2271
  flex-shrink: 0;
1594
2272
  }
1595
2273
 
2274
+ /* Dark theme support */
2275
+ .theme-dark .feedback-trigger-btn {
2276
+ background-color: #1a1a1a;
2277
+ border-color: #155eff;
2278
+ color: #155eff;
2279
+ }
2280
+
2281
+ .theme-dark .feedback-trigger-btn:hover {
2282
+ background-color: #155eff;
2283
+ color: #ffffff;
2284
+ }
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
+
1596
2379
  /* Side Panel Styles */
1597
2380
  .feedback-panel {
1598
2381
  position: fixed;
@@ -1616,7 +2399,7 @@
1616
2399
  left: 0;
1617
2400
  right: 0;
1618
2401
  bottom: 0;
1619
- background: rgba(0, 0, 0, 0.1);
2402
+ background: rgba(0, 0, 0, 0.5);
1620
2403
  opacity: 0;
1621
2404
  transition: opacity 0.3s ease;
1622
2405
  pointer-events: none;
@@ -1629,43 +2412,31 @@
1629
2412
  }
1630
2413
 
1631
2414
  .feedback-panel-content {
1632
- background: white;
2415
+ background: var(--bg-color, #ffffff);
2416
+ color: var(--text-color, #1F2937);
1633
2417
  height: 100%;
1634
2418
  display: flex;
1635
2419
  flex-direction: column;
1636
2420
  border-radius: 16px;
1637
- 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),
1638
2422
  0 10px 10px -5px rgba(0, 0, 0, 0.04),
1639
2423
  0 0 0 1px rgba(0, 0, 0, 0.05);
1640
2424
  }
1641
2425
 
1642
- .feedback-panel.theme-dark .feedback-panel-content {
1643
- background: #1F2937;
1644
- color: white;
1645
- }
1646
-
1647
2426
  .feedback-panel-header {
1648
2427
  display: flex;
1649
2428
  align-items: center;
1650
2429
  justify-content: space-between;
1651
- padding: 24px;
1652
- border-bottom: 1px solid #E5E7EB;
2430
+ padding: 16px 20px;
2431
+ border-bottom: 1px solid rgba(128, 128, 128, 0.2);
1653
2432
  flex-shrink: 0;
1654
2433
  }
1655
2434
 
1656
- .feedback-panel.theme-dark .feedback-panel-header {
1657
- border-bottom-color: #374151;
1658
- }
1659
-
1660
2435
  .feedback-panel-header h3 {
1661
2436
  margin: 0;
1662
2437
  font-size: 18px;
1663
2438
  font-weight: 600;
1664
- color: #111827;
1665
- }
1666
-
1667
- .feedback-panel.theme-dark .feedback-panel-header h3 {
1668
- color: white;
2439
+ color: var(--text-color, #111827);
1669
2440
  }
1670
2441
 
1671
2442
  .feedback-panel-close {
@@ -1673,7 +2444,8 @@
1673
2444
  border: none;
1674
2445
  font-size: 24px;
1675
2446
  cursor: pointer;
1676
- color: #6B7280;
2447
+ color: var(--text-color, #6B7280);
2448
+ opacity: 0.6;
1677
2449
  padding: 4px;
1678
2450
  width: 32px;
1679
2451
  height: 32px;
@@ -1685,28 +2457,19 @@
1685
2457
  }
1686
2458
 
1687
2459
  .feedback-panel-close:hover {
1688
- background: #F3F4F6;
1689
- color: #111827;
2460
+ opacity: 1;
2461
+ background: rgba(128, 128, 128, 0.1);
1690
2462
  }
1691
2463
 
1692
2464
  .feedback-panel-close:focus-visible {
1693
- outline: 2px solid #155EEF;
2465
+ outline: 2px solid var(--primary-color, #155EEF);
1694
2466
  outline-offset: 2px;
1695
2467
  }
1696
2468
 
1697
- .feedback-panel.theme-dark .feedback-panel-close {
1698
- color: #9CA3AF;
1699
- }
1700
-
1701
- .feedback-panel.theme-dark .feedback-panel-close:hover {
1702
- background: #374151;
1703
- color: white;
1704
- }
1705
-
1706
2469
  .feedback-panel-body {
1707
2470
  flex: 1;
1708
2471
  overflow-y: auto;
1709
- padding: 24px;
2472
+ padding: 16px 20px;
1710
2473
  }
1711
2474
 
1712
2475
  .feedback-form {
@@ -1730,23 +2493,21 @@
1730
2493
  font-size: 14px;
1731
2494
  font-weight: 500;
1732
2495
  line-height: 1.25;
1733
- color: #374151;
1734
- }
1735
-
1736
- .feedback-panel.theme-dark .feedback-form-group label {
1737
- color: #D1D5DB;
2496
+ color: var(--text-color, #374151);
2497
+ opacity: 0.8;
1738
2498
  }
1739
2499
 
1740
2500
  .feedback-form-group input {
1741
2501
  height: 44px;
1742
2502
  width: 100%;
1743
2503
  border-radius: 8px;
1744
- border: 1px solid #D1D5DB;
2504
+ border: 1px solid rgba(128, 128, 128, 0.3);
2505
+ background: rgba(128, 128, 128, 0.05);
1745
2506
  padding: 10px 14px;
1746
2507
  font-size: 15px;
1747
2508
  font-weight: 400;
1748
2509
  line-height: 1.5;
1749
- color: #1F2937;
2510
+ color: var(--text-color, #1F2937);
1750
2511
  font-family: inherit;
1751
2512
  outline: none;
1752
2513
  transition: all 0.2s ease;
@@ -1754,11 +2515,12 @@
1754
2515
 
1755
2516
  .feedback-form-group input::placeholder {
1756
2517
  font-size: 15px;
1757
- color: #9CA3AF;
2518
+ color: var(--text-color, #9CA3AF);
2519
+ opacity: 0.5;
1758
2520
  }
1759
2521
 
1760
2522
  .feedback-form-group input:focus {
1761
- border-color: #155EEF;
2523
+ border-color: var(--primary-color, #155EEF);
1762
2524
  box-shadow: 0 0 0 3px rgba(21, 94, 239, 0.1);
1763
2525
  }
1764
2526
 
@@ -1771,12 +2533,13 @@
1771
2533
  width: 100%;
1772
2534
  resize: vertical;
1773
2535
  border-radius: 8px;
1774
- border: 1px solid #D1D5DB;
2536
+ border: 1px solid rgba(128, 128, 128, 0.3);
2537
+ background: rgba(128, 128, 128, 0.05);
1775
2538
  padding: 10px 14px;
1776
2539
  font-size: 15px;
1777
2540
  font-weight: 400;
1778
2541
  line-height: 1.5;
1779
- color: #1F2937;
2542
+ color: var(--text-color, #1F2937);
1780
2543
  font-family: inherit;
1781
2544
  outline: none;
1782
2545
  transition: all 0.2s ease;
@@ -1784,11 +2547,12 @@
1784
2547
 
1785
2548
  .feedback-form-group textarea::placeholder {
1786
2549
  font-size: 15px;
1787
- color: #9CA3AF;
2550
+ color: var(--text-color, #9CA3AF);
2551
+ opacity: 0.5;
1788
2552
  }
1789
2553
 
1790
2554
  .feedback-form-group textarea:focus {
1791
- border-color: #155EEF;
2555
+ border-color: var(--primary-color, #155EEF);
1792
2556
  box-shadow: 0 0 0 3px rgba(21, 94, 239, 0.1);
1793
2557
  }
1794
2558
 
@@ -1796,18 +2560,6 @@
1796
2560
  outline: none;
1797
2561
  }
1798
2562
 
1799
- .feedback-panel.theme-dark .feedback-form-group input,
1800
- .feedback-panel.theme-dark .feedback-form-group textarea {
1801
- background: #374151;
1802
- border-color: #4B5563;
1803
- color: white;
1804
- }
1805
-
1806
- .feedback-panel.theme-dark .feedback-form-group input::placeholder,
1807
- .feedback-panel.theme-dark .feedback-form-group textarea::placeholder {
1808
- color: #6B7280;
1809
- }
1810
-
1811
2563
  .feedback-btn {
1812
2564
  position: relative;
1813
2565
  display: inline-flex;
@@ -1836,38 +2588,29 @@
1836
2588
  }
1837
2589
 
1838
2590
  .feedback-btn-submit {
1839
- background: #155EEF;
2591
+ background: var(--primary-color, #155EEF);
1840
2592
  color: white;
1841
2593
  width: 100%;
1842
2594
  }
1843
2595
 
1844
2596
  .feedback-btn-submit:hover:not(:disabled) {
1845
- background: #1A56DB;
2597
+ background: var(--primary-color, #155EEF);
2598
+ filter: brightness(0.9);
1846
2599
  }
1847
2600
 
1848
2601
  .feedback-btn-submit:active:not(:disabled) {
1849
- background: #1E429F;
2602
+ background: var(--primary-color, #155EEF);
2603
+ filter: brightness(0.8);
1850
2604
  }
1851
2605
 
1852
2606
  .feedback-btn-cancel {
1853
2607
  background: transparent;
1854
- color: #6B7280;
1855
- border: 1px solid #D1D5DB;
2608
+ color: var(--text-color, #6B7280);
2609
+ border: 1px solid rgba(128, 128, 128, 0.3);
1856
2610
  }
1857
2611
 
1858
2612
  .feedback-btn-cancel:hover:not(:disabled) {
1859
- background: #F9FAFB;
1860
- border-color: #9CA3AF;
1861
- color: #374151;
1862
- }
1863
-
1864
- .feedback-panel.theme-dark .feedback-btn-cancel {
1865
- color: #D1D5DB;
1866
- border-color: #4B5563;
1867
- }
1868
-
1869
- .feedback-panel.theme-dark .feedback-btn-cancel:hover:not(:disabled) {
1870
- background: #374151;
2613
+ background: rgba(128, 128, 128, 0.1);
1871
2614
  }
1872
2615
 
1873
2616
  .feedback-form-actions {
@@ -2074,6 +2817,70 @@
2074
2817
  display: none !important;
2075
2818
  }
2076
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
+ }
2077
2884
  `;
2078
2885
 
2079
2886
  function injectStyles() {
@@ -2151,6 +2958,7 @@
2151
2958
  ButtonWidget,
2152
2959
  TabWidget,
2153
2960
  InlineWidget,
2961
+ SurveyWidget,
2154
2962
  WidgetFactory,
2155
2963
  EventBus,
2156
2964
  APIService,
@@ -2234,6 +3042,7 @@
2234
3042
  exports.FeedbackSDK = FeedbackSDK;
2235
3043
  exports.InlineWidget = InlineWidget;
2236
3044
  exports.SDKError = SDKError;
3045
+ exports.SurveyWidget = SurveyWidget;
2237
3046
  exports.TabWidget = TabWidget;
2238
3047
  exports.ValidationError = ValidationError;
2239
3048
  exports.WidgetError = WidgetError;