lsa_tdx_feedback 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +328 -0
- data/INSTALLATION.md +244 -0
- data/LICENSE.txt +21 -0
- data/README.md +391 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/lsa_tdx_feedback.js +231 -0
- data/app/assets/stylesheets/lsa_tdx_feedback.css +279 -0
- data/app/controllers/lsa_tdx_feedback/feedback_controller.rb +71 -0
- data/app/views/lsa_tdx_feedback/shared/_feedback_modal.html.erb +95 -0
- data/config/routes.rb +3 -0
- data/example_application_tests.rb +0 -0
- data/lib/lsa_tdx_feedback/application_controller_extensions.rb +52 -0
- data/lib/lsa_tdx_feedback/configuration.rb +89 -0
- data/lib/lsa_tdx_feedback/engine.rb +65 -0
- data/lib/lsa_tdx_feedback/oauth_client.rb +81 -0
- data/lib/lsa_tdx_feedback/ticket_client.rb +106 -0
- data/lib/lsa_tdx_feedback/version.rb +3 -0
- data/lib/lsa_tdx_feedback/view_helpers.rb +37 -0
- data/lib/lsa_tdx_feedback.rb +31 -0
- metadata +223 -0
data/README.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# LsaTdxFeedback
|
|
2
|
+
|
|
3
|
+
A self-contained Rails gem for collecting user feedback via TeamDynamix (TDX) API for LSA applications. LsaTdxFeedback provides a secure, configurable solution that integrates seamlessly with any Rails application.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Secure Configuration**: All values must be provided by your application for maximum security
|
|
8
|
+
- **Self-Contained UI**: Beautiful, responsive feedback modal with its own CSS and JavaScript
|
|
9
|
+
- **TDX Integration**: Direct integration with TeamDynamix API for ticket creation
|
|
10
|
+
- **OAuth Authentication**: Secure client credentials flow authentication with tdxticket scope
|
|
11
|
+
- **Mobile Responsive**: Works perfectly on desktop and mobile devices
|
|
12
|
+
- **Framework Agnostic**: CSS and JavaScript work independently of your app's styling
|
|
13
|
+
- **Automatic Context**: Captures page URL, user agent, and user email automatically
|
|
14
|
+
- **Caching**: Smart token caching for optimal performance
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Add this line to your application's Gemfile:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem 'lsa_tdx_feedback'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
And then execute:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
$ bundle install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
### Rails Credentials (Required)
|
|
33
|
+
|
|
34
|
+
**Security Note**: All configuration values must be provided by your application. No default values are stored in the gem for security reasons.
|
|
35
|
+
|
|
36
|
+
Add your TDX API credentials to your Rails credentials:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
$ rails credentials:edit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
lsa_tdx_feedback:
|
|
44
|
+
# Required OAuth Configuration
|
|
45
|
+
oauth_url: 'https://your-tdx-instance.com/oauth2' # Note: Do NOT include /token
|
|
46
|
+
api_base_url: 'https://your-tdx-instance.com/api'
|
|
47
|
+
client_id: your_tdx_client_id
|
|
48
|
+
client_secret: your_tdx_client_secret
|
|
49
|
+
|
|
50
|
+
# Required TDX Configuration
|
|
51
|
+
app_id: 123
|
|
52
|
+
account_id: 456
|
|
53
|
+
service_offering_id: 789
|
|
54
|
+
|
|
55
|
+
# Required Ticket Configuration
|
|
56
|
+
default_type_id: 100
|
|
57
|
+
default_form_id: 200
|
|
58
|
+
default_classification: '300'
|
|
59
|
+
default_status_id: 400
|
|
60
|
+
default_priority_id: 500
|
|
61
|
+
default_source_id: 600
|
|
62
|
+
default_responsible_group_id: 700
|
|
63
|
+
default_service_id: 800
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Manual Configuration
|
|
67
|
+
|
|
68
|
+
If you prefer not to use Rails credentials, you can configure the gem manually in an initializer:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# config/initializers/lsa_tdx_feedback.rb
|
|
72
|
+
LsaTdxFeedback.configure do |config|
|
|
73
|
+
# Required OAuth Configuration
|
|
74
|
+
config.oauth_url = ENV['TDX_OAUTH_URL'] # Note: Do NOT include /token
|
|
75
|
+
config.api_base_url = ENV['TDX_API_BASE_URL']
|
|
76
|
+
config.client_id = ENV['TDX_CLIENT_ID']
|
|
77
|
+
config.client_secret = ENV['TDX_CLIENT_SECRET']
|
|
78
|
+
|
|
79
|
+
# Required TDX Configuration
|
|
80
|
+
config.app_id = ENV['TDX_APP_ID'].to_i
|
|
81
|
+
config.account_id = ENV['TDX_ACCOUNT_ID'].to_i
|
|
82
|
+
config.service_offering_id = ENV['TDX_SERVICE_OFFERING_ID'].to_i
|
|
83
|
+
|
|
84
|
+
# Required Ticket Configuration
|
|
85
|
+
config.default_type_id = ENV['TDX_TYPE_ID'].to_i
|
|
86
|
+
config.default_form_id = ENV['TDX_FORM_ID'].to_i
|
|
87
|
+
config.default_classification = ENV['TDX_CLASSIFICATION']
|
|
88
|
+
config.default_status_id = ENV['TDX_STATUS_ID'].to_i
|
|
89
|
+
config.default_priority_id = ENV['TDX_PRIORITY_ID'].to_i
|
|
90
|
+
config.default_source_id = ENV['TDX_SOURCE_ID'].to_i
|
|
91
|
+
config.default_responsible_group_id = ENV['TDX_RESPONSIBLE_GROUP_ID'].to_i
|
|
92
|
+
config.default_service_id = ENV['TDX_SERVICE_ID'].to_i
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Note**: If you use Rails credentials (recommended), the gem will automatically configure itself and you don't need to create this initializer file.
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
### Step 1: Mount the Engine Routes
|
|
101
|
+
|
|
102
|
+
Add the feedback gem routes to your application's `config/routes.rb`:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# config/routes.rb
|
|
106
|
+
Rails.application.routes.draw do
|
|
107
|
+
# Your existing routes...
|
|
108
|
+
|
|
109
|
+
# Mount the feedback gem engine
|
|
110
|
+
mount LsaTdxFeedback::Engine => "/lsa_tdx_feedback", as: "lsa_tdx_feedback"
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Step 2: Add the Feedback Modal and Assets (optimal placement)
|
|
115
|
+
|
|
116
|
+
For best performance and predictable ordering, include CSS in `<head>`, render the modal markup in the body, and place JS just before `</body>`:
|
|
117
|
+
|
|
118
|
+
```erb
|
|
119
|
+
<!-- app/views/layouts/application.html.erb -->
|
|
120
|
+
|
|
121
|
+
<head>
|
|
122
|
+
<%= lsa_tdx_feedback_css %>
|
|
123
|
+
<!-- your other tags ... -->
|
|
124
|
+
...
|
|
125
|
+
...
|
|
126
|
+
<%= csrf_meta_tags %>
|
|
127
|
+
<%= csp_meta_tag %>
|
|
128
|
+
<!-- etc. -->
|
|
129
|
+
...
|
|
130
|
+
...
|
|
131
|
+
...
|
|
132
|
+
...
|
|
133
|
+
</head>
|
|
134
|
+
|
|
135
|
+
<body>
|
|
136
|
+
...
|
|
137
|
+
<%= lsa_tdx_feedback_modal %>
|
|
138
|
+
<%= lsa_tdx_feedback_js %>
|
|
139
|
+
</body>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This avoids FOUC (flash of unstyled content) and ensures the script loads after the DOM.
|
|
143
|
+
|
|
144
|
+
### Advanced Usage
|
|
145
|
+
|
|
146
|
+
#### Performance vs convenience
|
|
147
|
+
|
|
148
|
+
- **Optimal loading (recommended)**: Use the separate helpers so CSS loads in `<head>` and JS loads just before `</body>`. This avoids FOUC and keeps asset order predictable.
|
|
149
|
+
- **Convenience**: Use `lsa_tdx_feedback` (all‑in‑one) for quick setup. It injects CSS/JS plus the modal where it’s called, which can delay CSS and is less ideal for performance.
|
|
150
|
+
|
|
151
|
+
#### Recommended placement (optimal)
|
|
152
|
+
|
|
153
|
+
```erb
|
|
154
|
+
<!-- app/views/layouts/application.html.erb -->
|
|
155
|
+
|
|
156
|
+
<head>
|
|
157
|
+
<%= lsa_tdx_feedback_css %>
|
|
158
|
+
<!-- your other tags ... -->
|
|
159
|
+
...
|
|
160
|
+
...
|
|
161
|
+
</head>
|
|
162
|
+
|
|
163
|
+
<body>
|
|
164
|
+
...
|
|
165
|
+
<%= lsa_tdx_feedback_modal %>
|
|
166
|
+
<%= lsa_tdx_feedback_js %>
|
|
167
|
+
</body>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
You can also include components separately for more control over placement:
|
|
171
|
+
|
|
172
|
+
```erb
|
|
173
|
+
<!-- In your layout head section -->
|
|
174
|
+
<%= lsa_tdx_feedback_css %>
|
|
175
|
+
|
|
176
|
+
<!-- In your layout body section (before closing </body> tag) -->
|
|
177
|
+
<%= lsa_tdx_feedback_js %>
|
|
178
|
+
|
|
179
|
+
<!-- Include just the modal (assets must be included separately) -->
|
|
180
|
+
<%= lsa_tdx_feedback_modal %>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Or use the combined assets helper:
|
|
184
|
+
|
|
185
|
+
```erb
|
|
186
|
+
<!-- Include both CSS and JavaScript together -->
|
|
187
|
+
<%= lsa_tdx_feedback_assets %>
|
|
188
|
+
|
|
189
|
+
<!-- Include just the modal (assets must be included separately) -->
|
|
190
|
+
<%= lsa_tdx_feedback_modal %>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### When to use explicit Rails asset tags
|
|
194
|
+
|
|
195
|
+
If you prefer explicit control, you can use Rails helpers directly:
|
|
196
|
+
|
|
197
|
+
```erb
|
|
198
|
+
<head>
|
|
199
|
+
<%= stylesheet_link_tag 'lsa_tdx_feedback', media: 'all', 'data-turbo-track': 'reload' %>
|
|
200
|
+
</head>
|
|
201
|
+
|
|
202
|
+
<body>
|
|
203
|
+
...
|
|
204
|
+
<%= lsa_tdx_feedback_modal %>
|
|
205
|
+
<%= javascript_include_tag 'lsa_tdx_feedback', defer: true, 'data-turbo-track': 'reload' %>
|
|
206
|
+
</body>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Use explicit tags when you need to:
|
|
210
|
+
|
|
211
|
+
- Set attributes like `defer`, `async`, `nonce` (CSP), `integrity`, `crossorigin`, `media`, or `preload`
|
|
212
|
+
- Control exact ordering relative to Bootstrap or your packs
|
|
213
|
+
- Swap delivery (CDN vs pipeline) or pin versions independently of the gem
|
|
214
|
+
- Integrate precisely with your asset setup (Importmap, Propshaft, Sprockets, etc.)
|
|
215
|
+
|
|
216
|
+
#### Convenience (all‑in‑one)
|
|
217
|
+
|
|
218
|
+
If you want a single helper that injects CSS, JS, and the modal where it’s called (good for quick trials or prototypes):
|
|
219
|
+
|
|
220
|
+
```erb
|
|
221
|
+
<!-- In your application layout (e.g., app/views/layouts/application.html.erb) -->
|
|
222
|
+
<%= lsa_tdx_feedback %>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Note: This may load CSS later than ideal and is less optimal for performance.
|
|
226
|
+
|
|
227
|
+
### Customizing User Email
|
|
228
|
+
|
|
229
|
+
By default, LsaTdxFeedback tries to automatically detect the current user's email. You can customize this by overriding the `current_user_email_for_feedback` method in your ApplicationController:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
class ApplicationController < ActionController::Base
|
|
233
|
+
private
|
|
234
|
+
|
|
235
|
+
def current_user_email_for_feedback
|
|
236
|
+
current_user&.email_address # or however you access user email
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## TDX API Configuration
|
|
242
|
+
|
|
243
|
+
### Required TDX Settings
|
|
244
|
+
|
|
245
|
+
1. **Client Credentials**: You need OAuth client credentials from your TDX administrator
|
|
246
|
+
2. **Responsible Group ID**: The TDX group that will receive feedback tickets
|
|
247
|
+
3. **App ID**: Your TDX ticketing application ID (usually provided by your TDX admin)
|
|
248
|
+
|
|
249
|
+
### Default TDX Values
|
|
250
|
+
|
|
251
|
+
The gem uses these default values based on University of Michigan's TDX setup:
|
|
252
|
+
|
|
253
|
+
- **OAuth URL**: *see API documentation*
|
|
254
|
+
- **API Base URL**: *see API documentation*
|
|
255
|
+
- **Type ID**: 28 (TeamDynamix)
|
|
256
|
+
- **Form ID**: 20 (Request Form)
|
|
257
|
+
- **Classification**: "46" (Request)
|
|
258
|
+
- **Status ID**: 77 (New)
|
|
259
|
+
- **Priority ID**: 20 (Medium)
|
|
260
|
+
- **Source ID**: 8 (Systems)
|
|
261
|
+
- **Service ID**: 67 (ITS-TeamDynamix Support)
|
|
262
|
+
|
|
263
|
+
You can override any of these in your configuration.
|
|
264
|
+
|
|
265
|
+
## Feedback Categories
|
|
266
|
+
|
|
267
|
+
The modal includes predefined categories that automatically set ticket priorities:
|
|
268
|
+
|
|
269
|
+
- **Bug Report** → High Priority
|
|
270
|
+
- **Urgent/Critical** → Emergency Priority
|
|
271
|
+
- **Suggestion/Enhancement/Feature** → Low Priority
|
|
272
|
+
- **General/Other** → Medium Priority (default)
|
|
273
|
+
|
|
274
|
+
## Styling
|
|
275
|
+
|
|
276
|
+
The gem includes completely self-contained CSS that won't interfere with your application's styles. All CSS classes are prefixed with `feedback-gem-` to avoid conflicts.
|
|
277
|
+
|
|
278
|
+
If you need to customize the appearance, you can override the styles in your application:
|
|
279
|
+
|
|
280
|
+
```css
|
|
281
|
+
/* Customize the trigger button */
|
|
282
|
+
.feedback-gem-trigger-btn {
|
|
283
|
+
background: #custom-color !important;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Customize the modal */
|
|
287
|
+
.feedback-gem-modal-content {
|
|
288
|
+
border-radius: 8px !important;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## JavaScript
|
|
293
|
+
|
|
294
|
+
The gem includes self-contained JavaScript that initializes automatically. It handles:
|
|
295
|
+
|
|
296
|
+
- Modal show/hide functionality
|
|
297
|
+
- Form submission via AJAX
|
|
298
|
+
- Loading states and error handling
|
|
299
|
+
- Keyboard navigation (ESC to close)
|
|
300
|
+
- Mobile responsive behavior
|
|
301
|
+
|
|
302
|
+
## Development
|
|
303
|
+
|
|
304
|
+
After checking out the repo, run:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
$ bundle install
|
|
308
|
+
$ bundle exec rake spec # Run tests
|
|
309
|
+
$ bundle exec rubocop # Run linter
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## API Reference
|
|
313
|
+
|
|
314
|
+
### Configuration Options
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
LsaTdxFeedback.configure do |config|
|
|
318
|
+
# Required
|
|
319
|
+
config.client_id = "your_client_id"
|
|
320
|
+
config.client_secret = "your_client_secret"
|
|
321
|
+
config.default_responsible_group_id = 123
|
|
322
|
+
|
|
323
|
+
# Optional TDX settings
|
|
324
|
+
config.oauth_url = "https://your-tdx-oauth-url"
|
|
325
|
+
config.api_base_url = "https://your-tdx-api-url"
|
|
326
|
+
config.app_id = 31
|
|
327
|
+
config.default_service_id = 67
|
|
328
|
+
config.default_type_id = 28
|
|
329
|
+
config.default_form_id = 20
|
|
330
|
+
config.default_classification = "46"
|
|
331
|
+
config.default_status_id = 77
|
|
332
|
+
config.default_priority_id = 20
|
|
333
|
+
config.default_source_id = 8
|
|
334
|
+
|
|
335
|
+
# Caching
|
|
336
|
+
config.cache_store = :redis_cache_store
|
|
337
|
+
config.cache_expiry = 3600 # Token cache expiry in seconds
|
|
338
|
+
end
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### View Helpers
|
|
342
|
+
|
|
343
|
+
- `lsa_tdx_feedback` - Includes both assets and modal (all-in-one)
|
|
344
|
+
- `lsa_tdx_feedback_assets` - Includes both CSS and JavaScript
|
|
345
|
+
- `lsa_tdx_feedback_css` - Includes only the CSS stylesheet
|
|
346
|
+
- `lsa_tdx_feedback_js` - Includes only the JavaScript file
|
|
347
|
+
- `lsa_tdx_feedback_modal` - Includes only the modal HTML
|
|
348
|
+
|
|
349
|
+
## Contributing
|
|
350
|
+
|
|
351
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lsa-mis/lsa_feedback.
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
356
|
+
|
|
357
|
+
## Troubleshooting
|
|
358
|
+
|
|
359
|
+
### Common Issues
|
|
360
|
+
|
|
361
|
+
1. **"client_id is required" error**
|
|
362
|
+
- Make sure you've configured your TDX credentials properly
|
|
363
|
+
- Check that your Rails credentials are accessible
|
|
364
|
+
|
|
365
|
+
2. **Modal doesn't appear or form submission fails**
|
|
366
|
+
- Ensure you've included `<%= lsa_tdx_feedback %>` in your layout
|
|
367
|
+
- **Check that you've mounted the engine routes** in your `config/routes.rb`
|
|
368
|
+
- Check browser console for JavaScript errors
|
|
369
|
+
- Verify the `/lsa_tdx_feedback/feedback` endpoint is accessible
|
|
370
|
+
- If using separate helpers, ensure CSS is in `<head>` and JS is before `</body>`
|
|
371
|
+
|
|
372
|
+
3. **Styling conflicts**
|
|
373
|
+
- All LsaTdxFeedback styles are prefixed with `feedback-gem-`
|
|
374
|
+
- Use browser dev tools to check for CSS conflicts
|
|
375
|
+
|
|
376
|
+
4. **OAuth/API errors**
|
|
377
|
+
- Verify your TDX credentials are correct
|
|
378
|
+
- Check that your TDX instance URLs are correct
|
|
379
|
+
- **Ensure oauth_url does NOT include /token** (e.g., use `https://your-tdx.com/oauth2` not `https://your-tdx.com/oauth2/token`)
|
|
380
|
+
- Ensure your responsible group ID exists in TDX
|
|
381
|
+
|
|
382
|
+
### Debug Mode
|
|
383
|
+
|
|
384
|
+
Enable debug logging by setting the Rails log level:
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
# In development.rb or production.rb
|
|
388
|
+
config.log_level = :debug
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
This will log OAuth token requests and API calls for troubleshooting.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LsaTdxFeedback JavaScript - Self-contained feedback modal functionality
|
|
3
|
+
*/
|
|
4
|
+
(function() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// Ensure we don't initialize multiple times
|
|
8
|
+
if (window.LsaTdxFeedback && window.LsaTdxFeedback.initialized) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var LsaTdxFeedback = {
|
|
13
|
+
initialized: false,
|
|
14
|
+
modal: null,
|
|
15
|
+
form: null,
|
|
16
|
+
|
|
17
|
+
init: function() {
|
|
18
|
+
if (this.initialized) return;
|
|
19
|
+
|
|
20
|
+
// Wait for DOM to be ready
|
|
21
|
+
if (document.readyState === 'loading') {
|
|
22
|
+
document.addEventListener('DOMContentLoaded', this.init.bind(this));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.bindEvents();
|
|
27
|
+
this.initialized = true;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
bindEvents: function() {
|
|
31
|
+
var self = this;
|
|
32
|
+
|
|
33
|
+
// Get modal and form elements
|
|
34
|
+
this.modal = document.getElementById('lsa-tdx-feedback-modal');
|
|
35
|
+
this.form = document.getElementById('lsa-tdx-feedback-form');
|
|
36
|
+
|
|
37
|
+
if (!this.modal || !this.form) {
|
|
38
|
+
console.warn('LsaTdxFeedback: Modal or form not found. Make sure to include the feedback modal partial.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Trigger button
|
|
43
|
+
var triggerBtn = document.getElementById('lsa-tdx-feedback-trigger');
|
|
44
|
+
if (triggerBtn) {
|
|
45
|
+
triggerBtn.addEventListener('click', function(e) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
self.showModal();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Close buttons
|
|
52
|
+
var closeBtn = this.modal.querySelector('.lsa-tdx-feedback-close-btn');
|
|
53
|
+
var cancelBtn = this.modal.querySelector('.lsa-tdx-feedback-cancel-btn');
|
|
54
|
+
var backdrop = this.modal.querySelector('.lsa-tdx-feedback-modal-backdrop');
|
|
55
|
+
|
|
56
|
+
if (closeBtn) {
|
|
57
|
+
closeBtn.addEventListener('click', function(e) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
self.hideModal();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (cancelBtn) {
|
|
64
|
+
cancelBtn.addEventListener('click', function(e) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
self.hideModal();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (backdrop) {
|
|
71
|
+
backdrop.addEventListener('click', function(e) {
|
|
72
|
+
if (e.target === backdrop) {
|
|
73
|
+
self.hideModal();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Form submission
|
|
79
|
+
this.form.addEventListener('submit', function(e) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
self.submitFeedback();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Escape key to close modal
|
|
85
|
+
document.addEventListener('keydown', function(e) {
|
|
86
|
+
if (e.key === 'Escape' && self.modal.style.display !== 'none') {
|
|
87
|
+
self.hideModal();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
showModal: function() {
|
|
93
|
+
if (!this.modal) return;
|
|
94
|
+
|
|
95
|
+
this.modal.style.display = 'block';
|
|
96
|
+
document.body.style.overflow = 'hidden';
|
|
97
|
+
|
|
98
|
+
// Focus on first input
|
|
99
|
+
var firstInput = this.modal.querySelector('select, input, textarea');
|
|
100
|
+
if (firstInput) {
|
|
101
|
+
setTimeout(function() {
|
|
102
|
+
firstInput.focus();
|
|
103
|
+
}, 100);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.hideMessage();
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
hideModal: function() {
|
|
110
|
+
if (!this.modal) return;
|
|
111
|
+
|
|
112
|
+
this.modal.style.display = 'none';
|
|
113
|
+
document.body.style.overflow = '';
|
|
114
|
+
this.resetForm();
|
|
115
|
+
this.hideMessage();
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
resetForm: function() {
|
|
119
|
+
if (!this.form) return;
|
|
120
|
+
|
|
121
|
+
this.form.reset();
|
|
122
|
+
this.setSubmitButtonState(false);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
submitFeedback: function() {
|
|
126
|
+
var self = this;
|
|
127
|
+
var formData = new FormData(this.form);
|
|
128
|
+
|
|
129
|
+
// Basic validation
|
|
130
|
+
var feedback = formData.get('feedback');
|
|
131
|
+
if (!feedback || feedback.trim().length === 0) {
|
|
132
|
+
this.showMessage('Please enter your feedback.', 'error');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.setSubmitButtonState(true);
|
|
137
|
+
this.hideMessage();
|
|
138
|
+
|
|
139
|
+
// Get CSRF token
|
|
140
|
+
var csrfToken = this.getCSRFToken();
|
|
141
|
+
|
|
142
|
+
// Prepare data
|
|
143
|
+
var data = {
|
|
144
|
+
feedback: {
|
|
145
|
+
category: formData.get('category'),
|
|
146
|
+
feedback: feedback,
|
|
147
|
+
email: formData.get('email'),
|
|
148
|
+
url: formData.get('url'),
|
|
149
|
+
user_agent: formData.get('user_agent'),
|
|
150
|
+
additional_info: formData.get('additional_info')
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Submit via fetch
|
|
155
|
+
fetch('/lsa_tdx_feedback/feedback', {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
'X-CSRF-Token': csrfToken,
|
|
160
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify(data)
|
|
163
|
+
})
|
|
164
|
+
.then(function(response) {
|
|
165
|
+
return response.json().then(function(data) {
|
|
166
|
+
return { response: response, data: data };
|
|
167
|
+
});
|
|
168
|
+
})
|
|
169
|
+
.then(function(result) {
|
|
170
|
+
self.setSubmitButtonState(false);
|
|
171
|
+
|
|
172
|
+
if (result.response.ok && result.data.success) {
|
|
173
|
+
self.showMessage(result.data.message || 'Thank you for your feedback!', 'success');
|
|
174
|
+
setTimeout(function() {
|
|
175
|
+
self.hideModal();
|
|
176
|
+
}, 2000);
|
|
177
|
+
} else {
|
|
178
|
+
self.showMessage(result.data.message || 'There was an error submitting your feedback.', 'error');
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
.catch(function(error) {
|
|
182
|
+
console.error('LsaTdxFeedback submission error:', error);
|
|
183
|
+
self.setSubmitButtonState(false);
|
|
184
|
+
self.showMessage('There was an error submitting your feedback. Please try again.', 'error');
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
setSubmitButtonState: function(loading) {
|
|
189
|
+
var submitBtn = this.modal.querySelector('.lsa-tdx-feedback-submit-btn');
|
|
190
|
+
var btnText = submitBtn.querySelector('.lsa-tdx-feedback-btn-text');
|
|
191
|
+
var spinner = submitBtn.querySelector('.lsa-tdx-feedback-loading-spinner');
|
|
192
|
+
|
|
193
|
+
if (loading) {
|
|
194
|
+
submitBtn.disabled = true;
|
|
195
|
+
btnText.style.display = 'none';
|
|
196
|
+
spinner.style.display = 'inline-flex';
|
|
197
|
+
} else {
|
|
198
|
+
submitBtn.disabled = false;
|
|
199
|
+
btnText.style.display = 'inline';
|
|
200
|
+
spinner.style.display = 'none';
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
showMessage: function(message, type) {
|
|
205
|
+
var messageEl = document.getElementById('lsa-tdx-feedback-message');
|
|
206
|
+
var contentEl = messageEl.querySelector('.lsa-tdx-feedback-message-content');
|
|
207
|
+
|
|
208
|
+
contentEl.textContent = message;
|
|
209
|
+
messageEl.className = 'lsa-tdx-feedback-message ' + (type || 'info');
|
|
210
|
+
messageEl.style.display = 'block';
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
hideMessage: function() {
|
|
214
|
+
var messageEl = document.getElementById('lsa-tdx-feedback-message');
|
|
215
|
+
if (messageEl) {
|
|
216
|
+
messageEl.style.display = 'none';
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
getCSRFToken: function() {
|
|
221
|
+
var token = document.querySelector('meta[name="csrf-token"]');
|
|
222
|
+
return token ? token.getAttribute('content') : '';
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Export to global scope
|
|
227
|
+
window.LsaTdxFeedback = LsaTdxFeedback;
|
|
228
|
+
|
|
229
|
+
// Auto-initialize
|
|
230
|
+
LsaTdxFeedback.init();
|
|
231
|
+
})();
|