'use client' import { useEffect, useState } from 'react' import { useParams } from 'next/navigation' import { useUnifiedSettings } from '@/lib/unified-settings-context' import { Select, DatePicker } from '@ciphera-net/ui' import { getDateRange, formatDate, getThisWeekRange, getThisMonthRange } from '@/lib/utils/dateRanges' import { CaretDown, CaretUp, MagnifyingGlass, ArrowSquareOut } from '@phosphor-icons/react' import { useDashboard, useGSCStatus, useGSCOverview, useGSCTopQueries, useGSCTopPages, useGSCNewQueries } from '@/lib/swr/dashboard' import { getGSCQueryPages, getGSCPageQueries } from '@/lib/api/gsc' import type { GSCDataRow } from '@/lib/api/gsc' import { SkeletonLine, StatCardSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons' import ClicksImpressionsChart from '@/components/search/ClicksImpressionsChart' // ─── Helpers ──────────────────────────────────────────────────── const formatPosition = (pos: number) => pos.toFixed(1) const formatCTR = (ctr: number) => (ctr * 100).toFixed(1) + '%' function formatChange(current: number, previous: number) { if (previous === 0) return null const change = ((current - previous) / previous) * 100 return { value: change, label: (change >= 0 ? '+' : '') + change.toFixed(1) + '%' } } function formatNumber(n: number) { if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M' if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K' return n.toLocaleString() } // ─── Page ─────────────────────────────────────────────────────── const PAGE_SIZE = 50 export default function SearchConsolePage() { const params = useParams() const siteId = params.id as string const { openUnifiedSettings } = useUnifiedSettings() // Date range const [period, setPeriod] = useState('28') const [dateRange, setDateRange] = useState(() => getDateRange(28)) const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) // View toggle const [activeView, setActiveView] = useState<'queries' | 'pages'>('queries') // Pagination const [queryPage, setQueryPage] = useState(0) const [pagePage, setPagePage] = useState(0) // Drill-down expansion const [expandedQuery, setExpandedQuery] = useState(null) const [expandedPage, setExpandedPage] = useState(null) const [expandedData, setExpandedData] = useState([]) const [expandedLoading, setExpandedLoading] = useState(false) // Data fetching const { data: gscStatus } = useGSCStatus(siteId) const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) const { data: overview } = useGSCOverview(siteId, dateRange.start, dateRange.end) const { data: topQueries, isLoading: queriesLoading } = useGSCTopQueries(siteId, dateRange.start, dateRange.end, PAGE_SIZE, queryPage * PAGE_SIZE) const { data: topPages, isLoading: pagesLoading } = useGSCTopPages(siteId, dateRange.start, dateRange.end, PAGE_SIZE, pagePage * PAGE_SIZE) const { data: newQueries } = useGSCNewQueries(siteId, dateRange.start, dateRange.end) const showSkeleton = useMinimumLoading(!gscStatus || (gscStatus?.connected && !overview)) const fadeClass = useSkeletonFade(showSkeleton) // Document title useEffect(() => { const domain = dashboard?.site?.domain document.title = domain ? `Search Console \u00b7 ${domain} | Pulse` : 'Search Console | Pulse' }, [dashboard?.site?.domain]) // Reset pagination when date range changes useEffect(() => { setQueryPage(0) setPagePage(0) setExpandedQuery(null) setExpandedPage(null) setExpandedData([]) }, [dateRange.start, dateRange.end]) // ─── Expand handlers ─────────────────────────────────────── async function handleExpandQuery(query: string) { if (expandedQuery === query) { setExpandedQuery(null) setExpandedData([]) return } setExpandedQuery(query) setExpandedPage(null) setExpandedLoading(true) try { const res = await getGSCQueryPages(siteId, query, dateRange.start, dateRange.end) setExpandedData(res.pages) } catch { setExpandedData([]) } finally { setExpandedLoading(false) } } async function handleExpandPage(page: string) { if (expandedPage === page) { setExpandedPage(null) setExpandedData([]) return } setExpandedPage(page) setExpandedQuery(null) setExpandedLoading(true) try { const res = await getGSCPageQueries(siteId, page, dateRange.start, dateRange.end) setExpandedData(res.queries) } catch { setExpandedData([]) } finally { setExpandedLoading(false) } } // ─── Loading skeleton ───────────────────────────────────── if (showSkeleton) { return (
{Array.from({ length: 10 }).map((_, i) => (
))}
) } // ─── Not connected state ────────────────────────────────── if (gscStatus && !gscStatus.connected) { return (

Connect Google Search Console

See how your site performs in Google Search. View top queries, pages, click-through rates, and average position data.

) } // ─── Connected — main view ──────────────────────────────── const clicksChange = overview ? formatChange(overview.total_clicks, overview.prev_clicks) : null const impressionsChange = overview ? formatChange(overview.total_impressions, overview.prev_impressions) : null const ctrChange = overview ? formatChange(overview.avg_ctr, overview.prev_avg_ctr) : null // For position, lower is better — invert the direction const positionChange = overview ? formatChange(overview.avg_position, overview.prev_avg_position) : null const queries = topQueries?.queries ?? [] const queriesTotal = topQueries?.total ?? 0 const pages = topPages?.pages ?? [] const pagesTotal = topPages?.total ?? 0 return (
{/* Header */}

Search Console

Google Search performance, queries, and page rankings