create-nativecore 0.1.0 → 0.2.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.
Files changed (175) hide show
  1. package/README.md +10 -18
  2. package/bin/index.mjs +407 -489
  3. package/package.json +4 -3
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Dashboard Page Controller
3
+ * Handles dynamic behavior for the dashboard page
4
+ */
5
+ import { trackEvents, trackSubscriptions } from '@utils/events.js';
6
+ import { useState, computed } from '@core/state.js';
7
+ import auth from '../services/auth.service.js';
8
+ import api from '../services/api.service.js';
9
+
10
+ export async function dashboardController(): Promise<() => void> {
11
+ const events = trackEvents();
12
+ const subs = trackSubscriptions();
13
+
14
+ const welcomeMsg = document.getElementById('welcome-message');
15
+ const overviewScore = document.getElementById('dashboard-overview-score');
16
+ const deliverySummary = document.getElementById('delivery-summary');
17
+ const usersStat = document.getElementById('stat-users');
18
+ const sessionsStat = document.getElementById('stat-sessions');
19
+ const revenueStat = document.getElementById('stat-revenue');
20
+ const newTodayStat = document.getElementById('stat-new-today');
21
+ const usersRing = document.getElementById('stat-users-ring');
22
+ const sessionsRing = document.getElementById('stat-sessions-ring');
23
+ const revenueRing = document.getElementById('stat-revenue-ring');
24
+ const newTodayRing = document.getElementById('stat-new-today-ring');
25
+ const adoptionHealth = document.getElementById('health-adoption');
26
+ const sessionHealth = document.getElementById('health-sessions');
27
+ const revenueHealth = document.getElementById('health-revenue');
28
+ const activityTimeline = document.getElementById('dashboard-activity-timeline');
29
+ const activityCountBadge = document.getElementById('activity-count-badge');
30
+ const metricsTable = document.getElementById('dashboard-metrics-table');
31
+ const signalCompleted = document.getElementById('signal-completed');
32
+ const signalRemaining = document.getElementById('signal-remaining');
33
+ const signalCompletion = document.getElementById('signal-completion');
34
+ const signalProgress = document.getElementById('signal-progress');
35
+ const signalStatus = document.getElementById('signal-status');
36
+
37
+ const completedState = useState(14);
38
+ const totalState = useState(18);
39
+ const completionState = computed(() => Math.round((completedState.value / totalState.value) * 100));
40
+ const remainingState = computed(() => Math.max(totalState.value - completedState.value, 0));
41
+ const statusState = computed(() => {
42
+ if (completionState.value >= 90) {
43
+ return { title: 'Release ready', body: 'Computed delivery status confirms the current workflow is ready for review.', variant: 'success' };
44
+ }
45
+ if (completionState.value >= 70) {
46
+ return { title: 'On track', body: 'Computed delivery status updates from local dashboard state.', variant: 'info' };
47
+ }
48
+ return { title: 'Needs attention', body: 'Completion dipped below the target threshold. Add or finish scope items to recover.', variant: 'warning' };
49
+ });
50
+
51
+ const user = auth.getUser();
52
+ if (user && welcomeMsg) {
53
+ welcomeMsg.textContent = `Welcome back, ${user.name || 'User'}. This workspace demonstrates a cleaner executive layout with authenticated data, core components, and live state patterns.`;
54
+ }
55
+
56
+ const renderSignalDemo = () => {
57
+ if (signalCompleted) signalCompleted.textContent = String(completedState.value);
58
+ if (signalRemaining) signalRemaining.textContent = String(remainingState.value);
59
+ if (signalCompletion) signalCompletion.textContent = `${completionState.value}%`;
60
+ signalProgress?.setAttribute('value', String(completionState.value));
61
+ if (signalStatus) {
62
+ signalStatus.setAttribute('variant', statusState.value.variant);
63
+ signalStatus.setAttribute('title', statusState.value.title);
64
+ signalStatus.textContent = statusState.value.body;
65
+ }
66
+ };
67
+
68
+ subs.watch(completedState.watch(renderSignalDemo));
69
+ subs.watch(totalState.watch(renderSignalDemo));
70
+ subs.watch(completionState.watch(renderSignalDemo));
71
+ subs.watch(remainingState.watch(renderSignalDemo));
72
+ subs.watch(statusState.watch(renderSignalDemo));
73
+ renderSignalDemo();
74
+
75
+ const loadData = async (forceRefresh = false) => {
76
+ try {
77
+ const data = await api.getCached('/dashboard/stats', {
78
+ ttl: 30,
79
+ revalidate: true,
80
+ forceRefresh,
81
+ queryKey: ['dashboard', 'stats'],
82
+ tags: ['dashboard'],
83
+ });
84
+
85
+ if (!data || typeof data !== 'object') {
86
+ throw new Error('Invalid response from server');
87
+ }
88
+
89
+ const users = Number(data.users ?? 0);
90
+ const sessions = Number(data.sessions ?? 0);
91
+ const revenue = Number(data.revenue ?? 0);
92
+ const newToday = Number(data.newToday ?? 0);
93
+
94
+ const userScore = Math.min(100, Math.round((users / 1500) * 100));
95
+ const sessionScore = Math.min(100, Math.round((sessions / 70) * 100));
96
+ const revenueScore = Math.min(100, Math.round((revenue / 60000) * 100));
97
+ const newTodayScore = Math.min(100, Math.round((newToday / 30) * 100));
98
+ const overview = Math.round((userScore + sessionScore + revenueScore) / 3);
99
+
100
+ if (usersStat) usersStat.textContent = users.toLocaleString();
101
+ if (sessionsStat) sessionsStat.textContent = sessions.toLocaleString();
102
+ if (revenueStat) revenueStat.textContent = `$${revenue.toLocaleString()}`;
103
+ if (newTodayStat) newTodayStat.textContent = newToday.toLocaleString();
104
+
105
+ usersRing?.setAttribute('value', String(userScore));
106
+ sessionsRing?.setAttribute('value', String(sessionScore));
107
+ revenueRing?.setAttribute('value', String(revenueScore));
108
+ newTodayRing?.setAttribute('value', String(newTodayScore));
109
+
110
+ adoptionHealth?.setAttribute('value', String(userScore));
111
+ sessionHealth?.setAttribute('value', String(sessionScore));
112
+ revenueHealth?.setAttribute('value', String(revenueScore));
113
+
114
+ if (overviewScore) overviewScore.textContent = `${overview}%`;
115
+ if (deliverySummary) deliverySummary.textContent = `${overview >= 80 ? 'Executive-ready' : 'Needs iteration'} overview`;
116
+
117
+ if (activityTimeline) {
118
+ const activities = Array.isArray(data.recentActivity) ? data.recentActivity : [];
119
+ if (activityCountBadge) {
120
+ activityCountBadge.setAttribute('count', String(activities.length));
121
+ }
122
+ activityTimeline.innerHTML = activities.map((item: { message?: string; time?: string }, index: number) => `
123
+ <nc-timeline-item
124
+ title="${index === 0 ? 'Latest event' : 'Workflow update'}"
125
+ time="${item.time ?? 'Recently'}"
126
+ status="${index === 0 ? 'active' : 'completed'}"
127
+ ${index === activities.length - 1 ? 'no-line' : ''}
128
+ >
129
+ ${item.message ?? 'Protected activity recorded.'}
130
+ </nc-timeline-item>
131
+ `).join('');
132
+ }
133
+
134
+ if (metricsTable) {
135
+ metricsTable.setAttribute('columns', JSON.stringify([
136
+ { key: 'metric', label: 'Metric', sortable: true },
137
+ { key: 'value', label: 'Value', sortable: true },
138
+ { key: 'status', label: 'Status', format: 'badge' },
139
+ ]));
140
+ metricsTable.setAttribute('rows', JSON.stringify([
141
+ { metric: 'Authenticated users', value: users.toLocaleString(), status: userScore >= 80 ? 'Healthy' : 'Watch' },
142
+ { metric: 'Active sessions', value: sessions.toLocaleString(), status: sessionScore >= 80 ? 'Healthy' : 'Watch' },
143
+ { metric: 'Revenue pipeline', value: `$${revenue.toLocaleString()}`, status: revenueScore >= 80 ? 'Healthy' : 'Watch' },
144
+ { metric: 'New today', value: newToday.toLocaleString(), status: newTodayScore >= 80 ? 'Strong' : 'Normal' },
145
+ ]));
146
+ }
147
+
148
+ } catch (error: unknown) {
149
+ console.error('Dashboard error:', error);
150
+ const message = error instanceof Error ? error.message : 'Unknown error';
151
+ if (deliverySummary) deliverySummary.textContent = 'Data unavailable';
152
+ if (activityCountBadge) activityCountBadge.setAttribute('count', '0');
153
+ if (activityTimeline) {
154
+ activityTimeline.innerHTML = `
155
+ <nc-timeline-item title="Dashboard load failed" time="Just now" status="error" no-line>
156
+ Failed to load dashboard data: ${message}
157
+ </nc-timeline-item>
158
+ `;
159
+ }
160
+ if (metricsTable) {
161
+ metricsTable.setAttribute('columns', JSON.stringify([
162
+ { key: 'metric', label: 'Metric' },
163
+ { key: 'value', label: 'Value' },
164
+ { key: 'status', label: 'Status', format: 'badge' },
165
+ ]));
166
+ metricsTable.setAttribute('rows', JSON.stringify([
167
+ { metric: 'Dashboard API', value: 'Unavailable', status: 'Error' },
168
+ ]));
169
+ }
170
+ }
171
+ };
172
+
173
+ await loadData();
174
+
175
+ events.onClick('#refreshBtn', () => {
176
+ api.invalidateQuery(['dashboard', 'stats'], { exact: true });
177
+ api.invalidateTags('dashboard');
178
+ void loadData(true);
179
+ });
180
+ events.onClick('#signal-complete-btn', () => {
181
+ if (completedState.value < totalState.value) {
182
+ completedState.value += 1;
183
+ }
184
+ });
185
+ events.onClick('#signal-add-btn', () => {
186
+ totalState.value += 1;
187
+ });
188
+ events.onClick('#signal-reset-btn', () => {
189
+ completedState.value = 14;
190
+ totalState.value = 18;
191
+ });
192
+
193
+ return () => {
194
+ completionState.dispose();
195
+ remainingState.dispose();
196
+ statusState.dispose();
197
+ events.cleanup();
198
+ subs.cleanup();
199
+ };
200
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Home Controller
3
+ * Updates the primary landing CTA based on authentication status.
4
+ */
5
+ import auth from '../services/auth.service.js';
6
+
7
+ export async function homeController(): Promise<() => void> {
8
+ const getStartedBtn = document.getElementById('get-started-btn') as HTMLAnchorElement | null;
9
+
10
+ if (getStartedBtn) {
11
+ if (auth.isAuthenticated()) {
12
+ getStartedBtn.href = '/dashboard';
13
+ getStartedBtn.textContent = 'Go to Dashboard';
14
+ } else {
15
+ getStartedBtn.href = '/docs';
16
+ getStartedBtn.textContent = 'Read the Docs';
17
+ }
18
+ }
19
+
20
+ return () => {};
21
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Controller Registry
3
+ */
4
+
5
+ export { homeController } from './home.controller.js';
6
+ export { loginController } from './login.controller.js';
7
+ export { dashboardController } from './dashboard.controller.js';
8
+ export { userDetailController } from './user-detail.controller.js';
9
+ export { componentsController } from './components.controller.js';
10
+ export { docsController } from './docs.controller.js';
11
+ export { testingController } from './testing.controller.js';
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Login Page Controller
3
+ * Handles dynamic behavior for the login page
4
+ */
5
+ import { trackEvents, trackSubscriptions } from '@utils/events.js';
6
+ import router from '@core/router.js';
7
+ import auth from '../services/auth.service.js';
8
+ import api from '../services/api.service.js';
9
+
10
+ export async function loginController(): Promise<() => void> {
11
+ const events = trackEvents();
12
+ const subs = trackSubscriptions();
13
+
14
+ const form = document.getElementById('loginForm') as (HTMLElement & {
15
+ getValues?: () => Record<string, string>;
16
+ }) | null;
17
+ const errorDiv = document.getElementById('login-error') as HTMLDivElement | null;
18
+ const loginBtn = document.getElementById('loginBtn') as HTMLElement | null;
19
+ const rememberMeCheckbox = document.getElementById('rememberMe') as HTMLElement | null;
20
+ const emailField = document.getElementById('email') as HTMLElement | null;
21
+ const passwordField = document.getElementById('password') as HTMLElement | null;
22
+
23
+ // Load saved email if "remember me" was checked
24
+ const savedEmail = localStorage.getItem('rememberedEmail');
25
+ if (savedEmail) {
26
+ emailField?.setAttribute('value', savedEmail);
27
+ rememberMeCheckbox?.setAttribute('checked', '');
28
+ }
29
+
30
+ const setButtonState = (isLoading: boolean) => {
31
+ if (!loginBtn) return;
32
+
33
+ if (isLoading) {
34
+ loginBtn.setAttribute('disabled', '');
35
+ loginBtn.textContent = 'Signing In...';
36
+ return;
37
+ }
38
+
39
+ loginBtn.removeAttribute('disabled');
40
+ loginBtn.textContent = 'Access Dashboard';
41
+ };
42
+
43
+ const setError = (message: string) => {
44
+ if (!errorDiv) return;
45
+ errorDiv.textContent = message;
46
+ errorDiv.hidden = false;
47
+ };
48
+
49
+ const clearError = () => {
50
+ if (!errorDiv) return;
51
+ errorDiv.hidden = true;
52
+ errorDiv.textContent = '';
53
+ };
54
+
55
+ const getInputValue = (element: HTMLElement | null): string => {
56
+ if (!element) return '';
57
+
58
+ const shadowInput = element.shadowRoot?.querySelector('input') as HTMLInputElement | null;
59
+ return shadowInput?.value ?? element.getAttribute('value') ?? '';
60
+ };
61
+
62
+ const focusInput = (element: HTMLElement | null) => {
63
+ const shadowInput = element?.shadowRoot?.querySelector('input') as HTMLInputElement | null;
64
+ shadowInput?.focus();
65
+ };
66
+
67
+ const handleSubmit = async (e: Event) => {
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+
71
+ if (!form || !loginBtn || !errorDiv) return;
72
+
73
+ const submitEvent = e as CustomEvent<{ values?: Record<string, string> }>;
74
+ const values = submitEvent.detail?.values ?? form.getValues?.() ?? {};
75
+ const email = (values.email ?? getInputValue(emailField)).trim();
76
+ const password = values.password ?? getInputValue(passwordField);
77
+ const rememberMe = rememberMeCheckbox?.hasAttribute('checked') ?? false;
78
+
79
+ clearError();
80
+
81
+ if (!email || !password) {
82
+ setError('Email and password are required. Use the demo credentials shown above.');
83
+ if (!email) {
84
+ focusInput(emailField);
85
+ } else {
86
+ focusInput(passwordField);
87
+ }
88
+ return;
89
+ }
90
+
91
+ setButtonState(true);
92
+
93
+ try {
94
+ const response = await api.post('/auth/login', { email, password });
95
+
96
+ if (rememberMe) {
97
+ localStorage.setItem('rememberedEmail', email);
98
+ } else {
99
+ localStorage.removeItem('rememberedEmail');
100
+ }
101
+
102
+ auth.setTokens(response.accessToken, response.refreshToken);
103
+ auth.setUser(response.user);
104
+ router.navigate('/dashboard');
105
+
106
+ } catch (error: unknown) {
107
+ console.error('Login error:', error);
108
+ const errorMessage = error instanceof Error ? error.message : 'Login failed. Please try again.';
109
+ setError(errorMessage);
110
+ emailField?.setAttribute('value', email);
111
+ passwordField?.setAttribute('value', '');
112
+ focusInput(passwordField);
113
+ } finally {
114
+ setButtonState(false);
115
+ }
116
+ };
117
+
118
+ events.on<CustomEvent<{ values: Record<string, string> }>>('#loginForm', 'submit', handleSubmit);
119
+ events.on<KeyboardEvent>('#loginForm', 'keydown', (event) => {
120
+ if (event.key === 'Enter') {
121
+ handleSubmit(event);
122
+ }
123
+ });
124
+
125
+ return () => {
126
+ events.cleanup();
127
+ subs.cleanup();
128
+ };
129
+ }
130
+
131
+