diff --git a/test-footer-subscribe.html b/test-footer-subscribe.html
new file mode 100644
index 0000000..765bad5
--- /dev/null
+++ b/test-footer-subscribe.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+ Footer Subscribe Button Test
+
+
+
+
+
+ Footer Subscribe Button - Fixed Layout
+ The subscriber count is now displayed below the button instead of inside it:
+
+
+
+
Without Subscribers
+
Button appears alone when subscriber count is 0
+
+
+
With Subscribers
+
Count appears below the button as secondary text
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-incident-subscriptions.html b/test-incident-subscriptions.html
new file mode 100644
index 0000000..ad81342
--- /dev/null
+++ b/test-incident-subscriptions.html
@@ -0,0 +1,286 @@
+
+
+
+
+
+ Incident Subscription Test
+
+
+
+
+
+
Incident Subscription Feature Demo
+
+
+
How Incident Subscriptions Work:
+
+ - Each incident can be individually subscribed to
+ - Click on an incident to expand it and see the "Subscribe to updates" button
+ - Subscribed incidents show a checkmark and "Subscribed to updates" text
+ - Click again to unsubscribe
+ - The component emits events for subscribe/unsubscribe actions
+ - You can pre-set subscribed incidents using the
subscribedIncidentIds property
+
+
+
+
+
+
+ Event Log:
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ts_web/elements/upl-statuspage-footer.ts b/ts_web/elements/upl-statuspage-footer.ts
index 0346f3d..cbfcea2 100644
--- a/ts_web/elements/upl-statuspage-footer.ts
+++ b/ts_web/elements/upl-statuspage-footer.ts
@@ -298,10 +298,16 @@ export class UplStatuspageFooter extends DeesElement {
border-color: ${colors.border.muted};
}
+ .subscribe-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: ${unsafeCSS(spacing.xs)};
+ }
+
.subscriber-count {
font-size: 12px;
- color: ${colors.text.muted};
- margin-top: ${unsafeCSS(spacing.xs)};
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
}
.error-message {
@@ -468,12 +474,14 @@ export class UplStatuspageFooter extends DeesElement {
+
+
+
+
Incident Subscription Management
+
{
+ const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
+
+ // Sample incidents with subscription
+ const currentIncidents: IIncidentDetails[] = [
+ {
+ id: 'sub-001',
+ title: 'Payment Gateway Intermittent Failures',
+ status: 'monitoring',
+ severity: 'major',
+ affectedServices: ['payment-gateway', 'checkout'],
+ startTime: Date.now() - 2 * 60 * 60 * 1000,
+ impact: 'Some customers may experience payment failures during checkout',
+ updates: [
+ {
+ id: 'sub-u1',
+ timestamp: Date.now() - 2 * 60 * 60 * 1000,
+ status: 'investigating',
+ message: 'We are investigating reports of payment failures',
+ author: 'Payment Team'
+ },
+ {
+ id: 'sub-u2',
+ timestamp: Date.now() - 1 * 60 * 60 * 1000,
+ status: 'identified',
+ message: 'Issue identified with payment processor API rate limits',
+ author: 'Payment Team'
+ },
+ {
+ id: 'sub-u3',
+ timestamp: Date.now() - 30 * 60 * 1000,
+ status: 'monitoring',
+ message: 'Temporary fix applied, monitoring for stability',
+ author: 'Payment Team'
+ }
+ ]
+ },
+ {
+ id: 'sub-002',
+ title: 'Email Delivery Delays',
+ status: 'identified',
+ severity: 'minor',
+ affectedServices: ['email-service'],
+ startTime: Date.now() - 45 * 60 * 1000,
+ impact: 'Transactional emails may be delayed by 5-10 minutes',
+ updates: [
+ {
+ id: 'sub-u4',
+ timestamp: Date.now() - 45 * 60 * 1000,
+ status: 'investigating',
+ message: 'Investigating email queue backlog',
+ author: 'Infrastructure Team'
+ },
+ {
+ id: 'sub-u5',
+ timestamp: Date.now() - 20 * 60 * 1000,
+ status: 'identified',
+ message: 'High volume causing queue delays, scaling up workers',
+ author: 'Infrastructure Team'
+ }
+ ]
+ }
+ ];
+
+ incidents.currentIncidents = currentIncidents;
+ incidents.pastIncidents = [];
+
+ // Pre-subscribe to first incident
+ incidents.subscribedIncidentIds = ['sub-001'];
+
+ // Create subscription status display
+ const statusDiv = document.createElement('div');
+ statusDiv.className = 'subscription-status';
+ statusDiv.style.cssText = `
+ margin-top: 16px;
+ padding: 16px;
+ background: #f0f9ff;
+ border: 1px solid #bae6fd;
+ border-radius: 6px;
+ font-size: 14px;
+ `;
+
+ const updateStatus = () => {
+ const subscribed = Array.from(incidents.subscribedIncidents || []);
+ statusDiv.innerHTML = `
+ Subscription Status:
+ ${subscribed.length === 0 ?
+ 'Not subscribed to any incidents' :
+ `Subscribed to ${subscribed.length} incident(s):
` +
+ subscribed.map(id => {
+ const incident = currentIncidents.find(i => i.id === id);
+ return incident ? `• ${incident.title}` : '';
+ }).filter(Boolean).join('
')}
+ `;
+ };
+
+ updateStatus();
+ wrapperElement.appendChild(statusDiv);
+
+ // Handle subscription events
+ incidents.addEventListener('incidentSubscribe', (event: CustomEvent) => {
+ console.log('Subscribed:', event.detail);
+ updateStatus();
+
+ // Show notification
+ const notification = document.createElement('div');
+ notification.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ padding: 16px;
+ background: #10b981;
+ color: white;
+ border-radius: 6px;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+ z-index: 1000;
+ animation: slideIn 0.3s ease;
+ `;
+ notification.textContent = `✓ Subscribed to: ${event.detail.incidentTitle}`;
+ document.body.appendChild(notification);
+ setTimeout(() => notification.remove(), 3000);
+ });
+
+ incidents.addEventListener('incidentUnsubscribe', (event: CustomEvent) => {
+ console.log('Unsubscribed:', event.detail);
+ updateStatus();
+
+ // Show notification
+ const notification = document.createElement('div');
+ notification.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ padding: 16px;
+ background: #6b7280;
+ color: white;
+ border-radius: 6px;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+ z-index: 1000;
+ animation: slideIn 0.3s ease;
+ `;
+ notification.textContent = `Unsubscribed from: ${event.detail.incident.title}`;
+ document.body.appendChild(notification);
+ setTimeout(() => notification.remove(), 3000);
+ });
+
+ // Add info text
+ const infoDiv = document.createElement('div');
+ infoDiv.style.cssText = `
+ margin-top: 12px;
+ padding: 12px;
+ background: #f3f4f6;
+ border-radius: 4px;
+ font-size: 13px;
+ color: #6b7280;
+ `;
+ infoDiv.innerHTML = `
+ How it works:
+ • Click on an incident to expand it
+ • Click "Subscribe to updates" to get notifications
+ • Subscribed incidents show a checkmark
+ • Click again to unsubscribe
+ `;
+ wrapperElement.appendChild(infoDiv);
+ }}
+ >
+
+
+
`;
\ No newline at end of file
diff --git a/ts_web/elements/upl-statuspage-incidents.ts b/ts_web/elements/upl-statuspage-incidents.ts
index 8d757b9..73aaa1f 100644
--- a/ts_web/elements/upl-statuspage-incidents.ts
+++ b/ts_web/elements/upl-statuspage-incidents.ts
@@ -51,16 +51,42 @@ export class UplStatuspageIncidents extends DeesElement {
})
public daysToShow = 90;
+ @property({
+ type: Array,
+ })
+ public subscribedIncidentIds: string[] = [];
+
@property({
type: Object,
state: true,
})
private expandedIncidents: Set = new Set();
+ @property({
+ type: Object,
+ state: true,
+ })
+ private subscribedIncidents: Set = new Set();
+
constructor() {
super();
}
+ async connectedCallback() {
+ await super.connectedCallback();
+ // Initialize subscribed incidents from the property
+ if (this.subscribedIncidentIds.length > 0) {
+ this.subscribedIncidents = new Set(this.subscribedIncidentIds);
+ }
+ }
+
+ updated(changedProperties: Map) {
+ super.updated(changedProperties);
+ if (changedProperties.has('subscribedIncidentIds')) {
+ this.subscribedIncidents = new Set(this.subscribedIncidentIds);
+ }
+ }
+
public static styles = [
plugins.domtools.elementBasic.staticStyles,
commonStyles,
@@ -312,6 +338,46 @@ export class UplStatuspageIncidents extends DeesElement {
.show-more-button:active {
transform: translateY(0);
}
+
+ .incident-actions {
+ display: flex;
+ gap: ${unsafeCSS(spacing.md)};
+ align-items: center;
+ margin-top: ${unsafeCSS(spacing.lg)};
+ padding-top: ${unsafeCSS(spacing.lg)};
+ border-top: 1px solid ${colors.border.default};
+ }
+
+ .subscribe-button {
+ display: inline-flex;
+ align-items: center;
+ gap: ${unsafeCSS(spacing.xs)};
+ padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.md)};
+ background: transparent;
+ border: 1px solid ${colors.border.default};
+ border-radius: ${unsafeCSS(borderRadius.base)};
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 400;
+ transition: all 0.2s ease;
+ color: ${colors.text.primary};
+ font-family: ${unsafeCSS(fonts.base)};
+ }
+
+ .subscribe-button:hover {
+ background: ${colors.background.secondary};
+ border-color: ${colors.border.muted};
+ }
+
+ .subscribe-button.subscribed {
+ background: ${cssManager.bdTheme('#f0fdf4', '#064e3b')};
+ border-color: ${cssManager.bdTheme('#86efac', '#047857')};
+ color: ${cssManager.bdTheme('#047857', '#86efac')};
+ }
+
+ .subscribe-button.subscribed:hover {
+ background: ${cssManager.bdTheme('#dcfce7', '#065f46')};
+ }
.collapsed-hint {
font-size: 12px;
@@ -470,6 +536,35 @@ export class UplStatuspageIncidents extends DeesElement {
Resolution: ${incident.resolution}
` : ''}
+
+
+
+ ${isCurrent ? html`
+
Get notified when this incident is updated or resolved
+ ` : ''}
+
` : ''}
@@ -570,6 +665,38 @@ export class UplStatuspageIncidents extends DeesElement {
console.log('Show more incidents');
}
+ private isSubscribedToIncident(incidentId: string): boolean {
+ return this.subscribedIncidents.has(incidentId);
+ }
+
+ private handleIncidentSubscribe(incident: IIncidentDetails) {
+ const newSubscribed = new Set(this.subscribedIncidents);
+ if (newSubscribed.has(incident.id)) {
+ newSubscribed.delete(incident.id);
+ this.dispatchEvent(new CustomEvent('incidentUnsubscribe', {
+ detail: {
+ incident,
+ incidentId: incident.id
+ },
+ bubbles: true,
+ composed: true
+ }));
+ } else {
+ newSubscribed.add(incident.id);
+ this.dispatchEvent(new CustomEvent('incidentSubscribe', {
+ detail: {
+ incident,
+ incidentId: incident.id,
+ incidentTitle: incident.title,
+ affectedServices: incident.affectedServices
+ },
+ bubbles: true,
+ composed: true
+ }));
+ }
+ this.subscribedIncidents = newSubscribed;
+ }
+
public dispatchReportNewIncident() {
this.dispatchEvent(new CustomEvent('reportNewIncident', {
bubbles: true,