@uptime.link/statuspage 1.0.73 → 1.1.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 (96) hide show
  1. package/dist_bundle/bundle.js +4096 -504
  2. package/dist_bundle/bundle.js.map +4 -4
  3. package/dist_ts_web/00_commitinfo_data.js +2 -2
  4. package/dist_ts_web/elements/index.d.ts +3 -0
  5. package/dist_ts_web/elements/index.js +6 -1
  6. package/dist_ts_web/elements/internal/uplinternal-miniheading.d.ts +1 -0
  7. package/dist_ts_web/elements/internal/uplinternal-miniheading.js +78 -28
  8. package/dist_ts_web/elements/upl-statuspage-assetsselector.d.ts +14 -0
  9. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.d.ts +1 -0
  10. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.js +575 -0
  11. package/dist_ts_web/elements/upl-statuspage-assetsselector.js +605 -43
  12. package/dist_ts_web/elements/upl-statuspage-footer.d.ts +46 -2
  13. package/dist_ts_web/elements/upl-statuspage-footer.demo.d.ts +1 -0
  14. package/dist_ts_web/elements/upl-statuspage-footer.demo.js +679 -0
  15. package/dist_ts_web/elements/upl-statuspage-footer.js +792 -61
  16. package/dist_ts_web/elements/upl-statuspage-header.d.ts +5 -1
  17. package/dist_ts_web/elements/upl-statuspage-header.demo.d.ts +1 -0
  18. package/dist_ts_web/elements/upl-statuspage-header.demo.js +220 -0
  19. package/dist_ts_web/elements/upl-statuspage-header.js +313 -86
  20. package/dist_ts_web/elements/upl-statuspage-incidents.d.ts +22 -4
  21. package/dist_ts_web/elements/upl-statuspage-incidents.demo.d.ts +1 -0
  22. package/dist_ts_web/elements/upl-statuspage-incidents.demo.js +1147 -0
  23. package/dist_ts_web/elements/upl-statuspage-incidents.js +750 -74
  24. package/dist_ts_web/elements/upl-statuspage-pagetitle.d.ts +15 -0
  25. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.d.ts +1 -0
  26. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.js +25 -0
  27. package/dist_ts_web/elements/upl-statuspage-pagetitle.js +148 -0
  28. package/dist_ts_web/elements/upl-statuspage-statsgrid.d.ts +23 -0
  29. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.d.ts +1 -0
  30. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.js +295 -0
  31. package/dist_ts_web/elements/upl-statuspage-statsgrid.js +374 -0
  32. package/dist_ts_web/elements/upl-statuspage-statusbar.d.ts +4 -0
  33. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.d.ts +1 -0
  34. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.js +365 -0
  35. package/dist_ts_web/elements/upl-statuspage-statusbar.js +357 -44
  36. package/dist_ts_web/elements/upl-statuspage-statusdetails.d.ts +14 -0
  37. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.d.ts +1 -0
  38. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.js +706 -0
  39. package/dist_ts_web/elements/upl-statuspage-statusdetails.js +373 -63
  40. package/dist_ts_web/elements/upl-statuspage-statusmonth.d.ts +15 -0
  41. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.d.ts +1 -0
  42. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.js +798 -0
  43. package/dist_ts_web/elements/upl-statuspage-statusmonth.js +474 -100
  44. package/dist_ts_web/interfaces/index.d.ts +84 -0
  45. package/dist_ts_web/interfaces/index.js +4 -0
  46. package/dist_ts_web/pages/index.d.ts +4 -1
  47. package/dist_ts_web/pages/index.js +5 -2
  48. package/dist_ts_web/pages/statuspage-allgreen.d.ts +1 -0
  49. package/dist_ts_web/pages/statuspage-allgreen.js +386 -0
  50. package/dist_ts_web/pages/statuspage-demo.d.ts +1 -0
  51. package/dist_ts_web/pages/statuspage-demo.js +616 -0
  52. package/dist_ts_web/pages/statuspage-maintenance.d.ts +1 -0
  53. package/dist_ts_web/pages/statuspage-maintenance.js +544 -0
  54. package/dist_ts_web/pages/statuspage-outage.d.ts +1 -0
  55. package/dist_ts_web/pages/statuspage-outage.js +543 -0
  56. package/dist_ts_web/styles/shared.styles.d.ts +80 -0
  57. package/dist_ts_web/styles/shared.styles.js +351 -0
  58. package/dist_watch/bundle.js +54534 -26433
  59. package/dist_watch/bundle.js.map +4 -4
  60. package/dist_watch/index.html +3 -10
  61. package/npmextra.json +9 -3
  62. package/package.json +19 -19
  63. package/readme.hints.md +292 -0
  64. package/readme.md +326 -149
  65. package/readme.plan.md +261 -0
  66. package/ts_web/00_commitinfo_data.ts +1 -1
  67. package/ts_web/elements/index.ts +6 -0
  68. package/ts_web/elements/internal/uplinternal-miniheading.ts +24 -17
  69. package/ts_web/elements/upl-statuspage-assetsselector.demo.ts +607 -0
  70. package/ts_web/elements/upl-statuspage-assetsselector.ts +526 -18
  71. package/ts_web/elements/upl-statuspage-footer.demo.ts +744 -0
  72. package/ts_web/elements/upl-statuspage-footer.ts +608 -30
  73. package/ts_web/elements/upl-statuspage-header.demo.ts +241 -0
  74. package/ts_web/elements/upl-statuspage-header.ts +220 -52
  75. package/ts_web/elements/upl-statuspage-incidents.demo.ts +1216 -0
  76. package/ts_web/elements/upl-statuspage-incidents.ts +649 -26
  77. package/ts_web/elements/upl-statuspage-pagetitle.demo.ts +25 -0
  78. package/ts_web/elements/upl-statuspage-pagetitle.ts +89 -0
  79. package/ts_web/elements/upl-statuspage-statsgrid.demo.ts +315 -0
  80. package/ts_web/elements/upl-statuspage-statsgrid.ts +306 -0
  81. package/ts_web/elements/upl-statuspage-statusbar.demo.ts +393 -0
  82. package/ts_web/elements/upl-statuspage-statusbar.ts +281 -20
  83. package/ts_web/elements/upl-statuspage-statusdetails.demo.ts +754 -0
  84. package/ts_web/elements/upl-statuspage-statusdetails.ts +297 -38
  85. package/ts_web/elements/upl-statuspage-statusmonth.demo.ts +876 -0
  86. package/ts_web/elements/upl-statuspage-statusmonth.ts +397 -76
  87. package/ts_web/interfaces/index.ts +95 -0
  88. package/ts_web/pages/index.ts +4 -1
  89. package/ts_web/pages/statuspage-allgreen.ts +412 -0
  90. package/ts_web/pages/statuspage-demo.ts +653 -0
  91. package/ts_web/pages/statuspage-maintenance.ts +570 -0
  92. package/ts_web/pages/statuspage-outage.ts +568 -0
  93. package/ts_web/styles/shared.styles.ts +367 -0
  94. package/dist_ts_web/pages/page1.d.ts +0 -1
  95. package/dist_ts_web/pages/page1.js +0 -11
  96. package/ts_web/pages/page1.ts +0 -11
