create-nativecore 0.1.1 → 0.2.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 (175) hide show
  1. package/README.md +6 -14
  2. package/bin/index.mjs +403 -431
  3. package/package.json +3 -2
  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 +653 -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,354 @@
1
+ /**
2
+ * Base Component Class for Web Components
3
+ * Extend this to create reusable custom HTML elements
4
+ */
5
+
6
+ // Types
7
+ export interface ComponentState {
8
+ [key: string]: any;
9
+ }
10
+
11
+ export interface ComponentConstructor {
12
+ useShadowDOM?: boolean;
13
+ observedAttributes?: string[];
14
+ }
15
+
16
+ type RenderContainer = HTMLElement | ShadowRoot | Element | DocumentFragment;
17
+
18
+ /**
19
+ * Base Component class for creating Web Components
20
+ */
21
+ export class Component extends HTMLElement {
22
+ state: ComponentState;
23
+ protected _mounted: boolean;
24
+ shadowRoot!: ShadowRoot | null;
25
+
26
+ static useShadowDOM?: boolean;
27
+ static observedAttributes?: string[];
28
+
29
+ constructor() {
30
+ super();
31
+ this.state = {};
32
+ this._mounted = false;
33
+
34
+ // Attach shadow root only if component opts-in via static property
35
+ const constructor = this.constructor as typeof Component;
36
+ if (constructor.useShadowDOM) {
37
+ this.attachShadow({ mode: 'open' });
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Called when component is added to DOM
43
+ */
44
+ connectedCallback(): void {
45
+ try {
46
+ this.render();
47
+ this.onMount();
48
+ this._mounted = true;
49
+ } catch (error) {
50
+ console.error(`Error rendering ${this.tagName.toLowerCase()}:`, error);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Called when component is removed from DOM
56
+ */
57
+ disconnectedCallback(): void {
58
+ this.onUnmount();
59
+ this._mounted = false;
60
+ }
61
+
62
+ /**
63
+ * Watch for attribute changes
64
+ */
65
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
66
+ if (oldValue !== newValue && this._mounted) {
67
+ this.onAttributeChange(name, oldValue, newValue);
68
+ this.render();
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Update component state and re-render
74
+ */
75
+ setState(newState: Partial<ComponentState>): void {
76
+ this.state = { ...this.state, ...newState };
77
+ if (this._mounted) {
78
+ this.render();
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get component state
84
+ */
85
+ getState(): ComponentState {
86
+ return { ...this.state };
87
+ }
88
+
89
+ /**
90
+ * Render the component
91
+ */
92
+ render(): void {
93
+ const html = this.template();
94
+ const target = this.shadowRoot ?? this;
95
+ patchHTML(target, html);
96
+ }
97
+
98
+ /**
99
+ * Template method - Override to return HTML string
100
+ */
101
+ template(): string {
102
+ return '';
103
+ }
104
+
105
+ /**
106
+ * Lifecycle hook - called when component mounts
107
+ */
108
+ onMount(): void {
109
+ // Override in child class
110
+ }
111
+
112
+ /**
113
+ * Lifecycle hook - called when component unmounts
114
+ */
115
+ onUnmount(): void {
116
+ // Override in child class
117
+ }
118
+
119
+ /**
120
+ * Lifecycle hook - called when attributes change
121
+ */
122
+ onAttributeChange(_name: string, _oldValue: string | null, _newValue: string | null): void {
123
+ // Override in child class
124
+ }
125
+
126
+ /**
127
+ * Helper: Query selector within component's shadow DOM or light DOM
128
+ */
129
+ $<E extends Element = Element>(selector: string): E | null {
130
+ return this.shadowRoot
131
+ ? this.shadowRoot.querySelector<E>(selector)
132
+ : this.querySelector<E>(selector);
133
+ }
134
+
135
+ /**
136
+ * Helper: Query selector all within component's shadow DOM or light DOM
137
+ */
138
+ $$<E extends Element = Element>(selector: string): NodeListOf<E> {
139
+ return this.shadowRoot
140
+ ? this.shadowRoot.querySelectorAll<E>(selector)
141
+ : this.querySelectorAll<E>(selector);
142
+ }
143
+
144
+ /**
145
+ * Helper: Add event listener with auto cleanup
146
+ */
147
+ on<K extends keyof HTMLElementEventMap>(
148
+ event: K,
149
+ handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any
150
+ ): void;
151
+ on<K extends keyof HTMLElementEventMap>(
152
+ event: K,
153
+ selector: string,
154
+ handler: (this: Element, ev: HTMLElementEventMap[K]) => any
155
+ ): void;
156
+ on<K extends keyof HTMLElementEventMap>(
157
+ event: K,
158
+ selectorOrHandler: string | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any),
159
+ handler?: (this: Element, ev: HTMLElementEventMap[K]) => any
160
+ ): void {
161
+ if (typeof selectorOrHandler === 'function') {
162
+ this.addEventListener(event, selectorOrHandler as EventListener);
163
+ } else if (handler) {
164
+ this.addEventListener(event, ((e: Event) => {
165
+ const target = e.target as Element;
166
+ if (target.matches(selectorOrHandler)) {
167
+ handler.call(target, e as any);
168
+ }
169
+ }) as EventListener);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Helper: Emit custom event
175
+ */
176
+ emitEvent<T = any>(
177
+ eventName: string,
178
+ detail: T = {} as T,
179
+ options: Partial<CustomEventInit<T>> = {}
180
+ ): boolean {
181
+ const event = new CustomEvent(eventName, {
182
+ detail,
183
+ bubbles: options.bubbles !== undefined ? options.bubbles : true,
184
+ composed: options.composed !== undefined ? options.composed : true,
185
+ cancelable: options.cancelable !== undefined ? options.cancelable : false
186
+ });
187
+ return this.dispatchEvent(event);
188
+ }
189
+
190
+ /**
191
+ * Helper: Get attribute or default value
192
+ */
193
+ attr(name: string, defaultValue: string | null = null): string | null {
194
+ return this.getAttribute(name) || defaultValue;
195
+ }
196
+
197
+ /**
198
+ * Helper: Set attribute
199
+ */
200
+ setAttr(name: string, value: string): void {
201
+ this.setAttribute(name, value);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Parse the latest template HTML into a fragment and reconcile it against the
207
+ * existing DOM so stable nodes, focus state, and form control state survive updates.
208
+ */
209
+ function patchHTML(target: HTMLElement | ShadowRoot, html: string): void {
210
+ const range = document.createRange();
211
+ range.selectNodeContents(target);
212
+ const fragment = range.createContextualFragment(html);
213
+ reconcileChildren(target, fragment);
214
+ }
215
+
216
+ /**
217
+ * Reconcile child nodes by index and patch matching nodes in place when possible.
218
+ */
219
+ function reconcileChildren(target: RenderContainer, source: RenderContainer): void {
220
+ const currentNodes = Array.from(target.childNodes);
221
+ const nextNodes = Array.from(source.childNodes);
222
+ const maxLength = Math.max(currentNodes.length, nextNodes.length);
223
+
224
+ for (let index = 0; index < maxLength; index++) {
225
+ const currentNode = currentNodes[index];
226
+ const nextNode = nextNodes[index];
227
+
228
+ if (!currentNode && nextNode) {
229
+ target.appendChild(nextNode);
230
+ continue;
231
+ }
232
+
233
+ if (currentNode && !nextNode) {
234
+ currentNode.remove();
235
+ continue;
236
+ }
237
+
238
+ if (currentNode && nextNode) {
239
+ reconcileNode(currentNode, nextNode);
240
+ }
241
+ }
242
+ }
243
+
244
+ function reconcileNode(currentNode: Node, nextNode: Node): void {
245
+ const parent = currentNode.parentNode;
246
+ if (!parent) return;
247
+
248
+ if (!canPatchNode(currentNode, nextNode)) {
249
+ parent.replaceChild(nextNode, currentNode);
250
+ return;
251
+ }
252
+
253
+ if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE) {
254
+ if (currentNode.textContent !== nextNode.textContent) {
255
+ currentNode.textContent = nextNode.textContent;
256
+ }
257
+ return;
258
+ }
259
+
260
+ const currentElement = currentNode as Element;
261
+ const nextElement = nextNode as Element;
262
+
263
+ syncAttributes(currentElement, nextElement);
264
+ syncFormControlState(currentElement, nextElement);
265
+ reconcileChildren(currentElement, nextElement);
266
+ }
267
+
268
+ function canPatchNode(currentNode: Node, nextNode: Node): boolean {
269
+ if (currentNode.nodeType !== nextNode.nodeType) {
270
+ return false;
271
+ }
272
+
273
+ if (currentNode.nodeType !== Node.ELEMENT_NODE) {
274
+ return true;
275
+ }
276
+
277
+ return (currentNode as Element).tagName === (nextNode as Element).tagName;
278
+ }
279
+
280
+ function syncAttributes(currentElement: Element, nextElement: Element): void {
281
+ const currentAttributes = Array.from(currentElement.attributes);
282
+ const nextAttributes = Array.from(nextElement.attributes);
283
+
284
+ currentAttributes.forEach(attribute => {
285
+ if (!nextElement.hasAttribute(attribute.name)) {
286
+ currentElement.removeAttribute(attribute.name);
287
+ }
288
+ });
289
+
290
+ nextAttributes.forEach(attribute => {
291
+ if (currentElement.getAttribute(attribute.name) !== attribute.value) {
292
+ currentElement.setAttribute(attribute.name, attribute.value);
293
+ }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Preserve input values, checked state, and focus where possible while syncing template updates.
299
+ */
300
+ function syncFormControlState(currentElement: Element, nextElement: Element): void {
301
+ if (currentElement instanceof HTMLInputElement && nextElement instanceof HTMLInputElement) {
302
+ syncInputElement(currentElement, nextElement);
303
+ return;
304
+ }
305
+
306
+ if (currentElement instanceof HTMLTextAreaElement && nextElement instanceof HTMLTextAreaElement) {
307
+ if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
308
+ currentElement.value = nextElement.value;
309
+ }
310
+ return;
311
+ }
312
+
313
+ if (currentElement instanceof HTMLSelectElement && nextElement instanceof HTMLSelectElement) {
314
+ if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
315
+ currentElement.value = nextElement.value;
316
+ }
317
+ }
318
+ }
319
+
320
+ function syncInputElement(currentElement: HTMLInputElement, nextElement: HTMLInputElement): void {
321
+ if (currentElement.type === 'checkbox' || currentElement.type === 'radio') {
322
+ currentElement.checked = nextElement.checked;
323
+ }
324
+
325
+ if (!isFocusedElement(currentElement) && currentElement.value !== nextElement.value) {
326
+ currentElement.value = nextElement.value;
327
+ }
328
+ }
329
+
330
+ function isFocusedElement(element: Element): boolean {
331
+ const root = element.getRootNode();
332
+
333
+ if (root instanceof ShadowRoot) {
334
+ return root.activeElement === element;
335
+ }
336
+
337
+ return element.ownerDocument?.activeElement === element;
338
+ }
339
+
340
+ /**
341
+ * Helper function to define custom element
342
+ */
343
+ export function defineComponent<T extends CustomElementConstructor>(
344
+ tagName: string,
345
+ componentClass: T
346
+ ): void {
347
+ try {
348
+ if (!customElements.get(tagName)) {
349
+ customElements.define(tagName, componentClass);
350
+ }
351
+ } catch (error) {
352
+ console.error(`Error defining ${tagName}:`, error);
353
+ }
354
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Global Error Handler
3
+ */
4
+ import { ERROR_MESSAGES } from '../constants/errorMessages.js';
5
+
6
+ export interface ErrorInfo {
7
+ message: string;
8
+ stack?: string;
9
+ timestamp: string;
10
+ context: Record<string, any>;
11
+ }
12
+
13
+ type ErrorListener = (errorInfo: ErrorInfo) => void;
14
+
15
+ class ErrorHandler {
16
+ private errorListeners = new Set<ErrorListener>();
17
+
18
+ constructor() {
19
+ this.setupGlobalHandlers();
20
+ }
21
+
22
+ private setupGlobalHandlers(): void {
23
+ window.addEventListener('error', (event) => {
24
+ console.error('Unhandled error:', event.error);
25
+ this.handleError(event.error);
26
+ event.preventDefault();
27
+ });
28
+
29
+ window.addEventListener('unhandledrejection', (event) => {
30
+ console.error('Unhandled promise rejection:', event.reason);
31
+ this.handleError(event.reason);
32
+ event.preventDefault();
33
+ });
34
+ }
35
+
36
+ handleError(error: Error | string, context: Record<string, any> = {}): void {
37
+ const errorInfo: ErrorInfo = {
38
+ message: this.getErrorMessage(error),
39
+ stack: (error as Error)?.stack,
40
+ timestamp: new Date().toISOString(),
41
+ context,
42
+ };
43
+
44
+ if (this.isDevelopment()) {
45
+ console.error('Error handled:', errorInfo);
46
+ }
47
+
48
+ this.notifyListeners(errorInfo);
49
+ }
50
+
51
+ private getErrorMessage(error: Error | string): string {
52
+ if (typeof error === 'string') return error;
53
+
54
+ if (error?.message?.includes('fetch') || error?.message?.includes('network')) {
55
+ return ERROR_MESSAGES.NETWORK_ERROR;
56
+ }
57
+
58
+ if (error?.message?.includes('timeout')) {
59
+ return ERROR_MESSAGES.TIMEOUT;
60
+ }
61
+
62
+ return error?.message || ERROR_MESSAGES.UNKNOWN_ERROR;
63
+ }
64
+
65
+ onError(listener: ErrorListener): () => void {
66
+ this.errorListeners.add(listener);
67
+ return () => this.errorListeners.delete(listener);
68
+ }
69
+
70
+ private notifyListeners(errorInfo: ErrorInfo): void {
71
+ this.errorListeners.forEach(listener => {
72
+ try {
73
+ listener(errorInfo);
74
+ } catch (err) {
75
+ console.error('Error in error listener:', err);
76
+ }
77
+ });
78
+ }
79
+
80
+ private isDevelopment(): boolean {
81
+ return window.location.hostname === 'localhost';
82
+ }
83
+ }
84
+
85
+ export default new ErrorHandler();