@geenius/seo 0.1.0 → 0.3.0

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.
Files changed (126) hide show
  1. package/package.json +16 -3
  2. package/packages/convex/dist/index.d.ts +56 -0
  3. package/packages/convex/dist/index.js +133 -0
  4. package/packages/convex/dist/index.js.map +1 -0
  5. package/packages/react/README.md +1 -1
  6. package/packages/react/dist/index.d.ts +156 -0
  7. package/packages/react/dist/index.js +567 -0
  8. package/packages/react/dist/index.js.map +1 -0
  9. package/packages/react-css/README.md +1 -1
  10. package/packages/react-css/dist/index.cjs +571 -0
  11. package/packages/react-css/dist/index.cjs.map +1 -0
  12. package/packages/react-css/{src/seo.css → dist/index.css} +7 -153
  13. package/packages/react-css/dist/index.css.map +1 -0
  14. package/packages/react-css/dist/index.d.cts +53 -0
  15. package/packages/react-css/dist/index.d.ts +53 -0
  16. package/packages/react-css/dist/index.js +539 -0
  17. package/packages/react-css/dist/index.js.map +1 -0
  18. package/packages/shared/README.md +1 -1
  19. package/packages/shared/dist/index.d.ts +262 -0
  20. package/packages/shared/dist/index.js +381 -0
  21. package/packages/shared/dist/index.js.map +1 -0
  22. package/packages/solidjs/README.md +1 -1
  23. package/packages/solidjs/dist/index.d.ts +133 -0
  24. package/packages/solidjs/dist/index.js +416 -0
  25. package/packages/solidjs/dist/index.js.map +1 -0
  26. package/packages/solidjs-css/README.md +1 -1
  27. package/packages/solidjs-css/dist/index.cjs +399 -0
  28. package/packages/solidjs-css/dist/index.cjs.map +1 -0
  29. package/packages/solidjs-css/{src/seo.css → dist/index.css} +7 -153
  30. package/packages/solidjs-css/dist/index.css.map +1 -0
  31. package/packages/solidjs-css/dist/index.d.cts +53 -0
  32. package/packages/solidjs-css/dist/index.d.ts +53 -0
  33. package/packages/solidjs-css/dist/index.js +367 -0
  34. package/packages/solidjs-css/dist/index.js.map +1 -0
  35. package/.changeset/config.json +0 -11
  36. package/.github/CODEOWNERS +0 -1
  37. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  38. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  39. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  40. package/.github/dependabot.yml +0 -11
  41. package/.github/workflows/ci.yml +0 -23
  42. package/.github/workflows/release.yml +0 -29
  43. package/.nvmrc +0 -1
  44. package/.project/ACCOUNT.yaml +0 -4
  45. package/.project/IDEAS.yaml +0 -7
  46. package/.project/PROJECT.yaml +0 -11
  47. package/.project/ROADMAP.yaml +0 -15
  48. package/CODE_OF_CONDUCT.md +0 -16
  49. package/CONTRIBUTING.md +0 -26
  50. package/SECURITY.md +0 -15
  51. package/SUPPORT.md +0 -8
  52. package/packages/convex/package.json +0 -42
  53. package/packages/convex/src/functions.ts +0 -5
  54. package/packages/convex/src/mutations.ts +0 -83
  55. package/packages/convex/src/queries.ts +0 -57
  56. package/packages/convex/src/schema.ts +0 -23
  57. package/packages/convex/tsconfig.json +0 -19
  58. package/packages/convex/tsup.config.ts +0 -18
  59. package/packages/react/package.json +0 -49
  60. package/packages/react/src/components/ArticleJsonLd.tsx +0 -42
  61. package/packages/react/src/components/BreadcrumbsJsonLd.tsx +0 -24
  62. package/packages/react/src/components/MetaEditor.tsx +0 -147
  63. package/packages/react/src/components/SEOHead.tsx +0 -107
  64. package/packages/react/src/components/SEOPreview.tsx +0 -42
  65. package/packages/react/src/components/SEOScoreCard.tsx +0 -51
  66. package/packages/react/src/components/SitemapViewer.tsx +0 -36
  67. package/packages/react/src/components/index.ts +0 -7
  68. package/packages/react/src/hooks/index.ts +0 -4
  69. package/packages/react/src/hooks/useSEO.ts +0 -27
  70. package/packages/react/src/hooks/useSEOAdmin.ts +0 -42
  71. package/packages/react/src/hooks/useSEOScore.ts +0 -7
  72. package/packages/react/src/hooks/useSitemap.ts +0 -8
  73. package/packages/react/src/index.ts +0 -51
  74. package/packages/react/src/index.tsx +0 -11
  75. package/packages/react/src/pages/SEOAdminPage.tsx +0 -101
  76. package/packages/react/src/pages/SEOAnalyticsPage.tsx +0 -96
  77. package/packages/react/src/pages/index.ts +0 -2
  78. package/packages/react/tsconfig.json +0 -19
  79. package/packages/react/tsup.config.ts +0 -12
  80. package/packages/react-css/package.json +0 -36
  81. package/packages/react-css/src/components/ArticleJsonLd.tsx +0 -42
  82. package/packages/react-css/src/components/BreadcrumbsJsonLd.tsx +0 -24
  83. package/packages/react-css/src/components/MetaEditor.tsx +0 -147
  84. package/packages/react-css/src/components/SEOHead.tsx +0 -95
  85. package/packages/react-css/src/components/SEOPreview.tsx +0 -42
  86. package/packages/react-css/src/components/SEOScoreCard.tsx +0 -42
  87. package/packages/react-css/src/components/SitemapViewer.tsx +0 -36
  88. package/packages/react-css/src/components/index.ts +0 -7
  89. package/packages/react-css/src/index.ts +0 -9
  90. package/packages/react-css/src/pages/SEOAdminPage.tsx +0 -88
  91. package/packages/react-css/src/pages/SEOAnalyticsPage.tsx +0 -82
  92. package/packages/react-css/src/pages/index.ts +0 -2
  93. package/packages/react-css/tsup.config.ts +0 -2
  94. package/packages/shared/package.json +0 -42
  95. package/packages/shared/src/__tests__/seo.test.ts +0 -70
  96. package/packages/shared/src/config.ts +0 -297
  97. package/packages/shared/src/index.ts +0 -207
  98. package/packages/shared/tsconfig.json +0 -18
  99. package/packages/shared/tsup.config.ts +0 -11
  100. package/packages/shared/vitest.config.ts +0 -4
  101. package/packages/solidjs/package.json +0 -45
  102. package/packages/solidjs/src/components/ArticleJsonLd.tsx +0 -35
  103. package/packages/solidjs/src/components/BreadcrumbsJsonLd.tsx +0 -24
  104. package/packages/solidjs/src/components/MetaEditor.tsx +0 -155
  105. package/packages/solidjs/src/components/SEOHead.tsx +0 -109
  106. package/packages/solidjs/src/components/SEOPreview.tsx +0 -42
  107. package/packages/solidjs/src/components/SEOScoreCard.tsx +0 -57
  108. package/packages/solidjs/src/components/SitemapViewer.tsx +0 -44
  109. package/packages/solidjs/src/components/index.ts +0 -7
  110. package/packages/solidjs/src/index.ts +0 -11
  111. package/packages/solidjs/src/pages/SEOAdminPage.tsx +0 -104
  112. package/packages/solidjs/src/pages/SEOAnalyticsPage.tsx +0 -102
  113. package/packages/solidjs/src/pages/index.ts +0 -2
  114. package/packages/solidjs/src/primitives/index.ts +0 -4
  115. package/packages/solidjs/src/primitives/useSEO.ts +0 -27
  116. package/packages/solidjs/src/primitives/useSEOAdmin.ts +0 -42
  117. package/packages/solidjs/src/primitives/useSEOScore.ts +0 -7
  118. package/packages/solidjs/src/primitives/useSitemap.ts +0 -8
  119. package/packages/solidjs/tsconfig.json +0 -20
  120. package/packages/solidjs/tsup.config.ts +0 -12
  121. package/packages/solidjs-css/package.json +0 -35
  122. package/packages/solidjs-css/src/index.ts +0 -5
  123. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  124. package/packages/solidjs-css/tsup.config.ts +0 -2
  125. package/pnpm-workspace.yaml +0 -2
  126. package/tsconfig.json +0 -23
