diff --git a/app/admin/filtered-traffic/page.tsx b/app/admin/filtered-traffic/page.tsx index 488f829..348a58b 100644 --- a/app/admin/filtered-traffic/page.tsx +++ b/app/admin/filtered-traffic/page.tsx @@ -28,8 +28,8 @@ export default function FilteredTrafficPage() {
-

Filtered Traffic

-

+

Filtered Traffic

+

{totalBlocked.toLocaleString()} spam referrers blocked in the last {days} days

@@ -52,22 +52,22 @@ export default function FilteredTrafficPage() {
{referrers.length === 0 ? ( -
+
No filtered referrers in this period
) : ( - - - + + + {referrers.map((r) => ( - + - diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index a777657..0eb6fa3 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -37,7 +37,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) return (
-

Pulse Admin

+

Pulse Admin

{children}
diff --git a/app/admin/orgs/[id]/page.tsx b/app/admin/orgs/[id]/page.tsx index bf93de2..74e1a55 100644 --- a/app/admin/orgs/[id]/page.tsx +++ b/app/admin/orgs/[id]/page.tsx @@ -107,7 +107,7 @@ export default function AdminOrgDetailPage() { return (
-

+

{org.business_name || 'Unnamed Organization'}

{org.organization_id} @@ -116,7 +116,7 @@ export default function AdminOrgDetailPage() {
{/* Current Status */}
-

Current Status

+

Current Status

Plan: {org.plan_id} @@ -145,7 +145,7 @@ export default function AdminOrgDetailPage() { {/* Sites */}
-

Sites ({org.sites.length})

+

Sites ({org.sites.length})

    {org.sites.map((site) => (
  • @@ -160,7 +160,7 @@ export default function AdminOrgDetailPage() { {/* Grant Plan Form */}
    -

    Grant Plan (Manual Override)

    +

    Grant Plan (Manual Override)

    @@ -196,7 +196,7 @@ export default function AdminOrgDetailPage() { type="datetime-local" value={periodEnd} onChange={(e) => setPeriodEnd(e.target.value)} - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2" required />
    diff --git a/app/admin/orgs/page.tsx b/app/admin/orgs/page.tsx index 59747ec..e1a2f3d 100644 --- a/app/admin/orgs/page.tsx +++ b/app/admin/orgs/page.tsx @@ -43,28 +43,28 @@ export default function AdminOrgsPage() { return (
    -

    Organizations

    +

    Organizations

    -

    All Organizations

    +

    All Organizations

DomainReasonBlockedDomainReasonBlocked
{r.domain}{r.domain} + {r.count.toLocaleString()}
- - - - - - - + + + + + + + {orgs.map((org) => ( -
NameOrg IDPlanStatusLimitUpdatedActionsNameOrg IDPlanStatusLimitUpdatedActions
+ {org.business_name || 'N/A'} diff --git a/app/admin/page.tsx b/app/admin/page.tsx index e4aa99d..05bb448 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -9,9 +9,9 @@ export default function AdminDashboard() { href="/admin/orgs" className="block transition-transform hover:scale-[1.02] rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm" > -

Organizations

-

Manage organization plans and limits

-

+

Organizations

+

Manage organization plans and limits

+

View all organizations, check billing status, and manually grant plans.

@@ -19,9 +19,9 @@ export default function AdminDashboard() { href="/admin/filtered-traffic" className="block transition-transform hover:scale-[1.02] rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-sm" > -

Filtered Traffic

-

Monitor blocked referrer spam

-

+

Filtered Traffic

+

Monitor blocked referrer spam

+

View domains blocked by the spam filter and check for false positives.

diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx index c0b9c84..39d8366 100644 --- a/app/changelog/page.tsx +++ b/app/changelog/page.tsx @@ -18,7 +18,7 @@ export default function ChangelogPage() { return (
-

+

Changelog

diff --git a/app/notifications/page.tsx b/app/notifications/page.tsx index 1e7ea60..eb31ee3 100644 --- a/app/notifications/page.tsx +++ b/app/notifications/page.tsx @@ -122,8 +122,8 @@ export default function NotificationsPage() { )}

-

Notifications

-

+

Notifications

+

Manage which notifications you receive in{' '} Organization Settings → Notifications @@ -137,7 +137,7 @@ export default function NotificationsPage() { {error} ) : notifications.length === 0 ? ( -

+

No notifications yet

Manage which notifications you receive in{' '} @@ -159,11 +159,11 @@ export default function NotificationsPage() {

{getTypeIcon(n.type)}
-

+

{n.title}

{n.body && ( -

{n.body}

+

{n.body}

)}

{formatTimeAgo(n.created_at)} @@ -182,11 +182,11 @@ export default function NotificationsPage() {

{getTypeIcon(n.type)}
-

+

{n.title}

{n.body && ( -

{n.body}

+

{n.body}

)}

{formatTimeAgo(n.created_at)} diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index 72b5307..dcbd41f 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -47,7 +47,7 @@ export default function OnboardingPage() {

-

+

Welcome to Pulse

diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx index c5b00b9..664999e 100644 --- a/app/share/[id]/page.tsx +++ b/app/share/[id]/page.tsx @@ -195,7 +195,7 @@ export default function PublicDashboardPage() {

-

+

Protected Dashboard

@@ -210,7 +210,7 @@ export default function PublicDashboardPage() { value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter password" - className="w-full px-4 py-2 border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange focus:border-transparent" + className="w-full px-4 py-2 border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-white focus:ring-2 focus:ring-brand-orange focus:border-transparent" autoFocus />

@@ -270,7 +270,7 @@ export default function PublicDashboardPage() {
Public Dashboard
-

+

{site.name} void }) { + return ( + + ) +} diff --git a/app/sites/[id]/behavior/page.tsx b/app/sites/[id]/behavior/page.tsx index 8e0a2c4..050b635 100644 --- a/app/sites/[id]/behavior/page.tsx +++ b/app/sites/[id]/behavior/page.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { useParams } from 'next/navigation' -import { getDateRange, formatDate } from '@ciphera-net/ui' +import { getDateRange, formatDate, getThisWeekRange, getThisMonthRange } from '@/lib/utils/dateRanges' import { Select, DatePicker } from '@ciphera-net/ui' import dynamic from 'next/dynamic' import { getRageClicks, getDeadClicks } from '@/lib/api/stats' @@ -15,20 +15,6 @@ import { BehaviorSkeleton, useMinimumLoading, useSkeletonFade } from '@/componen const ScrollDepth = dynamic(() => import('@/components/dashboard/ScrollDepth')) -function getThisWeekRange(): { start: string; end: string } { - const today = new Date() - const dayOfWeek = today.getDay() - const monday = new Date(today) - monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) - return { start: formatDate(monday), end: formatDate(today) } -} - -function getThisMonthRange(): { start: string; end: string } { - const today = new Date() - const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) - return { start: formatDate(firstOfMonth), end: formatDate(today) } -} - export default function BehaviorPage() { const params = useParams() const siteId = params.id as string @@ -74,10 +60,10 @@ export default function BehaviorPage() { {/* Header */}
-

+

Behavior

-

+

Frustration signals and user engagement patterns

diff --git a/app/sites/[id]/cdn/error.tsx b/app/sites/[id]/cdn/error.tsx new file mode 100644 index 0000000..e11400b --- /dev/null +++ b/app/sites/[id]/cdn/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function CDNError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/cdn/page.tsx b/app/sites/[id]/cdn/page.tsx index a244b55..b8872d0 100644 --- a/app/sites/[id]/cdn/page.tsx +++ b/app/sites/[id]/cdn/page.tsx @@ -177,10 +177,10 @@ export default function CDNPage() {
-

+

Connect BunnyCDN

-

+

Monitor your CDN performance including bandwidth usage, cache hit rates, request volumes, and geographic distribution.

-

+

CDN Analytics

-

+

BunnyCDN performance, bandwidth, and cache metrics

@@ -281,7 +281,7 @@ export default function CDNPage() { {/* Bandwidth chart */}
-

Bandwidth

+

Bandwidth

{daily.length > 0 ? ( @@ -317,8 +317,8 @@ export default function CDNPage() { if (!active || !payload?.length) return null return (
-

{formatDateShort(label)}

-

+

{formatDateShort(label)}

+

Total: {formatBytes(payload[0]?.value as number)}

{payload[1] && ( @@ -359,7 +359,7 @@ export default function CDNPage() {
{/* Requests chart */}
-

Requests

+

Requests

{daily.length > 0 ? ( @@ -385,8 +385,8 @@ export default function CDNPage() { if (!active || !payload?.length) return null return (
-

{formatDateShort(label)}

-

+

{formatDateShort(label)}

+

{formatNumber(payload[0]?.value as number)} requests

@@ -405,7 +405,7 @@ export default function CDNPage() { {/* Errors chart */}
-

Errors

+

Errors

{daily.length > 0 ? ( -

{formatDateShort(label)}

+

{formatDateShort(label)}

{payload.map((entry) => (

{entry.name}: {formatNumber(entry.value as number)} @@ -464,7 +464,7 @@ export default function CDNPage() { {/* Traffic Distribution */}

-

Traffic Distribution

+

Traffic Distribution

{countries.length > 0 ? ( <>
@@ -480,9 +480,9 @@ export default function CDNPage() {
{cc && getFlagIcon(cc)}
- {city} + {city}
- + {formatBytes(row.bandwidth)}
@@ -530,13 +530,13 @@ function OverviewCard({ return (
-

{label}

-

{value}

+

{label}

+

{value}

{changeLabel && (

{changeLabel} vs previous period

diff --git a/app/sites/[id]/funnels/[funnelId]/page.tsx b/app/sites/[id]/funnels/[funnelId]/page.tsx index d8fe3c8..0ce4fe1 100644 --- a/app/sites/[id]/funnels/[funnelId]/page.tsx +++ b/app/sites/[id]/funnels/[funnelId]/page.tsx @@ -25,7 +25,7 @@ export default function FunnelReportPage() { const [funnel, setFunnel] = useState(null) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) - const [dateRange, setDateRange] = useState(getDateRange(30)) + const [dateRange, setDateRange] = useState(() => getDateRange(30)) const [datePreset, setDatePreset] = useState<'7' | '30' | 'custom'>('30') const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) const [loadError, setLoadError] = useState<'not_found' | 'forbidden' | 'error' | null>(null) @@ -154,7 +154,7 @@ export default function FunnelReportPage() {
-

+

{funnel.name}

{funnel.description && ( @@ -236,7 +236,7 @@ export default function FunnelReportPage() { {trends && trends.dates.length > 1 && (
-

+

Conversion Trends

@@ -322,10 +322,10 @@ export default function FunnelReportPage() { - - - - + + + + @@ -338,13 +338,13 @@ export default function FunnelReportPage() { {i + 1}
-

{step.step.name}

-

{step.step.value}

+

{step.step.name}

+

{step.step.value}

diff --git a/app/sites/[id]/funnels/page.tsx b/app/sites/[id]/funnels/page.tsx index cec7b5b..5638bf0 100644 --- a/app/sites/[id]/funnels/page.tsx +++ b/app/sites/[id]/funnels/page.tsx @@ -40,7 +40,7 @@ export default function FunnelsPage() {
-

+

Funnels

@@ -65,7 +65,7 @@ export default function FunnelsPage() { className="mb-6" unoptimized /> -

+

No funnels yet

@@ -89,7 +89,7 @@ export default function FunnelsPage() {

-

+

{funnel.name}

{funnel.description && ( diff --git a/app/sites/[id]/journeys/page.tsx b/app/sites/[id]/journeys/page.tsx index 10d66e6..94fab56 100644 --- a/app/sites/[id]/journeys/page.tsx +++ b/app/sites/[id]/journeys/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { useParams } from 'next/navigation' import { motion } from 'framer-motion' -import { getDateRange, formatDate } from '@ciphera-net/ui' +import { getDateRange, formatDate, getThisWeekRange, getThisMonthRange } from '@/lib/utils/dateRanges' import { Select, DatePicker } from '@ciphera-net/ui' import ColumnJourney from '@/components/journeys/ColumnJourney' import SankeyJourney from '@/components/journeys/SankeyJourney' @@ -18,20 +18,6 @@ import { const DEFAULT_DEPTH = 4 -function getThisWeekRange(): { start: string; end: string } { - const today = new Date() - const dayOfWeek = today.getDay() - const monday = new Date(today) - monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) - return { start: formatDate(monday), end: formatDate(today) } -} - -function getThisMonthRange(): { start: string; end: string } { - const today = new Date() - const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) - return { start: formatDate(firstOfMonth), end: formatDate(today) } -} - export default function JourneysPage() { const params = useParams() const siteId = params.id as string @@ -91,10 +77,10 @@ export default function JourneysPage() { {/* Header */}
-

+

Journeys

-

+

How visitors navigate through your site

@@ -143,7 +129,7 @@ export default function JourneysPage() {
{/* Depth slider */}
-
+
2 steps {depth} steps deep @@ -196,7 +182,7 @@ export default function JourneysPage() { aria-selected={viewMode === mode} className={`relative px-3 py-1 text-xs font-medium transition-colors capitalize focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange rounded cursor-pointer ${ viewMode === mode - ? 'text-neutral-900 dark:text-white' + ? 'text-white' : 'text-neutral-400 dark:text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300' }`} > @@ -232,7 +218,7 @@ export default function JourneysPage() { {/* Footer */} {totalSessions > 0 && ( -
+
{totalSessions.toLocaleString()} sessions tracked
)} diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx index 5ad39da..892f135 100644 --- a/app/sites/[id]/page.tsx +++ b/app/sites/[id]/page.tsx @@ -17,7 +17,7 @@ import { type Stats, type DailyStat, } from '@/lib/api/stats' -import { getDateRange, formatDate } from '@ciphera-net/ui' +import { getDateRange, formatDate, getThisWeekRange, getThisMonthRange } from '@/lib/utils/dateRanges' import { toast } from '@ciphera-net/ui' import { Button } from '@ciphera-net/ui' import { Select, DatePicker, DownloadIcon } from '@ciphera-net/ui' @@ -63,19 +63,6 @@ function loadSavedSettings(): { } } -function getThisWeekRange(): { start: string; end: string } { - const today = new Date() - const dayOfWeek = today.getDay() - const monday = new Date(today) - monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) - return { start: formatDate(monday), end: formatDate(today) } -} - -function getThisMonthRange(): { start: string; end: string } { - const today = new Date() - const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) - return { start: formatDate(firstOfMonth), end: formatDate(today) } -} function getInitialDateRange(): { start: string; end: string } { const settings = loadSavedSettings() @@ -442,7 +429,7 @@ export default function SiteDashboardPage() {
-

+

{site.name}

diff --git a/app/sites/[id]/pagespeed/error.tsx b/app/sites/[id]/pagespeed/error.tsx new file mode 100644 index 0000000..04362bb --- /dev/null +++ b/app/sites/[id]/pagespeed/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function PageSpeedError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/pagespeed/page.tsx b/app/sites/[id]/pagespeed/page.tsx index 0e9354b..4eb5f27 100644 --- a/app/sites/[id]/pagespeed/page.tsx +++ b/app/sites/[id]/pagespeed/page.tsx @@ -9,6 +9,7 @@ import { toast, Button } from '@ciphera-net/ui' import { motion } from 'framer-motion' import ScoreGauge from '@/components/pagespeed/ScoreGauge' import { AreaChart as VisxAreaChart, Area as VisxArea, Grid as VisxGrid, XAxis as VisxXAxis, YAxis as VisxYAxis, ChartTooltip as VisxChartTooltip } from '@/components/ui/area-chart' +import { useMinimumLoading, useSkeletonFade } from '@/components/skeletons' // * Metric status thresholds (Google's Core Web Vitals thresholds) function getMetricStatus(metric: string, value: number | null): { label: string; color: string } { @@ -223,8 +224,10 @@ export default function PageSpeedPage() { } } - // * Loading state - if (isLoading && !latestChecks) return + // * Loading state with minimum display time (consistent with other pages) + const showSkeleton = useMinimumLoading(isLoading && !latestChecks) + const fadeClass = useSkeletonFade(showSkeleton) + if (showSkeleton) return if (!site) return

Site not found
const enabled = config?.enabled ?? false @@ -235,10 +238,10 @@ export default function PageSpeedPage() {
{/* Header */}
-

+

PageSpeed

-

+

Monitor your site's performance and Core Web Vitals

@@ -246,14 +249,14 @@ export default function PageSpeedPage() { {/* Empty state */}
- +
-

+

PageSpeed monitoring is disabled

-

+

Enable PageSpeed monitoring to track your site's performance scores, Core Web Vitals, and get actionable improvement suggestions.

@@ -263,7 +266,7 @@ export default function PageSpeedPage() {
StepVisitorsDrop-offConversionStepVisitorsDrop-offConversion
- + {step.visitors.toLocaleString()}
- - - - - + + + + + @@ -369,7 +356,7 @@ export default function SearchConsolePage() { )) ) : queries.length === 0 ? ( - @@ -391,7 +378,7 @@ export default function SearchConsolePage() { {/* Pagination */} {queriesTotal > PAGE_SIZE && (
-

+

Showing {queryPage * PAGE_SIZE + 1}-{Math.min((queryPage + 1) * PAGE_SIZE, queriesTotal)} of {queriesTotal.toLocaleString()}

@@ -421,12 +408,12 @@ export default function SearchConsolePage() {
- QueryClicksImpressionsCTRPosition + QueryClicksImpressionsCTRPosition
+ No query data available for this period.
- - - - - + + + + + @@ -443,7 +430,7 @@ export default function SearchConsolePage() { )) ) : pages.length === 0 ? ( - @@ -465,7 +452,7 @@ export default function SearchConsolePage() { {/* Pagination */} {pagesTotal > PAGE_SIZE && (
-

+

Showing {pagePage * PAGE_SIZE + 1}-{Math.min((pagePage + 1) * PAGE_SIZE, pagesTotal)} of {pagesTotal.toLocaleString()}

@@ -522,13 +509,13 @@ function OverviewCard({ return (
-

{label}

-

{value}

+

{label}

+

{value}

{change && (

{change.label} vs previous period

@@ -560,7 +547,7 @@ function QueryRow({
- + @@ -576,7 +563,7 @@ function QueryRow({ ))} ) : expandedData.length === 0 ? ( -

No pages found for this query.

+

No pages found for this query.

) : (
- PageClicksImpressionsCTRPosition + PageClicksImpressionsCTRPosition
+ No page data available for this period.
{row.query}{row.query} {row.clicks.toLocaleString()} {row.impressions.toLocaleString()} {formatCTR(row.ctr)}
@@ -631,7 +618,7 @@ function PageRow({ - + @@ -647,7 +634,7 @@ function PageRow({ ))} ) : expandedData.length === 0 ? ( -

No queries found for this page.

+

No queries found for this page.

) : (
{row.page}{row.page} {row.clicks.toLocaleString()} {row.impressions.toLocaleString()} {formatCTR(row.ctr)}
diff --git a/app/sites/[id]/settings/page.tsx b/app/sites/[id]/settings/page.tsx index 3b2d05d..a907e2b 100644 --- a/app/sites/[id]/settings/page.tsx +++ b/app/sites/[id]/settings/page.tsx @@ -738,9 +738,9 @@ export default function SiteSettingsPage() {
-

Site Settings

+

Site Settings

- Manage settings for {site.domain} + Manage settings for {site.domain}

@@ -860,8 +860,8 @@ export default function SiteSettingsPage() {
-

General Configuration

-

Update your site details and tracking script.

+

General Configuration

+

Update your site details and tracking script.

@@ -907,17 +907,17 @@ export default function SiteSettingsPage() { type="text" value={site.domain} disabled - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-neutral-100 dark:bg-neutral-800/50 text-neutral-500 dark:text-neutral-400 cursor-not-allowed" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-neutral-100 dark:bg-neutral-800/50 text-neutral-400 cursor-not-allowed" /> -

+

Domain cannot be changed after creation

-

Tracking Script

-

+

Tracking Script

+

Add this script to your website to start tracking visitors. Choose your framework for setup instructions.

: } {site.is_verified ? 'Verified' : 'Verify Installation'} -

+

{site.is_verified ? 'Your site is sending data correctly.' : 'Check if your site is sending data correctly.'}

@@ -964,7 +964,7 @@ export default function SiteSettingsPage() {

Danger Zone

-

Irreversible actions for your site.

+

Irreversible actions for your site.

@@ -1003,8 +1003,8 @@ export default function SiteSettingsPage() {
-

Visibility Settings

-

Manage who can view your dashboard.

+

Visibility Settings

+

Manage who can view your dashboard.

@@ -1014,8 +1014,8 @@ export default function SiteSettingsPage() {
-

Public Dashboard

-

+

Public Dashboard

+

Allow anyone with the link to view this dashboard

@@ -1041,7 +1041,7 @@ export default function SiteSettingsPage() { className="mt-6 pt-6 border-t border-neutral-200 dark:border-neutral-800 overflow-hidden space-y-6" >
-
@@ -1154,8 +1154,8 @@ export default function SiteSettingsPage() {
-

Data & Privacy

-

Control what visitor data is collected. Less data = more privacy.

+

Data & Privacy

+

Control what visitor data is collected. Less data = more privacy.

{/* Data Collection Controls */} @@ -1166,8 +1166,8 @@ export default function SiteSettingsPage() {
-

Page Paths

-

+

Page Paths

+

Track which pages visitors view

@@ -1187,8 +1187,8 @@ export default function SiteSettingsPage() {
-

Referrers

-

+

Referrers

+

Track where visitors come from

@@ -1208,8 +1208,8 @@ export default function SiteSettingsPage() {
-

Device Info

-

+

Device Info

+

Track browser, OS, and device type

@@ -1229,8 +1229,8 @@ export default function SiteSettingsPage() {
-

Geographic Data

-

+

Geographic Data

+

Control location tracking granularity

@@ -1253,8 +1253,8 @@ export default function SiteSettingsPage() {
-

Screen Resolution

-

+

Screen Resolution

+

Track visitor screen sizes

@@ -1277,8 +1277,8 @@ export default function SiteSettingsPage() {
-

Hide unknown locations

-

+

Hide unknown locations

+

Exclude entries where geographic data could not be resolved from location stats

@@ -1315,8 +1315,8 @@ export default function SiteSettingsPage() {
-

Keep raw event data for

-

+

Keep raw event data for

+

Events older than this are automatically deleted. Aggregated daily stats are kept permanently.

@@ -1353,8 +1353,8 @@ export default function SiteSettingsPage() {
-

Check frequency

-

+

Check frequency

+

How often PageSpeed Insights runs automated checks on your site.

@@ -1406,7 +1406,7 @@ export default function SiteSettingsPage() { focus:border-brand-orange focus:ring-4 focus:ring-brand-orange/10 outline-none transition-all duration-200 dark:text-white font-mono text-sm" />
-

+

Enter paths to exclude from tracking (one per line). Supports wildcards (e.g., /admin/*).

@@ -1417,7 +1417,7 @@ export default function SiteSettingsPage() {

For your privacy policy

-

+

Copy the text below into your site's Privacy Policy to describe your use of Pulse. It updates automatically based on your saved settings above.

@@ -1445,7 +1445,7 @@ export default function SiteSettingsPage() { {snippetCopied ? ( ) : ( - + @@ -1468,7 +1468,7 @@ export default function SiteSettingsPage() { {activeTab === 'bot' && (
-

Bot & Spam

+

Bot & Spam

Manage automated and manual bot filtering.

@@ -1627,8 +1627,8 @@ export default function SiteSettingsPage() { {activeTab === 'goals' && (
-

Goals & Events

-

+

Goals & Events

+

Define goals to label custom events (e.g. signup, purchase). Track with pulse.track('event_name') in your snippet.

@@ -1643,7 +1643,7 @@ export default function SiteSettingsPage() { )}
{goals.length === 0 ? ( -
+
No goals yet. Add a goal to give custom events a display name in the dashboard.
) : ( @@ -1653,8 +1653,8 @@ export default function SiteSettingsPage() { className="flex items-center justify-between py-3 px-4 rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-900/50" >
- {goal.name} - ({goal.event_name}) + {goal.name} + ({goal.event_name})
{canEdit && (
@@ -1686,16 +1686,16 @@ export default function SiteSettingsPage() { {activeTab === 'notifications' && (
-

Notifications

-

Configure how you receive reports and alerts.

+

Notifications

+

Configure how you receive reports and alerts.

{/* Reports subsection */}
-

Reports

-

Automatically deliver analytics reports via email or webhooks.

+

Reports

+

Automatically deliver analytics reports via email or webhooks.

{canEdit && (
) : reportSchedules.length === 0 ? ( -
+
No scheduled reports yet. Add a report to automatically receive analytics summaries.
) : ( @@ -1732,7 +1732,7 @@ export default function SiteSettingsPage() {
- + {getChannelLabel(schedule.channel)} @@ -1742,7 +1742,7 @@ export default function SiteSettingsPage() { {getReportTypeLabel(schedule.report_type)}
-

+

{schedule.channel === 'email' ? (schedule.channel_config as EmailConfig).recipients.join(', ') : (schedule.channel_config as WebhookConfig).url} @@ -1821,8 +1821,8 @@ export default function SiteSettingsPage() {

-

Alerts

-

Get notified when your site goes down or recovers.

+

Alerts

+

Get notified when your site goes down or recovers.

{canEdit && (
) : alertSchedules.length === 0 ? ( -
+
No alert channels configured. Add a channel to receive uptime alerts when your site goes down or recovers.
) : ( @@ -1859,14 +1859,14 @@ export default function SiteSettingsPage() {
- + {getChannelLabel(schedule.channel)} Uptime Alert
-

+

{schedule.channel === 'email' ? (schedule.channel_config as EmailConfig).recipients.join(', ') : (schedule.channel_config as WebhookConfig).url} @@ -1940,8 +1940,8 @@ export default function SiteSettingsPage() { {activeTab === 'integrations' && (

-

Integrations

-

Connect external services to enrich your analytics data.

+

Integrations

+

Connect external services to enrich your analytics data.

{/* Google Search Console */} @@ -1958,7 +1958,7 @@ export default function SiteSettingsPage() {
-

Google Search Console

+

Google Search Console

See which search queries bring visitors to your site, with impressions, clicks, CTR, and ranking position.

@@ -1968,7 +1968,7 @@ export default function SiteSettingsPage() { -

+

Pulse only requests read-only access. Your tokens are encrypted at rest and all data can be fully removed at any time.

@@ -2005,7 +2005,7 @@ export default function SiteSettingsPage() {
-

Google Search Console

+

Google Search Console

{gscStatus.google_email && (
-

Google Account

-

{gscStatus.google_email}

+

Google Account

+

{gscStatus.google_email}

)} {gscStatus.gsc_property && (
-

Property

-

{gscStatus.gsc_property}

+

Property

+

{gscStatus.gsc_property}

)} {gscStatus.last_synced_at && (
-

Last Synced

-

+

Last Synced

+

{new Date(gscStatus.last_synced_at).toLocaleString('en-GB')}

)} {gscStatus.created_at && (
-

Connected Since

-

+

Connected Since

+

{new Date(gscStatus.created_at).toLocaleString('en-GB')}

@@ -2121,7 +2121,7 @@ export default function SiteSettingsPage() {
-

BunnyCDN

+

BunnyCDN

Monitor CDN performance with bandwidth usage, cache hit rates, response times, and geographic distribution.

@@ -2131,7 +2131,7 @@ export default function SiteSettingsPage() { -

+

Your API key is encrypted at rest and only used to fetch read-only statistics. You can disconnect at any time.

@@ -2147,7 +2147,7 @@ export default function SiteSettingsPage() { setBunnySelectedZone(null) }} placeholder="BunnyCDN API key" - className="flex-1 px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white text-sm placeholder:text-neutral-400" + className="flex-1 px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-white dark:bg-neutral-900 text-white text-sm placeholder:text-neutral-400" />
-

BunnyCDN

+

BunnyCDN

{bunnyStatus.pull_zone_name && (
-

Pull Zone

-

{bunnyStatus.pull_zone_name}

+

Pull Zone

+

{bunnyStatus.pull_zone_name}

)} {bunnyStatus.last_synced_at && (
-

Last Synced

-

+

Last Synced

+

{new Date(bunnyStatus.last_synced_at).toLocaleString('en-GB')}

)} {bunnyStatus.created_at && (
-

Connected Since

-

+

Connected Since

+

{new Date(bunnyStatus.created_at).toLocaleString('en-GB')}

@@ -2354,7 +2354,7 @@ export default function SiteSettingsPage() { onChange={(e) => setGoalForm({ ...goalForm, name: e.target.value })} placeholder="e.g. Signups" autoFocus - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required />
@@ -2366,11 +2366,11 @@ export default function SiteSettingsPage() { onChange={(e) => setGoalForm({ ...goalForm, event_name: e.target.value })} placeholder="e.g. signup_click (letters, numbers, underscores only)" maxLength={64} - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required />
-

Letters, numbers, and underscores only. Spaces become underscores.

+

Letters, numbers, and underscores only. Spaces become underscores.

56 ? 'text-amber-500' : 'text-neutral-400'}`}>{goalForm.event_name.length}/64
{editingGoal && goalForm.event_name.trim().toLowerCase().replace(/\s+/g, '_') !== editingGoal.event_name && ( @@ -2423,10 +2423,10 @@ export default function SiteSettingsPage() { value={reportForm.recipients} onChange={(e) => setReportForm({ ...reportForm, recipients: e.target.value })} placeholder="email1@example.com, email2@example.com" - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required /> -

Comma-separated email addresses.

+

Comma-separated email addresses.

) : (
@@ -2438,7 +2438,7 @@ export default function SiteSettingsPage() { value={reportForm.webhookUrl} onChange={(e) => setReportForm({ ...reportForm, webhookUrl: e.target.value })} placeholder="https://hooks.example.com/..." - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required />
@@ -2582,10 +2582,10 @@ export default function SiteSettingsPage() { value={alertForm.recipients} onChange={(e) => setAlertForm({ ...alertForm, recipients: e.target.value })} placeholder="email1@example.com, email2@example.com" - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required /> -

Comma-separated email addresses.

+

Comma-separated email addresses.

) : (
@@ -2597,14 +2597,14 @@ export default function SiteSettingsPage() { value={alertForm.webhookUrl} onChange={(e) => setAlertForm({ ...alertForm, webhookUrl: e.target.value })} placeholder="https://hooks.example.com/..." - className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white" + className="w-full px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 text-white" required />
)}
-

+

Alerts are sent automatically when your site goes down or recovers. No schedule configuration needed.

diff --git a/app/sites/[id]/uptime/page.tsx b/app/sites/[id]/uptime/page.tsx index 67dda0b..8eebd0e 100644 --- a/app/sites/[id]/uptime/page.tsx +++ b/app/sites/[id]/uptime/page.tsx @@ -100,7 +100,7 @@ function getOverallStatusTextColor(status: string): string { case 'down': return 'text-red-600 dark:text-red-400' default: - return 'text-neutral-500 dark:text-neutral-400' + return 'text-neutral-400' } } @@ -168,22 +168,22 @@ function StatusBarTooltip({ style={{ left: position.x, top: position.y - 10, transform: 'translate(-50%, -100%)' }} >
-
{formattedDate}
+
{formattedDate}
{stat && stat.total_checks > 0 ? (
- Uptime - + Uptime + {formatUptime(stat.uptime_percentage)}
- Checks - {stat.total_checks} + Checks + {stat.total_checks}
- Avg Response - + Avg Response + {formatMs(Math.round(stat.avg_response_time_ms))}
@@ -275,7 +275,7 @@ function ResponseTimeChart({ checks }: { checks: UptimeCheck[] }) { return (
-

+

Response Time

@@ -406,10 +406,10 @@ export default function UptimePage() {
{/* Header */}
-

+

Uptime

-

+

Monitor your site's availability and response time

@@ -417,14 +417,14 @@ export default function UptimePage() { {/* Empty state */}
- +
-

+

Uptime monitoring is disabled

-

+

Enable uptime monitoring to track your site's availability and response time around the clock.

{canEdit && ( @@ -446,10 +446,10 @@ export default function UptimePage() { {/* Header */}
-

+

Uptime

-

+

Monitor your site's availability and response time

@@ -471,7 +471,7 @@ export default function UptimePage() {
- + {site.name} @@ -480,11 +480,11 @@ export default function UptimePage() {
- + {formatUptime(overallUptime)} uptime {monitor && ( -
+
Last checked {formatTimeAgo(monitor.monitor.last_checked_at)}
)} @@ -495,7 +495,7 @@ export default function UptimePage() { {/* 90-day uptime bar */} {monitor && (
-

+

90-Day Availability

@@ -512,39 +512,39 @@ export default function UptimePage() { {/* Monitor details grid */}
-
+
Status
- + {getStatusLabel(monitor.monitor.last_status)}
-
+
Response Time
- + {formatMs(monitor.monitor.last_response_time_ms)}
-
+
Check Interval
- + {monitor.monitor.check_interval_seconds >= 60 ? `${Math.floor(monitor.monitor.check_interval_seconds / 60)}m` : `${monitor.monitor.check_interval_seconds}s`}
-
+
Overall Uptime
- + {formatUptime(monitor.overall_uptime)}
@@ -559,7 +559,7 @@ export default function UptimePage() { {/* Recent checks */}
-

+

Recent Checks

@@ -576,7 +576,7 @@ export default function UptimePage() {
{check.status_code && ( - + {check.status_code} )} diff --git a/app/sites/new/page.tsx b/app/sites/new/page.tsx index 1638fd1..95feb41 100644 --- a/app/sites/new/page.tsx +++ b/app/sites/new/page.tsx @@ -113,7 +113,7 @@ export default function NewSitePage() {
-

+

Site created

@@ -137,7 +137,7 @@ export default function NewSitePage() { > Verify installation -

+

Check if your site is sending data correctly.

@@ -146,7 +146,7 @@ export default function NewSitePage() { @@ -174,7 +174,7 @@ export default function NewSitePage() { // * Step 1: Name & domain form return (
-

+

Create New Site

@@ -186,7 +186,7 @@ export default function NewSitePage() {
-
@@ -413,7 +413,7 @@ function WelcomeContent() { > {initial}
- + {org.organization_name || 'Organization'} {isCurrent && ( @@ -443,7 +443,7 @@ function WelcomeContent() { alt="Welcome to Pulse" className="w-48 h-auto mx-auto mb-6" /> -

+

Welcome to Pulse

@@ -475,7 +475,7 @@ function WelcomeContent() {

@@ -546,7 +546,7 @@ function WelcomeContent() { -

+

Check if your site is sending data correctly.

diff --git a/components/ErrorDisplay.tsx b/components/ErrorDisplay.tsx index 9ca2d01..a1d6d84 100644 --- a/components/ErrorDisplay.tsx +++ b/components/ErrorDisplay.tsx @@ -36,7 +36,7 @@ export default function ErrorDisplay({ className="w-56 h-auto mx-auto mb-8" /> -

+

{title}

diff --git a/components/Footer.tsx b/components/Footer.tsx index 7a0ff24..ee8fd54 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -48,7 +48,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate

-
+
© 2024-{year} Ciphera. All rights reserved.
@@ -88,7 +88,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate loading="lazy" className="w-9 h-9 object-contain group-hover:scale-105 transition-transform duration-300" /> - + Pulse @@ -125,7 +125,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate {/* * Products */}
-

Products

+

Products

    {footerLinks.products.map((link) => (
  • @@ -153,7 +153,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate {/* * Company */}
    -

    Company

    +

    Company

      {footerLinks.company.map((link) => (
    • @@ -181,7 +181,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate {/* * Resources */}
      -

      Resources

      +

      Resources

        {footerLinks.resources.map((link) => (
      • @@ -209,7 +209,7 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate {/* * Legal */}
        -

        Legal

        +

        Legal

          {footerLinks.legal.map((link) => (
        • @@ -232,10 +232,10 @@ export function Footer({ LinkComponent = Link, appName = 'Pulse', isAuthenticate {/* * Bottom bar */}
          -

          +

          © 2024-{year} Ciphera. All rights reserved.

          -

          +

          Where Privacy Still Exists

          diff --git a/components/behavior/FrustrationByPageTable.tsx b/components/behavior/FrustrationByPageTable.tsx index fa1169b..2331228 100644 --- a/components/behavior/FrustrationByPageTable.tsx +++ b/components/behavior/FrustrationByPageTable.tsx @@ -34,11 +34,11 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy return (
          -

          +

          Frustration by Page

          -

          +

          Pages with the most frustration signals

          @@ -72,7 +72,7 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy style={{ width: `${barWidth}%` }} /> {page.page_path} @@ -84,7 +84,7 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy {formatNumber(page.dead_clicks)} - + {formatNumber(page.total)} @@ -99,14 +99,17 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy ) : (
          - +
          -

          +

          No frustration signals detected

          -

          +

          Page-level frustration data will appear here once rage clicks or dead clicks are detected on your site.

          + + View setup guide +
          )}
          diff --git a/components/behavior/FrustrationSummaryCards.tsx b/components/behavior/FrustrationSummaryCards.tsx index 304c974..6f7cf48 100644 --- a/components/behavior/FrustrationSummaryCards.tsx +++ b/components/behavior/FrustrationSummaryCards.tsx @@ -31,7 +31,7 @@ function ChangeIndicator({ change }: { change: ReturnType }) { ? 'text-red-600 dark:text-red-400' : isDown ? 'text-green-600 dark:text-green-400' - : 'text-neutral-500 dark:text-neutral-400' + : 'text-neutral-400' }`} > {isUp ? '+' : ''}{change.value}% @@ -71,11 +71,11 @@ export default function FrustrationSummaryCards({ data, loading }: FrustrationSu
          {/* Rage Clicks */}
          -

          +

          Rage Clicks

          - + {data.rage_clicks.toLocaleString()} @@ -87,11 +87,11 @@ export default function FrustrationSummaryCards({ data, loading }: FrustrationSu {/* Dead Clicks */}
          -

          +

          Dead Clicks

          - + {data.dead_clicks.toLocaleString()} @@ -103,10 +103,10 @@ export default function FrustrationSummaryCards({ data, loading }: FrustrationSu {/* Total Frustration Signals */}
          -

          +

          Total Signals

          - + {totalSignals.toLocaleString()} {topPage ? ( diff --git a/components/behavior/FrustrationTable.tsx b/components/behavior/FrustrationTable.tsx index d362b14..acbdf9a 100644 --- a/components/behavior/FrustrationTable.tsx +++ b/components/behavior/FrustrationTable.tsx @@ -53,7 +53,7 @@ function SelectorCell({ selector }: { selector: string }) { className="flex items-center gap-1 min-w-0 group/copy cursor-pointer" title={selector} > - + {selector} @@ -145,7 +145,7 @@ export default function FrustrationTable({
          -

          +

          {title}

          {showViewAll && ( @@ -159,7 +159,7 @@ export default function FrustrationTable({ )}
          -

          +

          {description}

          @@ -182,15 +182,18 @@ export default function FrustrationTable({ alt="No frustration signals" className="w-44 h-auto mb-1" /> -

          +

          No {title.toLowerCase()} detected

          -

          +

          Frustration tracking requires the add-on script. Add it after your core Pulse script:

          {''} + + View setup guide +
          )}
          @@ -214,7 +217,7 @@ export default function FrustrationTable({ ))}
          ) : ( -

          +

          No data available

          )} diff --git a/components/behavior/FrustrationTrend.tsx b/components/behavior/FrustrationTrend.tsx index 9247113..18fb8f6 100644 --- a/components/behavior/FrustrationTrend.tsx +++ b/components/behavior/FrustrationTrend.tsx @@ -59,7 +59,7 @@ function CustomTooltip({ active, payload }: { active?: boolean; payload?: Array< className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ backgroundColor: item.fill }} /> - + {LABELS[item.type] ?? item.type} @@ -93,21 +93,21 @@ export default function FrustrationTrend({ summary, loading }: FrustrationTrendP return (
          -

          +

          Frustration Trend

          -

          +

          Rage vs. dead click breakdown

          - +
          -

          +

          No trend data yet

          -

          +

          Frustration trend data will appear here once rage clicks or dead clicks are detected on your site.

          @@ -118,11 +118,11 @@ export default function FrustrationTrend({ summary, loading }: FrustrationTrendP return (
          -

          +

          Frustration Trend

          -

          +

          {hasPrevious ? 'Rage and dead clicks split across current and previous period' : 'Rage vs. dead click breakdown'} diff --git a/components/dashboard/Chart.tsx b/components/dashboard/Chart.tsx index 633dbff..5a41b30 100644 --- a/components/dashboard/Chart.tsx +++ b/components/dashboard/Chart.tsx @@ -322,7 +322,7 @@ export default function Chart({ >

          {m.label}
          - + {m.change !== null && ( {m.isPositive ? : } @@ -357,7 +357,7 @@ export default function Chart({ {/* Toolbar */}
          - + {METRIC_CONFIGS.find((m) => m.key === metric)?.label}
          @@ -526,7 +526,7 @@ export default function Chart({ {ANNOTATION_LABELS[a.category] || 'Note'} · {formatEU(a.date)}{a.time ? ` at ${a.time}` : ''} -

          {a.text}

          +

          {a.text}

          ))} @@ -593,16 +593,16 @@ export default function Chart({ {annotationForm.visible && (
          -

          +

          {annotationForm.editingId ? 'Edit annotation' : 'Add annotation'}

          - +
          -
          @@ -658,7 +658,7 @@ export default function OrganizationSettings() {

          Danger Zone

          -

          Irreversible actions for this organization.

          +

          Irreversible actions for this organization.

          @@ -696,11 +696,11 @@ export default function OrganizationSettings() {
          {/* Invite Section */}
          -

          Organization Members

          -

          Manage who has access to this organization.

          +

          Organization Members

          +

          Manage who has access to this organization.

          -

          Invite New Member

          +

          Invite New Member

          -

          Active Members

          +

          Active Members

          {isLoadingMembers ? ( ) : members.length === 0 ? ( -
          No members found.
          +
          No members found.
          ) : ( members.map((member) => (
          @@ -758,10 +758,10 @@ export default function OrganizationSettings() { {member.user_email?.[0].toUpperCase() || '?'}
          -
          +
          {member.user_email || 'Unknown User'}
          -
          +
          Joined {formatDate(new Date(member.joined_at))}
          @@ -786,7 +786,7 @@ export default function OrganizationSettings() { {/* Pending Invitations */} {invitations.length > 0 && (
          -

          Pending Invitations

          +

          Pending Invitations

          {invitations.map((invite) => (
          @@ -795,10 +795,10 @@ export default function OrganizationSettings() {
          -
          +
          {invite.email}
          -
          +
          Invited as {invite.role} • Expires {formatDate(new Date(invite.expires_at))}
          @@ -821,8 +821,8 @@ export default function OrganizationSettings() { {activeTab === 'billing' && (
          -

          Billing & Subscription

          -

          Manage your plan, usage, and payment details.

          +

          Billing & Subscription

          +

          Manage your plan, usage, and payment details.

          {isLoadingSubscription ? ( @@ -832,7 +832,7 @@ export default function OrganizationSettings() {
          ) : !subscription ? (
          -

          Could not load subscription details.

          +

          Could not load subscription details.

          ) : ( @@ -915,7 +915,7 @@ export default function OrganizationSettings() { {/* Plan header */}
          - + {subscription.plan_id?.startsWith('price_') ? 'Pro' : (subscription.plan_id === 'free' || !subscription.plan_id ? 'Free' : subscription.plan_id)} Plan
          {(subscription.business_name || subscription.tax_id) && ( -
          +
          {subscription.business_name && (
          Billing for: {subscription.business_name}
          )} @@ -956,7 +956,7 @@ export default function OrganizationSettings() {
          Sites
          -
          +
          {typeof subscription.sites_count === 'number' ? (() => { const limit = getSitesLimitForPlan(subscription.plan_id) @@ -967,7 +967,7 @@ export default function OrganizationSettings() {
          Pageviews
          -
          +
          {subscription.pageview_limit > 0 && typeof subscription.pageview_usage === 'number' ? `${subscription.pageview_usage.toLocaleString()} / ${subscription.pageview_limit.toLocaleString()}` : '—'} @@ -993,7 +993,7 @@ export default function OrganizationSettings() {
          {subscription.subscription_status === 'trialing' ? 'Trial ends' : (subscription.cancel_at_period_end ? 'Access until' : 'Renews')}
          -
          +
          {(() => { const ts = subscription.current_period_end const d = ts ? new Date(ts) : null @@ -1005,7 +1005,7 @@ export default function OrganizationSettings() {
          Limit
          -
          +
          {subscription.pageview_limit > 0 ? `${subscription.pageview_limit.toLocaleString()} / mo` : 'Unlimited'}
          @@ -1038,19 +1038,19 @@ export default function OrganizationSettings() { {/* Order History */}
          -

          Recent orders

          +

          Recent orders

          {isLoadingInvoices ? ( ) : orders.length === 0 ? ( -
          No orders found.
          +
          No orders found.
          ) : ( <> {orders.map((order) => (
          - + {(order.total_amount / 100).toLocaleString('en-US', { style: 'currency', currency: order.currency.toUpperCase() })} @@ -1084,8 +1084,8 @@ export default function OrganizationSettings() { {activeTab === 'notifications' && (
          -

          Notification Settings

          -

          +

          Notification Settings

          +

          Choose which notification types you want to receive. These apply to the notification center for owners and admins.

          @@ -1094,7 +1094,7 @@ export default function OrganizationSettings() { ) : (
          -

          Notification categories

          +

          Notification categories

          {notificationCategories.map((cat) => (
          -

          {cat.label}

          -

          {cat.description}

          +

          {cat.label}

          +

          {cat.description}

          @@ -1173,7 +1173,7 @@ export default function OrganizationSettings() { placeholder="e.g. site_created" value={auditActionFilter} onChange={(e) => setAuditActionFilter(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" + className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" />
          @@ -1182,7 +1182,7 @@ export default function OrganizationSettings() { type="date" value={auditStartDate} onChange={(e) => setAuditStartDate(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" + className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" />
          @@ -1191,7 +1191,7 @@ export default function OrganizationSettings() { type="date" value={auditEndDate} onChange={(e) => setAuditEndDate(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" + className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all" />
          @@ -1240,10 +1240,10 @@ export default function OrganizationSettings() {
- - + ))} @@ -1255,7 +1255,7 @@ export default function OrganizationSettings() { {/* Pagination */} {auditTotal > auditPageSize && (
- + {auditPage * auditPageSize + 1}–{Math.min((auditPage + 1) * auditPageSize, auditTotal)} of {auditTotal}
@@ -1361,7 +1361,7 @@ export default function OrganizationSettings() { value={deleteConfirm} onChange={(e) => setDeleteConfirm(e.target.value)} autoComplete="off" - className="w-full px-3 py-2 text-sm border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400" + className="w-full px-3 py-2 text-sm border border-neutral-300 dark:border-neutral-700 rounded-lg bg-white dark:bg-neutral-800 text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400" placeholder="DELETE" />
@@ -1410,7 +1410,7 @@ export default function OrganizationSettings() { className="bg-white dark:bg-neutral-900 rounded-2xl shadow-2xl max-w-md w-full p-6 border border-neutral-200 dark:border-neutral-800" >
-

Cancel subscription?

+

Cancel subscription?

{formatDateTime(new Date(entry.occurred_at))} + {entry.actor_email || entry.actor_id || 'System'} {entry.action}{entry.action} {entry.resource_type}