besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
1,160 lines (997 loc) • 30.8 kB
JavaScript
/**
* Notification Banner Component
* Implements notification count display with sidebar functionality
* Integrates with CosmoDB via APIM internal endpoints
*/
import centralTokenManager from '../../utils/centralTokenManager.js';
import notificationsService from '../../services/notificationsService.js';
class NotificationBanner {
constructor(options = {}) {
this.options = {
containerId: 'notification-banner-container',
position: 'top-right', // top-right, top-left, etc.
autoRefresh: true,
refreshInterval: 30000, // 30 seconds
maxDisplayCount: 9, // Show "9+" for counts above this
...options,
};
this.initialized = false;
this.isVisible = false;
this.sidebarVisible = false;
this.currentNotifications = [];
this.unreadCounts = {};
this.refreshTimer = null;
// Sidebar elements
this.rightSidebar = null;
this.leftSidebar = null;
this.overlay = null;
this.init();
}
/**
* Initialize the notification banner
*/
async init() {
try {
// Create banner HTML structure
this.createBannerStructure();
// Setup event listeners
this.setupEventListeners();
// Load initial notification counts
await this.loadNotificationCounts();
// Setup auto-refresh if enabled
if (this.options.autoRefresh) {
this.startAutoRefresh();
}
this.initialized = true;
console.log('[NotificationBanner] [SUCCESS] Initialized successfully');
} catch (error) {
console.error(
'[NotificationBanner] [ERROR] Initialization failed:',
error
);
}
}
/**
* Create the banner HTML structure
*/
createBannerStructure() {
// Check if already exists
if (document.getElementById('notification-banner')) {
return;
}
const bannerHTML = `
<div id="notification-banner" class="notification-banner">
<div class="notification-button" id="notification-button">
<div class="notification-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>
</svg>
</div>
<div class="notification-counts" id="notification-counts">
<div class="count-badge total-count" id="total-count" style="display: none;">0</div>
</div>
</div>
</div>
<!-- Right Sidebar for Notification List -->
<div id="notification-right-sidebar" class="notification-sidebar right-sidebar" style="display: none;">
<div class="sidebar-header">
<h3>Notifications</h3>
<div class="sidebar-controls">
<button id="mark-all-read-btn" class="control-btn">Mark All Read</button>
<button id="close-sidebar-btn" class="control-btn close-btn">×</button>
</div>
</div>
<div class="sidebar-content">
<div class="notification-filters">
<button class="filter-btn active" data-filter="all">All (<span id="filter-count-all">0</span>)</button>
<button class="filter-btn" data-filter="unread">Unread (<span id="filter-count-unread">0</span>)</button>
<button class="filter-btn" data-filter="system">System (<span id="filter-count-system">0</span>)</button>
<button class="filter-btn" data-filter="billing">Billing (<span id="filter-count-billing">0</span>)</button>
<button class="filter-btn" data-filter="technical">Technical (<span id="filter-count-technical">0</span>)</button>
</div>
<div id="notification-list" class="notification-list">
<div class="loading-message">Loading notifications...</div>
</div>
</div>
</div>
<!-- Left Sidebar for Full Notification Content -->
<div id="notification-left-sidebar" class="notification-sidebar left-sidebar" style="display: none;">
<div class="sidebar-header">
<h3>Notification Details</h3>
<button id="close-detail-sidebar-btn" class="control-btn close-btn">×</button>
</div>
<div class="sidebar-content">
<div id="notification-detail-content" class="notification-detail">
<p>Select a notification to view details</p>
</div>
<div class="notification-actions">
<button id="mark-read-btn" class="action-btn primary">Mark as Read</button>
<button id="delete-notification-btn" class="action-btn secondary">Delete</button>
</div>
</div>
</div>
<!-- Overlay -->
<div id="notification-overlay" class="notification-overlay" style="display: none;"></div>
`;
// Insert banner into page
const container =
document.getElementById(this.options.containerId) || document.body;
const bannerElement = document.createElement('div');
bannerElement.innerHTML = bannerHTML;
container.appendChild(bannerElement);
// Add CSS styles
this.addStyles();
}
/**
* Add CSS styles for the notification banner
*/
addStyles() {
if (document.getElementById('notification-banner-styles')) {
return;
}
const styles = `
<style id="notification-banner-styles">
.notification-banner {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
font-family: Arial, sans-serif;
}
.notification-button {
background: #022d54;
color: white;
border: none;
border-radius: 50%;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
transition: all 0.3s ease;
position: relative;
}
.notification-button:hover {
background: #034a8b;
transform: scale(1.05);
}
.notification-icon {
display: flex;
align-items: center;
justify-content: center;
}
.notification-counts {
position: absolute;
top: -8px;
right: -8px;
}
.count-badge {
background: #dc3545;
color: white;
border-radius: 10px;
padding: 2px 6px;
font-size: 12px;
font-weight: bold;
min-width: 18px;
text-align: center;
line-height: 1.2;
}
.notification-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 9998;
}
.notification-sidebar {
position: fixed;
top: 0;
bottom: 0;
width: 400px;
background: white;
z-index: 9999;
box-shadow: 0 0 20px rgba(0,0,0,0.3);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.notification-sidebar.visible {
transform: translateX(0);
}
.right-sidebar {
right: 0;
}
.left-sidebar {
left: 0;
transform: translateX(-100%);
}
.left-sidebar.visible {
transform: translateX(0);
}
.sidebar-header {
background: #022d54;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-header h3 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.sidebar-controls {
display: flex;
gap: 0.5rem;
align-items: center;
}
.control-btn {
background: rgba(255,255,255,0.2);
color: white;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 4px;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
}
.control-btn:hover {
background: rgba(255,255,255,0.3);
}
.close-btn {
width: 28px;
height: 28px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
}
.sidebar-content {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.notification-filters {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-bottom: 1rem;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 0.5rem;
}
.filter-btn {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.filter-btn:hover {
background: #e9ecef;
}
.filter-btn.active {
background: #022d54;
color: white;
border-color: #022d54;
}
.notification-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.notification-item {
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 0.75rem;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.notification-item:hover {
background: #f8f9fa;
border-color: #022d54;
}
.notification-item.unread {
border-left: 4px solid #dc3545;
background: #fff9f9;
}
.notification-item.unread:hover {
background: #fff5f5;
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.5rem;
}
.notification-title {
font-weight: 500;
color: #022d54;
font-size: 14px;
margin: 0;
}
.notification-time {
font-size: 12px;
color: #6c757d;
}
.notification-preview {
color: #555;
font-size: 13px;
line-height: 1.4;
margin: 0;
}
.notification-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid #f0f0f0;
}
.notification-type {
background: #e9ecef;
color: #495057;
padding: 0.125rem 0.375rem;
border-radius: 12px;
font-size: 11px;
text-transform: uppercase;
font-weight: 500;
}
.notification-actions-inline {
display: flex;
gap: 0.25rem;
}
.action-btn-small {
background: transparent;
color: #6c757d;
border: 1px solid #e0e0e0;
border-radius: 3px;
padding: 0.125rem 0.375rem;
cursor: pointer;
font-size: 11px;
transition: all 0.2s;
}
.action-btn-small:hover {
background: #f8f9fa;
color: #022d54;
}
.notification-detail {
background: white;
border-radius: 6px;
padding: 1rem;
margin-bottom: 1rem;
}
.notification-detail h4 {
margin: 0 0 0.5rem 0;
color: #022d54;
}
.notification-detail .detail-content {
color: #555;
line-height: 1.6;
}
.notification-actions {
display: flex;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid #e0e0e0;
}
.action-btn {
padding: 0.5rem 1rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.action-btn.primary {
background: #022d54;
color: white;
border-color: #022d54;
}
.action-btn.primary:hover {
background: #034a8b;
}
.action-btn.secondary {
background: white;
color: #6c757d;
}
.action-btn.secondary:hover {
background: #f8f9fa;
color: #dc3545;
}
.loading-message {
text-align: center;
color: #6c757d;
padding: 2rem;
font-style: italic;
}
.error-message {
text-align: center;
color: #dc3545;
padding: 2rem;
background: #fff5f5;
border-radius: 6px;
border: 1px solid #f8d7da;
}
.empty-message {
text-align: center;
color: #6c757d;
padding: 2rem;
}
@media (max-width: 768px) {
.notification-sidebar {
width: 100%;
}
.notification-banner {
top: 10px;
right: 10px;
}
.notification-button {
width: 44px;
height: 44px;
}
}
</style>
`;
document.head.insertAdjacentHTML('beforeend', styles);
}
/**
* Setup event listeners
*/
setupEventListeners() {
// Banner click - show right sidebar
const notificationButton = document.getElementById('notification-button');
if (notificationButton) {
notificationButton.addEventListener('click', e => {
e.stopPropagation();
this.toggleRightSidebar();
});
}
// Close sidebar buttons
const closeSidebarBtn = document.getElementById('close-sidebar-btn');
if (closeSidebarBtn) {
closeSidebarBtn.addEventListener('click', () => this.hideRightSidebar());
}
const closeDetailSidebarBtn = document.getElementById(
'close-detail-sidebar-btn'
);
if (closeDetailSidebarBtn) {
closeDetailSidebarBtn.addEventListener('click', () =>
this.hideLeftSidebar()
);
}
// Overlay click - close sidebars
const overlay = document.getElementById('notification-overlay');
if (overlay) {
overlay.addEventListener('click', () => this.hideAllSidebars());
}
// Mark all read button
const markAllReadBtn = document.getElementById('mark-all-read-btn');
if (markAllReadBtn) {
markAllReadBtn.addEventListener('click', () => this.markAllAsRead());
}
// Filter buttons
const filterBtns = document.querySelectorAll('.filter-btn');
filterBtns.forEach(btn => {
btn.addEventListener('click', e => {
const filter = e.target.dataset.filter;
this.applyFilter(filter);
});
});
// Detail view action buttons
const markReadBtn = document.getElementById('mark-read-btn');
if (markReadBtn) {
markReadBtn.addEventListener('click', () => this.markCurrentAsRead());
}
const deleteNotificationBtn = document.getElementById(
'delete-notification-btn'
);
if (deleteNotificationBtn) {
deleteNotificationBtn.addEventListener('click', () =>
this.deleteCurrentNotification()
);
}
// Close sidebars on ESC key
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
this.hideAllSidebars();
}
});
}
/**
* Load notification counts from the server
*/
async loadNotificationCounts() {
try {
// Check if user is authenticated
if (!centralTokenManager.isAuthenticated()) {
console.log(
'[NotificationBanner] User not authenticated, hiding banner'
);
this.hideBanner();
return;
}
console.log(
'[NotificationBanner] [LOADING] Loading notification counts...'
);
// Get notification statistics
const stats = await notificationsService.getNotificationStats();
if (stats && stats.counts) {
this.updateNotificationCounts(stats.counts);
this.showBanner();
} else {
console.warn('[NotificationBanner] No notification stats received');
this.hideBanner();
}
} catch (error) {
console.error(
'[NotificationBanner] Failed to load notification counts:',
error
);
this.hideBanner();
}
}
/**
* Update notification count display
*/
updateNotificationCounts(counts) {
this.unreadCounts = counts;
const totalCount = counts.total_unread || 0;
const totalCountElement = document.getElementById('total-count');
if (totalCountElement) {
if (totalCount > 0) {
const displayCount =
totalCount > this.options.maxDisplayCount
? `${this.options.maxDisplayCount}+`
: totalCount.toString();
totalCountElement.textContent = displayCount;
totalCountElement.style.display = 'block';
} else {
totalCountElement.style.display = 'none';
}
}
// Update filter counts in sidebar
this.updateFilterCounts(counts);
console.log('[NotificationBanner] [SUCCESS] Counts updated:', counts);
}
/**
* Update filter count displays
*/
updateFilterCounts(counts) {
const filterCounts = {
all: counts.total || 0,
unread: counts.total_unread || 0,
system: counts.system || 0,
billing: counts.billing || 0,
technical: counts.technical || 0,
};
Object.entries(filterCounts).forEach(([filter, count]) => {
const element = document.getElementById(`filter-count-${filter}`);
if (element) {
element.textContent = count;
}
});
}
/**
* Show the notification banner
*/
showBanner() {
const banner = document.getElementById('notification-banner');
if (banner) {
banner.style.display = 'block';
this.isVisible = true;
}
}
/**
* Hide the notification banner
*/
hideBanner() {
const banner = document.getElementById('notification-banner');
if (banner) {
banner.style.display = 'none';
this.isVisible = false;
}
}
/**
* Toggle right sidebar visibility
*/
toggleRightSidebar() {
if (this.sidebarVisible) {
this.hideRightSidebar();
} else {
this.showRightSidebar();
}
}
/**
* Show right sidebar with notification list
*/
async showRightSidebar() {
const sidebar = document.getElementById('notification-right-sidebar');
const overlay = document.getElementById('notification-overlay');
if (sidebar && overlay) {
overlay.style.display = 'block';
sidebar.style.display = 'flex';
// Trigger animation
setTimeout(() => {
sidebar.classList.add('visible');
}, 10);
this.sidebarVisible = true;
// Load notifications
await this.loadNotifications();
}
}
/**
* Hide right sidebar
*/
hideRightSidebar() {
const sidebar = document.getElementById('notification-right-sidebar');
const overlay = document.getElementById('notification-overlay');
if (sidebar) {
sidebar.classList.remove('visible');
setTimeout(() => {
sidebar.style.display = 'none';
if (overlay) overlay.style.display = 'none';
}, 300);
this.sidebarVisible = false;
}
}
/**
* Show left sidebar with notification details
*/
showLeftSidebar(notification) {
const sidebar = document.getElementById('notification-left-sidebar');
if (sidebar) {
sidebar.style.display = 'flex';
// Trigger animation
setTimeout(() => {
sidebar.classList.add('visible');
}, 10);
// Update content
this.updateNotificationDetail(notification);
}
}
/**
* Hide left sidebar
*/
hideLeftSidebar() {
const sidebar = document.getElementById('notification-left-sidebar');
if (sidebar) {
sidebar.classList.remove('visible');
setTimeout(() => {
sidebar.style.display = 'none';
}, 300);
}
}
/**
* Hide all sidebars
*/
hideAllSidebars() {
this.hideRightSidebar();
this.hideLeftSidebar();
}
/**
* Load notifications list
*/
async loadNotifications(filter = 'all') {
try {
const listContainer = document.getElementById('notification-list');
if (!listContainer) return;
// Show loading message
listContainer.innerHTML =
'<div class="loading-message">Loading notifications...</div>';
// Get notifications
const options = {
page: 1,
limit: 50,
status: filter === 'unread' ? 'unread' : 'all',
type: ['system', 'billing', 'technical'].includes(filter)
? filter
: 'all',
};
const result = await notificationsService.getNotifications(options);
this.currentNotifications = result.notifications || [];
// Render notifications
this.renderNotifications(this.currentNotifications, filter);
} catch (error) {
console.error(
'[NotificationBanner] Failed to load notifications:',
error
);
const listContainer = document.getElementById('notification-list');
if (listContainer) {
listContainer.innerHTML =
'<div class="error-message">Failed to load notifications. Please try again.</div>';
}
}
}
/**
* Render notifications in the list
*/
renderNotifications(notifications, _activeFilter = 'all') {
const listContainer = document.getElementById('notification-list');
if (!listContainer) return;
if (notifications.length === 0) {
listContainer.innerHTML =
'<div class="empty-message">No notifications found.</div>';
return;
}
const notificationsHTML = notifications
.map(notification => {
const isUnread = notification.status === 'unread';
const timeAgo = this.formatTimeAgo(notification.created_at);
const preview = this.truncateText(
notification.content || notification.message || '',
100
);
return `
<div class="notification-item ${isUnread ? 'unread' : ''}" data-notification-id="${notification.id}">
<div class="notification-header">
<h4 class="notification-title">${this.escapeHtml(notification.title || 'Notification')}</h4>
<span class="notification-time">${timeAgo}</span>
</div>
<p class="notification-preview">${this.escapeHtml(preview)}</p>
<div class="notification-meta">
<span class="notification-type">${notification.type || 'general'}</span>
<div class="notification-actions-inline">
${isUnread ? '<button class="action-btn-small mark-read" data-action="mark-read">Mark Read</button>' : ''}
<button class="action-btn-small view-detail" data-action="view">View</button>
</div>
</div>
</div>
`;
})
.join('');
listContainer.innerHTML = notificationsHTML;
// Add click listeners to notification items
listContainer.addEventListener('click', e => {
const notificationItem = e.target.closest('.notification-item');
if (notificationItem) {
const notificationId = notificationItem.dataset.notificationId;
const notification = notifications.find(n => n.id === notificationId);
if (e.target.dataset.action === 'mark-read') {
e.stopPropagation();
this.markNotificationAsRead(notificationId);
} else if (
e.target.dataset.action === 'view' ||
!e.target.dataset.action
) {
this.showNotificationDetail(notification);
}
}
});
}
/**
* Apply filter to notifications
*/
async applyFilter(filter) {
// Update active filter button
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
document
.querySelector(`[data-filter="${filter}"]`)
?.classList.add('active');
// Load filtered notifications
await this.loadNotifications(filter);
}
/**
* Show notification detail in left sidebar
*/
showNotificationDetail(notification) {
if (!notification) return;
this.currentNotification = notification;
this.showLeftSidebar(notification);
}
/**
* Update notification detail content
*/
updateNotificationDetail(notification) {
const contentContainer = document.getElementById(
'notification-detail-content'
);
if (!contentContainer || !notification) return;
const formattedDate = new Date(notification.created_at).toLocaleString();
contentContainer.innerHTML = `
<h4>${this.escapeHtml(notification.title || 'Notification')}</h4>
<div class="detail-meta" style="margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid #e0e0e0;">
<p style="margin: 0; font-size: 13px; color: #6c757d;">
<strong>Type:</strong> ${notification.type || 'general'} |
<strong>Date:</strong> ${formattedDate} |
<strong>Status:</strong> ${notification.status}
</p>
</div>
<div class="detail-content">
${notification.content || notification.message || 'No content available.'}
</div>
`;
// Update action buttons state
const markReadBtn = document.getElementById('mark-read-btn');
if (markReadBtn) {
markReadBtn.style.display =
notification.status === 'unread' ? 'inline-block' : 'none';
}
}
/**
* Mark notification as read
*/
async markNotificationAsRead(notificationId) {
try {
await notificationsService.updateNotificationStatus(
notificationId,
'read'
);
// Update local state
const notification = this.currentNotifications.find(
n => n.id === notificationId
);
if (notification) {
notification.status = 'read';
}
// Update current notification if it's the same
if (this.currentNotification?.id === notificationId) {
this.currentNotification.status = 'read';
this.updateNotificationDetail(this.currentNotification);
}
// Refresh counts and list
await this.loadNotificationCounts();
await this.loadNotifications();
console.log('[NotificationBanner] [SUCCESS] Notification marked as read');
} catch (error) {
console.error(
'[NotificationBanner] Failed to mark notification as read:',
error
);
}
}
/**
* Mark current notification as read
*/
async markCurrentAsRead() {
if (this.currentNotification) {
await this.markNotificationAsRead(this.currentNotification.id);
}
}
/**
* Mark all notifications as read
*/
async markAllAsRead() {
try {
await notificationsService.markAllAsRead();
// Update local state
this.currentNotifications.forEach(notification => {
notification.status = 'read';
});
// Refresh counts and list
await this.loadNotificationCounts();
await this.loadNotifications();
console.log(
'[NotificationBanner] [SUCCESS] All notifications marked as read'
);
} catch (error) {
console.error(
'[NotificationBanner] Failed to mark all notifications as read:',
error
);
}
}
/**
* Delete current notification
*/
async deleteCurrentNotification() {
if (!this.currentNotification) return;
if (confirm('Are you sure you want to delete this notification?')) {
try {
await notificationsService.deleteNotification(
this.currentNotification.id
);
// Remove from local state
this.currentNotifications = this.currentNotifications.filter(
n => n.id !== this.currentNotification.id
);
// Hide detail sidebar
this.hideLeftSidebar();
// Refresh counts and list
await this.loadNotificationCounts();
await this.loadNotifications();
console.log('[NotificationBanner] [SUCCESS] Notification deleted');
} catch (error) {
console.error(
'[NotificationBanner] Failed to delete notification:',
error
);
}
}
}
/**
* Start auto-refresh timer
*/
startAutoRefresh() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
this.refreshTimer = setInterval(async () => {
if (this.isVisible) {
await this.loadNotificationCounts();
}
}, this.options.refreshInterval);
}
/**
* Stop auto-refresh timer
*/
stopAutoRefresh() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
/**
* Format time ago string
*/
formatTimeAgo(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'Just now';
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
if (diffInSeconds < 86400)
return `${Math.floor(diffInSeconds / 3600)}h ago`;
if (diffInSeconds < 2592000)
return `${Math.floor(diffInSeconds / 86400)}d ago`;
return date.toLocaleDateString();
}
/**
* Truncate text to specified length
*/
truncateText(text, maxLength) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
/**
* Escape HTML to prevent XSS
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Destroy the notification banner
*/
destroy() {
this.stopAutoRefresh();
// Remove elements
const elements = [
'notification-banner',
'notification-right-sidebar',
'notification-left-sidebar',
'notification-overlay',
];
elements.forEach(id => {
const element = document.getElementById(id);
if (element) {
element.remove();
}
});
// Remove styles
const styles = document.getElementById('notification-banner-styles');
if (styles) {
styles.remove();
}
this.initialized = false;
console.log('[NotificationBanner] Destroyed');
}
}
// Export for use
export default NotificationBanner;