sanity-plugin-seofields 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -308,6 +308,16 @@ export declare interface SeoFieldsPluginConfig {
308
308
  fontSize?: string
309
309
  }
310
310
  | undefined
311
+ /**
312
+ * Enable preview/demo mode to show dummy data.
313
+ * Useful for testing, documentation, or showcasing the dashboard.
314
+ * When enabled, displays realistic sample documents with various SEO scores.
315
+ * Defaults to `false`.
316
+ *
317
+ * @example
318
+ * previewMode: true
319
+ */
320
+ previewMode?: boolean
311
321
  }
312
322
  }
313
323
 
@@ -413,6 +423,13 @@ declare interface SeoHealthDashboardProps {
413
423
  * Defaults to `"No documents found"`.
414
424
  */
415
425
  noDocuments?: React_2.ReactNode
426
+ /**
427
+ * Enable preview/demo mode to show dummy data.
428
+ * Useful for testing, documentation, or showcasing the dashboard.
429
+ * When enabled, displays realistic sample documents with various SEO scores.
430
+ * Defaults to `false`.
431
+ */
432
+ previewMode?: boolean
416
433
  }
417
434
 
418
435
  export declare interface SeoHealthMetrics {
package/dist/index.d.ts CHANGED
@@ -308,6 +308,16 @@ export declare interface SeoFieldsPluginConfig {
308
308
  fontSize?: string
309
309
  }
310
310
  | undefined
311
+ /**
312
+ * Enable preview/demo mode to show dummy data.
313
+ * Useful for testing, documentation, or showcasing the dashboard.
314
+ * When enabled, displays realistic sample documents with various SEO scores.
315
+ * Defaults to `false`.
316
+ *
317
+ * @example
318
+ * previewMode: true
319
+ */
320
+ previewMode?: boolean
311
321
  }
312
322
  }
313
323
 
@@ -413,6 +423,13 @@ declare interface SeoHealthDashboardProps {
413
423
  * Defaults to `"No documents found"`.
414
424
  */
415
425
  noDocuments?: React_2.ReactNode
426
+ /**
427
+ * Enable preview/demo mode to show dummy data.
428
+ * Useful for testing, documentation, or showcasing the dashboard.
429
+ * When enabled, displays realistic sample documents with various SEO scores.
430
+ * Defaults to `false`.
431
+ */
432
+ previewMode?: boolean
416
433
  }
417
434
 
