diff --git a/app/notifications/page.tsx b/app/notifications/page.tsx
index c3f1b94..b46a912 100644
--- a/app/notifications/page.tsx
+++ b/app/notifications/page.tsx
@@ -14,32 +14,12 @@ import {
type Notification,
} from '@/lib/api/notifications'
import { getAuthErrorMessage } from '@/lib/utils/authErrors'
-import { AlertTriangleIcon, CheckCircleIcon, Button, ArrowLeftIcon } from '@ciphera-net/ui'
+import { formatTimeAgo, getTypeIcon } from '@/lib/utils/notifications'
+import { Button, ArrowLeftIcon } from '@ciphera-net/ui'
import { toast } from '@ciphera-net/ui'
const PAGE_SIZE = 50
-function formatTimeAgo(dateStr: string): string {
- const d = new Date(dateStr)
- const now = new Date()
- const diffMs = now.getTime() - d.getTime()
- const diffMins = Math.floor(diffMs / 60000)
- const diffHours = Math.floor(diffMs / 3600000)
- const diffDays = Math.floor(diffMs / 86400000)
- if (diffMins < 1) return 'Just now'
- if (diffMins < 60) return `${diffMins}m ago`
- if (diffHours < 24) return `${diffHours}h ago`
- if (diffDays < 7) return `${diffDays}d ago`
- return d.toLocaleDateString()
-}
-
-function getTypeIcon(type: string) {
- if (type.includes('down') || type.includes('degraded') || type.startsWith('billing_')) {
- return
- }
- return
-}
-
export default function NotificationsPage() {
const { user } = useAuth()
const [notifications, setNotifications] = useState([])
diff --git a/components/notifications/NotificationCenter.tsx b/components/notifications/NotificationCenter.tsx
index 70b0508..1258e72 100644
--- a/components/notifications/NotificationCenter.tsx
+++ b/components/notifications/NotificationCenter.tsx
@@ -8,7 +8,8 @@ import { useEffect, useState, useRef } from 'react'
import Link from 'next/link'
import { listNotifications, markNotificationRead, markAllNotificationsRead, type Notification } from '@/lib/api/notifications'
import { getAuthErrorMessage } from '@/lib/utils/authErrors'
-import { AlertTriangleIcon, CheckCircleIcon, SettingsIcon } from '@ciphera-net/ui'
+import { formatTimeAgo, getTypeIcon } from '@/lib/utils/notifications'
+import { SettingsIcon } from '@ciphera-net/ui'
// * Bell icon (simple SVG, no extra deps)
function BellIcon({ className }: { className?: string }) {
@@ -31,27 +32,6 @@ function BellIcon({ className }: { className?: string }) {
)
}
-function formatTimeAgo(dateStr: string): string {
- const d = new Date(dateStr)
- const now = new Date()
- const diffMs = now.getTime() - d.getTime()
- const diffMins = Math.floor(diffMs / 60000)
- const diffHours = Math.floor(diffMs / 3600000)
- const diffDays = Math.floor(diffMs / 86400000)
- if (diffMins < 1) return 'Just now'
- if (diffMins < 60) return `${diffMins}m ago`
- if (diffHours < 24) return `${diffHours}h ago`
- if (diffDays < 7) return `${diffDays}d ago`
- return d.toLocaleDateString()
-}
-
-function getTypeIcon(type: string) {
- if (type.includes('down') || type.includes('degraded') || type.startsWith('billing_')) {
- return
- }
- return
-}
-
const LOADING_DELAY_MS = 250
const POLL_INTERVAL_MS = 90_000
diff --git a/components/settings/OrganizationSettings.tsx b/components/settings/OrganizationSettings.tsx
index 7cd052f..8268940 100644
--- a/components/settings/OrganizationSettings.tsx
+++ b/components/settings/OrganizationSettings.tsx
@@ -281,17 +281,17 @@ export default function OrganizationSettings() {
}, [currentOrgId])
useEffect(() => {
- if (activeTab === 'notifications' && currentOrgId) {
+ if (activeTab === 'notifications' && currentOrgId && user?.role !== 'member') {
loadNotificationSettings()
}
- }, [activeTab, currentOrgId, loadNotificationSettings])
+ }, [activeTab, currentOrgId, loadNotificationSettings, user?.role])
// * Redirect members away from Notifications tab (owners/admins only)
useEffect(() => {
if (activeTab === 'notifications' && user?.role === 'member') {
handleTabChange('general')
}
- }, [activeTab, user?.role])
+ }, [activeTab, user?.role, handleTabChange])
// If no org ID, we are in personal organization context, so don't show org settings
if (!currentOrgId) {
diff --git a/lib/utils/notifications.tsx b/lib/utils/notifications.tsx
new file mode 100644
index 0000000..9ed6c7c
--- /dev/null
+++ b/lib/utils/notifications.tsx
@@ -0,0 +1,28 @@
+import { AlertTriangleIcon, CheckCircleIcon } from '@ciphera-net/ui'
+
+/**
+ * Formats a date string as a human-readable relative time (e.g. "5m ago", "2h ago").
+ */
+export function formatTimeAgo(dateStr: string): string {
+ const d = new Date(dateStr)
+ const now = new Date()
+ const diffMs = now.getTime() - d.getTime()
+ const diffMins = Math.floor(diffMs / 60000)
+ const diffHours = Math.floor(diffMs / 3600000)
+ const diffDays = Math.floor(diffMs / 86400000)
+ if (diffMins < 1) return 'Just now'
+ if (diffMins < 60) return `${diffMins}m ago`
+ if (diffHours < 24) return `${diffHours}h ago`
+ if (diffDays < 7) return `${diffDays}d ago`
+ return d.toLocaleDateString()
+}
+
+/**
+ * Returns the icon for a notification type (alert for down/degraded/billing, check for success).
+ */
+export function getTypeIcon(type: string) {
+ if (type.includes('down') || type.includes('degraded') || type.startsWith('billing_')) {
+ return
+ }
+ return
+}