| 1 |
import { __ } from "@wordpress/i18n"; |
| 2 |
import React, { useEffect, useState } from "react"; |
| 3 |
import { createPortal } from "react-dom"; |
| 4 |
import { I } from "./common/icons"; |
| 5 |
import { cn, DateRange } from "./common/ui"; |
| 6 |
import DarkModeToggle from "../../components/DarkModeToggle"; |
| 7 |
|
| 8 |
export const TITLES = { |
| 9 |
overview: { title: __( "Overview", "betterdocs" ), subtitle: __( "Reader behavior across all your knowledge bases.", "betterdocs" ) }, |
| 10 |
"ai-traffic": { title: __( "AI Traffic", "betterdocs" ), subtitle: __( "Which AI agents are reading your docs — and how often.", "betterdocs" ) }, |
| 11 |
articles: { title: __( "Docs Performance", "betterdocs" ), subtitle: __( "How each doc is performing — views, unique readers, and trends.", "betterdocs" ) }, |
| 12 |
search: { title: __( "Search Analytics", "betterdocs" ), subtitle: __( "What your readers are looking for — and what they can't find.", "betterdocs" ) }, |
| 13 |
engagement: { title: __( "Reader Engagement", "betterdocs" ), subtitle: __( "Geography, sources, devices, and languages.", "betterdocs" ) }, |
| 14 |
feedback: { title: __( "Feedback Inbox", "betterdocs" ), subtitle: __( "Likes, dislikes, and comments — all in one feed.", "betterdocs" ) }, |
| 15 |
links: { title: __( "Link Health", "betterdocs" ), subtitle: __( "Broken links and redirects across your articles.", "betterdocs" ) }, |
| 16 |
authors: { title: __( "Author Performance", "betterdocs" ), subtitle: __( "Per-author contribution and reception across the knowledge base.", "betterdocs" ) }, |
| 17 |
settings: { title: __( "Analytics Settings", "betterdocs" ), subtitle: __( "Configure data retention, privacy, and integrations.", "betterdocs" ) } |
| 18 |
}; |
| 19 |
|
| 20 |
/* The tab title + sub-header + filters are portalled into the admin header's |
| 21 |
second-row .betterdocs-dashboard-container so the analytics title sits in the |
| 22 |
unified header (consistent with FAQ Builder + Pro). |
| 23 |
|
| 24 |
`titles` / `fallbackRoute` are parameterised so the Content Intelligence teaser |
| 25 |
(content-intelligence/index.js) can reuse this exact portal — same header slot, |
| 26 |
same dark-mode switcher — with its own per-module heading map. */ |
| 27 |
export function PageHeader( { route, days, onDays, showFilters = true, titles = TITLES, fallbackRoute = "overview" } ) { |
| 28 |
const [ host, setHost ] = useState( null ); |
| 29 |
|
| 30 |
useEffect( () => { |
| 31 |
const target = document.querySelector( ".betterdocs-dashboard-header > .betterdocs-dashboard-container" ); |
| 32 |
if ( ! target ) { |
| 33 |
return; |
| 34 |
} |
| 35 |
const node = document.createElement( "div" ); |
| 36 |
node.className = "bda-analytics-header"; |
| 37 |
target.appendChild( node ); |
| 38 |
setHost( node ); |
| 39 |
return () => { node.remove(); }; |
| 40 |
}, [] ); |
| 41 |
|
| 42 |
if ( ! host ) { |
| 43 |
return null; |
| 44 |
} |
| 45 |
|
| 46 |
const meta = titles[ route ] || titles[ fallbackRoute ]; |
| 47 |
|
| 48 |
return createPortal( |
| 49 |
<div className="betterdocs-row"> |
| 50 |
<div className="betterdocs-col betterdocs-header-left betterdocs-col-xs-6"> |
| 51 |
<div className="bda-page-heading"> |
| 52 |
<h2 className="betterdocs-dashboard-header-title">{ meta.title }</h2> |
| 53 |
{ meta.subtitle && <p className="bda-page-sub">{ meta.subtitle }</p> } |
| 54 |
</div> |
| 55 |
</div> |
| 56 |
<div className="betterdocs-col betterdocs-header-right betterdocs-col-xs-6 betterdocs-d-flex betterdocs-justify-end"> |
| 57 |
{ showFilters && <DateRange value={ days } onChange={ onDays } /> } |
| 58 |
<ul className="betterdocs-dashboard-header-action-list betterdocs-text-align-right"> |
| 59 |
<li><DarkModeToggle /></li> |
| 60 |
</ul> |
| 61 |
</div> |
| 62 |
</div>, |
| 63 |
host |
| 64 |
); |
| 65 |
} |
| 66 |
|
| 67 |
/* Pro flag — every tab is gated in Free (Overview now shows a locked teaser |
| 68 |
too). The `pro` field controls the inline nav badge AND which view renders. */ |
| 69 |
const NAV = [ |
| 70 |
{ id: "overview", label: __( "Overview", "betterdocs" ), icon: I.Home, pro: true }, |
| 71 |
{ id: "ai-traffic", label: __( "AI Traffic", "betterdocs" ), icon: I.Sparkle, pro: true }, |
| 72 |
{ id: "articles", label: __( "Docs Performance", "betterdocs" ), icon: I.Article, pro: true }, |
| 73 |
{ id: "search", label: __( "Search Analytics", "betterdocs" ), icon: I.Search, pro: true }, |
| 74 |
{ id: "engagement", label: __( "Reader Engagement", "betterdocs" ), icon: I.Users, pro: true }, |
| 75 |
{ id: "feedback", label: __( "Feedback Inbox", "betterdocs" ), icon: I.Inbox, pro: true }, |
| 76 |
{ id: "links", label: __( "Link Health", "betterdocs" ), icon: I.Link, pro: true }, |
| 77 |
{ id: "authors", label: __( "Author Performance", "betterdocs" ), icon: I.Pen, pro: true } |
| 78 |
]; |
| 79 |
|
| 80 |
const NAV_CONFIG = [ |
| 81 |
{ id: "settings", label: __( "Settings", "betterdocs" ), icon: I.Settings, pro: true } |
| 82 |
]; |
| 83 |
|
| 84 |
export function AnalyticsNav( { active, onNav } ) { |
| 85 |
const renderItem = ( item ) => { |
| 86 |
const Icon = item.icon; |
| 87 |
const isActive = item.id === active; |
| 88 |
return ( |
| 89 |
<button key={ item.id } className={ cn( "bda-nav-item", isActive && "active" ) } onClick={ () => onNav( item.id ) }> |
| 90 |
<Icon size={ 16 } className="bda-nav-ico" strokeWidth={ isActive ? 2.2 : 1.8 } /> |
| 91 |
<span>{ item.label }</span> |
| 92 |
</button> |
| 93 |
); |
| 94 |
}; |
| 95 |
return ( |
| 96 |
<nav className="bda-nav"> |
| 97 |
<div className="bda-nav-items"> |
| 98 |
{ NAV.map( renderItem ) } |
| 99 |
</div> |
| 100 |
<div className="bda-nav-items"> |
| 101 |
{ NAV_CONFIG.map( renderItem ) } |
| 102 |
</div> |
| 103 |
</nav> |
| 104 |
); |
| 105 |
} |
| 106 |
|