418
435
  export declare interface SeoHealthMetrics {
package/dist/index.js CHANGED
@@ -1172,6 +1172,20 @@ const DashboardContainer = dt.div`
1172
1172
  font-weight: 700;
1173
1173
  color: #111827;
1174
1174
  letter-spacing: -0.3px;
1175
+ display: flex;
1176
+ align-items: center;
1177
+ gap: 10px;
1178
+ `, PreviewBadge = dt.span`
1179
+ display: inline-block;
1180
+ background: #fef3c7;
1181
+ color: #92400e;
1182
+ font-size: 11px;
1183
+ font-weight: 600;
1184
+ padding: 4px 8px;
1185
+ border-radius: 4px;
1186
+ text-transform: uppercase;
1187
+ letter-spacing: 0.5px;
1188
+ margin-left: 8px;
1175
1189
  `, PageSubtitle = dt.p`
1176
1190
  margin: 0;
1177
1191
  font-size: 13px;
@@ -1357,8 +1371,8 @@ const DashboardContainer = dt.div`
1357
1371
  border-radius: 5px;
1358
1372
  font-size: 11px;
1359
1373
  font-weight: 500;
1360
- background: #ede9fe;
1361
- color: #5b21b6;
1374
+ background: ${(p) => p.$bgColor || "#ede9fe"};
1375
+ color: ${(p) => p.$textColor || "#5b21b6"};
1362
1376
  `, TypeText = dt.span`
1363
1377
  font-size: 12px;
1364
1378
  font-weight: 500;
@@ -1541,7 +1555,48 @@ const DashboardContainer = dt.div`
1541
1555
  text-align: center;
1542
1556
  color: #9ca3af;
1543
1557
  font-size: 13px;
1544
- `, getStatusCategory = (score) => score >= 80 ? "excellent" : score >= 60 ? "good" : score >= 40 ? "fair" : score > 0 ? "poor" : "missing", scoreMetaTitle = (title) => {
1558
+ `, TYPE_COLOR_PALETTE = [
1559
+ { bg: "#dbeafe", text: "#0c4a6e" },
1560
+ // Blue
1561
+ { bg: "#dcfce7", text: "#14532d" },
1562
+ // Green
1563
+ { bg: "#fce7f3", text: "#500724" },
1564
+ // Pink
1565
+ { bg: "#fed7aa", text: "#7c2d12" },
1566
+ // Orange
1567
+ { bg: "#e9d5ff", text: "#581c87" },
1568
+ // Purple
1569
+ { bg: "#f3e8ff", text: "#3f0f5c" },
1570
+ // Deep Purple
1571
+ { bg: "#ccfbf1", text: "#134e4a" },
1572
+ // Teal
1573
+ { bg: "#ddd6fe", text: "#3730a3" },
1574
+ // Indigo
1575
+ { bg: "#fca5a5", text: "#7f1d1d" },
1576
+ // Red
1577
+ { bg: "#a7f3d0", text: "#065f46" },
1578
+ // Emerald
1579
+ { bg: "#fbbf24", text: "#78350f" },
1580
+ // Amber
1581
+ { bg: "#c4b5fd", text: "#3b0764" },
1582
+ // Violet
1583
+ { bg: "#f0fdf4", text: "#15803d" },
1584
+ // Light Green
1585
+ { bg: "#fef2f2", text: "#991b1b" },
1586
+ // Light Red
1587
+ { bg: "#f5f3ff", text: "#5b21b6" },
1588
+ // Light Purple
1589
+ { bg: "#fffbeb", text: "#92400e" }
1590
+ // Light Amber
1591
+ ], getTypeColor = (type) => {
1592
+ let hash2 = 0;
1593
+ for (let i = 0; i < type.length; i += 1) {
1594
+ const char2 = type.charCodeAt(i);
1595
+ hash2 = Math.abs(hash2 * 31 + char2);
1596
+ }
1597
+ const colorIndex = hash2 % TYPE_COLOR_PALETTE.length;
1598
+ return TYPE_COLOR_PALETTE[colorIndex];
1599
+ }, getStatusCategory = (score) => score >= 80 ? "excellent" : score >= 60 ? "good" : score >= 40 ? "fair" : score > 0 ? "poor" : "missing", scoreMetaTitle = (title) => {
1545
1600
  const issues = [];
1546
1601
  let score = 0;
1547
1602
  return title && title.length >= 50 && title.length <= 60 ? score = 15 : title && title.length > 0 ? (score = 10, title.length < 50 && issues.push("Meta title too short (< 50 chars)"), title.length > 60 && issues.push("Meta title too long (> 60 chars)")) : issues.push("Missing meta title"), { score, issues };
@@ -1572,7 +1627,135 @@ const DashboardContainer = dt.div`
1572
1627
  totalScore += twitterScore.score, allIssues.push(...twitterScore.issues), robots2 && !robots2.noIndex && (totalScore += 5);
1573
1628
  const status = getStatusCategory(totalScore);
1574
1629
  return { score: totalScore, status, issues: allIssues };
1575
- }, resolveTypeLabel = (type, typeLabels) => typeLabels?.[type] ?? type, buildTitleProjection = (titleField) => !titleField || titleField === "title" ? "title" : typeof titleField == "string" ? `"title": ${titleField}` : `"title": select(${Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ")}, title)`, SeoHealthDashboard = ({
1630
+ }, resolveTypeLabel = (type, typeLabels) => typeLabels?.[type] ?? type, buildTitleProjection = (titleField) => !titleField || titleField === "title" ? "title" : typeof titleField == "string" ? `"title": ${titleField}` : `"title": select(${Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ")}, title)`, generateDummyData = () => [
1631
+ {
1632
+ _id: "preview-post-1",
1633
+ _type: "post",
1634
+ title: "Getting Started with SEO Best Practices",
1635
+ slug: { current: "getting-started-seo" },
1636
+ _updatedAt: new Date(Date.now() - 1728e5).toISOString(),
1637
+ seo: {
1638
+ title: "Getting Started with SEO Best Practices | My Blog",
1639
+ description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
1640
+ keywords: ["seo", "best practices", "optimization"],
1641
+ metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
1642
+ openGraph: {
1643
+ title: "SEO Best Practices Guide",
1644
+ description: "Master SEO optimization",
1645
+ image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
1646
+ type: "article"
1647
+ },
1648
+ twitter: {
1649
+ title: "SEO Best Practices",
1650
+ description: "Learn SEO optimization",
1651
+ image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
1652
+ card: "summary_large_image"
1653
+ }
1654
+ }
1655
+ },
1656
+ {
1657
+ _id: "preview-post-2",
1658
+ _type: "post",
1659
+ title: "Advanced Analytics Strategy",
1660
+ slug: { current: "advanced-analytics" },
1661
+ _updatedAt: new Date(Date.now() - 432e6).toISOString(),
1662
+ seo: {
1663
+ title: "Advanced Analytics",
1664
+ description: "Strategy tips",
1665
+ keywords: ["analytics", "data"],
1666
+ openGraph: {
1667
+ title: "Analytics Guide"
1668
+ }
1669
+ }
1670
+ },
1671
+ {
1672
+ _id: "preview-page-1",
1673
+ _type: "page",
1674
+ title: "About Us",
1675
+ slug: { current: "about" },
1676
+ _updatedAt: new Date(Date.now() - 864e6).toISOString(),
1677
+ seo: {
1678
+ title: "About",
1679
+ keywords: ["company", "team"],
1680
+ metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
1681
+ }
1682
+ },
1683
+ {
1684
+ _id: "preview-post-3",
1685
+ _type: "post",
1686
+ title: "Content Marketing Trends for 2024",
1687
+ slug: { current: "content-marketing-trends" },
1688
+ _updatedAt: new Date(Date.now() - 864e5).toISOString(),
1689
+ seo: {
1690
+ title: "Content Marketing Trends 2024",
1691
+ description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
1692
+ keywords: ["content marketing", "trends", "strategy", "engagement"],
1693
+ metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
1694
+ openGraph: {
1695
+ title: "Content Marketing Trends 2024",
1696
+ description: "Latest trends in content marketing",
1697
+ image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
1698
+ type: "article"
1699
+ },
1700
+ twitter: {
1701
+ title: "Content Marketing Trends",
1702
+ description: "Discover the latest trends",
1703
+ card: "summary"
1704
+ }
1705
+ }
1706
+ },
1707
+ {
1708
+ _id: "preview-post-4",
1709
+ _type: "product",
1710
+ title: "Pro Plan",
1711
+ slug: { current: "pro-plan" },
1712
+ _updatedAt: new Date(Date.now() - 1296e6).toISOString(),
1713
+ seo: {
1714
+ title: "Pro",
1715
+ keywords: ["pricing"]
1716
+ }
1717
+ },
1718
+ {
1719
+ _id: "preview-page-2",
1720
+ _type: "page",
1721
+ title: "Contact",
1722
+ slug: { current: "contact" },
1723
+ _updatedAt: new Date(Date.now() - 6912e5).toISOString(),
1724
+ seo: {
1725
+ openGraph: {
1726
+ title: "Get in Touch"
1727
+ }
1728
+ }
1729
+ },
1730
+ {
1731
+ _id: "preview-post-5",
1732
+ _type: "post",
1733
+ title: "Mobile Optimization Guide",
1734
+ slug: { current: "mobile-optimization" },
1735
+ _updatedAt: new Date(Date.now() - 2592e5).toISOString(),
1736
+ seo: {
1737
+ title: "Mobile Optimization Guide: Best Practices for Responsive Design",
1738
+ description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
1739
+ keywords: ["mobile", "optimization", "responsive", "performance"],
1740
+ metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
1741
+ openGraph: {
1742
+ title: "Mobile Optimization Best Practices",
1743
+ description: "Master mobile web optimization",
1744
+ image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
1745
+ type: "article"
1746
+ },
1747
+ twitter: {
1748
+ title: "Mobile Optimization Tips",
1749
+ description: "Responsive design best practices",
1750
+ image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
1751
+ card: "summary_large_image"
1752
+ }
1753
+ }
1754
+ }
1755
+ ].map((doc) => ({
1756
+ ...doc,
1757
+ health: calculateHealthScore(doc)
1758
+ })), SeoHealthDashboard = ({
1576
1759
  icon = "\u{1F4CA}",
1577
1760
  title = "SEO Health Dashboard",
1578
1761
  description = "Monitor and optimize SEO fields across all your documents",
@@ -1589,10 +1772,15 @@ const DashboardContainer = dt.div`
1589
1772
  docBadge,
1590
1773
  loadingLicense,
1591
1774
  loadingDocuments,
1592
- noDocuments
1775
+ noDocuments,
1776
+ previewMode = !1
1593
1777
  }) => {
1594
1778
  const client = sanity.useClient({ apiVersion }), [licenseStatus, setLicenseStatus] = o.useState("loading"), [documents, setDocuments] = o.useState([]), [loading, setLoading] = o.useState(!0), [searchQuery, setSearchQuery] = o.useState(""), [filterStatus, setFilterStatus] = o.useState("all"), [filterType, setFilterType] = o.useState("all"), [sortBy, setSortBy] = o.useState("score"), [activePopover, setActivePopover] = o.useState(null), VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license", CACHE_TTL_MS = 3600 * 1e3, validateLicense = o.useCallback(
1595
1779
  async (forceRefresh = !1) => {
1780
+ if (previewMode) {
1781
+ setLicenseStatus("valid");
1782
+ return;
1783
+ }
1596
1784
  if (!licenseKey) {
1597
1785
  setLicenseStatus("invalid");
1598
1786
  return;
@@ -1632,11 +1820,11 @@ const DashboardContainer = dt.div`
1632
1820
  }
1633
1821
  },
1634
1822
  // eslint-disable-next-line react-hooks/exhaustive-deps
1635
- [licenseKey]
1823
+ [licenseKey, previewMode]
1636
1824
  );
1637
1825
  o.useEffect(() => {
1638
1826
  validateLicense();
1639
- }, [licenseKey]);
1827
+ }, [licenseKey, previewMode]);
1640
1828
  const handleMouseEnterIssues = (el, issues) => {
1641
1829
  if (!el) return;
1642
1830
  const rect = el.getBoundingClientRect(), popoverWidth = 280, viewportWidth = window.innerWidth;
@@ -1646,7 +1834,10 @@ const DashboardContainer = dt.div`
1646
1834
  o.useEffect(() => {
1647
1835
  (async () => {
1648
1836
  try {
1649
- setLoading(!0);
1837
+ if (setLoading(!0), previewMode) {
1838
+ setDocuments(generateDummyData());
1839
+ return;
1840
+ }
1650
1841
  let groqQuery, params = {};
1651
1842
  if (customQuery)
1652
1843
  groqQuery = customQuery;
@@ -1680,7 +1871,16 @@ const DashboardContainer = dt.div`
1680
1871
  setLoading(!1);
1681
1872
  }
