@fullstackdatasolutions/articles 0.1.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 (53) hide show
  1. package/README.md +324 -0
  2. package/dist/index.cjs +1027 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +232 -0
  5. package/dist/index.d.ts +232 -0
  6. package/dist/index.js +974 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/server.cjs +732 -0
  9. package/dist/server.cjs.map +1 -0
  10. package/dist/server.d.cts +127 -0
  11. package/dist/server.d.ts +127 -0
  12. package/dist/server.js +684 -0
  13. package/dist/server.js.map +1 -0
  14. package/package.json +82 -0
  15. package/src/ArticleCard.tsx +63 -0
  16. package/src/ArticleCategoryGrid.tsx +73 -0
  17. package/src/ArticleSchemas.tsx +82 -0
  18. package/src/ArticleSearchBar.tsx +50 -0
  19. package/src/ArticlesHero.tsx +33 -0
  20. package/src/ArticlesPage.tsx +167 -0
  21. package/src/CategoryArticlesPage.tsx +84 -0
  22. package/src/CommentForm.tsx +98 -0
  23. package/src/CommentItem.tsx +123 -0
  24. package/src/CommentThread.tsx +40 -0
  25. package/src/CommentsSection.tsx +84 -0
  26. package/src/FeaturedArticle.tsx +63 -0
  27. package/src/LatestArticles.tsx +42 -0
  28. package/src/LatestArticlesSection.tsx +68 -0
  29. package/src/__tests__/ArticleCard.test.tsx +78 -0
  30. package/src/__tests__/ArticleCategoryGrid.test.tsx +98 -0
  31. package/src/__tests__/ArticleSchemas.test.tsx +130 -0
  32. package/src/__tests__/ArticleSearchBar.test.tsx +79 -0
  33. package/src/__tests__/ArticlesHero.test.tsx +38 -0
  34. package/src/__tests__/ArticlesPage.test.tsx +155 -0
  35. package/src/__tests__/CategoryArticlesPage.test.tsx +156 -0
  36. package/src/__tests__/CommentForm.test.tsx +80 -0
  37. package/src/__tests__/CommentItem.test.tsx +149 -0
  38. package/src/__tests__/CommentsSection.test.tsx +118 -0
  39. package/src/__tests__/FeaturedArticle.test.tsx +85 -0
  40. package/src/__tests__/LatestArticles.test.tsx +157 -0
  41. package/src/__tests__/LatestArticlesSection.test.tsx +105 -0
  42. package/src/__tests__/jest-dom.d.ts +1 -0
  43. package/src/__tests__/seoUtils.test.ts +217 -0
  44. package/src/__tests__/useArticles.test.ts +207 -0
  45. package/src/articleTypes.ts +21 -0
  46. package/src/articlesConfig.ts +98 -0
  47. package/src/commentTypes.ts +14 -0
  48. package/src/index.ts +35 -0
  49. package/src/markdown.ts +314 -0
  50. package/src/seoUtils.ts +155 -0
  51. package/src/server-articles.ts +265 -0
  52. package/src/server.ts +25 -0
  53. package/src/useArticles.ts +93 -0
