diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx
index c3fe5ac..30396c4 100644
--- a/app/auth/callback/page.tsx
+++ b/app/auth/callback/page.tsx
@@ -5,7 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation'
import { useAuth } from '@/lib/auth/context'
import { AUTH_URL, default as apiRequest } from '@/lib/api/client'
import { exchangeAuthCode, setSessionAction } from '@/app/actions/auth'
-import { authMessageFromErrorType, type AuthErrorType } from '@/lib/utils/authErrors'
+import { authMessageFromErrorType, type AuthErrorType } from '@ciphera-net/ui'
import { LoadingOverlay } from '@ciphera-net/ui'
function AuthCallbackContent() {
diff --git a/app/notifications/page.tsx b/app/notifications/page.tsx
index b46a912..be979d5 100644
--- a/app/notifications/page.tsx
+++ b/app/notifications/page.tsx
@@ -13,9 +13,9 @@ import {
markAllNotificationsRead,
type Notification,
} from '@/lib/api/notifications'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { formatTimeAgo, getTypeIcon } from '@/lib/utils/notifications'
-import { Button, ArrowLeftIcon } from '@ciphera-net/ui'
+import { Button, ArrowLeftIcon, Spinner } from '@ciphera-net/ui'
import { toast } from '@ciphera-net/ui'
const PAGE_SIZE = 50
@@ -129,7 +129,7 @@ export default function NotificationsPage() {
{loading ? (
diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx
index 934c8ff..72b5307 100644
--- a/app/onboarding/page.tsx
+++ b/app/onboarding/page.tsx
@@ -4,7 +4,7 @@ import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createOrganization } from '@/lib/api/organization'
import { useAuth } from '@/lib/auth/context'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay } from '@ciphera-net/ui'
import { Button, Input } from '@ciphera-net/ui'
diff --git a/app/page.tsx b/app/page.tsx
index 9c15ae4..90eed1d 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -12,7 +12,7 @@ import SiteList from '@/components/sites/SiteList'
import { Button } from '@ciphera-net/ui'
import { BarChartIcon, LockIcon, ZapIcon, CheckCircleIcon, XIcon, GlobeIcon } from '@ciphera-net/ui'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
function DashboardPreview() {
return (
diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx
index 912f158..30dcfbf 100644
--- a/app/share/[id]/page.tsx
+++ b/app/share/[id]/page.tsx
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from 'react'
import { useParams, useSearchParams, useRouter } from 'next/navigation'
import { getPublicDashboard, getPublicStats, getPublicDailyStats, getPublicRealtime, getPublicPerformanceByPage, type DashboardData, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay, Button } from '@ciphera-net/ui'
import Chart from '@/components/dashboard/Chart'
import TopPages from '@/components/dashboard/ContentStats'
diff --git a/app/sites/[id]/funnels/[funnelId]/page.tsx b/app/sites/[id]/funnels/[funnelId]/page.tsx
index 2e200ef..687e5c4 100644
--- a/app/sites/[id]/funnels/[funnelId]/page.tsx
+++ b/app/sites/[id]/funnels/[funnelId]/page.tsx
@@ -16,7 +16,7 @@ import {
ResponsiveContainer,
Cell
} from 'recharts'
-import { getDateRange } from '@/lib/utils/format'
+import { getDateRange } from '@ciphera-net/ui'
const CHART_COLORS_LIGHT = {
border: '#E5E5E5',
diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx
index 89ea7db..d541511 100644
--- a/app/sites/[id]/page.tsx
+++ b/app/sites/[id]/page.tsx
@@ -6,9 +6,9 @@ import { useParams, useRouter } from 'next/navigation'
import { motion } from 'framer-motion'
import { getSite, type Site } from '@/lib/api/sites'
import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, getCountries, getCities, getRegions, getBrowsers, getOS, getDevices, getScreenResolutions, getEntryPages, getExitPages, getDashboard, getCampaigns, getPerformanceByPage, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
-import { formatNumber, formatDuration, getDateRange } from '@/lib/utils/format'
+import { formatNumber, formatDuration, getDateRange } from '@ciphera-net/ui'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay, Button } from '@ciphera-net/ui'
import { Select, DatePicker, DownloadIcon } from '@ciphera-net/ui'
import ExportModal from '@/components/dashboard/ExportModal'
diff --git a/app/sites/[id]/realtime/page.tsx b/app/sites/[id]/realtime/page.tsx
index f3be638..78cb713 100644
--- a/app/sites/[id]/realtime/page.tsx
+++ b/app/sites/[id]/realtime/page.tsx
@@ -5,7 +5,7 @@ import { useParams, useRouter } from 'next/navigation'
import { getSite, type Site } from '@/lib/api/sites'
import { getRealtimeVisitors, getSessionDetails, type Visitor, type SessionEvent } from '@/lib/api/realtime'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay, UserIcon } from '@ciphera-net/ui'
import { motion, AnimatePresence } from 'framer-motion'
diff --git a/app/sites/[id]/settings/page.tsx b/app/sites/[id]/settings/page.tsx
index 32935e2..c537b68 100644
--- a/app/sites/[id]/settings/page.tsx
+++ b/app/sites/[id]/settings/page.tsx
@@ -5,7 +5,7 @@ import { useParams, useRouter } from 'next/navigation'
import { getSite, updateSite, resetSiteData, deleteSite, type Site, type GeoDataLevel } from '@/lib/api/sites'
import { listGoals, createGoal, updateGoal, deleteGoal, type Goal } from '@/lib/api/goals'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay } from '@ciphera-net/ui'
import VerificationModal from '@/components/sites/VerificationModal'
import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock'
diff --git a/app/sites/[id]/uptime/page.tsx b/app/sites/[id]/uptime/page.tsx
index 2aa04d7..49c093a 100644
--- a/app/sites/[id]/uptime/page.tsx
+++ b/app/sites/[id]/uptime/page.tsx
@@ -19,7 +19,7 @@ import {
} from '@/lib/api/uptime'
import { toast } from '@ciphera-net/ui'
import { useTheme } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { LoadingOverlay, Button, Modal } from '@ciphera-net/ui'
import {
AreaChart,
diff --git a/app/sites/new/page.tsx b/app/sites/new/page.tsx
index 0ca6617..8180b11 100644
--- a/app/sites/new/page.tsx
+++ b/app/sites/new/page.tsx
@@ -7,7 +7,7 @@ import { createSite, listSites, getSite, type Site } from '@/lib/api/sites'
import { getSubscription } from '@/lib/api/billing'
import { trackSiteCreatedFromDashboard, trackSiteCreatedScriptCopied } from '@/lib/welcomeAnalytics'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { Button, Input } from '@ciphera-net/ui'
import { CheckCircleIcon } from '@ciphera-net/ui'
import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock'
diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx
index 7193206..bab5fbe 100644
--- a/app/welcome/page.tsx
+++ b/app/welcome/page.tsx
@@ -21,7 +21,7 @@ import { createSite, type Site } from '@/lib/api/sites'
import { setSessionAction } from '@/app/actions/auth'
import { useAuth } from '@/lib/auth/context'
import apiRequest from '@/lib/api/client'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import {
trackWelcomeStepView,
trackWelcomeWorkspaceSelected,
diff --git a/components/CodeBlock.tsx b/components/CodeBlock.tsx
deleted file mode 100644
index 8379a41..0000000
--- a/components/CodeBlock.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @file Reusable code block component for integration guide pages.
- *
- * Renders a VS-Code-style code block with a filename tab header.
- */
-
-interface CodeBlockProps {
- /** Filename displayed in the tab header */
- filename: string
- /** The code string to render inside the block */
- children: string
-}
-
-/**
- * Renders a dark-themed code snippet with a filename tab.
- */
-export function CodeBlock({ filename, children }: CodeBlockProps) {
- return (
-
- )
-}
diff --git a/components/Footer.tsx b/components/Footer.tsx
index ac6ed50..cf7865a 100644
--- a/components/Footer.tsx
+++ b/components/Footer.tsx
@@ -2,8 +2,7 @@
import Link from 'next/link'
import Image from 'next/image'
-import { GithubIcon, TwitterIcon } from '@ciphera-net/ui'
-import SwissFlagIcon from './SwissFlagIcon'
+import { GithubIcon, TwitterIcon, SwissFlagIcon } from '@ciphera-net/ui'
interface FooterProps {
LinkComponent?: any
diff --git a/components/LoadingOverlay.tsx b/components/LoadingOverlay.tsx
deleted file mode 100644
index 342cc27..0000000
--- a/components/LoadingOverlay.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client'
-
-import React, { useEffect, useState } from 'react'
-import { createPortal } from 'react-dom'
-
-interface LoadingOverlayProps {
- logoSrc?: string
- title?: string
-}
-
-export default function LoadingOverlay({
- logoSrc = "/ciphera_icon_no_margins.png",
- title = "Pulse"
-}: LoadingOverlayProps) {
- const [mounted, setMounted] = useState(false)
-
- useEffect(() => {
- setMounted(true)
- return () => setMounted(false)
- }, [])
-
- if (!mounted) return null
-
- return createPortal(
-
-
-
-

-
- CipheraPulse
-
-
-
-
-
,
- document.body
- )
-}
diff --git a/components/SwissFlagIcon.tsx b/components/SwissFlagIcon.tsx
deleted file mode 100644
index 4c8ea5e..0000000
--- a/components/SwissFlagIcon.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client'
-
-import type { SVGProps } from 'react'
-
-// * Swiss flag icon – official proportions (cross 1/6 height). Uses real Swiss federal red.
-const SWISS_RED = '#E41E26' // * Official Swiss flag red (federal identity)
-
-export default function SwissFlagIcon(props: SVGProps
) {
- const { className, ...rest } = props
- return (
-
- )
-}
diff --git a/components/dashboard/Campaigns.tsx b/components/dashboard/Campaigns.tsx
index 9e3a923..96c7ebf 100644
--- a/components/dashboard/Campaigns.tsx
+++ b/components/dashboard/Campaigns.tsx
@@ -2,8 +2,8 @@
import { useState, useEffect, useMemo } from 'react'
import Link from 'next/link'
-import { formatNumber } from '@/lib/utils/format'
-import { Modal, ArrowRightIcon, Button } from '@ciphera-net/ui'
+import { formatNumber } from '@ciphera-net/ui'
+import { Modal, ArrowRightIcon, Button, Spinner } from '@ciphera-net/ui'
import { ChevronDownIcon, DownloadIcon } from '@ciphera-net/ui'
import { getCampaigns, CampaignStat } from '@/lib/api/stats'
import { getReferrerFavicon, getReferrerIcon, getReferrerDisplayName } from '@/lib/utils/icons'
@@ -293,7 +293,7 @@ export default function Campaigns({ siteId, dateRange }: CampaignsProps) {
{isLoadingFull ? (
) : (
diff --git a/components/dashboard/Chart.tsx b/components/dashboard/Chart.tsx
index 89ff932..f5c08e5 100644
--- a/components/dashboard/Chart.tsx
+++ b/components/dashboard/Chart.tsx
@@ -13,7 +13,7 @@ import {
ReferenceLine,
} from 'recharts'
import type { TooltipProps } from 'recharts'
-import { formatNumber, formatDuration, formatUpdatedAgo } from '@/lib/utils/format'
+import { formatNumber, formatDuration, formatUpdatedAgo } from '@ciphera-net/ui'
import { ArrowUpRightIcon, ArrowDownRightIcon, BarChartIcon, Select, Button, DownloadIcon } from '@ciphera-net/ui'
import { Checkbox } from '@ciphera-net/ui'
diff --git a/components/dashboard/ContentStats.tsx b/components/dashboard/ContentStats.tsx
index 7f6852b..b7d65dd 100644
--- a/components/dashboard/ContentStats.tsx
+++ b/components/dashboard/ContentStats.tsx
@@ -1,9 +1,9 @@
'use client'
import { useState, useEffect } from 'react'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import { TopPage, getTopPages, getEntryPages, getExitPages } from '@/lib/api/stats'
-import { Modal, ArrowUpRightIcon, LayoutDashboardIcon } from '@ciphera-net/ui'
+import { Modal, ArrowUpRightIcon, LayoutDashboardIcon, Spinner } from '@ciphera-net/ui'
interface ContentStatsProps {
topPages: TopPage[]
@@ -174,7 +174,7 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
{isLoadingFull ? (
) : (
diff --git a/components/dashboard/Countries.tsx b/components/dashboard/Countries.tsx
index 49ce2ef..fba76a9 100644
--- a/components/dashboard/Countries.tsx
+++ b/components/dashboard/Countries.tsx
@@ -1,7 +1,7 @@
'use client'
import { useState } from 'react'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import * as Flags from 'country-flag-icons/react/3x2'
import WorldMap from './WorldMap'
import { GlobeIcon } from '@ciphera-net/ui'
diff --git a/components/dashboard/ExportModal.tsx b/components/dashboard/ExportModal.tsx
index bef7d2e..ca4ce39 100644
--- a/components/dashboard/ExportModal.tsx
+++ b/components/dashboard/ExportModal.tsx
@@ -6,7 +6,7 @@ import * as XLSX from 'xlsx'
import jsPDF from 'jspdf'
import autoTable from 'jspdf-autotable'
import type { DailyStat } from './Chart'
-import { formatNumber, formatDuration } from '@/lib/utils/format'
+import { formatNumber, formatDuration } from '@ciphera-net/ui'
import { getReferrerDisplayName, mergeReferrersByDisplayName } from '@/lib/utils/icons'
import type { TopPage, TopReferrer, CampaignStat } from '@/lib/api/stats'
diff --git a/components/dashboard/GoalStats.tsx b/components/dashboard/GoalStats.tsx
index fde2a91..6c0571f 100644
--- a/components/dashboard/GoalStats.tsx
+++ b/components/dashboard/GoalStats.tsx
@@ -1,7 +1,7 @@
'use client'
import Link from 'next/link'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import { BookOpenIcon, ArrowRightIcon } from '@ciphera-net/ui'
import type { GoalCountStat } from '@/lib/api/stats'
diff --git a/components/dashboard/Locations.tsx b/components/dashboard/Locations.tsx
index 56df267..4d85d34 100644
--- a/components/dashboard/Locations.tsx
+++ b/components/dashboard/Locations.tsx
@@ -1,12 +1,12 @@
'use client'
import { useState, useEffect } from 'react'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import * as Flags from 'country-flag-icons/react/3x2'
// @ts-ignore
import iso3166 from 'iso-3166-2'
import WorldMap from './WorldMap'
-import { Modal, GlobeIcon } from '@ciphera-net/ui'
+import { Modal, GlobeIcon, Spinner } from '@ciphera-net/ui'
import { SiTorproject } from 'react-icons/si'
import { FaUserSecret, FaSatellite } from 'react-icons/fa'
import { getCountries, getCities, getRegions } from '@/lib/api/stats'
@@ -289,7 +289,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
{isLoadingFull ? (
) : (
diff --git a/components/dashboard/TechSpecs.tsx b/components/dashboard/TechSpecs.tsx
index c0f78ba..04f3274 100644
--- a/components/dashboard/TechSpecs.tsx
+++ b/components/dashboard/TechSpecs.tsx
@@ -1,10 +1,10 @@
'use client'
import { useState, useEffect } from 'react'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import { getBrowserIcon, getOSIcon, getDeviceIcon } from '@/lib/utils/icons'
import { MdMonitor } from 'react-icons/md'
-import { Modal, GridIcon } from '@ciphera-net/ui'
+import { Modal, GridIcon, Spinner } from '@ciphera-net/ui'
import { getBrowsers, getOS, getDevices, getScreenResolutions } from '@/lib/api/stats'
interface TechSpecsProps {
@@ -190,7 +190,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
{isLoadingFull ? (
) : (
diff --git a/components/dashboard/TopPages.tsx b/components/dashboard/TopPages.tsx
index 14d5de3..6976105 100644
--- a/components/dashboard/TopPages.tsx
+++ b/components/dashboard/TopPages.tsx
@@ -1,6 +1,6 @@
'use client'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import { LayoutDashboardIcon } from '@ciphera-net/ui'
interface TopPagesProps {
diff --git a/components/dashboard/TopReferrers.tsx b/components/dashboard/TopReferrers.tsx
index 5906876..4349118 100644
--- a/components/dashboard/TopReferrers.tsx
+++ b/components/dashboard/TopReferrers.tsx
@@ -1,9 +1,9 @@
'use client'
import { useState, useEffect } from 'react'
-import { formatNumber } from '@/lib/utils/format'
+import { formatNumber } from '@ciphera-net/ui'
import { getReferrerDisplayName, getReferrerFavicon, getReferrerIcon, mergeReferrersByDisplayName } from '@/lib/utils/icons'
-import { Modal, GlobeIcon } from '@ciphera-net/ui'
+import { Modal, GlobeIcon, Spinner } from '@ciphera-net/ui'
import { getTopReferrers, TopReferrer } from '@/lib/api/stats'
interface TopReferrersProps {
@@ -135,7 +135,7 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
{isLoadingFull ? (
) : (
diff --git a/components/notifications/NotificationCenter.tsx b/components/notifications/NotificationCenter.tsx
index 1258e72..dad2ca4 100644
--- a/components/notifications/NotificationCenter.tsx
+++ b/components/notifications/NotificationCenter.tsx
@@ -7,7 +7,7 @@
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 { getAuthErrorMessage } from '@ciphera-net/ui'
import { formatTimeAgo, getTypeIcon } from '@/lib/utils/notifications'
import { SettingsIcon } from '@ciphera-net/ui'
diff --git a/components/settings/OrganizationSettings.tsx b/components/settings/OrganizationSettings.tsx
index 8268940..9a56c0f 100644
--- a/components/settings/OrganizationSettings.tsx
+++ b/components/settings/OrganizationSettings.tsx
@@ -21,7 +21,7 @@ import { TRAFFIC_TIERS, PLAN_ID_SOLO, getTierIndexForLimit, getLimitForTierIndex
import { getAuditLog, AuditLogEntry, GetAuditLogParams } from '@/lib/api/audit'
import { getNotificationSettings, updateNotificationSettings } from '@/lib/api/notification-settings'
import { toast } from '@ciphera-net/ui'
-import { getAuthErrorMessage } from '@/lib/utils/authErrors'
+import { getAuthErrorMessage } from '@ciphera-net/ui'
import { motion, AnimatePresence } from 'framer-motion'
import {
AlertTriangleIcon,
@@ -35,6 +35,7 @@ import {
DownloadIcon,
ExternalLinkIcon,
LayoutDashboardIcon,
+ Spinner,
} from '@ciphera-net/ui'
// * Bell icon for notifications tab
@@ -699,7 +700,7 @@ export default function OrganizationSettings() {
{isLoadingMembers ? (
) : members.length === 0 ? (
No members found.
@@ -780,7 +781,7 @@ export default function OrganizationSettings() {
{isLoadingSubscription ? (
) : !subscription ? (
@@ -926,7 +927,7 @@ export default function OrganizationSettings() {
{isLoadingInvoices ? (
) : invoices.length === 0 ? (
No invoices found.
@@ -990,7 +991,7 @@ export default function OrganizationSettings() {
{isLoadingNotificationSettings ? (
) : (
@@ -1117,7 +1118,7 @@ export default function OrganizationSettings() {
{isLoadingAudit ? (
) : (auditEntries ?? []).length === 0 ? (
No audit events found.
diff --git a/lib/api/client.ts b/lib/api/client.ts
index a3fcac5..e012341 100644
--- a/lib/api/client.ts
+++ b/lib/api/client.ts
@@ -2,7 +2,7 @@
* HTTP client wrapper for API calls
*/
-import { authMessageFromStatus, AUTH_ERROR_MESSAGES } from '@/lib/utils/authErrors'
+import { authMessageFromStatus, AUTH_ERROR_MESSAGES } from '@ciphera-net/ui'
/** Request timeout in ms; network errors surface as user-facing "Network error, please try again." */
const FETCH_TIMEOUT_MS = 30_000
diff --git a/lib/integration-guides.tsx b/lib/integration-guides.tsx
index 43dba84..5cfaf17 100644
--- a/lib/integration-guides.tsx
+++ b/lib/integration-guides.tsx
@@ -8,7 +8,7 @@
*/
import { type ReactNode } from 'react'
-import { CodeBlock } from '@/components/CodeBlock'
+import { CodeBlock } from '@ciphera-net/ui'
// * ─── Guide registry ─────────────────────────────────────────────────────────
diff --git a/lib/utils/authErrors.ts b/lib/utils/authErrors.ts
deleted file mode 100644
index ef138c9..0000000
--- a/lib/utils/authErrors.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Auth error message mapping for user-facing copy.
- * Maps status codes and error types to safe, actionable messages (no sensitive details).
- */
-
-export const AUTH_ERROR_MESSAGES = {
- /** Shown when session/token is expired; prompts re-login. */
- SESSION_EXPIRED: 'Session expired, please sign in again.',
- /** Shown when credentials are invalid (e.g. wrong password, invalid token). */
- INVALID_CREDENTIALS: 'Invalid credentials',
- /** Shown on network failure or timeout; prompts retry. */
- NETWORK: 'Network error, please try again.',
- /** Generic fallback for server/unknown errors. */
- GENERIC: 'Something went wrong, please try again.',
-} as const
-
-/**
- * Returns the user-facing message for a given HTTP status from an API/auth response.
- * Used when building ApiError messages and when mapping server-returned error types.
- */
-export function authMessageFromStatus(status: number): string {
- if (status === 401) return AUTH_ERROR_MESSAGES.SESSION_EXPIRED
- if (status === 403) return AUTH_ERROR_MESSAGES.INVALID_CREDENTIALS
- if (status >= 500) return AUTH_ERROR_MESSAGES.GENERIC
- return AUTH_ERROR_MESSAGES.GENERIC
-}
-
-/** Error type returned by auth server actions for mapping to user-facing copy. */
-export type AuthErrorType = 'network' | 'expired' | 'invalid' | 'server'
-
-/**
- * Maps server-action error type (e.g. from exchangeAuthCode) to user-facing message.
- * Used in auth callback so no sensitive details are shown.
- */
-export function authMessageFromErrorType(type: AuthErrorType): string {
- switch (type) {
- case 'expired':
- return AUTH_ERROR_MESSAGES.SESSION_EXPIRED
- case 'invalid':
- return AUTH_ERROR_MESSAGES.INVALID_CREDENTIALS
- case 'network':
- return AUTH_ERROR_MESSAGES.NETWORK
- case 'server':
- default:
- return AUTH_ERROR_MESSAGES.GENERIC
- }
-}
-
-/**
- * Maps an error (e.g. ApiError, network/abort) to a safe user-facing message.
- * Use this when displaying API/auth errors in the UI so expired, invalid, and network
- * cases show the correct copy without exposing sensitive details.
- */
-export function getAuthErrorMessage(error: unknown): string {
- if (!error) return AUTH_ERROR_MESSAGES.GENERIC
- const err = error as { status?: number; name?: string; message?: string }
- if (typeof err.status === 'number') return authMessageFromStatus(err.status)
- if (err.name === 'AbortError') return AUTH_ERROR_MESSAGES.NETWORK
- if (err instanceof Error && (err.name === 'TypeError' || /fetch|network|failed to fetch/i.test(err.message || ''))) {
- return AUTH_ERROR_MESSAGES.NETWORK
- }
- return AUTH_ERROR_MESSAGES.GENERIC
-}
diff --git a/lib/utils/format.ts b/lib/utils/format.ts
deleted file mode 100644
index 485efa6..0000000
--- a/lib/utils/format.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Format numbers with commas
- */
-export function formatNumber(num: number): string {
- return new Intl.NumberFormat('en-US').format(num)
-}
-
-/**
- * Format date to YYYY-MM-DD
- */
-export function formatDate(date: Date): string {
- return date.toISOString().split('T')[0]
-}
-
-/**
- * Get date range for last N days
- */
-export function getDateRange(days: number): { start: string; end: string } {
- const end = new Date()
- const start = new Date()
- start.setDate(start.getDate() - days)
- return {
- start: formatDate(start),
- end: formatDate(end),
- }
-}
-
-/**
- * Format "updated X ago" for polling indicators (e.g. "Just now", "12 seconds ago")
- */
-export function formatUpdatedAgo(timestamp: number): string {
- const diff = Math.floor((Date.now() - timestamp) / 1000)
- if (diff < 5) return 'Just now'
- if (diff < 60) return `${diff} seconds ago`
- if (diff < 120) return '1 minute ago'
- const minutes = Math.floor(diff / 60)
- return `${minutes} minutes ago`
-}
-
-/**
- * Format relative time (e.g., "2 hours ago")
- */
-export function formatRelativeTime(date: string | Date): string {
- const d = typeof date === 'string' ? new Date(date) : date
- const now = new Date()
- const diff = now.getTime() - d.getTime()
- const seconds = Math.floor(diff / 1000)
- const minutes = Math.floor(seconds / 60)
- const hours = Math.floor(minutes / 60)
- const days = Math.floor(hours / 24)
-
- if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`
- if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`
- if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`
- return 'Just now'
-}
-/**
- * Format duration in seconds to "1m 30s" or "30s"
- */
-export function formatDuration(seconds: number): string {
- if (!seconds) return '0s'
-
- const m = Math.floor(seconds / 60)
- const s = Math.floor(seconds % 60)
-
- if (m > 0) {
- return `${m}m ${s}s`
- }
- return `${s}s`
-}
diff --git a/package-lock.json b/package-lock.json
index 99267af..c1f34dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "pulse-frontend",
"version": "0.6.0-alpha",
"dependencies": {
- "@ciphera-net/ui": "^0.0.56",
+ "@ciphera-net/ui": "^0.0.57",
"@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0",
"axios": "^1.13.2",
@@ -1539,9 +1539,9 @@
}
},
"node_modules/@ciphera-net/ui": {
- "version": "0.0.56",
- "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.56/dfa7f3a01be81773c16c68868d0517a67e5c5860",
- "integrity": "sha512-cOdq5Yu6riKQkJe6O1GNpv3k044qSr/K465uPtsAQL604gc9rC6Ooyom+jabt+N8yM3qoXL5KDaNiUBjKdTP0g==",
+ "version": "0.0.57",
+ "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.57/1839d6ea6184b8aefa921ceeaa1d1d50d532e6a8",
+ "integrity": "sha512-sgdBajwBgmZnqnZ/kJ1PYpb4XR2j/yPXw1xHyMpNaLa/wLYXqylJ1ffQ3aRE7BB37IkGgDO+fzeQVjYnJvHSBA==",
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"clsx": "^2.1.0",
diff --git a/package.json b/package.json
index 187f321..04fe563 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
- "@ciphera-net/ui": "^0.0.56",
+ "@ciphera-net/ui": "^0.0.57",
"@ducanh2912/next-pwa": "^10.2.9",
"@radix-ui/react-icons": "^1.3.0",
"axios": "^1.13.2",