PageSourceSearch

https://picksmart.ai/static/js/feedback.js?v=5.5.0

js picksmart.ai collected 2026-10-05 15:01:14 UTC 9,655 bytes, 265 lines download raw bytes

1/**
2 * Floating feedback button with modal
3 * Allows users to submit feedback which is stored on the server
4 */
5
6(function() {
7    'use strict';
8
9    // Create expandable FAB with tour and feedback options
10    function createExpandableFAB() {
11        const fabContainer = document.createElement('div');
12        fabContainer.className = 'help-fab-container';
13        fabContainer.innerHTML = `
14            <!-- Expanded menu (hidden by default) -->
15            <div class="fab-menu" id="fab-menu">
16                <div class="fab-menu-item" data-action="tour">
17                    <span class="fab-icon">🎯</span>
18                    <span class="fab-label">Take Tour</span>
19                </div>
20                <div class="fab-menu-item" data-action="feedback">
21                    <span class="fab-icon">💬</span>
22                    <span class="fab-label">Send Feedback</span>
23                </div>
24            </div>
25
26            <!-- Main FAB button -->
27            <button class="help-fab" id="help-fab" title="Help & Feedback" aria-label="Help and Feedback Menu">
28                <svg class="fab-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
29                    <circle cx="12" cy="12" r="10"></circle>
30                    <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
31                    <line x1="12" y1="17" x2="12.01" y2="17"></line>
32                </svg>
33            </button>
34        `;
35
36        document.body.appendChild(fabContainer);
37        attachFABListeners();
38    }
39
40    // Attach event listeners to FAB
41    function attachFABListeners() {
42        const fabButton = document.getElementById('help-fab');
43        const fabMenu = document.getElementById('fab-menu');
44        const menuItems = document.querySelectorAll('.fab-menu-item');
45
46        // Toggle menu on FAB click
47        fabButton.addEventListener('click', (e) => {
48            e.stopPropagation();
49            toggleFABMenu();
50        });
51
52        // Handle menu item clicks
53        menuItems.forEach(item => {
54            item.addEventListener('click', (e) => {
55                e.stopPropagation();
56                const action = item.getAttribute('data-action');
57
58                // Close menu
59                closeFABMenu();
60
61                // Execute action
62                if (action === 'tour') {
63                    if (window.startTour) {
64                        window.startTour();
65                    }
66                } else if (action === 'feedback') {
67                    openFeedbackModal();
68                }
69            });
70        });
71
72        // Close menu when clicking outside
73        document.addEventListener('click', (e) => {
74            if (!e.target.closest('.help-fab-container')) {
75                closeFABMenu();
76            }
77        });
78
79        // Close menu on Escape key
80        document.addEventListener('keydown', (e) => {
81            if (e.key === 'Escape') {
82                closeFABMenu();
83            }
84        });
85    }
86
87    // Toggle FAB menu open/closed
88    function toggleFABMenu() {
89        const menu = document.getElementById('fab-menu');
90        menu.classList.toggle('open');
91    }
92
93    // Close FAB menu
94    function closeFABMenu() {
95        const menu = document.getElementById('fab-menu');
96        if (menu) {
97            menu.classList.remove('open');
98        }
99    }
100
101    // Create feedback modal
102    function createFeedbackModal() {
103        const modal = document.createElement('div');
104        modal.id = 'feedback-modal';
105        modal.className = 'feedback-modal';
106        modal.innerHTML = `
107            <div class="feedback-modal-content">
108                <div class="feedback-modal-header">
109                    <h3>Send Feedback</h3>
110                    <button class="feedback-close" onclick="window.closeFeedbackModal()">&times;</button>
111                </div>
112                <div class="feedback-modal-body">
113                    <p>Help us improve! Share your thoughts, suggestions, or report issues.</p>
114                    <textarea
115                        id="feedback-textarea"
116                        placeholder="Enter your feedback here..."
117                        rows="6"
118                        maxlength="1000"></textarea>
119                    <div class="feedback-char-count">
120                        <span id="feedback-char-counter">0</span> / 1000 characters
121                    </div>
122                </div>
123                <div class="feedback-modal-footer">
124                    <button class="feedback-btn feedback-btn-cancel" onclick="window.closeFeedbackModal()">Cancel</button>
125                    <button class="feedback-btn feedback-btn-submit" onclick="window.submitFeedback()">Submit</button>
126                </div>
127                <div id="feedback-status" class="feedback-status"></div>
128            </div>
129        `;
130        document.body.appendChild(modal);
131
132        // Character counter
133        const textarea = modal.querySelector('#feedback-textarea');
134        const counter = modal.querySelector('#feedback-char-counter');
135        textarea.addEventListener('input', function() {
136            counter.textContent = textarea.value.length;
137        });
138
139        // Close modal on outside click
140        modal.addEventListener('click', function(e) {
141            if (e.target === modal) {
142                closeFeedbackModal();
143            }
144        });
145
146        // Close modal on Escape key
147        document.addEventListener('keydown', function(e) {
148            if (e.key === 'Escape' && modal.classList.contains('show')) {
149                closeFeedbackModal();
150            }
151        });
152    }
153
154    // Open modal
155    function openFeedbackModal() {
156        const modal = document.getElementById('feedback-modal');
157        if (!modal) {
158            createFeedbackModal();
159        }
160        setTimeout(() => {
161            document.getElementById('feedback-modal').classList.add('show');
162            document.getElementById('feedback-textarea').focus();
163        }, 10);
164    }
165
166    // Close modal
167    function closeFeedbackModal() {
168        const modal = document.getElementById('feedback-modal');
169        if (modal) {
170            modal.classList.remove('show');
171            // Clear textarea and status after animation
172            setTimeout(() => {
173                document.getElementById('feedback-textarea').value = '';
174                document.getElementById('feedback-char-counter').textContent = '0';
175                document.getElementById('feedback-status').textContent = '';
176                document.getElementById('feedback-status').className = 'feedback-status';
177            }, 300);
178        }
179    }
180
181    // Submit feedback
182    function submitFeedback() {
183        const textarea = document.getElementById('feedback-textarea');
184        const statusDiv = document.getElementById('feedback-status');
185        const submitBtn = document.querySelector('.feedback-btn-submit');
186
187        const feedback = textarea.value.trim();
188
189        // Validation
190        if (!feedback) {
191            statusDiv.textContent = '⚠️ Please enter your feedback before submitting.';
192            statusDiv.className = 'feedback-status error';
193            return;
194        }
195
196        if (feedback.length < 10) {
197            statusDiv.textContent = '⚠️ Feedback must be at least 10 characters.';
198            statusDiv.className = 'feedback-status error';
199            return;
200        }
201
202        // Disable button during submission
203        submitBtn.disabled = true;
204        submitBtn.textContent = 'Sending...';
205        statusDiv.textContent = '📤 Sending feedback...';
206        statusDiv.className = 'feedback-status info';
207
208        // Gather metadata
209        const metadata = {
210            feedback: feedback,
211            url: window.location.href,
212            pathname: window.location.pathname,
213            timestamp: new Date().toISOString(),
214            userAgent: navigator.userAgent,
215            screenResolution: `${window.screen.width}x${window.screen.height}`,
216            viewportSize: `${window.innerWidth}x${window.innerHeight}`
217        };
218
219        // Send to server
220        fetch('/api/feedback', {
221            method: 'POST',
222            headers: {
223                'Content-Type': 'application/json',
224            },
225            body: JSON.stringify(metadata)
226        })
227        .then(response => {
228            if (!response.ok) {
229                throw new Error('Network response was not ok');
230            }
231            return response.json();
232        })
233        .then(data => {
234            statusDiv.textContent = '✅ Thank you! Your feedback has been submitted.';
235            statusDiv.className = 'feedback-status success';
236            submitBtn.disabled = false;
237            submitBtn.textContent = 'Submit';
238
239            // Close modal after 2 seconds
240            setTimeout(() => {
241                closeFeedbackModal();
242            }, 2000);
243        })
244        .catch(error => {
245            console.error('Feedback submission error:', error);
246            statusDiv.textContent = '❌ Failed to submit feedback. Please try again.';
247            statusDiv.className = 'feedback-status error';
248            submitBtn.disabled = false;
249            submitBtn.textContent = 'Submit';
250        });
251    }
252
253    // Expose functions globally for onclick handlers
254    window.openFeedbackModal = openFeedbackModal;
255    window.closeFeedbackModal = closeFeedbackModal;
256    window.submitFeedback = submitFeedback;
257
258    // Initialize when DOM is ready
259    if (document.readyState === 'loading') {
260        document.addEventListener('DOMContentLoaded', createExpandableFAB);
261    } else {
262        createExpandableFAB();
263    }
264
265})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.