1682
1873
  })();
1683
- }, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes), JSON.stringify(titleField)]);
1874
+ }, [
1875
+ client,
1876
+ customQuery,
1877
+ queryRequireSeo,
1878
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1879
+ JSON.stringify(queryTypes),
1880
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1881
+ JSON.stringify(titleField),
1882
+ previewMode
1883
+ ]);
1684
1884
  const uniqueDocumentTypes = o.useMemo(() => {
1685
1885
  const types2 = new Set(documents.map((doc) => doc._type));
1686
1886
  return Array.from(types2).sort();
@@ -1749,9 +1949,12 @@ export default defineConfig({
1749
1949
  licenseStatus === "valid" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1750
1950
  /* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
1751
1951
  /* @__PURE__ */ jsxRuntime.jsxs(PageTitle, { children: [
1752
- icon,
1753
- " ",
1754
- title
1952
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1953
+ icon,
1954
+ " ",
1955
+ title
1956
+ ] }),
1957
+ previewMode && /* @__PURE__ */ jsxRuntime.jsx(PreviewBadge, { children: "Preview Mode" })
1755
1958
  ] }),
1756
1959
  /* @__PURE__ */ jsxRuntime.jsx(PageSubtitle, { children: description })
1757
1960
  ] }),
@@ -1867,7 +2070,10 @@ export default defineConfig({
1867
2070
  }
1868
2071
  )