@@ -1,5 +1,7 @@
1
- import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager } from '@design.estate/dees-element';
1
+ import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, unsafeCSS } from '@design.estate/dees-element';
2
2
  import * as domtools from '@design.estate/dees-domtools';
3
+ import * as sharedStyles from '../styles/shared.styles.js';
4
+ import { demoFunc } from './upl-statuspage-footer.demo.js';
3
5
 
4
6
  declare global {
5
7
  interface HTMLElementTagNameMap {
@@ -10,18 +12,80 @@ declare global {
10
12
  @customElement('upl-statuspage-footer')
11
13
  export class UplStatuspageFooter extends DeesElement {
12
14
  // STATIC
13
- public static demo = () => html`
14
- <upl-statuspage-footer></upl-statuspage-footer>
15
- `;
15
+ public static demo = demoFunc;
16
16
 
17
17
  // INSTANCE
18
- @property()
19
- public legalInfo: string = "https://lossless.gmbh";
18
+ @property({ type: String })
19
+ accessor companyName: string = '';
20
20
 
21
- @property({
22
- type: Boolean
23
- })
24
- public whitelabel = false;
21
+ @property({ type: String })
22
+ accessor legalUrl: string = '';
23
+
24
+ @property({ type: String })
25
+ accessor supportEmail: string = '';
26
+
27
+ @property({ type: String })
28
+ accessor statusPageUrl: string = '';
29
+
30
+ @property({ type: Boolean })
31
+ accessor whitelabel: boolean = false;
32
+
33
+ @property({ type: Number })
34
+ accessor lastUpdated: number | null = null;
35
+
36
+ @property({ type: Number })
37
+ accessor currentYear: number = new Date().getFullYear();
38
+
39
+ @property({ type: Array })
40
+ accessor socialLinks: Array<{ platform: string; url: string }> = [];
41
+
42
+ @property({ type: Array })
43
+ accessor additionalLinks: Array<{ label: string; url: string }> = [];
44
+
45
+ @property({ type: String })
46
+ accessor rssFeedUrl: string = '';
47
+
48
+ @property({ type: String })
49
+ accessor apiStatusUrl: string = '';
50
+
51
+ @property({ type: Boolean })
52
+ accessor loading: boolean = false;
53
+
54
+ @property({ type: String })
55
+ accessor errorMessage: string | null = null;
56
+
57
+ @property({ type: Boolean })
58
+ accessor offline: boolean = false;
59
+
60
+ @property({ type: String })
61
+ accessor latestStatusUpdate: string = '';
62
+
63
+ @property({ type: Boolean })
64
+ accessor enableSubscribe: boolean = false;
65
+
66
+ @property({ type: Boolean })
67
+ accessor enableReportIssue: boolean = false;
68
+
69
+ @property({ type: Boolean })
70
+ accessor enableLanguageSelector: boolean = false;
71
+
72
+ @property({ type: Boolean })
73
+ accessor enableThemeToggle: boolean = false;
74
+
75
+ @property({ type: Array })
76
+ accessor languageOptions: Array<{ code: string; label: string }> = [];
77
+
78
+ @property({ type: String })
79
+ accessor currentLanguage: string = 'en';
80
+
81
+ @property({ type: String })
82
+ accessor currentTheme: string = 'light';
83
+
84
+ @property({ type: Number })
85
+ accessor subscriberCount: number = 0;
86
+
87
+ @property({ type: Object })
88
+ accessor customBranding: { primaryColor?: string; logoUrl?: string; footerText?: string } | null = null;
25
89
 
26
90
 
27
91
  constructor() {
@@ -30,43 +94,557 @@ export class UplStatuspageFooter extends DeesElement {
30
94
 
31
95
  public static styles = [
32
96
  domtools.elementBasic.staticStyles,
97
+ sharedStyles.commonStyles,
33
98
  css`
34
99
  :host {
35
- display: block;
36
- background: ${cssManager.bdTheme('#ffffff', '#000000')};
37
- font-family: Inter;
38
- color: ${cssManager.bdTheme('#333333', '#ffffff')};
100
+ display: block;
101
+ background: ${sharedStyles.colors.background.primary};
102
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
103
+ color: ${sharedStyles.colors.text.primary};
104
+ font-size: 14px;
105
+ border-top: 1px solid ${sharedStyles.colors.border.default};
106
+ }
107
+
108
+ .container {
109
+ max-width: 1200px;
110
+ margin: 0 auto;
111
+ padding: ${unsafeCSS(sharedStyles.spacing['2xl'])} ${unsafeCSS(sharedStyles.spacing.lg)};
112
+ }
113
+
114
+ .footer-content {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: ${unsafeCSS(sharedStyles.spacing.xl)};
118
+ }
119
+
120
+ .footer-main {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ align-items: start;
124
+ gap: ${unsafeCSS(sharedStyles.spacing['2xl'])};
125
+ }
126
+
127
+ .company-info {
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
131
+ }
132
+
133
+ .company-name {
134
+ font-size: 16px;
135
+ font-weight: 500;
136
+ color: ${sharedStyles.colors.text.primary};
137
+ letter-spacing: -0.01em;
138
+ }
139
+
140
+ .company-links {
141
+ display: flex;
142
+ flex-wrap: wrap;
143
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
144
+ }
145
+
146
+ .footer-link {
147
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
148
+ text-decoration: none;
149
+ transition: color 0.15s ease;
150
+ font-size: 13px;
151
+ font-weight: 400;
152
+ }
153
+
154
+ .footer-link:hover {
155
+ color: ${sharedStyles.colors.text.primary};
156
+ }
157
+
158
+ .footer-actions {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
162
+ }
163
+
164
+ .action-button {
165
+ padding: 8px 16px;
166
+ height: 36px;
167
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
168
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
169
+ border-radius: 6px;
170
+ cursor: pointer;
171
+ text-align: center;
172
+ transition: all 0.15s ease;
173
+ white-space: nowrap;
174
+ font-size: 13px;
175
+ font-weight: 400;
176
+ color: ${sharedStyles.colors.text.primary};
177
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ }
182
+
183
+ .action-button:hover {
184
+ background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
185
+ border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
186
+ }
187
+
188
+ .footer-bottom {
189
+ display: flex;
190
+ justify-content: space-between;
191
+ align-items: center;
192
+ padding-top: ${unsafeCSS(sharedStyles.spacing.lg)};
193
+ margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
194
+ border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
195
+ }
196
+
197
+ .footer-meta {
198
+ display: flex;
199
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
200
+ align-items: center;
201
+ flex-wrap: wrap;
202
+ }
203
+
204
+ .social-links {
205
+ display: flex;
206
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
207
+ align-items: center;
208
+ }
209
+
210
+ .social-link {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ width: 32px;
215
+ height: 32px;
216
+ border-radius: 6px;
217
+ transition: all 0.15s ease;
218
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
219
+ }
220
+
221
+ .social-link:hover {
222
+ color: ${sharedStyles.colors.text.primary};
223
+ background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
224
+ }
225
+
226
+ .social-link svg {
227
+ width: 16px;
228
+ height: 16px;
229
+ fill: currentColor;
230
+ }
231
+
232
+ .copyright {
233
+ font-size: 13px;
234
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
235
+ }
236
+
237
+ .last-updated {
238
+ font-size: 12px;
239
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
240
+ }
241
+
242
+ .powered-by {
243
+ font-size: 12px;
244
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
245
+ text-align: right;
246
+ }
247
+
248
+ .powered-by a {
249
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
250
+ text-decoration: none;
251
+ transition: color 0.15s ease;
252
+ }
253
+
254
+ .powered-by a:hover {
255
+ color: ${sharedStyles.colors.text.primary};
256
+ }
257
+
258
+ .status-update {
259
+ padding: 12px 16px;
260
+ background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
261
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
262
+ border-radius: 6px;
263
+ font-size: 13px;
264
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
265
+ line-height: 1.5;
266
+ color: ${cssManager.bdTheme('#4b5563', '#d1d5db')};
267
+ }
268
+
269
+ .language-selector {
270
+ position: relative;
271
+ }
272
+
273
+ .language-selector select {
274
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
275
+ border: 1px solid ${sharedStyles.colors.border.default};
276
+ background: ${sharedStyles.colors.background.primary};
277
+ color: ${sharedStyles.colors.text.primary};
278
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
279
+ font-size: 14px;
280
+ cursor: pointer;
281
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
282
+ }
283
+
284
+ .theme-toggle {
285
+ cursor: pointer;
286
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
287
+ border: 1px solid ${sharedStyles.colors.border.default};
288
+ background: transparent;
289
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
290
+ font-size: 14px;
291
+ transition: all 0.2s ease;
292
+ color: ${sharedStyles.colors.text.primary};
293
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
294
+ }
295
+
296
+ .theme-toggle:hover {
297
+ background: ${sharedStyles.colors.background.secondary};
298
+ border-color: ${sharedStyles.colors.border.muted};
299
+ }
300
+
301
+ .subscribe-wrapper {
302
+ display: flex;
303
+ flex-direction: column;
304
+ align-items: center;
305
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
306
+ }
307
+
308
+ .subscriber-count {
309
+ font-size: 12px;
310
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
311
+ }
312
+
313
+ .error-message {
314
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
315
+ background: ${cssManager.bdTheme('#fee9e9', '#7f1d1d')};
316
+ color: ${cssManager.bdTheme('#dc2626', '#fca5a5')};
317
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
318
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
319
+ font-size: 14px;
320
+ border: 1px solid ${cssManager.bdTheme('#fecaca', '#991b1b')};
321
+ }
322
+
323
+ .offline-indicator {
324
+ display: inline-flex;
325
+ align-items: center;
326
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
327
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
328
+ background: ${sharedStyles.colors.status.degraded};
329
+ color: white;
330
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
331
+ font-size: 13px;
332
+ font-weight: 500;
333
+ }
334
+
335
+ .loading-skeleton {
336
+ height: 200px;
337
+ background: ${cssManager.bdTheme(
338
+ 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
339
+ 'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
340
+ )};
341
+ background-size: 200% 100%;
342
+ animation: loading 1.5s infinite;
343
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.md)};
344
+ }
345
+
346
+ @keyframes loading {
347
+ 0% { background-position: 200% 0; }
348
+ 100% { background-position: -200% 0; }
349
+ }
350
+
351
+ .additional-links {
352
+ display: flex;
353
+ flex-wrap: wrap;
354
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
355
+ margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
356
+ }
357
+
358
+ .additional-link {
359
+ font-size: 13px;
360
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
361
+ text-decoration: none;
362
+ transition: color 0.15s ease;
363
+ }
364
+
365
+ .additional-link:hover {
366
+ color: ${sharedStyles.colors.text.primary};
367
+ }
368
+
369
+ @media (max-width: 640px) {
370
+ .container {
371
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.md)};
372
+ }
373
+
374
+ .footer-main {
375
+ flex-direction: column;
376
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
377
+ }
378
+
379
+ .footer-bottom {
380
+ flex-direction: column;
381
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
382
+ align-items: start;
383
+ }
384
+
385
+ .footer-meta {
386
+ flex-direction: column;
387
+ align-items: start;
388
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
389
+ }
390
+
391
+ .company-links {
392
+ flex-direction: column;
393
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
39
394
  }
40
395
 
41
- .mainbox {
42
- max-width: 900px;
43
- margin: auto;
44
- padding-top: 20px;
45
- padding-bottom: 20px;
396
+ .powered-by {
397
+ text-align: left;
46
398
  }
47
-
399
+ }
48
400
  `
49
401
  ]
50
402
 
51
403
  public render(): TemplateResult {
404
+ if (this.loading) {
405
+ return html`
406
+ <div class="container">
407
+ <div class="loading-skeleton"></div>
408
+ </div>
409
+ `;
410
+ }
411
+
52
412
  return html`
53
- ${domtools.elementBasic.styles}
54
- <style></style>
55
- <div class="mainbox">
56
- Hi there
413
+ <div class="container">
414
+ <div class="footer-content">
415
+ ${this.errorMessage ? html`
416
+ <div class="error-message">${this.errorMessage}</div>
417
+ ` : ''}
418
+
419
+ ${this.offline ? html`
420
+ <div class="offline-indicator">
421
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
422
+ <path d="M6 2V6M6 10H6.01M11 6C11 8.76142 8.76142 11 6 11C3.23858 11 1 8.76142 1 6C1 3.23858 3.23858 1 6 1C8.76142 1 11 3.23858 11 6Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
423
+ </svg>
424
+ You are currently offline
425
+ </div>
426
+ ` : ''}
427
+
428
+ ${this.latestStatusUpdate ? html`
429
+ <div class="status-update">
430
+ Latest Update: ${this.latestStatusUpdate}
431
+ </div>
432
+ ` : ''}
433
+
434
+ <div class="footer-main">
435
+ <div class="company-info">
436
+ ${this.companyName ? html`
437
+ <div class="company-name">${this.companyName}</div>
438
+ ` : ''}
439
+
440
+ <div class="company-links">
441
+ ${this.legalUrl && this.isValidUrl(this.legalUrl) ? html`
442
+ <a href="${this.legalUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'legal', this.legalUrl)}>Legal</a>
443
+ ` : ''}
444
+
445
+ ${this.supportEmail && this.isValidEmail(this.supportEmail) ? html`
446
+ <a href="mailto:${this.supportEmail}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'support', this.supportEmail)}>Support</a>
447
+ ` : ''}
448
+
449
+ ${this.statusPageUrl && this.isValidUrl(this.statusPageUrl) ? html`
450
+ <a href="${this.statusPageUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'status', this.statusPageUrl)}>Status Page</a>
451
+ ` : ''}
452
+
453
+ ${this.rssFeedUrl && this.isValidUrl(this.rssFeedUrl) ? html`
454
+ <a href="${this.rssFeedUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'rss', this.rssFeedUrl)}>RSS Feed</a>
455
+ ` : ''}
456
+
457
+ ${this.apiStatusUrl && this.isValidUrl(this.apiStatusUrl) ? html`
458
+ <a href="${this.apiStatusUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'api', this.apiStatusUrl)}>API Status</a>
459
+ ` : ''}
460
+ </div>
461
+
462
+ ${this.additionalLinks && this.additionalLinks.length > 0 ? html`
463
+ <div class="additional-links">
464
+ ${this.additionalLinks.map(link => html`
465
+ ${link.label && link.url && this.isValidUrl(link.url) ? html`
466
+ <a href="${link.url}" class="additional-link" @click=${(e: Event) => this.handleLinkClick(e, 'additional', link.url, link.label)}>
467
+ ${link.label}
468
+ </a>
469
+ ` : ''}
470
+ `)}
471
+ </div>
472
+ ` : ''}
473
+ </div>
474
+
475
+ <div class="footer-actions">
476
+ ${this.enableSubscribe ? html`
477
+ <div class="subscribe-wrapper">
478
+ <button class="action-button" @click=${this.handleSubscribeClick}>
479
+ Subscribe to Updates
480
+ </button>
481
+ ${this.subscriberCount > 0 ? html`
482
+ <div class="subscriber-count">${this.subscriberCount.toLocaleString()} subscribers</div>
483
+ ` : ''}
484
+ </div>
485
+ ` : ''}
486
+
487
+ ${this.enableReportIssue ? html`
488
+ <button class="action-button" @click=${this.handleReportIncidentClick}>
489
+ Report an Issue
490
+ </button>
491
+ ` : ''}
492
+
493
+ ${this.enableLanguageSelector && this.languageOptions.length > 0 ? html`
494
+ <div class="language-selector">
495
+ <select @change=${this.handleLanguageChange} .value=${this.currentLanguage}>
496
+ ${this.languageOptions.map(option => html`
497
+ <option value="${option.code}" ?selected=${option.code === this.currentLanguage}>
498
+ ${option.label}
499
+ </option>
500
+ `)}
501
+ </select>
502
+ </div>
503
+ ` : ''}
504
+
505
+ ${this.enableThemeToggle ? html`
506
+ <button class="theme-toggle" @click=${this.handleThemeToggle}>
507
+ ${this.currentTheme === 'dark' ? '☀️' : '🌙'} ${this.currentTheme === 'dark' ? 'Light' : 'Dark'} Mode
508
+ </button>
509
+ ` : ''}
510
+ </div>
511
+ </div>
512
+
513
+ <div class="footer-bottom">
514
+ <div class="footer-meta">
515
+ <div class="copyright">
516
+ © ${this.currentYear} ${this.companyName || 'Status Page'}
517
+ </div>
518
+
519
+ ${this.lastUpdated ? html`
520
+ <div class="last-updated">
521
+ Last updated: ${this.formatLastUpdated()}
522
+ </div>
523
+ ` : ''}
524
+
525
+ ${this.socialLinks && this.socialLinks.length > 0 ? html`
526
+ <div class="social-links">
527
+ ${this.socialLinks.map(social => html`
528
+ ${social.platform && social.url && this.isValidUrl(social.url) ? html`
529
+ <a href="${social.url}" class="social-link" title="${social.platform}" @click=${(e: Event) => this.handleLinkClick(e, 'social', social.url, social.platform)}>
530
+ ${this.getSocialIcon(social.platform)}
531
+ </a>
532
+ ` : ''}
533
+ `)}
534
+ </div>
535
+ ` : ''}
536
+ </div>
537
+
538
+ ${!this.whitelabel ? html`
539
+ <div class="powered-by">
540
+ ${this.customBranding?.footerText || html`
541
+ Powered by <a href="https://uptime.link" target="_blank" @click=${(e: Event) => this.handleLinkClick(e, 'powered-by', 'https://uptime.link')}>uptime.link</a>
542
+ `}
543
+ </div>
544
+ ` : ''}
545
+ </div>
546
+ </div>
57
547
  </div>
58
548
  `;
59
549
  }
60
550
 
551
+ private isValidUrl(url: string): boolean {
552
+ if (!url) return false;
553
+ try {
554
+ new URL(url);
555
+ return true;
556
+ } catch {
557
+ return url.startsWith('#') || url.startsWith('/');
558
+ }
559
+ }
560
+
561
+ private isValidEmail(email: string): boolean {
562
+ if (!email) return false;
563
+ return email.includes('@');
564
+ }
565
+
566
+ private formatLastUpdated(): string {
567
+ if (!this.lastUpdated) return 'Never';
568
+ const date = new Date(this.lastUpdated);
569
+ const now = Date.now();
570
+ const diff = now - this.lastUpdated;
571
+
572
+ if (diff < 60000) {
573
+ return 'Just now';
574
+ } else if (diff < 3600000) {
575
+ const minutes = Math.floor(diff / 60000);
576
+ return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
577
+ } else if (diff < 86400000) {
578
+ const hours = Math.floor(diff / 3600000);
579
+ return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
580
+ } else {
581
+ return date.toLocaleDateString();
582
+ }
583
+ }
584
+
585
+ private getSocialIcon(platform: string): TemplateResult {
586
+ const icons: Record<string, TemplateResult> = {
587
+ twitter: html`<svg viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>`,
588
+ github: html`<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>`,
589
+ linkedin: html`<svg viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>`,
590
+ facebook: html`<svg viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>`,
591
+ youtube: html`<svg viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>`,
592
+ instagram: html`<svg viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zM5.838 12a6.162 6.162 0 1 1 12.324 0 6.162 6.162 0 0 1-12.324 0zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm4.965-10.405a1.44 1.44 0 1 1 2.881.001 1.44 1.44 0 0 1-2.881-.001z"/></svg>`,
593
+ slack: html`<svg viewBox="0 0 24 24"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"/></svg>`,
594
+ discord: html`<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>`,
595
+ generic: html`<svg viewBox="0 0 24 24"><path d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z"/></svg>`
596
+ };
597
+ return icons[platform.toLowerCase()] || icons.generic;
598
+ }
599
+
600
+ private handleLinkClick(event: Event, type: string, url: string, label?: string) {
601
+ this.dispatchEvent(new CustomEvent('footerLinkClick', {
602
+ detail: { type, url, label },
603
+ bubbles: true,
604
+ composed: true
605
+ }));
606
+ }
607
+
608
+ private handleSubscribeClick() {
609
+ this.dispatchEvent(new CustomEvent('subscribeClick', {
610
+ bubbles: true,
611
+ composed: true
612
+ }));
613
+ }
614
+
615
+ private handleReportIncidentClick() {
616
+ this.dispatchEvent(new CustomEvent('reportIncidentClick', {
617
+ bubbles: true,
618
+ composed: true
619
+ }));
620
+ }
621
+
622
+ private handleLanguageChange(event: Event) {
623
+ const select = event.target as HTMLSelectElement;
624
+ const language = select.value;
625
+ this.currentLanguage = language;
626
+ this.dispatchEvent(new CustomEvent('languageChange', {
627
+ detail: { language },
628
+ bubbles: true,
629
+ composed: true
630
+ }));
631
+ }
632
+
633
+ private handleThemeToggle() {
634
+ const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
635
+ this.currentTheme = newTheme;
636
+ this.dispatchEvent(new CustomEvent('themeToggle', {
637
+ detail: { theme: newTheme },
638
+ bubbles: true,
639
+ composed: true
640
+ }));
641
+ }
642
+
61
643
  public dispatchReportNewIncident() {
62
- this.dispatchEvent(new CustomEvent('reportNewIncident', {
63
-
64
- }))
644
+ this.handleReportIncidentClick();
65
645
  }
66
646
 
67
647
  public dispatchStatusSubscribe() {
68
- this.dispatchEvent(new CustomEvent('statusSubscribe', {
69
-
70
- }))
648
+ this.handleSubscribeClick();
71
649
  }
72
650
  }