@currentjs/gen 0.2.2 → 0.3.1

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/README.md +256 -0
  3. package/dist/cli.js +26 -0
  4. package/dist/commands/createApp.js +2 -0
  5. package/dist/commands/generateAll.js +153 -29
  6. package/dist/commands/migrateCommit.d.ts +1 -0
  7. package/dist/commands/migrateCommit.js +201 -0
  8. package/dist/generators/controllerGenerator.d.ts +7 -0
  9. package/dist/generators/controllerGenerator.js +60 -29
  10. package/dist/generators/domainModelGenerator.d.ts +7 -0
  11. package/dist/generators/domainModelGenerator.js +57 -3
  12. package/dist/generators/serviceGenerator.d.ts +16 -1
  13. package/dist/generators/serviceGenerator.js +125 -12
  14. package/dist/generators/storeGenerator.d.ts +8 -0
  15. package/dist/generators/storeGenerator.js +133 -7
  16. package/dist/generators/templateGenerator.d.ts +19 -0
  17. package/dist/generators/templateGenerator.js +216 -11
  18. package/dist/generators/templates/appTemplates.d.ts +8 -7
  19. package/dist/generators/templates/appTemplates.js +11 -1572
  20. package/dist/generators/templates/data/appTsTemplate +39 -0
  21. package/dist/generators/templates/data/appYamlTemplate +4 -0
  22. package/dist/generators/templates/data/cursorRulesTemplate +671 -0
  23. package/dist/generators/templates/data/errorTemplate +28 -0
  24. package/dist/generators/templates/data/frontendScriptTemplate +739 -0
  25. package/dist/generators/templates/data/mainViewTemplate +16 -0
  26. package/dist/generators/templates/data/translationsTemplate +68 -0
  27. package/dist/generators/templates/data/tsConfigTemplate +19 -0
  28. package/dist/generators/templates/viewTemplates.d.ts +10 -1
  29. package/dist/generators/templates/viewTemplates.js +138 -6
  30. package/dist/generators/validationGenerator.d.ts +5 -0
  31. package/dist/generators/validationGenerator.js +51 -0
  32. package/dist/utils/constants.d.ts +3 -0
  33. package/dist/utils/constants.js +5 -2
  34. package/dist/utils/generationRegistry.js +1 -1
  35. package/dist/utils/migrationUtils.d.ts +49 -0
  36. package/dist/utils/migrationUtils.js +291 -0
  37. package/howto.md +157 -65
  38. package/package.json +3 -2