package/dist/index.js ADDED
@@ -0,0 +1,974 @@
1
+ "use client"
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+ var __async = (__this, __arguments, generator) => {
23
+ return new Promise((resolve, reject) => {
24
+ var fulfilled = (value) => {
25
+ try {
26
+ step(generator.next(value));
27
+ } catch (e) {
28
+ reject(e);
29
+ }
30
+ };
31
+ var rejected = (value) => {
32
+ try {
33
+ step(generator.throw(value));
34
+ } catch (e) {
35
+ reject(e);
36
+ }
37
+ };
38
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
39
+ step((generator = generator.apply(__this, __arguments)).next());
40
+ });
41
+ };
42
+
43
+ // src/ArticleCategoryGrid.tsx
44
+ import Image from "next/image";
45
+ import Link from "next/link";
46
+ import { useState, useEffect } from "react";
47
+ import { jsx, jsxs } from "react/jsx-runtime";
48
+ function ArticleCategoryGrid({ categories, pageSize = 8 }) {
49
+ const [visibleCount, setVisibleCount] = useState(pageSize);
50
+ useEffect(() => {
51
+ setVisibleCount(pageSize);
52
+ }, [categories, pageSize]);
53
+ if (categories.length === 0)
54
+ return null;
55
+ const visible = categories.slice(0, visibleCount);
56
+ const hasMore = visibleCount < categories.length;
57
+ return /* @__PURE__ */ jsx("section", { className: "py-16 bg-background", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
58
+ /* @__PURE__ */ jsxs("div", { className: "text-center mb-12", children: [
59
+ /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold text-foreground mb-4", children: "Browse by Category" }),
60
+ /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: "Explore our articles by topic to find the insights most relevant to your interests." })
61
+ ] }),
62
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-6", children: visible.map((cat) => /* @__PURE__ */ jsx(Link, { href: `/articles/category/${cat.slug}`, children: /* @__PURE__ */ jsxs("div", { className: "group overflow-hidden rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer bg-card", children: [
63
+ /* @__PURE__ */ jsx("div", { className: "relative h-52 overflow-hidden", children: /* @__PURE__ */ jsx(
64
+ Image,
65
+ {
66
+ src: cat.featuredImage,
67
+ alt: cat.name,
68
+ fill: true,
69
+ sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw",
70
+ className: "object-cover group-hover:scale-105 transition-transform duration-300"
71
+ }
72
+ ) }),
73
+ /* @__PURE__ */ jsxs("div", { className: "p-4 text-center", children: [
74
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground text-base", children: cat.name }),
75
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
76
+ cat.count,
77
+ " ",
78
+ cat.count === 1 ? "article" : "articles"
79
+ ] })
80
+ ] })
81
+ ] }) }, cat.slug)) }),
82
+ hasMore && /* @__PURE__ */ jsx("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx(
83
+ "button",
84
+ {
85
+ type: "button",
86
+ onClick: () => setVisibleCount((c) => c + pageSize),
87
+ className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
88
+ children: "Load more categories"
89
+ }
90
+ ) })
91
+ ] }) });
92
+ }
93
+
94
+ // src/ArticleSchemas.tsx
95
+ import { jsx as jsx2 } from "react/jsx-runtime";
96
+ function ArticleSchema({ article, articleUrl, siteName }) {
97
+ const schema = __spreadProps(__spreadValues({
98
+ "@context": "https://schema.org",
99
+ "@type": "Article",
100
+ headline: article.title,
101
+ description: article.excerpt,
102
+ image: article.featuredImage
103
+ }, article.date && { datePublished: new Date(article.date).toISOString() }), {
104
+ author: { "@type": "Person", name: article.author },
105
+ publisher: { "@type": "Organization", name: siteName },
106
+ mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl }
107
+ });
108
+ return /* @__PURE__ */ jsx2(
109
+ "script",
110
+ {
111
+ type: "application/ld+json",
112
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
113
+ }
114
+ );
115
+ }
116
+ function BreadcrumbSchema({ items }) {
117
+ const schema = {
118
+ "@context": "https://schema.org",
119
+ "@type": "BreadcrumbList",
120
+ itemListElement: items.map((item, index) => ({
121
+ "@type": "ListItem",
122
+ position: index + 1,
123
+ name: item.name,
124
+ item: item.url
125
+ }))
126
+ };
127
+ return /* @__PURE__ */ jsx2(
128
+ "script",
129
+ {
130
+ type: "application/ld+json",
131
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
132
+ }
133
+ );
134
+ }
135
+ function CollectionPageSchema({
136
+ title,
137
+ description,
138
+ url,
139
+ articleCount
140
+ }) {
141
+ const schema = {
142
+ "@context": "https://schema.org",
143
+ "@type": "CollectionPage",
144
+ name: title,
145
+ description,
146
+ url,
147
+ numberOfItems: articleCount
148
+ };
149
+ return /* @__PURE__ */ jsx2(
150
+ "script",
151
+ {
152
+ type: "application/ld+json",
153
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
154
+ }
155
+ );
156
+ }
157
+
158
+ // src/ArticleSearchBar.tsx
159
+ import { Search, X } from "lucide-react";
160
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
161
+ function ArticleSearchBar({
162
+ value,
163
+ onChange,
164
+ disabled,
165
+ resultCount
166
+ }) {
167
+ return /* @__PURE__ */ jsx3("section", { className: "py-8 bg-background border-b border-border", children: /* @__PURE__ */ jsxs2("div", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8", children: [
168
+ /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
169
+ /* @__PURE__ */ jsx3(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5" }),
170
+ /* @__PURE__ */ jsx3(
171
+ "input",
172
+ {
173
+ type: "text",
174
+ placeholder: "Search articles (3+ characters required)...",
175
+ value,
176
+ onChange: (e) => onChange(e.target.value),
177
+ disabled,
178
+ className: "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 pl-10 pr-10 py-3 text-lg"
179
+ }
180
+ ),
181
+ value && /* @__PURE__ */ jsx3(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ onClick: () => onChange(""),
186
+ "aria-label": "Clear search",
187
+ className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground",
188
+ children: /* @__PURE__ */ jsx3(X, { className: "h-5 w-5" })
189
+ }
190
+ )
191
+ ] }),
192
+ value && /* @__PURE__ */ jsx3("div", { className: "mt-2 text-sm text-muted-foreground", children: resultCount === 0 ? "No articles found matching your search." : `Found ${resultCount} article${resultCount === 1 ? "" : "s"} matching "${value}"` })
193
+ ] }) });
194
+ }
195
+
196
+ // src/ArticlesHero.tsx
197
+ import Link2 from "next/link";
198
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
199
+ function ArticlesHero() {
200
+ return /* @__PURE__ */ jsx4("section", { className: "bg-linear-to-r from-gray-50 to-gray-100 py-16 md:py-24", children: /* @__PURE__ */ jsx4("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs3("div", { className: "max-w-3xl mx-auto text-center", children: [
201
+ /* @__PURE__ */ jsx4("h1", { className: "text-4xl md:text-5xl font-bold text-foreground mb-6", children: "Vox Populus Insights" }),
202
+ /* @__PURE__ */ jsx4("p", { className: "text-xl text-muted-foreground mb-8", children: "Expert analysis, campaign strategies, and voter engagement insights from the frontlines of democracy." }),
203
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col sm:flex-row gap-4 justify-center", children: [
204
+ /* @__PURE__ */ jsx4(
205
+ Link2,
206
+ {
207
+ href: "#latest",
208
+ className: "inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors",
209
+ children: "Browse Latest Articles"
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx4(
213
+ Link2,
214
+ {
215
+ href: "/contact",
216
+ className: "inline-flex items-center justify-center px-4 py-2 border border-primary text-primary hover:bg-primary/10 rounded-md font-medium text-sm transition-colors",
217
+ children: "Contact Our Team"
218
+ }
219
+ )
220
+ ] })
221
+ ] }) }) });
222
+ }
223
+
224
+ // src/FeaturedArticle.tsx
225
+ import Link3 from "next/link";
226
+ import Image2 from "next/image";
227
+ import { ArrowRight, Calendar, Clock, User } from "lucide-react";
228
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
229
+ function FeaturedArticle({ article }) {
230
+ return /* @__PURE__ */ jsx5("section", { className: "py-16 bg-background", children: /* @__PURE__ */ jsx5("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs4("div", { className: "grid lg:grid-cols-2 gap-12 items-center", children: [
231
+ /* @__PURE__ */ jsxs4("div", { children: [
232
+ /* @__PURE__ */ jsx5("span", { className: "inline-block bg-primary/10 text-primary text-sm font-medium px-3 py-1 rounded-full mb-4", children: "FEATURED ARTICLE" }),
233
+ /* @__PURE__ */ jsx5("h2", { className: "text-3xl font-bold text-foreground mb-4", children: article.title }),
234
+ /* @__PURE__ */ jsx5("p", { className: "text-lg text-muted-foreground mb-6", children: article.excerpt }),
235
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-6 text-sm text-muted-foreground mb-6", children: [
236
+ article.date && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
237
+ /* @__PURE__ */ jsx5(Calendar, { className: "h-4 w-4" }),
238
+ /* @__PURE__ */ jsx5("span", { children: article.date })
239
+ ] }),
240
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
241
+ /* @__PURE__ */ jsx5(Clock, { className: "h-4 w-4" }),
242
+ /* @__PURE__ */ jsx5("span", { children: article.readTime })
243
+ ] }),
244
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
245
+ /* @__PURE__ */ jsx5(User, { className: "h-4 w-4" }),
246
+ /* @__PURE__ */ jsx5("span", { children: article.author })
247
+ ] })
248
+ ] }),
249
+ /* @__PURE__ */ jsxs4(
250
+ Link3,
251
+ {
252
+ href: `/articles/${article.slug}`,
253
+ className: "inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors",
254
+ children: [
255
+ "Read Full Article",
256
+ /* @__PURE__ */ jsx5(ArrowRight, { className: "ml-2 h-4 w-4" })
257
+ ]
258
+ }
259
+ )
260
+ ] }),
261
+ /* @__PURE__ */ jsxs4("div", { className: "bg-card rounded-lg p-8", children: [
262
+ /* @__PURE__ */ jsx5(
263
+ Image2,
264
+ {
265
+ src: article.featuredImage,
266
+ alt: article.title,
267
+ width: 800,
268
+ height: 450,
269
+ className: "w-full h-64 object-cover rounded-lg mb-4"
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-2", children: /* @__PURE__ */ jsx5("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) })
273
+ ] })
274
+ ] }) }) });
275
+ }
276
+
277
+ // src/LatestArticlesSection.tsx
278
+ import { Search as Search2 } from "lucide-react";
279
+
280
+ // src/LatestArticles.tsx
281
+ import { useState as useState2, useEffect as useEffect2 } from "react";
282
+
283
+ // src/ArticleCard.tsx
284
+ import Image3 from "next/image";
285
+ import Link4 from "next/link";
286
+ import { ArrowRight as ArrowRight2, Calendar as Calendar2, Clock as Clock2 } from "lucide-react";
287
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
288
+ function ArticleCard({ article }) {
289
+ return /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
290
+ /* @__PURE__ */ jsx6("div", { className: "p-0", children: /* @__PURE__ */ jsx6(
291
+ Image3,
292
+ {
293
+ src: article.featuredImage,
294
+ alt: article.title,
295
+ className: "w-full h-48 object-cover rounded-t-lg",
296
+ width: 400,
297
+ height: 200
298
+ }
299
+ ) }),
300
+ /* @__PURE__ */ jsxs5("div", { className: "p-6", children: [
301
+ /* @__PURE__ */ jsx6("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ jsx6("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
302
+ /* @__PURE__ */ jsx6("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ jsx6(
303
+ Link4,
304
+ {
305
+ href: `/articles/${article.slug}`,
306
+ className: "hover:text-indigo-600 transition-colors",
307
+ children: article.title
308
+ }
309
+ ) }),
310
+ /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
311
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
312
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
313
+ article.date && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
314
+ /* @__PURE__ */ jsx6(Calendar2, { className: "h-3 w-3" }),
315
+ /* @__PURE__ */ jsx6("span", { children: article.date })
316
+ ] }),
317
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
318
+ /* @__PURE__ */ jsx6(Clock2, { className: "h-3 w-3" }),
319
+ /* @__PURE__ */ jsx6("span", { children: article.readTime })
320
+ ] })
321
+ ] }),
322
+ /* @__PURE__ */ jsx6(
323
+ Link4,
324
+ {
325
+ href: `/articles/${article.slug}`,
326
+ className: "inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors",
327
+ "aria-label": `Read ${article.title}`,
328
+ children: /* @__PURE__ */ jsx6(ArrowRight2, { className: "h-4 w-4" })
329
+ }
330
+ )
331
+ ] })
332
+ ] })
333
+ ] });
334
+ }
335
+
336
+ // src/LatestArticles.tsx
337
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
338
+ function LatestArticles({ articles, pageSize }) {
339
+ const [visibleCount, setVisibleCount] = useState2(pageSize);
340
+ useEffect2(() => {
341
+ setVisibleCount(pageSize);
342
+ }, [articles, pageSize]);
343
+ const visible = articles.slice(0, visibleCount);
344
+ const hasMore = visibleCount < articles.length;
345
+ return /* @__PURE__ */ jsxs6("div", { children: [
346
+ /* @__PURE__ */ jsx7("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ jsx7(ArticleCard, { article }, article.slug)) }),
347
+ hasMore && /* @__PURE__ */ jsx7("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx7(
348
+ "button",
349
+ {
350
+ type: "button",
351
+ onClick: () => setVisibleCount((c) => c + pageSize),
352
+ className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
353
+ children: "Load more articles"
354
+ }
355
+ ) })
356
+ ] });
357
+ }
358
+
359
+ // src/LatestArticlesSection.tsx
360
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
361
+ function DescriptionText({
362
+ searchQuery,
363
+ count
364
+ }) {
365
+ const pluralSuffix = count === 1 ? "" : "s";
366
+ const text = searchQuery ? `Found ${count} article${pluralSuffix} matching your search.` : "Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.";
367
+ return /* @__PURE__ */ jsx8("p", { className: "text-lg text-muted-foreground", children: text });
368
+ }
369
+ function LatestArticlesSection({
370
+ articles,
371
+ searchQuery,
372
+ onClearSearch,
373
+ pageSize = 6
374
+ }) {
375
+ return /* @__PURE__ */ jsx8("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ jsxs7("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
376
+ /* @__PURE__ */ jsxs7("div", { className: "text-center mb-12", children: [
377
+ /* @__PURE__ */ jsx8("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
378
+ /* @__PURE__ */ jsx8(DescriptionText, { searchQuery, count: articles.length })
379
+ ] }),
380
+ articles.length === 0 && searchQuery ? /* @__PURE__ */ jsx8("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxs7("div", { className: "max-w-md mx-auto", children: [
381
+ /* @__PURE__ */ jsx8(Search2, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
382
+ /* @__PURE__ */ jsx8("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
383
+ /* @__PURE__ */ jsxs7("p", { className: "text-muted-foreground mb-4", children: [
384
+ `We couldn't find any articles matching "`,
385
+ searchQuery,
386
+ '". Try adjusting your search terms.'
387
+ ] }),
388
+ /* @__PURE__ */ jsx8(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ onClick: onClearSearch,
393
+ className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
394
+ children: "Clear search"
395
+ }
396
+ )
397
+ ] }) }) : /* @__PURE__ */ jsx8(LatestArticles, { articles, pageSize }, searchQuery)
398
+ ] }) });
399
+ }
400
+
401
+ // src/articlesConfig.ts
402
+ var DEFAULT_PAGE_SIZE = 6;
403
+ var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
404
+ var DEFAULT_LAYOUT = [
405
+ "hero",
406
+ "search",
407
+ "featured",
408
+ "latest",
409
+ "categories"
410
+ ];
411
+
412
+ // src/useArticles.ts
413
+ import { useState as useState3, useCallback, useEffect as useEffect3, useRef } from "react";
414
+ function useArticles() {
415
+ const [searchQuery, setSearchQuery] = useState3("");
416
+ const debounceRef = useRef(null);
417
+ const [articles, setArticles] = useState3([]);
418
+ const [categories, setCategories] = useState3([]);
419
+ const [loading, setLoading] = useState3(true);
420
+ const [error, setError] = useState3(null);
421
+ const fetchArticles = useCallback((query) => __async(this, null, function* () {
422
+ try {
423
+ setLoading(true);
424
+ const url = query ? `/api/articles?q=${encodeURIComponent(query)}` : "/api/articles";
425
+ const response = yield fetch(url);
426
+ if (!response.ok)
427
+ throw new Error("Failed to fetch articles");
428
+ const data = yield response.json();
429
+ setArticles(data.articles);
430
+ if (!(query == null ? void 0 : query.trim())) {
431
+ const catMap = /* @__PURE__ */ new Map();
432
+ for (const article of data.articles) {
433
+ for (const cat of article.categories) {
434
+ if (!catMap.has(cat)) {
435
+ catMap.set(cat, { count: 0, featuredImage: article.featuredImage });
436
+ }
437
+ catMap.get(cat).count++;
438
+ }
439
+ }
440
+ setCategories(
441
+ Array.from(catMap.entries()).map(([name, { count, featuredImage }]) => ({
442
+ name,
443
+ slug: name.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, ""),
444
+ count,
445
+ featuredImage
446
+ })).sort((a, b) => b.count - a.count)
447
+ );
448
+ }
449
+ setError(null);
450
+ } catch (err) {
451
+ setError(err instanceof Error ? err.message : "Failed to load articles");
452
+ setArticles([]);
453
+ } finally {
454
+ setLoading(false);
455
+ }
456
+ }), []);
457
+ useEffect3(() => {
458
+ fetchArticles("");
459
+ }, [fetchArticles]);
460
+ const handleSearch = useCallback(
461
+ (query) => {
462
+ setSearchQuery(query);
463
+ if (debounceRef.current)
464
+ clearTimeout(debounceRef.current);
465
+ if (query === "") {
466
+ fetchArticles("");
467
+ } else if (query.length >= 3) {
468
+ debounceRef.current = setTimeout(() => fetchArticles(query), 1e3);
469
+ }
470
+ },
471
+ [fetchArticles]
472
+ );
473
+ useEffect3(
474
+ () => () => {
475
+ if (debounceRef.current)
476
+ clearTimeout(debounceRef.current);
477
+ },
478
+ []
479
+ );
480
+ return { articles, categories, searchQuery, loading, error, handleSearch };
481
+ }
482
+
483
+ // src/ArticlesPage.tsx
484
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
485
+ function buildThemeVars(theme) {
486
+ if (!theme)
487
+ return {};
488
+ const vars = {};
489
+ if (theme.fontFamily)
490
+ vars["--articles-font-family"] = theme.fontFamily;
491
+ if (theme.headerColor)
492
+ vars["--articles-header-color"] = theme.headerColor;
493
+ if (theme.textColor)
494
+ vars["--articles-text-color"] = theme.textColor;
495
+ if (theme.backgroundColor)
496
+ vars["--articles-bg-color"] = theme.backgroundColor;
497
+ if (theme.linkColor)
498
+ vars["--articles-link-color"] = theme.linkColor;
499
+ if (theme.linkHoverColor)
500
+ vars["--articles-link-hover-color"] = theme.linkHoverColor;
501
+ if (theme.linkTextDecoration)
502
+ vars["--articles-link-decoration"] = theme.linkTextDecoration;
503
+ if (theme.headerFontWeight)
504
+ vars["--articles-header-font-weight"] = String(theme.headerFontWeight);
505
+ if (theme.headerFontSize)
506
+ vars["--articles-header-font-size"] = theme.headerFontSize;
507
+ if (theme.bodyFontSize)
508
+ vars["--articles-body-font-size"] = theme.bodyFontSize;
509
+ if (theme.lineHeight)
510
+ vars["--articles-line-height"] = theme.lineHeight;
511
+ if (theme.borderRadius)
512
+ vars["--articles-border-radius"] = theme.borderRadius;
513
+ return vars;
514
+ }
515
+ function renderSection(section, ctx) {
516
+ const {
517
+ articles,
518
+ displayedArticles,
519
+ categories,
520
+ searchQuery,
521
+ loading,
522
+ error,
523
+ handleSearch,
524
+ pageSize,
525
+ categoriesPageSize
526
+ } = ctx;
527
+ switch (section) {
528
+ case "hero":
529
+ return /* @__PURE__ */ jsx9(ArticlesHero, {}, "hero");
530
+ case "search":
531
+ return /* @__PURE__ */ jsx9(
532
+ ArticleSearchBar,
533
+ {
534
+ value: searchQuery,
535
+ onChange: handleSearch,
536
+ disabled: loading,
537
+ resultCount: articles.length
538
+ },
539
+ "search"
540
+ );
541
+ case "featured":
542
+ return articles.length > 0 && !searchQuery ? /* @__PURE__ */ jsx9(FeaturedArticle, { article: articles[0] }, "featured") : null;
543
+ case "latest":
544
+ if (loading && articles.length === 0) {
545
+ return /* @__PURE__ */ jsx9("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx9("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ jsxs8("div", { className: "text-center", children: [
546
+ /* @__PURE__ */ jsx9("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
547
+ /* @__PURE__ */ jsx9("p", { className: "text-muted-foreground", children: "Loading articles..." })
548
+ ] }) }) }, "latest");
549
+ }
550
+ if (error) {
551
+ return /* @__PURE__ */ jsx9("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx9("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ jsxs8("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
552
+ /* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
553
+ /* @__PURE__ */ jsx9("p", { className: "text-red-600 mb-4", children: error }),
554
+ /* @__PURE__ */ jsx9(
555
+ "button",
556
+ {
557
+ type: "button",
558
+ onClick: () => handleSearch(""),
559
+ className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
560
+ children: "Try again"
561
+ }
562
+ )
563
+ ] }) }) }, "latest");
564
+ }
565
+ return /* @__PURE__ */ jsx9(
566
+ LatestArticlesSection,
567
+ {
568
+ articles: displayedArticles,
569
+ searchQuery,
570
+ onClearSearch: () => handleSearch(""),
571
+ pageSize
572
+ },
573
+ "latest"
574
+ );
575
+ case "categories":
576
+ return searchQuery ? null : /* @__PURE__ */ jsx9(
577
+ ArticleCategoryGrid,
578
+ {
579
+ categories,
580
+ pageSize: categoriesPageSize
581
+ },
582
+ "categories"
583
+ );
584
+ default:
585
+ return null;
586
+ }
587
+ }
588
+ function ArticlesPage({ config }) {
589
+ var _a, _b, _c;
590
+ const state = useArticles();
591
+ const layout = (_a = config.layout) != null ? _a : DEFAULT_LAYOUT;
592
+ const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
593
+ const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
594
+ const themeVars = buildThemeVars(config.theme);
595
+ const hasFeatured = layout.includes("featured");
596
+ const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
597
+ const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
598
+ const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
599
+ return /* @__PURE__ */ jsxs8("div", { style: themeVars, children: [
600
+ layout.map((section) => renderSection(section, ctx)),
601
+ state.articles.length > 0 && /* @__PURE__ */ jsx9(
602
+ CollectionPageSchema,
603
+ {
604
+ title: `Articles | ${config.siteName}`,
605
+ description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
606
+ url: `${config.siteUrl}/articles`,
607
+ articleCount: state.articles.length
608
+ }
609
+ )
610
+ ] });
611
+ }
612
+
613
+ // src/CategoryArticlesPage.tsx
614
+ import Image4 from "next/image";
615
+ import Link5 from "next/link";
616
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
617
+ function getCategoryDescription(config, slug, categoryName) {
618
+ var _a;
619
+ const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
620
+ if (!raw)
621
+ return { short: `Browse all articles in ${categoryName}.` };
622
+ if (typeof raw === "string")
623
+ return { short: raw };
624
+ return raw;
625
+ }
626
+ function CategoryArticlesPage({ category, articles, config }) {
627
+ var _a;
628
+ if (articles.length === 0)
629
+ return null;
630
+ const categoryName = articles[0].category;
631
+ const heroImage = articles[0].featuredImage;
632
+ const description = getCategoryDescription(config, category, categoryName);
633
+ const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
634
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
635
+ return /* @__PURE__ */ jsxs9("div", { children: [
636
+ /* @__PURE__ */ jsx10(
637
+ BreadcrumbSchema,
638
+ {
639
+ items: [
640
+ { name: "Home", url: siteUrl },
641
+ { name: "Articles", url: `${siteUrl}/articles` },
642
+ { name: categoryName, url: `${siteUrl}/articles/category/${category}` }
643
+ ]
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsxs9("section", { className: "relative h-72 md:h-96 flex items-center justify-center overflow-hidden", children: [
647
+ /* @__PURE__ */ jsx10(
648
+ Image4,
649
+ {
650
+ src: heroImage,
651
+ alt: categoryName,
652
+ fill: true,
653
+ sizes: "100vw",
654
+ className: "object-cover",
655
+ priority: true
656
+ }
657
+ ),
658
+ /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 bg-black/60" }),
659
+ /* @__PURE__ */ jsxs9("div", { className: "relative z-10 text-center text-white px-4", children: [
660
+ /* @__PURE__ */ jsx10("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
661
+ /* @__PURE__ */ jsx10("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
662
+ description.long && /* @__PURE__ */ jsx10("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
663
+ /* @__PURE__ */ jsxs9("p", { className: "text-lg opacity-70 mt-2", children: [
664
+ articles.length,
665
+ " article",
666
+ articles.length === 1 ? "" : "s"
667
+ ] })
668
+ ] })
669
+ ] }),
670
+ /* @__PURE__ */ jsx10("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsxs9("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
671
+ /* @__PURE__ */ jsxs9("div", { className: "mb-8 flex items-center justify-between", children: [
672
+ /* @__PURE__ */ jsx10(Link5, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
673
+ /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: description.short })
674
+ ] }),
675
+ /* @__PURE__ */ jsx10(LatestArticles, { articles, pageSize })
676
+ ] }) })
677
+ ] });
678
+ }
679
+
680
+ // src/CommentsSection.tsx
681
+ import { useSession, signIn } from "next-auth/react";
682
+ import { useCallback as useCallback2, useEffect as useEffect4, useState as useState6 } from "react";
683
+
684
+ // src/CommentForm.tsx
685
+ import { useState as useState4 } from "react";
686
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
687
+ var MAX_LENGTH = 2e3;
688
+ var WARN_THRESHOLD = 1800;
689
+ function submitLabel(submitting, parentId) {
690
+ if (submitting)
691
+ return "Posting\u2026";
692
+ return parentId ? "Reply" : "Post comment";
693
+ }
694
+ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
695
+ const [body, setBody] = useState4("");
696
+ const [submitting, setSubmitting] = useState4(false);
697
+ const [error, setError] = useState4(null);
698
+ const remaining = MAX_LENGTH - body.length;
699
+ function handleSubmit(e) {
700
+ return __async(this, null, function* () {
701
+ var _a;
702
+ e.preventDefault();
703
+ if (!body.trim() || submitting)
704
+ return;
705
+ setSubmitting(true);
706
+ setError(null);
707
+ try {
708
+ const res = yield fetch(`/api/articles/${articleSlug}/comments`, {
709
+ method: "POST",
710
+ headers: { "Content-Type": "application/json" },
711
+ body: JSON.stringify({ body: body.trim(), parentId: parentId != null ? parentId : null })
712
+ });
713
+ if (!res.ok) {
714
+ const data = yield res.json();
715
+ setError((_a = data.error) != null ? _a : "Failed to post comment");
716
+ return;
717
+ }
718
+ setBody("");
719
+ onSuccess();
720
+ } catch (e2) {
721
+ setError("Network error \u2014 please try again");
722
+ } finally {
723
+ setSubmitting(false);
724
+ }
725
+ });
726
+ }
727
+ return /* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
728
+ /* @__PURE__ */ jsx11(
729
+ "textarea",
730
+ {
731
+ value: body,
732
+ onChange: (e) => setBody(e.target.value),
733
+ maxLength: MAX_LENGTH,
734
+ rows: parentId ? 3 : 4,
735
+ placeholder: parentId ? "Write a reply\u2026" : "Join the discussion\u2026",
736
+ disabled: submitting,
737
+ className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50 resize-none",
738
+ "aria-label": parentId ? "Reply text" : "Comment text"
739
+ }
740
+ ),
741
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs10("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
742
+ remaining,
743
+ " characters remaining"
744
+ ] }),
745
+ error && /* @__PURE__ */ jsx11("p", { className: "text-xs text-destructive", children: error }),
746
+ /* @__PURE__ */ jsxs10("div", { className: "flex gap-2", children: [
747
+ /* @__PURE__ */ jsx11(
748
+ "button",
749
+ {
750
+ type: "submit",
751
+ disabled: submitting || body.trim().length === 0 || body.length > MAX_LENGTH,
752
+ className: "rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",
753
+ children: submitLabel(submitting, parentId)
754
+ }
755
+ ),
756
+ onCancel && /* @__PURE__ */ jsx11(
757
+ "button",
758
+ {
759
+ type: "button",
760
+ onClick: onCancel,
761
+ className: "rounded-md px-4 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground",
762
+ children: "Cancel"
763
+ }
764
+ )
765
+ ] })
766
+ ] });
767
+ }
768
+
769
+ // src/CommentItem.tsx
770
+ import { useState as useState5 } from "react";
771
+ import { Fragment, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
772
+ function relativeTime(iso) {
773
+ const diff = Date.now() - new Date(iso).getTime();
774
+ const minutes = Math.floor(diff / 6e4);
775
+ if (minutes < 1)
776
+ return "just now";
777
+ if (minutes < 60)
778
+ return `${minutes}m ago`;
779
+ const hours = Math.floor(minutes / 60);
780
+ if (hours < 24)
781
+ return `${hours}h ago`;
782
+ const days = Math.floor(hours / 24);
783
+ if (days < 30)
784
+ return `${days}d ago`;
785
+ const months = Math.floor(days / 30);
786
+ if (months < 12)
787
+ return `${months}mo ago`;
788
+ return `${Math.floor(months / 12)}y ago`;
789
+ }
790
+ function CommentItem({
791
+ comment,
792
+ articleSlug,
793
+ currentUserId,
794
+ isReply = false,
795
+ onRefresh
796
+ }) {
797
+ const [showReplyForm, setShowReplyForm] = useState5(false);
798
+ const [deleting, setDeleting] = useState5(false);
799
+ const replies = "replies" in comment ? comment.replies : [];
800
+ const canReply = !isReply && !!currentUserId && !comment.isDeleted;
801
+ const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
802
+ function handleDelete() {
803
+ return __async(this, null, function* () {
804
+ if (deleting)
805
+ return;
806
+ setDeleting(true);
807
+ try {
808
+ yield fetch(`/api/articles/${articleSlug}/comments/${comment.id}`, { method: "DELETE" });
809
+ onRefresh();
810
+ } finally {
811
+ setDeleting(false);
812
+ }
813
+ });
814
+ }
815
+ return /* @__PURE__ */ jsxs11("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
816
+ /* @__PURE__ */ jsx12("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx12("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs11(Fragment, { children: [
817
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-2", children: [
818
+ /* @__PURE__ */ jsx12("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
819
+ /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
820
+ ] }),
821
+ /* @__PURE__ */ jsx12("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
822
+ /* @__PURE__ */ jsxs11("div", { className: "flex gap-3 pt-1", children: [
823
+ canReply && /* @__PURE__ */ jsx12(
824
+ "button",
825
+ {
826
+ onClick: () => setShowReplyForm((v) => !v),
827
+ className: "text-xs text-muted-foreground hover:text-foreground",
828
+ children: showReplyForm ? "Cancel" : "Reply"
829
+ }
830
+ ),
831
+ canDelete && /* @__PURE__ */ jsx12(
832
+ "button",
833
+ {
834
+ onClick: handleDelete,
835
+ disabled: deleting,
836
+ className: "text-xs text-muted-foreground hover:text-destructive disabled:opacity-50",
837
+ children: deleting ? "Deleting\u2026" : "Delete"
838
+ }
839
+ )
840
+ ] })
841
+ ] }) }),
842
+ showReplyForm && /* @__PURE__ */ jsx12("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx12(
843
+ CommentForm,
844
+ {
845
+ articleSlug,
846
+ parentId: comment.id,
847
+ onSuccess: () => {
848
+ setShowReplyForm(false);
849
+ onRefresh();
850
+ },
851
+ onCancel: () => setShowReplyForm(false)
852
+ }
853
+ ) }),
854
+ replies.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx12(
855
+ CommentItem,
856
+ {
857
+ comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
858
+ articleSlug,
859
+ currentUserId,
860
+ isReply: true,
861
+ onRefresh
862
+ },
863
+ reply.id
864
+ )) })
865
+ ] });
866
+ }
867
+
868
+ // src/CommentThread.tsx
869
+ import { jsx as jsx13 } from "react/jsx-runtime";
870
+ function CommentThread({
871
+ comments,
872
+ articleSlug,
873
+ currentUserId,
874
+ onRefresh
875
+ }) {
876
+ if (comments.length === 0) {
877
+ return /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
878
+ }
879
+ return /* @__PURE__ */ jsx13("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx13(
880
+ CommentItem,
881
+ {
882
+ comment,
883
+ articleSlug,
884
+ currentUserId,
885
+ onRefresh
886
+ },
887
+ comment.id
888
+ )) });
889
+ }
890
+
891
+ // src/CommentsSection.tsx
892
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
893
+ function CommentsSection({ articleSlug, config }) {
894
+ var _a, _b, _c, _d, _e, _f;
895
+ const { data: session, status } = useSession();
896
+ const [comments, setComments] = useState6([]);
897
+ const [loading, setLoading] = useState6(true);
898
+ const [fetchError, setFetchError] = useState6(null);
899
+ const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
900
+ const enabled = config.enabled && perArticleEnabled;
901
+ const currentUserId = (_f = (_e = (_c = session == null ? void 0 : session.user) == null ? void 0 : _c.email) != null ? _e : (_d = session == null ? void 0 : session.user) == null ? void 0 : _d.name) != null ? _f : void 0;
902
+ const fetchComments = useCallback2(() => __async(this, null, function* () {
903
+ setLoading(true);
904
+ setFetchError(null);
905
+ try {
906
+ const res = yield fetch(`/api/articles/${articleSlug}/comments`);
907
+ if (!res.ok)
908
+ throw new Error("Failed to load comments");
909
+ const data = yield res.json();
910
+ setComments(data.comments);
911
+ } catch (e) {
912
+ setFetchError("Could not load comments. Please try again.");
913
+ } finally {
914
+ setLoading(false);
915
+ }
916
+ }), [articleSlug]);
917
+ useEffect4(() => {
918
+ if (enabled) {
919
+ fetchComments().catch(() => void 0);
920
+ }
921
+ }, [fetchComments, enabled]);
922
+ if (!enabled)
923
+ return null;
924
+ const count = comments.length;
925
+ const label = count === 1 ? "1 comment" : `${count} comments`;
926
+ return /* @__PURE__ */ jsxs12("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
927
+ /* @__PURE__ */ jsx14("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
928
+ status === "authenticated" ? /* @__PURE__ */ jsx14(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
929
+ /* @__PURE__ */ jsx14(
930
+ "button",
931
+ {
932
+ onClick: () => signIn(),
933
+ className: "text-primary underline hover:no-underline focus:outline-none",
934
+ children: "Sign in"
935
+ }
936
+ ),
937
+ " ",
938
+ "to join the discussion."
939
+ ] }),
940
+ fetchError && /* @__PURE__ */ jsx14("p", { className: "text-sm text-destructive", children: fetchError }),
941
+ loading ? /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx14(
942
+ CommentThread,
943
+ {
944
+ comments,
945
+ articleSlug,
946
+ currentUserId,
947
+ onRefresh: fetchComments
948
+ }
949
+ )
950
+ ] });
951
+ }
952
+ export {
953
+ ArticleCard,
954
+ ArticleCategoryGrid,
955
+ ArticleSchema,
956
+ ArticleSearchBar,
957
+ ArticlesHero,
958
+ ArticlesPage,
959
+ BreadcrumbSchema,
960
+ CategoryArticlesPage,
961
+ CollectionPageSchema,
962
+ CommentForm,
963
+ CommentItem,
964
+ CommentThread,
965
+ CommentsSection,
966
+ DEFAULT_CATEGORIES_PAGE_SIZE,
967
+ DEFAULT_LAYOUT,
968
+ DEFAULT_PAGE_SIZE,
969
+ FeaturedArticle,
970
+ LatestArticles,
971
+ LatestArticlesSection,
972
+ useArticles
973
+ };
974
+ //# sourceMappingURL=index.js.map