@@ -0,0 +1,567 @@
1
+ import { calcSEOScore, generateSitemapXml, buildCanonical, breadcrumbSchema, articleSchema } from '@geenius/seo-shared';
2
+ export * from '@geenius/seo-shared';
3
+ import { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/index.ts
7
+ var SeoContext = createContext(null);
8
+ function SeoProvider({ config, children }) {
9
+ return /* @__PURE__ */ jsx(SeoContext.Provider, { value: { config }, children });
10
+ }
11
+ function useSEOContext() {
12
+ const ctx = useContext(SeoContext);
13
+ if (!ctx) {
14
+ throw new Error("useSEOContext must be used within a <SeoProvider>");
15
+ }
16
+ return ctx;
17
+ }
18
+ function useSEO(options) {
19
+ const [meta, setMeta] = useState(options?.initialMeta ?? null);
20
+ const [isLoading, setIsLoading] = useState(false);
21
+ const updateMeta = useCallback((newMeta) => {
22
+ setMeta(newMeta);
23
+ }, []);
24
+ useEffect(() => {
25
+ if (!options?.path) return;
26
+ setIsLoading(false);
27
+ }, [options?.path]);
28
+ return { meta, updateMeta, isLoading };
29
+ }
30
+ function useSEOAdmin(options) {
31
+ const [pages, setPages] = useState(options?.initialPages ?? []);
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ useEffect(() => {
34
+ if (options?.initialPages) {
35
+ setPages(options.initialPages);
36
+ }
37
+ }, [options?.initialPages]);
38
+ const upsertMeta = useCallback(async (path, meta) => {
39
+ if (options?.onUpsert) {
40
+ await options.onUpsert(path, meta);
41
+ }
42
+ setPages((prev) => {
43
+ const existing = prev.findIndex((p) => p.path === path);
44
+ if (existing >= 0) {
45
+ const next = [...prev];
46
+ next[existing] = { path, meta };
47
+ return next;
48
+ }
49
+ return [...prev, { path, meta }];
50
+ });
51
+ }, [options?.onUpsert]);
52
+ const deleteMeta = useCallback(async (path) => {
53
+ if (options?.onDelete) {
54
+ await options.onDelete(path);
55
+ }
56
+ setPages((prev) => prev.filter((p) => p.path !== path));
57
+ }, [options?.onDelete]);
58
+ return { pages, upsertMeta, deleteMeta, isLoading };
59
+ }
60
+ function useSEOScore(meta) {
61
+ return useMemo(() => calcSEOScore(meta), [meta]);
62
+ }
63
+ function useSitemap(entries) {
64
+ const xml = useMemo(() => generateSitemapXml(entries), [entries]);
65
+ return { xml };
66
+ }
67
+ function useCanonical(path) {
68
+ const { config } = useSEOContext();
69
+ return useMemo(() => {
70
+ const resolvedPath = path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
71
+ return buildCanonical(resolvedPath, config.siteUrl);
72
+ }, [path, config.siteUrl]);
73
+ }
74
+ function useStructuredData(data) {
75
+ useEffect(() => {
76
+ if (!data || Array.isArray(data) && data.length === 0) return;
77
+ const items = Array.isArray(data) ? data : [data];
78
+ const scripts = [];
79
+ for (const item of items) {
80
+ const script = document.createElement("script");
81
+ script.type = "application/ld+json";
82
+ script.textContent = JSON.stringify(item);
83
+ document.head.appendChild(script);
84
+ scripts.push(script);
85
+ }
86
+ return () => {
87
+ for (const script of scripts) {
88
+ script.remove();
89
+ }
90
+ };
91
+ }, [data]);
92
+ }
93
+ function SEOHead({ meta }) {
94
+ useEffect(() => {
95
+ document.title = meta.title;
96
+ const setMetaTag = (name, content, isProperty = false) => {
97
+ let tag = document.querySelector(
98
+ `meta[${isProperty ? "property" : "name"}="${name}"]`
99
+ );
100
+ if (!tag) {
101
+ tag = document.createElement("meta");
102
+ isProperty ? tag.setAttribute("property", name) : tag.setAttribute("name", name);
103
+ document.head.appendChild(tag);
104
+ }
105
+ tag.content = content;
106
+ };
107
+ setMetaTag("description", meta.description);
108
+ if (meta.keywords.length > 0) {
109
+ setMetaTag("keywords", meta.keywords.join(", "));
110
+ }
111
+ if (meta.robots) {
112
+ setMetaTag("robots", meta.robots);
113
+ }
114
+ setMetaTag("og:title", meta.og.title, true);
115
+ setMetaTag("og:description", meta.og.description, true);
116
+ if (meta.og.image) {
117
+ setMetaTag("og:image", meta.og.image, true);
118
+ if (meta.og.imageAlt) {
119
+ setMetaTag("og:image:alt", meta.og.imageAlt, true);
120
+ }
121
+ }
122
+ setMetaTag("og:type", meta.og.type, true);
123
+ setMetaTag("og:url", meta.og.url, true);
124
+ if (meta.og.siteName) {
125
+ setMetaTag("og:site_name", meta.og.siteName, true);
126
+ }
127
+ setMetaTag("twitter:card", meta.twitter.card);
128
+ setMetaTag("twitter:title", meta.twitter.title);
129
+ setMetaTag("twitter:description", meta.twitter.description);
130
+ if (meta.twitter.image) {
131
+ setMetaTag("twitter:image", meta.twitter.image);
132
+ }
133
+ if (meta.twitter.creator) {
134
+ setMetaTag("twitter:creator", meta.twitter.creator);
135
+ }
136
+ if (meta.canonical) {
137
+ let canonicalLink = document.querySelector('link[rel="canonical"]');
138
+ if (!canonicalLink) {
139
+ canonicalLink = document.createElement("link");
140
+ canonicalLink.rel = "canonical";
141
+ document.head.appendChild(canonicalLink);
142
+ }
143
+ canonicalLink.href = meta.canonical;
144
+ }
145
+ if (meta.alternates) {
146
+ Object.entries(meta.alternates).forEach(([lang, url]) => {
147
+ let altLink = document.querySelector(
148
+ `link[rel="alternate"][hreflang="${lang}"]`
149
+ );
150
+ if (!altLink) {
151
+ altLink = document.createElement("link");
152
+ altLink.rel = "alternate";
153
+ altLink.setAttribute("hreflang", lang);
154
+ document.head.appendChild(altLink);
155
+ }
156
+ altLink.href = url;
157
+ });
158
+ }
159
+ if (meta.jsonLd && meta.jsonLd.length > 0) {
160
+ meta.jsonLd.forEach((schema) => {
161
+ let script = document.querySelector('script[type="application/ld+json"]');
162
+ if (!script) {
163
+ script = document.createElement("script");
164
+ script.type = "application/ld+json";
165
+ document.head.appendChild(script);
166
+ }
167
+ script.textContent = JSON.stringify(schema);
168
+ });
169
+ }
170
+ }, [meta]);
171
+ return null;
172
+ }
173
+ function SEOPreview({ meta }) {
174
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
175
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
176
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Google Search Preview" }),
177
+ /* @__PURE__ */ jsxs("div", { className: "bg-white p-4 rounded border border-gray-200", children: [
178
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-green-700", children: new URL(meta.og.url).hostname }),
179
+ /* @__PURE__ */ jsx("div", { className: "text-lg text-blue-600 hover:underline cursor-pointer truncate", children: meta.title }),
180
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-600 line-clamp-2", children: meta.description })
181
+ ] })
182
+ ] }),
183
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
184
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Social Media Preview" }),
185
+ /* @__PURE__ */ jsxs("div", { className: "bg-gray-900 rounded overflow-hidden max-w-sm", children: [
186
+ meta.og.image && /* @__PURE__ */ jsx(
187
+ "img",
188
+ {
189
+ src: meta.og.image,
190
+ alt: meta.og.imageAlt || meta.og.title,
191
+ className: "w-full h-48 object-cover"
192
+ }
193
+ ),
194
+ /* @__PURE__ */ jsxs("div", { className: "p-3 text-white", children: [
195
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm truncate", children: meta.og.title }),
196
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400 line-clamp-2", children: meta.og.description }),
197
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500 mt-1 truncate", children: meta.og.url })
198
+ ] })
199
+ ] })
200
+ ] })
201
+ ] });
202
+ }
203
+ function SEOScoreCard({ meta }) {
204
+ const { score, issues } = useSEOScore(meta);
205
+ const getScoreColor = () => {
206
+ if (score >= 80) return "text-green-600";
207
+ if (score >= 50) return "text-yellow-600";
208
+ return "text-red-600";
209
+ };
210
+ const getBarColor = () => {
211
+ if (score >= 80) return "bg-green-500";
212
+ if (score >= 50) return "bg-yellow-500";
213
+ return "bg-red-500";
214
+ };
215
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 border border-gray-200 rounded", children: [
216
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
217
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-gray-900", children: "SEO Score" }),
218
+ /* @__PURE__ */ jsxs("span", { className: `text-2xl font-bold ${getScoreColor()}`, children: [
219
+ score,
220
+ "/100"
221
+ ] })
222
+ ] }),
223
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsx("div", { className: "h-2 bg-gray-200 rounded overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: `h-full ${getBarColor()} transition-all`, style: { width: `${score}%` } }) }) }),
224
+ issues.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
225
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-700", children: "Issues Found" }),
226
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: issues.map((issue, i) => /* @__PURE__ */ jsxs("li", { className: "text-sm text-red-600 flex items-start", children: [
227
+ /* @__PURE__ */ jsx("span", { className: "mr-2", children: "\u2022" }),
228
+ issue
229
+ ] }, i)) })
230
+ ] })
231
+ ] });
232
+ }
233
+ function MetaEditor({ meta, onChange }) {
234
+ const [localMeta, setLocalMeta] = useState(meta);
235
+ const handleChange = (updates) => {
236
+ const updated = { ...localMeta, ...updates };
237
+ setLocalMeta(updated);
238
+ onChange(updated);
239
+ };
240
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6 p-4 border border-gray-200 rounded", children: [
241
+ /* @__PURE__ */ jsxs("div", { children: [
242
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: [
243
+ "Title ",
244
+ localMeta.title.length,
245
+ "/60"
246
+ ] }),
247
+ /* @__PURE__ */ jsx(
248
+ "input",
249
+ {
250
+ type: "text",
251
+ value: localMeta.title,
252
+ onChange: (e) => handleChange({
253
+ title: e.target.value.slice(0, 60)
254
+ }),
255
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
256
+ placeholder: "Page title"
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: localMeta.title.length < 30 ? "Too short" : localMeta.title.length > 60 ? "Too long" : "Good length" })
260
+ ] }),
261
+ /* @__PURE__ */ jsxs("div", { children: [
262
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: [
263
+ "Description ",
264
+ localMeta.description.length,
265
+ "/160"
266
+ ] }),
267
+ /* @__PURE__ */ jsx(
268
+ "textarea",
269
+ {
270
+ value: localMeta.description,
271
+ onChange: (e) => handleChange({
272
+ description: e.target.value.slice(0, 160)
273
+ }),
274
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
275
+ rows: 3,
276
+ placeholder: "Page description"
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: localMeta.description.length < 50 ? "Too short" : localMeta.description.length > 160 ? "Too long" : "Good length" })
280
+ ] }),
281
+ /* @__PURE__ */ jsxs("div", { children: [
282
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: "Keywords" }),
283
+ /* @__PURE__ */ jsx(
284
+ "input",
285
+ {
286
+ type: "text",
287
+ value: localMeta.keywords.join(", "),
288
+ onChange: (e) => handleChange({
289
+ keywords: e.target.value.split(",").map((k) => k.trim())
290
+ }),
291
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
292
+ placeholder: "keyword1, keyword2, keyword3"
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("div", { children: [
297
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: "Canonical URL" }),
298
+ /* @__PURE__ */ jsx(
299
+ "input",
300
+ {
301
+ type: "url",
302
+ value: localMeta.canonical || "",
303
+ onChange: (e) => handleChange({
304
+ canonical: e.target.value
305
+ }),
306
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
307
+ placeholder: "https://example.com/page"
308
+ }
309
+ )
310
+ ] }),
311
+ /* @__PURE__ */ jsxs("div", { children: [
312
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: "OG Image URL" }),
313
+ /* @__PURE__ */ jsx(
314
+ "input",
315
+ {
316
+ type: "url",
317
+ value: localMeta.og.image || "",
318
+ onChange: (e) => handleChange({
319
+ og: { ...localMeta.og, image: e.target.value }
320
+ }),
321
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
322
+ placeholder: "https://example.com/image.jpg"
323
+ }
324
+ )
325
+ ] }),
326
+ /* @__PURE__ */ jsxs("div", { children: [
327
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: "Twitter Card Type" }),
328
+ /* @__PURE__ */ jsxs(
329
+ "select",
330
+ {
331
+ value: localMeta.twitter.card,
332
+ onChange: (e) => handleChange({
333
+ twitter: {
334
+ ...localMeta.twitter,
335
+ card: e.target.value
336
+ }
337
+ }),
338
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
339
+ children: [
340
+ /* @__PURE__ */ jsx("option", { value: "summary", children: "Summary" }),
341
+ /* @__PURE__ */ jsx("option", { value: "summary_large_image", children: "Summary Large Image" })
342
+ ]
343
+ }
344
+ )
345
+ ] }),
346
+ /* @__PURE__ */ jsxs("div", { children: [
347
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-semibold text-gray-900 mb-1", children: "Robots Directive" }),
348
+ /* @__PURE__ */ jsx(
349
+ "input",
350
+ {
351
+ type: "text",
352
+ value: localMeta.robots || "",
353
+ onChange: (e) => handleChange({
354
+ robots: e.target.value
355
+ }),
356
+ className: "w-full px-3 py-2 border border-gray-300 rounded text-sm",
357
+ placeholder: "index, follow"
358
+ }
359
+ )
360
+ ] })
361
+ ] });
362
+ }
363
+ function SitemapViewer({ entries }) {
364
+ return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
365
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-100 border-b", children: /* @__PURE__ */ jsxs("tr", { children: [
366
+ /* @__PURE__ */ jsx("th", { className: "text-left p-3", children: "URL" }),
367
+ /* @__PURE__ */ jsx("th", { className: "text-left p-3", children: "Last Modified" }),
368
+ /* @__PURE__ */ jsx("th", { className: "text-left p-3", children: "Change Frequency" }),
369
+ /* @__PURE__ */ jsx("th", { className: "text-left p-3", children: "Priority" })
370
+ ] }) }),
371
+ /* @__PURE__ */ jsx("tbody", { children: entries.map((entry, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b hover:bg-gray-50", children: [
372
+ /* @__PURE__ */ jsx("td", { className: "p-3 font-mono text-xs text-blue-600 break-all", children: /* @__PURE__ */ jsx("a", { href: entry.url, target: "_blank", rel: "noopener noreferrer", className: "hover:underline", children: entry.url }) }),
373
+ /* @__PURE__ */ jsx("td", { className: "p-3 text-gray-600", children: entry.lastmod || "-" }),
374
+ /* @__PURE__ */ jsx("td", { className: "p-3 text-gray-600", children: entry.changefreq || "-" }),
375
+ /* @__PURE__ */ jsx("td", { className: "p-3 text-gray-600", children: entry.priority || "-" })
376
+ ] }, i)) })
377
+ ] }) });
378
+ }
379
+ function BreadcrumbsJsonLd({ items }) {
380
+ useEffect(() => {
381
+ const script = document.createElement("script");
382
+ script.type = "application/ld+json";
383
+ script.textContent = JSON.stringify(breadcrumbSchema(items));
384
+ document.head.appendChild(script);
385
+ return () => {
386
+ document.head.removeChild(script);
387
+ };
388
+ }, [items]);
389
+ return null;
390
+ }
391
+ function ArticleJsonLd({
392
+ title,
393
+ description,
394
+ author,
395
+ datePublished,
396
+ url,
397
+ image
398
+ }) {
399
+ useEffect(() => {
400
+ const script = document.createElement("script");
401
+ script.type = "application/ld+json";
402
+ script.textContent = JSON.stringify(
403
+ articleSchema({
404
+ title,
405
+ description,
406
+ author,
407
+ datePublished,
408
+ url,
409
+ image
410
+ })
411
+ );
412
+ document.head.appendChild(script);
413
+ return () => {
414
+ document.head.removeChild(script);
415
+ };
416
+ }, [title, description, author, datePublished, url, image]);
417
+ return null;
418
+ }
419
+
420
+ // src/components/JsonLd.tsx
421
+ function JsonLd({ type, data }) {
422
+ const schema = {
423
+ "@context": "https://schema.org",
424
+ "@type": type,
425
+ ...data
426
+ };
427
+ useStructuredData(schema);
428
+ return null;
429
+ }
430
+ function SEOAdminPage() {
431
+ const { pages, upsertMeta, deleteMeta, isLoading } = useSEOAdmin();
432
+ const [selectedPath, setSelectedPath] = useState(null);
433
+ const [editingMeta, setEditingMeta] = useState(null);
434
+ const currentPage = pages.find((p) => p.path === selectedPath);
435
+ const handleSave = async () => {
436
+ if (selectedPath && editingMeta) {
437
+ await upsertMeta(selectedPath, editingMeta);
438
+ setEditingMeta(null);
439
+ }
440
+ };
441
+ const handleDelete = async () => {
442
+ if (selectedPath) {
443
+ await deleteMeta(selectedPath);
444
+ setSelectedPath(null);
445
+ setEditingMeta(null);
446
+ }
447
+ };
448
+ if (isLoading) return /* @__PURE__ */ jsx("div", { className: "p-4", children: "Loading..." });
449
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto p-6", children: [
450
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold mb-6", children: "SEO Admin" }),
451
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-4 gap-6", children: [
452
+ /* @__PURE__ */ jsxs("div", { className: "lg:col-span-1 border border-gray-200 rounded p-4", children: [
453
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold mb-4", children: "Pages" }),
454
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: pages.map((page) => /* @__PURE__ */ jsx(
455
+ "button",
456
+ {
457
+ onClick: () => {
458
+ setSelectedPath(page.path);
459
+ setEditingMeta(page.meta);
460
+ },
461
+ className: `w-full text-left p-2 rounded text-sm transition ${selectedPath === page.path ? "bg-blue-100 text-blue-900" : "hover:bg-gray-100"}`,
462
+ children: page.path || "/"
463
+ },
464
+ page.path
465
+ )) })
466
+ ] }),
467
+ currentPage && editingMeta && /* @__PURE__ */ jsxs("div", { className: "lg:col-span-3 space-y-6", children: [
468
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [
469
+ /* @__PURE__ */ jsxs("div", { children: [
470
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold mb-4", children: "Edit Metadata" }),
471
+ /* @__PURE__ */ jsx(
472
+ MetaEditor,
473
+ {
474
+ meta: editingMeta,
475
+ onChange: setEditingMeta
476
+ }
477
+ )
478
+ ] }),
479
+ /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
480
+ /* @__PURE__ */ jsxs("div", { children: [
481
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold mb-4", children: "Preview" }),
482
+ /* @__PURE__ */ jsx(SEOPreview, { meta: editingMeta })
483
+ ] }),
484
+ /* @__PURE__ */ jsx(SEOScoreCard, { meta: editingMeta })
485
+ ] })
486
+ ] }),
487
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
488
+ /* @__PURE__ */ jsx(
489
+ "button",
490
+ {
491
+ onClick: handleSave,
492
+ className: "px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700",
493
+ children: "Save"
494
+ }
495
+ ),
496
+ /* @__PURE__ */ jsx(
497
+ "button",
498
+ {
499
+ onClick: handleDelete,
500
+ className: "px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700",
501
+ children: "Delete"
502
+ }
503
+ )
504
+ ] })
505
+ ] })
506
+ ] })
507
+ ] });
508
+ }
509
+ function SEOAnalyticsPage() {
510
+ const [topPages, setTopPages] = useState([]);
511
+ const [isLoading, setIsLoading] = useState(false);
512
+ useEffect(() => {
513
+ setIsLoading(true);
514
+ setIsLoading(false);
515
+ }, []);
516
+ if (isLoading) return /* @__PURE__ */ jsx("div", { className: "p-4", children: "Loading..." });
517
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto p-6", children: [
518
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold mb-6", children: "SEO Analytics" }),
519
+ /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
520
+ /* @__PURE__ */ jsxs("div", { className: "border border-gray-200 rounded p-4", children: [
521
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold mb-4", children: "Top Pages by Views" }),
522
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
523
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-100 border-b", children: /* @__PURE__ */ jsxs("tr", { children: [
524
+ /* @__PURE__ */ jsx("th", { className: "text-left p-3", children: "Page" }),
525
+ /* @__PURE__ */ jsx("th", { className: "text-right p-3", children: "Views" }),
526
+ /* @__PURE__ */ jsx("th", { className: "text-right p-3", children: "Avg Time (s)" }),
527
+ /* @__PURE__ */ jsx("th", { className: "text-right p-3", children: "Bounce Rate" })
528
+ ] }) }),
529
+ /* @__PURE__ */ jsx("tbody", { children: topPages.map((page, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b hover:bg-gray-50", children: [
530
+ /* @__PURE__ */ jsx("td", { className: "p-3 font-mono text-xs", children: page.path }),
531
+ /* @__PURE__ */ jsx("td", { className: "p-3 text-right", children: page.views.toLocaleString() }),
532
+ /* @__PURE__ */ jsx("td", { className: "p-3 text-right", children: page.avgTimeOnPage.toFixed(1) }),
533
+ /* @__PURE__ */ jsxs("td", { className: "p-3 text-right", children: [
534
+ (page.bounceRate * 100).toFixed(1),
535
+ "%"
536
+ ] })
537
+ ] }, i)) })
538
+ ] }) }),
539
+ topPages.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-center text-gray-500 py-8", children: "No analytics data available" })
540
+ ] }),
541
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [
542
+ /* @__PURE__ */ jsxs("div", { className: "border border-gray-200 rounded p-4", children: [
543
+ /* @__PURE__ */ jsx("h3", { className: "text-sm text-gray-600 mb-1", children: "Total Views" }),
544
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: topPages.reduce((sum, p) => sum + p.views, 0).toLocaleString() })
545
+ ] }),
546
+ /* @__PURE__ */ jsxs("div", { className: "border border-gray-200 rounded p-4", children: [
547
+ /* @__PURE__ */ jsx("h3", { className: "text-sm text-gray-600 mb-1", children: "Average Time on Page" }),
548
+ /* @__PURE__ */ jsxs("p", { className: "text-2xl font-bold", children: [
549
+ topPages.length > 0 ? (topPages.reduce((sum, p) => sum + p.avgTimeOnPage, 0) / topPages.length).toFixed(1) : "0",
550
+ "s"
551
+ ] })
552
+ ] }),
553
+ /* @__PURE__ */ jsxs("div", { className: "border border-gray-200 rounded p-4", children: [
554
+ /* @__PURE__ */ jsx("h3", { className: "text-sm text-gray-600 mb-1", children: "Average Bounce Rate" }),
555
+ /* @__PURE__ */ jsxs("p", { className: "text-2xl font-bold", children: [
556
+ topPages.length > 0 ? (topPages.reduce((sum, p) => sum + p.bounceRate, 0) / topPages.length * 100).toFixed(1) : "0",
557
+ "%"
558
+ ] })
559
+ ] })
560
+ ] })
561
+ ] })
562
+ ] });
563
+ }
564
+
565
+ export { ArticleJsonLd, BreadcrumbsJsonLd, JsonLd, MetaEditor, SEOAdminPage, SEOAnalyticsPage, SEOHead, SEOPreview, SEOScoreCard, SeoProvider, SitemapViewer, useCanonical, useSEO, useSEOAdmin, useSEOContext, useSEOScore, useSitemap, useStructuredData };
566
+ //# sourceMappingURL=index.js.map
567
+ //# sourceMappingURL=index.js.map