1869
2072
  ] }) }),
1870
- showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { children: resolveTypeLabel(doc._type, typeLabels) }) }),
2073
+ showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : (() => {
2074
+ const typeColor = getTypeColor(doc._type);
2075
+ return /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, typeLabels) });
2076
+ })() }),
1871
2077
  /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: /* @__PURE__ */ jsxRuntime.jsxs(ScoreBadge, { $score: doc.health.score, children: [
1872
2078
  doc.health.score,
1873
2079
  "%"
@@ -2874,7 +3080,8 @@ const resolveDashboardConfig = (healthDashboard) => {
2874
3080
  docBadge: cfg?.docBadge,
2875
3081
  loadingLicense: cfg?.content?.loadingLicense,
2876
3082
  loadingDocuments: cfg?.content?.loadingDocuments,
2877
- noDocuments: cfg?.content?.noDocuments
3083
+ noDocuments: cfg?.content?.noDocuments,
3084
+ previewMode: cfg?.previewMode
2878
3085
  };
2879
3086
  }, seofields = sanity.definePlugin((config = {}) => {
2880
3087
  const { healthDashboard = !0 } = config, dash = resolveDashboardConfig(healthDashboard), BoundSeoHealthTool = () => o__default.default.createElement(SeoHealthTool, {
@@ -2894,7 +3101,8 @@ const resolveDashboardConfig = (healthDashboard) => {
2894
3101
  docBadge: dash.docBadge,
2895
3102
  loadingLicense: dash.loadingLicense,
2896
3103
  loadingDocuments: dash.loadingDocuments,
2897
- noDocuments: dash.noDocuments
3104
+ noDocuments: dash.noDocuments,
3105
+ previewMode: dash.previewMode
2898
3106
  });
2899
3107
  return {
2900
3108
  name: "sanity-plugin-seofields",