@@ -0,0 +1,739 @@
1
+ /**
2
+ * Common Frontend Functions for Generated Apps
3
+ * This script provides utilities for UI feedback, navigation, form handling, and SPA-like behavior
4
+ */
5
+
6
+ // Global configuration
7
+ window.AppConfig = {
8
+ toastDuration: 3000,
9
+ modalDuration: 1200,
10
+ animationDuration: 300,
11
+ debounceDelay: 300,
12
+ translations: {},
13
+ currentLang: null,
14
+ isTranslationEnabled: false, // change to true to enable translations
15
+ };
16
+
17
+ (() => {
18
+ // ===== AUTHENTICATION HELPERS =====
19
+
20
+ /**
21
+ * Get authentication token from localStorage
22
+ * @returns {string|null} JWT token or null if not found
23
+ */
24
+ function getAuthToken() {
25
+ return localStorage.getItem('authToken') || null;
26
+ }
27
+
28
+ /**
29
+ * Set authentication token in localStorage
30
+ * @param {string} token - JWT token to store
31
+ */
32
+ function setAuthToken(token) {
33
+ if (token) {
34
+ localStorage.setItem('authToken', token);
35
+ document.cookie = `authToken=${token}; max-age=31536000; path=/`;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Remove authentication token from localStorage
41
+ */
42
+ function clearAuthToken() {
43
+ localStorage.removeItem('authToken');
44
+ document.cookie = `authToken=; max-age=0; path=/`;
45
+ }
46
+
47
+ /**
48
+ * Build headers object with authentication if token exists
49
+ * @param {object} additionalHeaders - Additional headers to merge
50
+ * @returns {object} Headers object with auth header if token exists
51
+ */
52
+ function buildAuthHeaders(additionalHeaders = {}) {
53
+ const headers = { ...additionalHeaders };
54
+ const token = getAuthToken();
55
+
56
+ if (token) {
57
+ headers['Authorization'] = `Bearer ${token}`;
58
+ }
59
+
60
+ return headers;
61
+ }
62
+
63
+ // ===== TRANSLATION FUNCTIONS =====
64
+
65
+ /**
66
+ * Get current language
67
+ * Priority: localStorage -> navigator.language -> 'en'
68
+ * @returns {string} Current language code
69
+ */
70
+ function getCurrentLanguage() {
71
+ if (window.AppConfig.currentLang) {
72
+ return window.AppConfig.currentLang;
73
+ }
74
+
75
+ // 1. Check localStorage
76
+ const storedLang = localStorage.getItem('lang');
77
+ if (storedLang) {
78
+ window.AppConfig.currentLang = storedLang;
79
+ return storedLang;
80
+ }
81
+
82
+ // 2. Check browser language (Accept-Language equivalent)
83
+ const browserLang = navigator.language || navigator.languages?.[0];
84
+ if (browserLang) {
85
+ // Extract language code (e.g., 'en-US' -> 'en')
86
+ const langCode = browserLang.split('-')[0];
87
+ window.AppConfig.currentLang = langCode;
88
+ return langCode;
89
+ }
90
+
91
+ // 3. Default fallback
92
+ window.AppConfig.currentLang = 'en';
93
+ return 'en';
94
+ }
95
+
96
+ /**
97
+ * Translate string to current language
98
+ * @param {string} str - String in default language to translate
99
+ * @returns {string} Translated string or original if translation not found
100
+ */
101
+ function t(str) {
102
+ if (!str || typeof str !== 'string') return str;
103
+
104
+ const currentLang = getCurrentLanguage();
105
+
106
+ // If current language is the default or no translations loaded, return original
107
+ if (currentLang === 'en' || !window.AppConfig.translations || !window.AppConfig.translations[currentLang]) {
108
+ return str;
109
+ }
110
+
111
+ const translation = window.AppConfig.translations[currentLang][str];
112
+ return translation || str;
113
+ }
114
+
115
+ /**
116
+ * Set current language and save to localStorage
117
+ * @param {string} langKey - Language code (e.g., 'en', 'ru', 'pl')
118
+ */
119
+ function setLang(langKey) {
120
+ if (!langKey || typeof langKey !== 'string') return;
121
+
122
+ window.AppConfig.currentLang = langKey;
123
+ localStorage.setItem('lang', langKey);
124
+
125
+ // Optionally reload page to apply translations
126
+ // Uncomment the next line if you want automatic page reload on language change
127
+ // window.location.reload();
128
+ }
129
+
130
+ /**
131
+ * Load translations from JSON file
132
+ * @param {string} url - URL to translations JSON file (default: '/translations.json')
133
+ */
134
+ function loadTranslations(url = '/translations.json') {
135
+ if (!window.AppConfig.isTranslationEnabled) return;
136
+ fetch(url)
137
+ .then(response => {
138
+ if (!response.ok) {
139
+ console.warn('Translations file not found:', url);
140
+ return {};
141
+ }
142
+ return response.json();
143
+ })
144
+ .then(translations => {
145
+ window.AppConfig.translations = translations || {};
146
+ })
147
+ .catch(error => {
148
+ console.warn('Failed to load translations:', error);
149
+ window.AppConfig.translations = {};
150
+ });
151
+ }
152
+
153
+ // ===== UI FEEDBACK & NOTIFICATIONS =====
154
+
155
+ /**
156
+ * Show a toast notification
157
+ * @param {string} message - The message to display
158
+ * @param {string} type - 'success', 'error', 'info', 'warning'
159
+ */
160
+ function showToast(message, type = 'info') {
161
+ // Translate the message
162
+ message = t(message);
163
+ const toast = document.createElement('div');
164
+ toast.className = 'app-toast app-toast-' + type;
165
+ toast.textContent = message;
166
+ toast.style.cssText = `
167
+ position: fixed;
168
+ top: 20px;
169
+ right: 20px;
170
+ padding: 12px 24px;
171
+ border-radius: 4px;
172
+ color: white;
173
+ font-weight: 500;
174
+ z-index: 10000;
175
+ max-width: 300px;
176
+ word-wrap: break-word;
177
+ transition: all \${window.AppConfig.animationDuration}ms ease;
178
+ transform: translateX(100%);
179
+ opacity: 0;
180
+ `;
181
+
182
+ // Type-specific styling
183
+ const colors = {
184
+ success: '#10b981',
185
+ error: '#ef4444',
186
+ warning: '#f59e0b',
187
+ info: '#3b82f6'
188
+ };
189
+ toast.style.backgroundColor = colors[type] || colors.info;
190
+
191
+ document.body.appendChild(toast);
192
+
193
+ // Animate in
194
+ setTimeout(() => {
195
+ toast.style.transform = 'translateX(0)';
196
+ toast.style.opacity = '1';
197
+ }, 10);
198
+
199
+ // Auto remove
200
+ setTimeout(() => {
201
+ toast.style.transform = 'translateX(100%)';
202
+ toast.style.opacity = '0';
203
+ setTimeout(() => {
204
+ if (toast.parentNode) {
205
+ toast.parentNode.removeChild(toast);
206
+ }
207
+ }, window.AppConfig.animationDuration);
208
+ }, window.AppConfig.toastDuration);
209
+ }
210
+
211
+ /**
212
+ * Display inline message in specific container
213
+ * @param {string} elementId - ID of the target element
214
+ * @param {string} message - The message to display
215
+ * @param {string} type - 'success', 'error', 'info', 'warning'
216
+ */
217
+ function showMessage(elementId, message, type = 'info') {
218
+ const element = getElementSafely('#' + elementId);
219
+ if (!element) return;
220
+
221
+ // Translate the message
222
+ message = t(message);
223
+ element.textContent = message;
224
+ element.className = 'app-message app-message-' + type;
225
+ element.style.cssText = `
226
+ padding: 8px 12px;
227
+ border-radius: 4px;
228
+ margin: 8px 0;
229
+ font-size: 14px;
230
+ display: block;
231
+ `;
232
+
233
+ // Type-specific styling
234
+ const styles = {
235
+ success: 'background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0;',
236
+ error: 'background: #fee2e2; color: #991b1b; border: 1px solid #fca5a5;',
237
+ warning: 'background: #fef3c7; color: #92400e; border: 1px solid #fcd34d;',
238
+ info: 'background: #dbeafe; color: #1e40af; border: 1px solid #93c5fd;'
239
+ };
240
+ element.style.cssText += styles[type] || styles.info;
241
+ }
242
+
243
+ /**
244
+ * Show modal dialog
245
+ * @param {string} modalId - ID of the modal element
246
+ * @param {string} message - The message to display
247
+ * @param {string} type - 'success', 'error', 'info', 'warning'
248
+ */
249
+ function showModal(modalId, message, type = 'info') {
250
+ let modal = getElementSafely('#' + modalId);
251
+
252
+ if (!modal) {
253
+ // Create modal if it doesn't exist
254
+ modal = document.createElement('dialog');
255
+ modal.id = modalId;
256
+ modal.innerHTML = `
257
+ <div class="modal-content">
258
+ <div class="modal-header">
259
+ <button class="modal-close" onclick="this.closest('dialog').close()">&times;</button>
260
+ </div>
261
+ <div class="modal-body"></div>
262
+ </div>
263
+ `;
264
+ modal.style.cssText = `
265
+ border: none;
266
+ border-radius: 8px;
267
+ padding: 0;
268
+ max-width: 400px;
269
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
270
+ `;
271
+ document.body.appendChild(modal);
272
+ }
273
+
274
+ const content = modal.querySelector('.modal-body');
275
+ if (content) {
276
+ // Translate the message
277
+ message = t(message);
278
+ content.textContent = message;
279
+ content.className = 'modal-body modal-' + type;
280
+ content.style.cssText = 'padding: 20px; text-align: center;';
281
+ }
282
+
283
+ if (modal.showModal) {
284
+ modal.showModal();
285
+ setTimeout(() => {
286
+ try { modal.close(); } catch(e) {}
287
+ }, window.AppConfig.modalDuration);
288
+ }
289
+ }
290
+
291
+ // ===== FORM & CONTENT MANAGEMENT =====
292
+
293
+ /**
294
+ * Safe element removal with animation
295
+ * @param {string} selector - CSS selector for element to remove
296
+ */
297
+ function removeElement(selector) {
298
+ const element = getElementSafely(selector);
299
+ if (!element) return;
300
+
301
+ element.style.transition = `opacity ${window.AppConfig.animationDuration}ms ease`;
302
+ element.style.opacity = '0';
303
+
304
+ setTimeout(() => {
305
+ if (element.parentNode) {
306
+ element.parentNode.removeChild(element);
307
+ }
308
+ }, window.AppConfig.animationDuration);
309
+ }
310
+
311
+ // ===== CUSTOM SPA INTEGRATION =====
312
+
313
+ /**
314
+ * Centralized success handler that executes strategy array
315
+ * @param {object} response - The response object
316
+ * @param {string[]} strategy - Array of strategy actions
317
+ * @param {object} options - Additional options (basePath, entityName, etc.)
318
+ */
319
+ function handleFormSuccess(response, strategy = ['back', 'toast'], options = {}) {
320
+ const { basePath, entityName = 'Item' } = options;
321
+
322
+ strategy.forEach(action => {
323
+ switch (action) {
324
+ case 'toast':
325
+ showToast(`${entityName} saved successfully`, 'success');
326
+ break;
327
+ case 'message':
328
+ if (options.messageId) {
329
+ showMessage(options.messageId, `${entityName} saved successfully`, 'success');
330
+ }
331
+ break;
332
+ case 'modal':
333
+ if (options.modalId) {
334
+ showModal(options.modalId, `${entityName} saved successfully`, 'success');
335
+ }
336
+ break;
337
+ case 'remove':
338
+ // If targetSelector is specified, remove that element instead of the form
339
+ if (options.targetSelector) {
340
+ removeElement(options.targetSelector);
341
+ } else if (options.formSelector) {
342
+ removeElement(options.formSelector);
343
+ }
344
+ break;
345
+ case 'redirect':
346
+ if (basePath) {
347
+ if (basePath.startsWith('/') || basePath.startsWith('http')) {
348
+ navigateToPage(basePath);
349
+ }
350
+ }
351
+ break;
352
+ case 'back':
353
+ if (window.history.length > 1) {
354
+ window.history.back();
355
+ } else {
356
+ window.location.href = '/';
357
+ }
358
+ break;
359
+ case 'reload':
360
+ case 'refresh':
361
+ showToast(t('Reloading...'), 'info');
362
+ setTimeout(() => {
363
+ window.location.reload();
364
+ }, 500);
365
+ break;
366
+ }
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Handle internal link navigation via fetch
372
+ * @param {string} url - The URL to navigate to
373
+ * @param {Element} targetElement - Element to update with new content (default: #main)
374
+ */
375
+ function navigateToPage(url, targetElement = null) {
376
+ const target = targetElement || document.querySelector('#main');
377
+ if (!target) return;
378
+
379
+ showLoading('#main');
380
+
381
+ fetch(url, {
382
+ headers: buildAuthHeaders({
383
+ 'Accept': 'text/html',
384
+ 'X-Partial-Content': 'true'
385
+ })
386
+ })
387
+ .then(response => {
388
+ if (!response.ok) {
389
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
390
+ }
391
+ return response.text();
392
+ })
393
+ .then(html => {
394
+ target.innerHTML = html;
395
+ // Update browser history
396
+ window.history.pushState({}, '', url);
397
+ // Re-initialize event listeners for new content
398
+ initializeEventListeners();
399
+ })
400
+ .catch(error => {
401
+ console.error('Navigation failed:', error);
402
+ showToast('Failed to load page', 'error');
403
+ // Fallback to normal navigation
404
+ window.location.href = url;
405
+ })
406
+ .finally(() => {
407
+ hideLoading('#main');
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Convert form value to appropriate type based on field type
413
+ * @param {string} value - Raw form value
414
+ * @param {string} fieldType - Field type (number, boolean, etc.)
415
+ * @returns {any} Converted value
416
+ */
417
+ function convertFieldValue(value, fieldType) {
418
+ if (!value || value === '') {
419
+ return null;
420
+ }
421
+
422
+ switch (fieldType.toLowerCase()) {
423
+ case 'number':
424
+ case 'int':
425
+ case 'integer':
426
+ const intVal = parseInt(value, 10);
427
+ return isNaN(intVal) ? null : intVal;
428
+
429
+ case 'float':
430
+ case 'decimal':
431
+ const floatVal = parseFloat(value);
432
+ return isNaN(floatVal) ? null : floatVal;
433
+
434
+ case 'boolean':
435
+ case 'bool':
436
+ if (value === 'true') return true;
437
+ if (value === 'false') return false;
438
+ return Boolean(value);
439
+
440
+ case 'enum':
441
+ case 'string':
442
+ case 'text':
443
+ default:
444
+ return value;
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Handle form submission via fetch with JSON data
450
+ * @param {HTMLFormElement} form - The form element
451
+ * @param {string[]} strategy - Strategy actions to execute on success
452
+ * @param {object} options - Additional options
453
+ */
454
+ function submitForm(form, strategy = ['back', 'toast'], options = {}) {
455
+ const formData = new FormData(form);
456
+ const jsonData = {};
457
+
458
+ // Get field types from form data attribute
459
+ const fieldTypesAttr = form.getAttribute('data-field-types');
460
+ const fieldTypes = fieldTypesAttr ? JSON.parse(fieldTypesAttr) : {};
461
+
462
+ // Convert FormData to JSON with proper typing
463
+ for (const [key, value] of formData.entries()) {
464
+ const fieldType = fieldTypes[key] || 'string';
465
+ const convertedValue = convertFieldValue(value, fieldType);
466
+
467
+ if (jsonData[key]) {
468
+ // Handle multiple values (e.g., checkboxes)
469
+ if (Array.isArray(jsonData[key])) {
470
+ jsonData[key].push(convertedValue);
471
+ } else {
472
+ jsonData[key] = [jsonData[key], convertedValue];
473
+ }
474
+ } else {
475
+ jsonData[key] = convertedValue;
476
+ }
477
+ }
478
+
479
+ const url = form.getAttribute('data-action') || form.action;
480
+ const method = (form.getAttribute('data-method') || form.method || 'POST').toUpperCase();
481
+
482
+ showLoading(form);
483
+
484
+ fetch(url, {
485
+ method,
486
+ headers: buildAuthHeaders({
487
+ 'Content-Type': 'application/json',
488
+ 'Accept': 'application/json'
489
+ }),
490
+ body: JSON.stringify(jsonData)
491
+ })
492
+ .then(response => {
493
+ if (!response.ok) {
494
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
495
+ }
496
+ return response.json();
497
+ })
498
+ .then(data => {
499
+ handleFormSuccess(data, strategy, options);
500
+ })
501
+ .catch(error => {
502
+ console.error('Form submission failed:', error);
503
+ handleFormError({ message: error.message || 'Form submission failed' }, options);
504
+ })
505
+ .finally(() => {
506
+ hideLoading(form);
507
+ });
508
+ }
509
+
510
+ /**
511
+ * Standardized error handling for forms
512
+ * @param {object} response - The error response
513
+ * @param {object} options - Options including target elements
514
+ */
515
+ function handleFormError(response, options = {}) {
516
+ const message = response.message || 'An error occurred';
517
+
518
+ if (options.messageId) {
519
+ showMessage(options.messageId, message, 'error');
520
+ } else {
521
+ showToast(message, 'error');
522
+ }
523
+ }
524
+
525
+ // ===== UTILITY FUNCTIONS =====
526
+
527
+ /**
528
+ * Safe element selection with error handling
529
+ * @param {string} selector - CSS selector
530
+ * @returns {Element|null}
531
+ */
532
+ function getElementSafely(selector) {
533
+ try {
534
+ return document.querySelector(selector);
535
+ } catch (e) {
536
+ console.warn('Invalid selector:', selector);
537
+ return null;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Debounce utility for search/input handlers
543
+ * @param {Function} fn - Function to debounce
544
+ * @param {number} delay - Delay in milliseconds
545
+ * @returns {Function}
546
+ */
547
+ function debounce(fn, delay = window.AppConfig.debounceDelay) {
548
+ let timeoutId;
549
+ return function(...args) {
550
+ clearTimeout(timeoutId);
551
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
552
+ };
553
+ }
554
+
555
+ /**
556
+ * Show loading state for target element
557
+ * @param {string} target - CSS selector for target element
558
+ */
559
+ function showLoading(target) {
560
+ const element = getElementSafely(target);
561
+ if (!element) return;
562
+
563
+ element.style.position = 'relative';
564
+ element.style.pointerEvents = 'none';
565
+ element.style.opacity = '0.6';
566
+
567
+ const loader = document.createElement('div');
568
+ loader.className = 'app-loader';
569
+ loader.style.cssText = `
570
+ position: absolute;
571
+ top: 50%;
572
+ left: 50%;
573
+ transform: translate(-50%, -50%);
574
+ width: 20px;
575
+ height: 20px;
576
+ border: 2px solid #f3f3f3;
577
+ border-top: 2px solid #3498db;
578
+ border-radius: 50%;
579
+ animation: spin 1s linear infinite;
580
+ z-index: 1000;
581
+ `;
582
+
583
+ element.appendChild(loader);
584
+
585
+ // Add CSS animation if not exists
586
+ if (!document.querySelector('#app-loader-styles')) {
587
+ const style = document.createElement('style');
588
+ style.id = 'app-loader-styles';
589
+ style.textContent = `
590
+ @keyframes spin {
591
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
592
+ 100% { transform: translate(-50%, -50%) rotate(360deg); }
593
+ }
594
+ `;
595
+ document.head.appendChild(style);
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Hide loading state for target element
601
+ * @param {string} target - CSS selector for target element
602
+ */
603
+ function hideLoading(target) {
604
+ const element = getElementSafely(target);
605
+ if (!element) return;
606
+
607
+ element.style.pointerEvents = '';
608
+ element.style.opacity = '';
609
+
610
+ const loader = element.querySelector('.app-loader');
611
+ if (loader) {
612
+ loader.remove();
613
+ }
614
+ }
615
+
616
+ // ===== INITIALIZATION =====
617
+
618
+ /**
619
+ * Initialize event listeners for links and forms
620
+ */
621
+ function initializeEventListeners() {
622
+ // Handle internal links
623
+ document.querySelectorAll('a[href]').forEach(link => {
624
+ const href = link.getAttribute('href');
625
+
626
+ // Skip external links, anchors, and special protocols
627
+ if (!href ||
628
+ href.startsWith('http://') ||
629
+ href.startsWith('https://') ||
630
+ href.startsWith('mailto:') ||
631
+ href.startsWith('tel:') ||
632
+ href.startsWith('#') ||
633
+ href.startsWith('javascript:')) {
634
+ return;
635
+ }
636
+
637
+ // Remove any existing event listeners and add new one
638
+ link.removeEventListener('click', handleLinkClick);
639
+ link.addEventListener('click', handleLinkClick);
640
+ });
641
+
642
+ // Handle forms with strategy
643
+ document.querySelectorAll('form[data-strategy]').forEach(form => {
644
+ // Remove any existing event listeners and add new one
645
+ form.removeEventListener('submit', handleFormSubmit);
646
+ form.addEventListener('submit', handleFormSubmit);
647
+ });
648
+ }
649
+
650
+ /**
651
+ * Handle link click for internal navigation
652
+ * @param {Event} event - Click event
653
+ */
654
+ function handleLinkClick(event) {
655
+ event.preventDefault();
656
+ const href = event.currentTarget.getAttribute('href');
657
+ if (href) {
658
+ navigateToPage(href);
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Handle form submission
664
+ * @param {Event} event - Submit event
665
+ */
666
+ function handleFormSubmit(event) {
667
+ event.preventDefault();
668
+ const form = event.target;
669
+
670
+ // Check for confirmation message
671
+ const confirmMessage = form.getAttribute('data-confirm-message');
672
+ if (confirmMessage) {
673
+ const confirmed = confirm(t(confirmMessage));
674
+ if (!confirmed) {
675
+ return; // User cancelled
676
+ }
677
+ }
678
+
679
+ const strategyAttr = form.getAttribute('data-strategy');
680
+ const strategy = strategyAttr ? JSON.parse(strategyAttr) : ['back', 'toast'];
681
+
682
+ // Extract options from form data attributes
683
+ const options = {
684
+ basePath: form.getAttribute('data-base-path') || '',
685
+ entityName: form.getAttribute('data-entity-name') || 'Item',
686
+ messageId: form.getAttribute('data-message-id'),
687
+ modalId: form.getAttribute('data-modal-id'),
688
+ formSelector: `form[data-template="${form.getAttribute('data-template')}"]`,
689
+ targetSelector: form.getAttribute('data-target-selector')
690
+ };
691
+
692
+ submitForm(form, strategy, options);
693
+ }
694
+
695
+ // Set up event handlers on page load
696
+ document.addEventListener('DOMContentLoaded', function() {
697
+ // Initialize event listeners
698
+ initializeEventListeners();
699
+
700
+ // Handle browser back/forward buttons
701
+ window.addEventListener('popstate', function(event) {
702
+ // Reload the page content for the current URL
703
+ navigateToPage(window.location.pathname);
704
+ });
705
+
706
+ // Load translations on page load
707
+ loadTranslations();
708
+ });
709
+
710
+ // Expose functions globally
711
+ window.App = {
712
+ auth: {
713
+ setAuthToken,
714
+ clearAuthToken,
715
+ buildAuthHeaders,
716
+ },
717
+ lang: {
718
+ t,
719
+ set: setLang,
720
+ get: getCurrentLanguage,
721
+ },
722
+ ui: {
723
+ showToast,
724
+ showMessage,
725
+ showModal,
726
+ showLoading,
727
+ hideLoading,
728
+ },
729
+ nav: {
730
+ go: navigateToPage,
731
+ submit: submitForm,
732
+ },
733
+ utils: {
734
+ $: getElementSafely,
735
+ debounce,
736
+ initializeEventListeners,
737
+ },
738
+ };
739
+ })();