| 1 |
import React from "react"; |
| 2 |
import { useState, useEffect } from "@wordpress/element"; |
| 3 |
import { __, sprintf } from "@wordpress/i18n"; |
| 4 |
import { decodeEntities } from "@wordpress/html-entities"; |
| 5 |
import { Sparkle, IconDoc } from "../../sample-docs/icons"; |
| 6 |
import { AiBadge, GenerateBtn, PopulatedDocs } from "../../sample-docs/Pieces"; |
| 7 |
import SampleDocsFlow from "../../sample-docs/SampleDocsFlow"; |
| 8 |
import { sampleDocsEnabled, fetchExistingDocs } from "../../sample-docs/api"; |
| 9 |
|
| 10 |
/* Quick Setup "Create Content" field — replaces the old DocCreate.gif. |
| 11 |
Leads with the AI promo banner ("Skip the blank page"). If the site |
| 12 |
already has docs/categories (or sample docs were generated this session), |
| 13 |
it shows them read-only in the All Docs grid instead of the empty prompt. |
| 14 |
Renders inside the tab content area only; tab header + left nav untouched. */ |
| 15 |
|
| 16 |
/* Persist the generated docs for the wizard session so leaving the page |
| 17 |
(Dashboard / All Docs) and returning keeps the populated view. */ |
| 18 |
const LS_GEN_KEY = "betterdocs_setup_generated_docs"; |
| 19 |
|
| 20 |
const slimCats = (cats) => |
| 21 |
(cats || []).map((c, i) => ({ |
| 22 |
id: c.id || "sd_" + i, |
| 23 |
name: decodeEntities(c.name || ""), |
| 24 |
icon: c.icon || "book", |
| 25 |
color: c.color || "#00B884", |
| 26 |
articles: (c.articles || []).map((a) => decodeEntities(typeof a === "string" ? a : a?.title || "")), |
| 27 |
})); |
| 28 |
|
| 29 |
const readStoredDocs = () => { |
| 30 |
try { |
| 31 |
const s = window.localStorage?.getItem(LS_GEN_KEY); |
| 32 |
const parsed = s ? JSON.parse(s) : null; |
| 33 |
return Array.isArray(parsed) && parsed.length ? parsed : null; |
| 34 |
} catch (e) { |
| 35 |
return null; |
| 36 |
} |
| 37 |
}; |
| 38 |
|
| 39 |
function SampleDocsGenerator(props) { |
| 40 |
const [flowOpen, setFlowOpen] = useState(false); |
| 41 |
const [generated, setGenerated] = useState(readStoredDocs); |
| 42 |
const [existing, setExisting] = useState(null); |
| 43 |
const aiEnabled = sampleDocsEnabled(); |
| 44 |
|
| 45 |
// Detect content the site already has, so the step reflects reality |
| 46 |
// instead of always prompting "No categories yet". |
| 47 |
useEffect(() => { |
| 48 |
if (generated) return; // a fresh generation takes precedence |
| 49 |
let alive = true; |
| 50 |
fetchExistingDocs().then((cats) => { |
| 51 |
if (alive && cats && cats.length) { |
| 52 |
setExisting(slimCats(cats)); |
| 53 |
} |
| 54 |
}); |
| 55 |
return () => { |
| 56 |
alive = false; |
| 57 |
}; |
| 58 |
}, []); |
| 59 |
|
| 60 |
if (!aiEnabled) { |
| 61 |
return null; |
| 62 |
} |
| 63 |
|
| 64 |
// What to display in the grid: a fresh generation wins, else existing content. |
| 65 |
const docs = generated || existing; |
| 66 |
const totalDocs = docs ? docs.reduce((n, c) => n + (c.articles?.length || 0), 0) : 0; |
| 67 |
|
| 68 |
return ( |
| 69 |
<div className={`betterdocs-sd-qs ${props?.classes || ""}`}> |
| 70 |
{/* AI promo + empty prompt only when there are no docs yet. */} |
| 71 |
{!docs && ( |
| 72 |
<> |
| 73 |
{/* AI promo banner — green gradient hero, matches the design. */} |
| 74 |
<div className="betterdocs-sd-promo betterdocs-sd-fade"> |
| 75 |
<span className="betterdocs-sd-promo-glow" aria-hidden="true" /> |
| 76 |
<span className="betterdocs-sd-promo-icon"> |
| 77 |
<Sparkle size={28} /> |
| 78 |
</span> |
| 79 |
<div className="betterdocs-sd-promo-body"> |
| 80 |
<div className="betterdocs-sd-promo-head"> |
| 81 |
<h4 className="betterdocs-sd-promo-title"> |
| 82 |
{__("Skip the blank page", "betterdocs")} |
| 83 |
</h4> |
| 84 |
<AiBadge /> |
| 85 |
</div> |
| 86 |
<p className="betterdocs-sd-promo-text"> |
| 87 |
{__("BetterDocs can read your site and generate a tailored starter knowledge base — categories and draft articles — in about a minute.", "betterdocs")} |
| 88 |
</p> |
| 89 |
</div> |
| 90 |
<GenerateBtn onClick={() => setFlowOpen(true)}> |
| 91 |
{__("Generate Sample Docs", "betterdocs")} |
| 92 |
</GenerateBtn> |
| 93 |
</div> |
| 94 |
|
| 95 |
<div className="betterdocs-sd-manual-divider"> |
| 96 |
<span className="betterdocs-sd-manual-line" /> |
| 97 |
<span className="betterdocs-sd-manual-label">{__("or build it yourself", "betterdocs")}</span> |
| 98 |
<span className="betterdocs-sd-manual-line" /> |
| 99 |
</div> |
| 100 |
<div className="betterdocs-sd-manual"> |
| 101 |
<span className="betterdocs-sd-manual-icon"> |
| 102 |
<IconDoc size={24} /> |
| 103 |
</span> |
| 104 |
<div className="betterdocs-sd-manual-title">{__("No categories yet", "betterdocs")}</div> |
| 105 |
<div className="betterdocs-sd-manual-text"> |
| 106 |
{__("Add your first category and articles manually from the All Docs screen after setup.", "betterdocs")} |
| 107 |
</div> |
| 108 |
</div> |
| 109 |
</> |
| 110 |
)} |
| 111 |
|
| 112 |
{docs && docs.length > 0 && ( |
| 113 |
<div className="betterdocs-sd-qs-result"> |
| 114 |
<div className="betterdocs-sd-qs-result-head"> |
| 115 |
{sprintf( |
| 116 |
generated |
| 117 |
? __("%1$d categories · %2$d docs created", "betterdocs") |
| 118 |
: __("%1$d categories · %2$d docs", "betterdocs"), |
| 119 |
docs.length, |
| 120 |
totalDocs |
| 121 |
)} |
| 122 |
</div> |
| 123 |
{/* Read-only preview — no actions available inside the wizard. */} |
| 124 |
<PopulatedDocs cats={docs} /> |
| 125 |
</div> |
| 126 |
)} |
| 127 |
|
| 128 |
{flowOpen && ( |
| 129 |
<SampleDocsFlow |
| 130 |
contentType="docs" |
| 131 |
onClose={() => setFlowOpen(false)} |
| 132 |
onComplete={(result) => { |
| 133 |
setFlowOpen(false); |
| 134 |
if (result?.categories?.length) { |
| 135 |
const slim = slimCats(result.categories); |
| 136 |
setGenerated(slim); |
| 137 |
try { |
| 138 |
window.localStorage?.setItem(LS_GEN_KEY, JSON.stringify(slim)); |
| 139 |
} catch (e) {} |
| 140 |
} |
| 141 |
}} |
| 142 |
/> |
| 143 |
)} |
| 144 |
</div> |
| 145 |
); |
| 146 |
} |
| 147 |
|
| 148 |
export default SampleDocsGenerator; |
| 149 |
|