singularity-components 0.1.195 → 0.1.196

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 (147) hide show
  1. package/dist/components/blocks/badges/category-badge.d.ts +11 -0
  2. package/dist/components/blocks/badges/category-badge.js +34 -0
  3. package/dist/components/blocks/badges/category-badge.js.map +1 -0
  4. package/dist/components/blocks/cards/blogpost-card.d.ts +3 -1
  5. package/dist/components/blocks/cards/blogpost-card.js +9 -4
  6. package/dist/components/blocks/cards/blogpost-card.js.map +1 -1
  7. package/dist/components/blocks/directory/author-card.d.ts +10 -0
  8. package/dist/components/blocks/directory/author-card.js +50 -0
  9. package/dist/components/blocks/directory/author-card.js.map +1 -0
  10. package/dist/components/blocks/directory/category-card.d.ts +10 -0
  11. package/dist/components/blocks/directory/category-card.js +26 -0
  12. package/dist/components/blocks/directory/category-card.js.map +1 -0
  13. package/dist/components/blocks/extras/extras-hub-card.d.ts +16 -0
  14. package/dist/components/blocks/extras/extras-hub-card.js +21 -0
  15. package/dist/components/blocks/extras/extras-hub-card.js.map +1 -0
  16. package/dist/components/blocks/gallery/image-gallery.d.ts +14 -0
  17. package/dist/components/blocks/gallery/image-gallery.js +211 -0
  18. package/dist/components/blocks/gallery/image-gallery.js.map +1 -0
  19. package/dist/components/blocks/index.d.ts +11 -0
  20. package/dist/components/blocks/index.js +10 -0
  21. package/dist/components/blocks/index.js.map +1 -1
  22. package/dist/components/blocks/loading/loading-skeletons.d.ts +15 -0
  23. package/dist/components/blocks/loading/loading-skeletons.js +78 -0
  24. package/dist/components/blocks/loading/loading-skeletons.js.map +1 -0
  25. package/dist/components/blocks/marketing/page-hero.d.ts +13 -0
  26. package/dist/components/blocks/marketing/page-hero.js +37 -0
  27. package/dist/components/blocks/marketing/page-hero.js.map +1 -0
  28. package/dist/components/blocks/marketing/stats-grid.d.ts +16 -0
  29. package/dist/components/blocks/marketing/stats-grid.js +30 -0
  30. package/dist/components/blocks/marketing/stats-grid.js.map +1 -0
  31. package/dist/components/blocks/marketing/timeline.d.ts +17 -0
  32. package/dist/components/blocks/marketing/timeline.js +45 -0
  33. package/dist/components/blocks/marketing/timeline.js.map +1 -0
  34. package/dist/components/blocks/marketing/values-grid.d.ts +16 -0
  35. package/dist/components/blocks/marketing/values-grid.js +29 -0
  36. package/dist/components/blocks/marketing/values-grid.js.map +1 -0
  37. package/dist/components/index.d.ts +28 -1
  38. package/dist/components/pages/about/about-page.d.ts +5 -0
  39. package/dist/components/pages/about/about-page.js +161 -0
  40. package/dist/components/pages/about/about-page.js.map +1 -0
  41. package/dist/components/pages/admin/admin-page.js +4 -1
  42. package/dist/components/pages/admin/admin-page.js.map +1 -1
  43. package/dist/components/pages/author/author-page.d.ts +8 -0
  44. package/dist/components/pages/author/author-page.js +107 -0
  45. package/dist/components/pages/author/author-page.js.map +1 -0
  46. package/dist/components/pages/authors/authors-page.d.ts +5 -0
  47. package/dist/components/pages/authors/authors-page.js +25 -0
  48. package/dist/components/pages/authors/authors-page.js.map +1 -0
  49. package/dist/components/pages/blogpost/blogpost.d.ts +4 -1
  50. package/dist/components/pages/blogpost/blogpost.js +38 -18
  51. package/dist/components/pages/blogpost/blogpost.js.map +1 -1
  52. package/dist/components/pages/categories/categories-page.d.ts +5 -0
  53. package/dist/components/pages/categories/categories-page.js +33 -0
  54. package/dist/components/pages/categories/categories-page.js.map +1 -0
  55. package/dist/components/pages/category/category-page.js +3 -1
  56. package/dist/components/pages/category/category-page.js.map +1 -1
  57. package/dist/components/pages/contact/contact-page.d.ts +5 -0
  58. package/dist/components/pages/contact/contact-page.js +173 -0
  59. package/dist/components/pages/contact/contact-page.js.map +1 -0
  60. package/dist/components/pages/content-blocks/content-blocks-page.d.ts +5 -0
  61. package/dist/components/pages/content-blocks/content-blocks-page.js +86 -0
  62. package/dist/components/pages/content-blocks/content-blocks-page.js.map +1 -0
  63. package/dist/components/pages/extras/extras-hub-page.d.ts +10 -0
  64. package/dist/components/pages/extras/extras-hub-page.js +110 -0
  65. package/dist/components/pages/extras/extras-hub-page.js.map +1 -0
  66. package/dist/components/pages/index.d.ts +14 -0
  67. package/dist/components/pages/index.js +12 -0
  68. package/dist/components/pages/index.js.map +1 -1
  69. package/dist/components/pages/membership/membership-page.d.ts +5 -0
  70. package/dist/components/pages/membership/membership-page.js +131 -0
  71. package/dist/components/pages/membership/membership-page.js.map +1 -0
  72. package/dist/components/pages/mosaic/mosaic-page.d.ts +5 -0
  73. package/dist/components/pages/mosaic/mosaic-page.js +81 -0
  74. package/dist/components/pages/mosaic/mosaic-page.js.map +1 -0
  75. package/dist/components/pages/newsletter/newsletter-page.d.ts +5 -0
  76. package/dist/components/pages/newsletter/newsletter-page.js +148 -0
  77. package/dist/components/pages/newsletter/newsletter-page.js.map +1 -0
  78. package/dist/components/pages/resources/resources-page.d.ts +5 -0
  79. package/dist/components/pages/resources/resources-page.js +24 -0
  80. package/dist/components/pages/resources/resources-page.js.map +1 -0
  81. package/dist/components/pages/startpage/startpage.js +6 -4
  82. package/dist/components/pages/startpage/startpage.js.map +1 -1
  83. package/dist/components/primitives/accordion/accordion.js +14 -16
  84. package/dist/components/primitives/accordion/accordion.js.map +1 -1
  85. package/dist/components/primitives/badge/badge.js +1 -1
  86. package/dist/components/primitives/badge/badge.js.map +1 -1
  87. package/dist/components/primitives/buttons/button.d.ts +2 -2
  88. package/dist/components/primitives/buttons/icon-button.d.ts +1 -1
  89. package/dist/components/primitives/collapsible/collapsible.js +4 -1
  90. package/dist/components/primitives/collapsible/collapsible.js.map +1 -1
  91. package/dist/components/primitives/dropdown-menu/dropdown-menu.js +6 -1
  92. package/dist/components/primitives/dropdown-menu/dropdown-menu.js.map +1 -1
  93. package/dist/components/primitives/forms/checkbox.js +1 -1
  94. package/dist/components/primitives/forms/checkbox.js.map +1 -1
  95. package/dist/components/primitives/forms/field.d.ts +4 -2
  96. package/dist/components/primitives/forms/field.js +4 -2
  97. package/dist/components/primitives/forms/field.js.map +1 -1
  98. package/dist/components/primitives/forms/form-control.d.ts +28 -0
  99. package/dist/components/primitives/forms/form-control.js +40 -0
  100. package/dist/components/primitives/forms/form-control.js.map +1 -0
  101. package/dist/components/primitives/forms/form.d.ts +12 -0
  102. package/dist/components/primitives/forms/form.js +30 -0
  103. package/dist/components/primitives/forms/form.js.map +1 -0
  104. package/dist/components/primitives/forms/select.js +12 -12
  105. package/dist/components/primitives/forms/select.js.map +1 -1
  106. package/dist/components/primitives/icon/icon.d.ts +3 -2
  107. package/dist/components/primitives/icon/icon.js +2 -1
  108. package/dist/components/primitives/icon/icon.js.map +1 -1
  109. package/dist/components/primitives/index.d.ts +4 -0
  110. package/dist/components/primitives/index.js +3 -0
  111. package/dist/components/primitives/index.js.map +1 -1
  112. package/dist/components/primitives/layout/layout.d.ts +1 -1
  113. package/dist/components/primitives/link/link.d.ts +2 -2
  114. package/dist/components/primitives/sheet/sheet.js +1 -1
  115. package/dist/components/primitives/sheet/sheet.js.map +1 -1
  116. package/dist/components/primitives/stack/stack.d.ts +2 -2
  117. package/dist/components/primitives/text/internal/text-element.d.ts +8 -2
  118. package/dist/components/primitives/text/internal/text-element.js +3 -0
  119. package/dist/components/primitives/text/internal/text-element.js.map +1 -1
  120. package/dist/components/primitives/text/text-code.d.ts +1 -1
  121. package/dist/components/templates/index.d.ts +1 -0
  122. package/dist/components/templates/index.js +1 -0
  123. package/dist/components/templates/index.js.map +1 -1
  124. package/dist/components/templates/loading-screen/loading-screen.d.ts +10 -0
  125. package/dist/components/templates/loading-screen/loading-screen.js +39 -0
  126. package/dist/components/templates/loading-screen/loading-screen.js.map +1 -0
  127. package/dist/css/variables.css +2 -0
  128. package/dist/css/variables.css.map +1 -1
  129. package/dist/data/posts.d.ts +5 -0
  130. package/dist/data/posts.js +37 -4
  131. package/dist/data/posts.js.map +1 -1
  132. package/dist/index.d.ts +28 -1
  133. package/dist/lib/forms/field-props.d.ts +60 -0
  134. package/dist/lib/forms/field-props.js +60 -0
  135. package/dist/lib/forms/field-props.js.map +1 -0
  136. package/dist/lib/forms/index.d.ts +11 -0
  137. package/dist/lib/forms/index.js +3 -0
  138. package/dist/lib/forms/index.js.map +1 -0
  139. package/dist/lib/forms/tanstack-field.d.ts +56 -0
  140. package/dist/lib/forms/tanstack-field.js +114 -0
  141. package/dist/lib/forms/tanstack-field.js.map +1 -0
  142. package/dist/lib/index.d.ts +11 -0
  143. package/dist/lib/index.js +1 -0
  144. package/dist/lib/index.js.map +1 -1
  145. package/dist/main.css +396 -87
  146. package/dist/main.css.map +1 -1
  147. package/package.json +14 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/admin/admin-page.tsx"],"sourcesContent":["\"use client\";\nimport { useState, useMemo, useEffect } from \"react\";\nimport {\n Layout,\n Button,\n Input,\n Textarea,\n Label,\n Heading,\n Text,\n Icon,\n} from \"../../primitives/index\";\nimport { Card, CardContent } from \"../../blocks/index\";\nimport { posts as initialPosts, type BlogPost } from \"../../../data/posts\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\n\nconst POSTS_PER_PAGE = 5;\n\nexport interface AdminPageProps {\n initialEditId?: number;\n}\n\nexport function AdminPage({ initialEditId }: AdminPageProps) {\n const [postsList, setPostsList] = useState<BlogPost[]>(initialPosts);\n const [editing, setEditing] = useState<number | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const { toast } = useToast();\n\n const emptyPost: BlogPost = {\n id: 0,\n title: \"\",\n excerpt: \"\",\n content: \"\",\n primaryImage: \"\",\n originalImage: \"\",\n date: new Date().toISOString().slice(0, 10),\n author: \"Admin\",\n categories: [],\n };\n\n const [form, setForm] = useState(emptyPost);\n\n useEffect(() => {\n if (initialEditId) {\n const post = postsList.find((p) => p.id === initialEditId);\n if (post) {\n setForm({\n id: post.id,\n title: post.title,\n excerpt: post.excerpt,\n content: post.content,\n primaryImage: post.primaryImage,\n originalImage: post.originalImage,\n date: post.date,\n author: post.author,\n categories: post.categories,\n });\n setEditing(post.id);\n setShowForm(true);\n }\n }\n }, [initialEditId, postsList]);\n\n const handleSave = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.title.trim()) return;\n\n if (editing !== null) {\n setPostsList((prev) =>\n prev.map((p) => (p.id === editing ? { ...p, ...form } : p)),\n );\n toast.success(\"Post updated\", {\n description: `\"${form.title}\" has been saved.`,\n });\n setEditing(null);\n } else {\n const nextId = Math.max(...postsList.map((p) => p.id), 0) + 1;\n setPostsList((prev) => [{ ...form, id: nextId }, ...prev]);\n toast.success(\"Post created\", {\n description: `\"${form.title}\" has been published.`,\n });\n }\n\n setForm(emptyPost);\n setShowForm(false);\n };\n\n const handleEdit = (post: BlogPost) => {\n setForm(post);\n setEditing(post.id);\n setShowForm(true);\n };\n\n const [page, setPage] = useState(1);\n\n const handleDelete = (id: number) => {\n setPostsList((prev) => {\n const updated = prev.filter((p) => p.id !== id);\n const newTotalPages = Math.ceil(updated.length / POSTS_PER_PAGE);\n if (page > newTotalPages) setPage(Math.max(1, newTotalPages));\n return updated;\n });\n toast.message(\"Post deleted\");\n };\n\n const filteredPosts = useMemo(() => {\n if (!searchQuery.trim()) return postsList;\n const q = searchQuery.toLowerCase();\n return postsList.filter((p) => p.title.toLowerCase().includes(q));\n }, [postsList, searchQuery]);\n\n const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE);\n const paginatedPosts = useMemo(\n () =>\n filteredPosts.slice((page - 1) * POSTS_PER_PAGE, page * POSTS_PER_PAGE),\n [filteredPosts, page],\n );\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:space-y-8\">\n <div className=\"sg:flex sg:items-center sg:justify-between\">\n <div>\n <Heading variant=\"h1\">Admin</Heading>\n <Text className=\"sg:mt-1\" foreground=\"muted-foreground\">\n Manage your blog posts.\n </Text>\n </div>\n <Button\n onClick={() => {\n setShowForm(!showForm);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n <Icon icon=\"Plus\" size=\"xs\" className=\"sg:mr-1\" /> New post\n </Button>\n </div>\n\n {/* Search */}\n <div className=\"sg:relative\">\n <Icon\n icon=\"Search\"\n size=\"xs\"\n color=\"muted-foreground\"\n className=\"sg:absolute sg:left-3 sg:top-1/2 sg:-translate-y-1/2\"\n />\n <Input\n placeholder=\"Search posts by title...\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setPage(1);\n }}\n className=\"sg:pl-9\"\n />\n </div>\n\n {/* Form */}\n {showForm && (\n <Card>\n <CardContent className=\"sg:p-6 sg:mt-6\">\n <Heading variant=\"h3\" className=\"sg:mb-4\">\n {editing !== null ? \"Edit Post\" : \"New Post\"}\n </Heading>\n <form onSubmit={handleSave} className=\"sg:space-y-4\">\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"title\">Title</Label>\n <Input\n id=\"title\"\n value={form.title}\n onChange={(e) =>\n setForm({ ...form, title: e.target.value })\n }\n required\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"excerpt\">Excerpt</Label>\n <Input\n id=\"excerpt\"\n value={form.excerpt || \"\"}\n onChange={(e) =>\n setForm({ ...form, excerpt: e.target.value })\n }\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"image\">Image URL</Label>\n <Input\n id=\"image\"\n value={form.primaryImage}\n onChange={(e) =>\n setForm({ ...form, primaryImage: e.target.value })\n }\n placeholder=\"https://...\"\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"content\">Content</Label>\n <Textarea\n id=\"content\"\n value={(form.content || \"\") as string}\n onChange={(e) =>\n setForm({\n ...form,\n content: e.target.value,\n })\n }\n className=\"sg:min-h-[200px]\"\n />\n </div>\n <div className=\"sg:flex sg:gap-2 sg:pt-2\">\n <Button type=\"submit\">\n {editing !== null ? \"Update\" : \"Publish\"}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setShowForm(false);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n Cancel\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n )}\n\n {/* Posts list */}\n <div className=\"sg:space-y-3\">\n {paginatedPosts.map((post) => (\n <Card key={post.id}>\n <CardContent className=\"sg:p-4 sg:flex sg:items-center sg:justify-between sg:pt-4\">\n <div className=\"sg:flex-1 sg:min-w-0\">\n <div className=\"sg:font-medium sg:truncate\">\n {post.title}\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author}\n </Text>\n </div>\n <div className=\"sg:flex sg:gap-2 sg:ml-4\">\n <Button\n variant=\"ghost\"\n onClick={() => handleEdit(post)}\n aria-label={`Edit ${post.title}`}\n className=\"sg:px-2\"\n >\n <Icon icon=\"Pencil\" size=\"xs\" />\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => handleDelete(post.id)}\n aria-label={`Delete ${post.title}`}\n className=\"sg:px-2 sg:text-destructive sg:hover:text-destructive sg:hover:bg-destructive/10\"\n >\n <Icon icon=\"Trash2\" size=\"xs\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n {paginatedPosts.length === 0 && (\n <Text\n foreground=\"muted-foreground\"\n className=\"sg:text-center sg:py-8\"\n >\n No posts found.\n </Text>\n )}\n </div>\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"sg:flex sg:items-center sg:justify-center sg:gap-2 sg:pt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === 1}\n onClick={() => setPage(page - 1)}\n >\n <Icon icon=\"ChevronLeft\" size=\"xs\" className=\"sg:mr-1\" />{\" \"}\n Previous\n </Button>\n <Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:px-3\">\n Page {page} of {totalPages}\n </Text>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === totalPages}\n onClick={() => setPage(page + 1)}\n >\n Next <Icon icon=\"ChevronRight\" size=\"xs\" className=\"sg:ml-1\" />\n </Button>\n </div>\n )}\n </div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/admin/admin-page.tsx"],"sourcesContent":["\"use client\";\nimport { useState, useMemo, useEffect } from \"react\";\nimport {\n Layout,\n Button,\n Input,\n Textarea,\n Label,\n Heading,\n Text,\n Icon,\n} from \"../../primitives/index\";\nimport { Card, CardContent } from \"../../blocks/index\";\nimport { posts as initialPosts, type BlogPost } from \"../../../data/posts\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\n\nconst POSTS_PER_PAGE = 5;\n\nexport interface AdminPageProps {\n initialEditId?: number;\n}\n\nexport function AdminPage({ initialEditId }: AdminPageProps) {\n const [postsList, setPostsList] = useState<BlogPost[]>(initialPosts);\n const [editing, setEditing] = useState<number | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const { toast } = useToast();\n\n const emptyPost: BlogPost = {\n id: 0,\n slug: \"\",\n title: \"\",\n excerpt: \"\",\n content: \"\",\n primaryImage: \"\",\n originalImage: \"\",\n date: new Date().toISOString().slice(0, 10),\n author: \"Admin\",\n categories: [],\n };\n\n const [form, setForm] = useState(emptyPost);\n\n useEffect(() => {\n if (initialEditId) {\n const post = postsList.find((p) => p.id === initialEditId);\n if (post) {\n setForm({\n id: post.id,\n slug: post.slug,\n title: post.title,\n excerpt: post.excerpt,\n content: post.content,\n primaryImage: post.primaryImage,\n originalImage: post.originalImage,\n date: post.date,\n author: post.author,\n categories: post.categories,\n });\n setEditing(post.id);\n setShowForm(true);\n }\n }\n }, [initialEditId, postsList]);\n\n const handleSave = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.title.trim()) return;\n\n if (editing !== null) {\n setPostsList((prev) =>\n prev.map((p) => (p.id === editing ? { ...p, ...form } : p)),\n );\n toast.success(\"Post updated\", {\n description: `\"${form.title}\" has been saved.`,\n });\n setEditing(null);\n } else {\n const nextId = Math.max(...postsList.map((p) => p.id), 0) + 1;\n const slug =\n form.slug ||\n form.title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\") ||\n `post-${nextId}`;\n setPostsList((prev) => [{ ...form, id: nextId, slug }, ...prev]);\n toast.success(\"Post created\", {\n description: `\"${form.title}\" has been published.`,\n });\n }\n\n setForm(emptyPost);\n setShowForm(false);\n };\n\n const handleEdit = (post: BlogPost) => {\n setForm(post);\n setEditing(post.id);\n setShowForm(true);\n };\n\n const [page, setPage] = useState(1);\n\n const handleDelete = (id: number) => {\n setPostsList((prev) => {\n const updated = prev.filter((p) => p.id !== id);\n const newTotalPages = Math.ceil(updated.length / POSTS_PER_PAGE);\n if (page > newTotalPages) setPage(Math.max(1, newTotalPages));\n return updated;\n });\n toast.message(\"Post deleted\");\n };\n\n const filteredPosts = useMemo(() => {\n if (!searchQuery.trim()) return postsList;\n const q = searchQuery.toLowerCase();\n return postsList.filter((p) => p.title.toLowerCase().includes(q));\n }, [postsList, searchQuery]);\n\n const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE);\n const paginatedPosts = useMemo(\n () =>\n filteredPosts.slice((page - 1) * POSTS_PER_PAGE, page * POSTS_PER_PAGE),\n [filteredPosts, page],\n );\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:space-y-8\">\n <div className=\"sg:flex sg:items-center sg:justify-between\">\n <div>\n <Heading variant=\"h1\">Admin</Heading>\n <Text className=\"sg:mt-1\" foreground=\"muted-foreground\">\n Manage your blog posts.\n </Text>\n </div>\n <Button\n onClick={() => {\n setShowForm(!showForm);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n <Icon icon=\"Plus\" size=\"xs\" className=\"sg:mr-1\" /> New post\n </Button>\n </div>\n\n {/* Search */}\n <div className=\"sg:relative\">\n <Icon\n icon=\"Search\"\n size=\"xs\"\n color=\"muted-foreground\"\n className=\"sg:absolute sg:left-3 sg:top-1/2 sg:-translate-y-1/2\"\n />\n <Input\n placeholder=\"Search posts by title...\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setPage(1);\n }}\n className=\"sg:pl-9\"\n />\n </div>\n\n {/* Form */}\n {showForm && (\n <Card>\n <CardContent className=\"sg:p-6 sg:mt-6\">\n <Heading variant=\"h3\" className=\"sg:mb-4\">\n {editing !== null ? \"Edit Post\" : \"New Post\"}\n </Heading>\n <form onSubmit={handleSave} className=\"sg:space-y-4\">\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"title\">Title</Label>\n <Input\n id=\"title\"\n value={form.title}\n onChange={(e) =>\n setForm({ ...form, title: e.target.value })\n }\n required\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"excerpt\">Excerpt</Label>\n <Input\n id=\"excerpt\"\n value={form.excerpt || \"\"}\n onChange={(e) =>\n setForm({ ...form, excerpt: e.target.value })\n }\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"image\">Image URL</Label>\n <Input\n id=\"image\"\n value={form.primaryImage}\n onChange={(e) =>\n setForm({ ...form, primaryImage: e.target.value })\n }\n placeholder=\"https://...\"\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"content\">Content</Label>\n <Textarea\n id=\"content\"\n value={(form.content || \"\") as string}\n onChange={(e) =>\n setForm({\n ...form,\n content: e.target.value,\n })\n }\n className=\"sg:min-h-[200px]\"\n />\n </div>\n <div className=\"sg:flex sg:gap-2 sg:pt-2\">\n <Button type=\"submit\">\n {editing !== null ? \"Update\" : \"Publish\"}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setShowForm(false);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n Cancel\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n )}\n\n {/* Posts list */}\n <div className=\"sg:space-y-3\">\n {paginatedPosts.map((post) => (\n <Card key={post.id}>\n <CardContent className=\"sg:p-4 sg:flex sg:items-center sg:justify-between sg:pt-4\">\n <div className=\"sg:flex-1 sg:min-w-0\">\n <div className=\"sg:font-medium sg:truncate\">\n {post.title}\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author}\n </Text>\n </div>\n <div className=\"sg:flex sg:gap-2 sg:ml-4\">\n <Button\n variant=\"ghost\"\n onClick={() => handleEdit(post)}\n aria-label={`Edit ${post.title}`}\n className=\"sg:px-2\"\n >\n <Icon icon=\"Pencil\" size=\"xs\" />\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => handleDelete(post.id)}\n aria-label={`Delete ${post.title}`}\n className=\"sg:px-2 sg:text-destructive sg:hover:text-destructive sg:hover:bg-destructive/10\"\n >\n <Icon icon=\"Trash2\" size=\"xs\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n {paginatedPosts.length === 0 && (\n <Text\n foreground=\"muted-foreground\"\n className=\"sg:text-center sg:py-8\"\n >\n No posts found.\n </Text>\n )}\n </div>\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"sg:flex sg:items-center sg:justify-center sg:gap-2 sg:pt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === 1}\n onClick={() => setPage(page - 1)}\n >\n <Icon icon=\"ChevronLeft\" size=\"xs\" className=\"sg:mr-1\" />{\" \"}\n Previous\n </Button>\n <Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:px-3\">\n Page {page} of {totalPages}\n </Text>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === totalPages}\n onClick={() => setPage(page + 1)}\n >\n Next <Icon icon=\"ChevronRight\" size=\"xs\" className=\"sg:ml-1\" />\n </Button>\n </div>\n )}\n </div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+
3
+ type Props = {
4
+ slug?: string;
5
+ };
6
+ declare function AuthorPage({ slug }: Props): React.JSX.Element;
7
+
8
+ export { AuthorPage };
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { motion } from "framer-motion";
5
+ import {
6
+ Layout,
7
+ Heading,
8
+ Text,
9
+ TextSpan,
10
+ Icon,
11
+ Badges
12
+ } from "../../primitives/index.js";
13
+ import {
14
+ Avatar,
15
+ AvatarFallback,
16
+ AvatarImage
17
+ } from "../../primitives/avatar/avatar.js";
18
+ import CategoryBadge from "../../blocks/badges/category-badge.js";
19
+ import { LinkButton } from "../../primitives/buttons/link-button.js";
20
+ import { PostListWithFilters } from "../../blocks/post-list/post-list-with-filters.js";
21
+ import { authors } from "../../../data/authors.js";
22
+ import { posts } from "../../../data/posts.js";
23
+ function AuthorPage({ slug = "elena-marsh" }) {
24
+ const author = authors.find((a) => a.slug === slug);
25
+ const authorPosts = useMemo(
26
+ () => author ? posts.filter((p) => p.author === author.name) : [],
27
+ [author]
28
+ );
29
+ if (!author) {
30
+ return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsxs(Layout.Col1, { hideDiv: true, className: "sg:text-center", children: [
31
+ /* @__PURE__ */ jsx(Heading, { variant: "h2", className: "sg:mb-4", children: "Author not found" }),
32
+ /* @__PURE__ */ jsx(LinkButton, { variant: "outline", to: "/extras/authors", iconStart: "ArrowLeft", children: "Back to Authors" })
33
+ ] }) });
34
+ }
35
+ return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, children: /* @__PURE__ */ jsxs(
36
+ motion.div,
37
+ {
38
+ initial: { opacity: 0, y: 20 },
39
+ animate: { opacity: 1, y: 0 },
40
+ transition: { duration: 0.3 },
41
+ children: [
42
+ /* @__PURE__ */ jsx(
43
+ LinkButton,
44
+ {
45
+ variant: "ghost",
46
+ size: "sm",
47
+ to: "/extras/authors",
48
+ iconStart: "ArrowLeft",
49
+ className: "sg:mb-6",
50
+ children: "All Authors"
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:flex-col sm:sg:flex-row sg:items-start sg:gap-6 sg:mb-12", children: [
54
+ /* @__PURE__ */ jsxs(Avatar, { className: "sg:h-24 sg:w-24", children: [
55
+ /* @__PURE__ */ jsx(AvatarImage, { src: author.avatar, alt: author.name }),
56
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "sg:text-2xl", children: author.name.split(" ").map((n) => n[0]).join("") })
57
+ ] }),
58
+ /* @__PURE__ */ jsxs("div", { children: [
59
+ /* @__PURE__ */ jsx(Heading, { variant: "h1", children: author.name }),
60
+ /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-center sg:gap-4 sg:mt-2", children: [
61
+ /* @__PURE__ */ jsxs(Text, { size: "sm", foreground: "muted-foreground", className: "sg:flex sg:items-center sg:gap-1", children: [
62
+ /* @__PURE__ */ jsx(Icon, { icon: "Briefcase", className: "sg:h-3.5 sg:w-3.5" }),
63
+ author.role
64
+ ] }),
65
+ /* @__PURE__ */ jsxs(Text, { size: "sm", foreground: "muted-foreground", className: "sg:flex sg:items-center sg:gap-1", children: [
66
+ /* @__PURE__ */ jsx(Icon, { icon: "MapPin", className: "sg:h-3.5 sg:w-3.5" }),
67
+ author.location
68
+ ] })
69
+ ] }),
70
+ /* @__PURE__ */ jsx(
71
+ Text,
72
+ {
73
+ foreground: "muted-foreground",
74
+ className: "sg:mt-4 sg:leading-relaxed sg:max-w-2xl",
75
+ children: author.bio
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx(Badges, { className: "sg:mt-4", children: author.interests.map((interest) => /* @__PURE__ */ jsx(CategoryBadge, { category: interest, clickable: true }, interest)) })
79
+ ] })
80
+ ] }),
81
+ /* @__PURE__ */ jsxs(Heading, { variant: "h3", className: "sg:mb-6", children: [
82
+ "Posts by ",
83
+ author.name,
84
+ " ",
85
+ /* @__PURE__ */ jsxs(TextSpan, { foreground: "muted-foreground", size: "lg", className: "sg:font-normal", children: [
86
+ "(",
87
+ authorPosts.length,
88
+ ")"
89
+ ] })
90
+ ] }),
91
+ /* @__PURE__ */ jsx(
92
+ PostListWithFilters,
93
+ {
94
+ posts: authorPosts,
95
+ hideSearch: true,
96
+ hideAuthorFilter: true,
97
+ filterMode: "drawer"
98
+ }
99
+ )
100
+ ]
101
+ }
102
+ ) }) });
103
+ }
104
+ export {
105
+ AuthorPage
106
+ };
107
+ //# sourceMappingURL=author-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/pages/author/author-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useMemo } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport {\r\n\tLayout,\r\n\tHeading,\r\n\tText,\r\n\tTextSpan,\r\n\tIcon,\r\n\tBadges,\r\n} from \"../../primitives/index\";\r\nimport {\r\n\tAvatar,\r\n\tAvatarFallback,\r\n\tAvatarImage,\r\n} from \"../../primitives/avatar/avatar\";\r\nimport CategoryBadge from \"../../blocks/badges/category-badge\";\r\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\r\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\r\nimport { authors } from \"../../../data/authors\";\r\nimport { posts } from \"../../../data/posts\";\r\n\r\ntype Props = {\r\n\tslug?: string;\r\n};\r\n\r\nexport function AuthorPage({ slug = \"elena-marsh\" }: Props) {\r\n\tconst author = authors.find((a) => a.slug === slug);\r\n\r\n\tconst authorPosts = useMemo(\r\n\t\t() => (author ? posts.filter((p) => p.author === author.name) : []),\r\n\t\t[author],\r\n\t);\r\n\r\n\tif (!author) {\r\n\t\treturn (\r\n\t\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t\t<Layout.Col1 hideDiv className=\"sg:text-center\">\r\n\t\t\t\t\t<Heading variant=\"h2\" className=\"sg:mb-4\">\r\n\t\t\t\t\t\tAuthor not found\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<LinkButton variant=\"outline\" to=\"/extras/authors\" iconStart=\"ArrowLeft\">\r\n\t\t\t\t\t\tBack to Authors\r\n\t\t\t\t\t</LinkButton>\r\n\t\t\t\t</Layout.Col1>\r\n\t\t\t</Layout>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv>\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<LinkButton\r\n\t\t\t\t\t\tvariant=\"ghost\"\r\n\t\t\t\t\t\tsize=\"sm\"\r\n\t\t\t\t\t\tto=\"/extras/authors\"\r\n\t\t\t\t\t\ticonStart=\"ArrowLeft\"\r\n\t\t\t\t\t\tclassName=\"sg:mb-6\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\tAll Authors\r\n\t\t\t\t\t</LinkButton>\r\n\r\n\t\t\t\t\t<div className=\"sg:flex sg:flex-col sm:sg:flex-row sg:items-start sg:gap-6 sg:mb-12\">\r\n\t\t\t\t\t\t<Avatar className=\"sg:h-24 sg:w-24\">\r\n\t\t\t\t\t\t\t<AvatarImage src={author.avatar} alt={author.name} />\r\n\t\t\t\t\t\t\t<AvatarFallback className=\"sg:text-2xl\">\r\n\t\t\t\t\t\t\t\t{author.name\r\n\t\t\t\t\t\t\t\t\t.split(\" \")\r\n\t\t\t\t\t\t\t\t\t.map((n) => n[0])\r\n\t\t\t\t\t\t\t\t\t.join(\"\")}\r\n\t\t\t\t\t\t\t</AvatarFallback>\r\n\t\t\t\t\t\t</Avatar>\r\n\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t<Heading variant=\"h1\">{author.name}</Heading>\r\n\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-center sg:gap-4 sg:mt-2\">\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"Briefcase\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.role}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"MapPin\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.location}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t<Text\r\n\t\t\t\t\t\t\t\tforeground=\"muted-foreground\"\r\n\t\t\t\t\t\t\t\tclassName=\"sg:mt-4 sg:leading-relaxed sg:max-w-2xl\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t{author.bio}\r\n\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t<Badges className=\"sg:mt-4\">\r\n\t\t\t\t\t\t\t\t{author.interests.map((interest) => (\r\n\t\t\t\t\t\t\t\t\t<CategoryBadge key={interest} category={interest} clickable />\r\n\t\t\t\t\t\t\t\t))}\r\n\t\t\t\t\t\t\t</Badges>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t<Heading variant=\"h3\" className=\"sg:mb-6\">\r\n\t\t\t\t\t\tPosts by {author.name}{\" \"}\r\n\t\t\t\t\t\t<TextSpan foreground=\"muted-foreground\" size=\"lg\" className=\"sg:font-normal\">\r\n\t\t\t\t\t\t\t({authorPosts.length})\r\n\t\t\t\t\t\t</TextSpan>\r\n\t\t\t\t\t</Heading>\r\n\r\n\t\t\t\t\t<PostListWithFilters\r\n\t\t\t\t\t\tposts={authorPosts}\r\n\t\t\t\t\t\thideSearch\r\n\t\t\t\t\t\thideAuthorFilter\r\n\t\t\t\t\t\tfilterMode=\"drawer\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function AuthorsPage(): React.JSX.Element;
4
+
5
+ export { AuthorsPage };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { motion } from "framer-motion";
4
+ import { Layout, Heading, Text } from "../../primitives/index.js";
5
+ import { AuthorCard } from "../../blocks/directory/author-card.js";
6
+ import { authors } from "../../../data/authors.js";
7
+ function AuthorsPage() {
8
+ return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, className: "sg:max-w-3xl", children: /* @__PURE__ */ jsxs(
9
+ motion.div,
10
+ {
11
+ initial: { opacity: 0, y: 20 },
12
+ animate: { opacity: 1, y: 0 },
13
+ transition: { duration: 0.3 },
14
+ children: [
15
+ /* @__PURE__ */ jsx(Heading, { variant: "h1", className: "sg:mb-2", children: "Authors" }),
16
+ /* @__PURE__ */ jsx(Text, { foreground: "muted-foreground", className: "sg:mb-10", children: "Meet the voices behind the stories." }),
17
+ /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6", children: authors.map((author) => /* @__PURE__ */ jsx(AuthorCard, { author }, author.slug)) })
18
+ ]
19
+ }
20
+ ) }) });
21
+ }
22
+ export {
23
+ AuthorsPage
24
+ };
25
+ //# sourceMappingURL=authors-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/pages/authors/authors-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { motion } from \"framer-motion\";\r\nimport { Layout, Heading, Text } from \"../../primitives/index\";\r\nimport { AuthorCard } from \"../../blocks/directory/author-card\";\r\nimport { authors } from \"../../../data/authors\";\r\n\r\nexport function AuthorsPage() {\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-3xl\">\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Heading variant=\"h1\" className=\"sg:mb-2\">\r\n\t\t\t\t\t\tAuthors\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<Text foreground=\"muted-foreground\" className=\"sg:mb-10\">\r\n\t\t\t\t\t\tMeet the voices behind the stories.\r\n\t\t\t\t\t</Text>\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-6\">\r\n\t\t\t\t\t\t{authors.map((author) => (\r\n\t\t\t\t\t\t\t<AuthorCard key={author.slug} author={author} />\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAWI,SAKC,KALD;AATJ,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAEjB,SAAS,cAAc;AAC7B,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,gBAC9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACA,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,qBAE1C;AAAA,QACA,oBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW,iDAEzD;AAAA,QACA,oBAAC,SAAI,WAAU,oBACb,kBAAQ,IAAI,CAAC,WACb,oBAAC,cAA6B,UAAb,OAAO,IAAsB,CAC9C,GACF;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
@@ -1,5 +1,8 @@
1
1
  import * as React from 'react';
2
2
 
3
- declare function BlogPost(): React.JSX.Element;
3
+ interface BlogPostProps {
4
+ slug?: string;
5
+ }
6
+ declare function BlogPost({ slug: propSlug }: BlogPostProps): React.JSX.Element;
4
7
 
5
8
  export { BlogPost };
@@ -9,7 +9,6 @@ import {
9
9
  Link,
10
10
  TextTime,
11
11
  UiImage,
12
- Badge,
13
12
  Badges,
14
13
  Icon,
15
14
  Textarea,
@@ -20,6 +19,8 @@ import {
20
19
  } from "../../primitives/index.js";
21
20
  import { ArrowLeft, ArrowRight } from "lucide-react";
22
21
  import EmptyState from "../../blocks/empty-state/EmptyState.js";
22
+ import CategoryBadge from "../../blocks/badges/category-badge.js";
23
+ import ImageGallery from "../../blocks/gallery/image-gallery.js";
23
24
  import { useToast } from "../../primitives/sonner/use-toast.js";
24
25
  import { authors } from "../../../data/authors.js";
25
26
  import {
@@ -28,10 +29,13 @@ import {
28
29
  AvatarImage
29
30
  } from "../../primitives/avatar/avatar.js";
30
31
  import BlogPostCard from "../../blocks/cards/blogpost-card.js";
31
- function BlogPost() {
32
- const postId = 1;
33
- const post = posts.find((p) => p.id === postId);
34
- const postIndex = posts.findIndex((p) => p.id === postId);
32
+ function getReadTime(text) {
33
+ const words = text.trim().split(/\s+/).length;
34
+ return Math.max(1, Math.ceil(words / 200));
35
+ }
36
+ function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
37
+ const post = posts.find((p) => p.slug === propSlug);
38
+ const postIndex = posts.findIndex((p) => p.slug === propSlug);
35
39
  const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;
36
40
  const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;
37
41
  const isLoggedIn = true;
@@ -43,6 +47,10 @@ function BlogPost() {
43
47
  const [likes, setLikes] = useState(0);
44
48
  const [isGenerating, setIsGenerating] = useState(false);
45
49
  const [isValidate, setIsValidate] = useState(false);
50
+ const readTime = useMemo(
51
+ () => post ? getReadTime(post.content) : 0,
52
+ [post]
53
+ );
46
54
  const relatedPosts = useMemo(() => {
47
55
  if (!post) return [];
48
56
  return posts.filter(
@@ -158,7 +166,7 @@ function BlogPost() {
158
166
  );
159
167
  });
160
168
  };
161
- const mockedAuthor = authors[0];
169
+ const authorData = authors.find((a) => a.name === post?.author) ?? authors[0];
162
170
  return /* @__PURE__ */ jsxs(Fragment, { children: [
163
171
  /* @__PURE__ */ jsx(Layout, { type: "container", children: /* @__PURE__ */ jsx(Layout.Col1, { className: "sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden", children: /* @__PURE__ */ jsx(
164
172
  UiImage,
@@ -174,11 +182,14 @@ function BlogPost() {
174
182
  /* @__PURE__ */ jsxs(TextTime, { size: "sm", foreground: "muted-foreground", children: [
175
183
  post.date,
176
184
  " \xB7 ",
177
- post.author
185
+ post.author,
186
+ " \xB7 ",
187
+ readTime,
188
+ " min read"
178
189
  ] })
179
190
  ] }),
180
191
  /* @__PURE__ */ jsx(Heading, { variant: "h1", className: "sg:mt-2", children: post.title }),
181
- post.categories.length > 0 && /* @__PURE__ */ jsx(Badges, { className: "sg:mt-4", children: post.categories.map((cat) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: cat }, cat)) }),
192
+ post.categories.length > 0 && /* @__PURE__ */ jsx(Badges, { className: "sg:mt-4", children: post.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBadge, { category: cat, clickable: true }, cat)) }),
182
193
  /* @__PURE__ */ jsx(
183
194
  Text,
184
195
  {
@@ -228,6 +239,7 @@ function BlogPost() {
228
239
  ] })
229
240
  ] }),
230
241
  /* @__PURE__ */ jsx("div", { className: "sg:mt-8 sg:border-t sg:pt-8", children: renderBody(post.content || "") }),
242
+ post.gallery && post.gallery.length > 0 && /* @__PURE__ */ jsx("div", { className: "sg:mt-12", children: /* @__PURE__ */ jsx(ImageGallery, { images: post.gallery }) }),
231
243
  /* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden", children: /* @__PURE__ */ jsx(
232
244
  UiImage,
233
245
  {
@@ -238,7 +250,6 @@ function BlogPost() {
238
250
  }
239
251
  ) }),
240
252
  (() => {
241
- const authorData = mockedAuthor;
242
253
  return /* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:border-t sg:pt-8", children: /* @__PURE__ */ jsxs(
243
254
  Link,
244
255
  {
@@ -307,7 +318,7 @@ function BlogPost() {
307
318
  Link,
308
319
  {
309
320
  variant: "no-decoration",
310
- to: `/posts/${prevPost.id}`,
321
+ to: `/posts/${prevPost.slug}`,
311
322
  className: "sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right",
312
323
  "aria-label": `Previous post: ${prevPost.title}`,
313
324
  title: `Previous post: ${prevPost.title}`,
@@ -340,7 +351,7 @@ function BlogPost() {
340
351
  Link,
341
352
  {
342
353
  variant: "no-decoration",
343
- to: `/posts/${nextPost.id}`,
354
+ to: `/posts/${nextPost.slug}`,
344
355
  className: "sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right",
345
356
  "aria-label": `Next post: ${nextPost.title}`,
346
357
  title: `Next post: ${nextPost.title}`,
@@ -376,7 +387,14 @@ function BlogPost() {
376
387
  comments.length,
377
388
  ")"
378
389
  ] }),
379
- /* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.map((c) => /* @__PURE__ */ jsxs("div", { className: "sg:rounded-lg sg:bg-accent sg:p-4", children: [
390
+ /* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.length === 0 ? /* @__PURE__ */ jsx(
391
+ EmptyState,
392
+ {
393
+ icon: "MessageSquare",
394
+ title: "No comments yet",
395
+ description: "Be the first to share your thoughts on this post."
396
+ }
397
+ ) : comments.map((c) => /* @__PURE__ */ jsxs("div", { className: "sg:rounded-lg sg:bg-accent sg:p-4", children: [
380
398
  /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-center sg:gap-2 sg:mb-1", children: [
381
399
  /* @__PURE__ */ jsx(TextSpan, { fontweight: "medium", size: "sm", children: c.author }),
382
400
  /* @__PURE__ */ jsx(
@@ -421,12 +439,14 @@ function BlogPost() {
421
439
  /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3", children: relatedPosts.map((p) => /* @__PURE__ */ jsx(
422
440
  BlogPostCard,
423
441
  {
424
- id: post.id,
425
- image: post.primaryImage,
426
- categories: post.categories,
427
- date: post.date,
428
- title: post.title,
429
- excerpt: post.excerpt
442
+ id: p.id,
443
+ slug: p.slug,
444
+ image: p.primaryImage,
445
+ categories: p.categories,
446
+ date: p.date,
447
+ title: p.title,
448
+ excerpt: p.excerpt,
449
+ clickableCategories: true
430
450
  },
431
451
  p.id
432
452
  )) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/blogpost/blogpost.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo, useState } from \"react\";\nimport DOMPurify from \"isomorphic-dompurify\";\nimport { mockComments, posts, type Comment } from \"../../../data/posts\";\nimport {\n Button,\n Layout,\n Link,\n TextTime,\n UiImage,\n Badge,\n Badges,\n Icon,\n Textarea,\n Input,\n Heading,\n Text,\n TextSpan,\n} from \"../../primitives/index\";\nimport { ArrowLeft, ArrowRight } from \"lucide-react\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\nimport { authors } from \"../../../data/authors\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"../../primitives/avatar/avatar\";\nimport BlogPostCard from \"../../blocks/cards/blogpost-card\";\n\nfunction BlogPost() {\n const postId = 1; // In a real app, this would come from the URL params\n\n const post = posts.find((p) => p.id === postId);\n const postIndex = posts.findIndex((p) => p.id === postId);\n const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;\n const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;\n\n const isLoggedIn = true;\n const { toast } = useToast();\n\n const [comments, setComments] = useState<Comment[]>(mockComments);\n const [name, setName] = useState(\"\");\n const [text, setText] = useState(\"\");\n\n const [hasLiked, setHasLiked] = useState(false);\n const [likes, setLikes] = useState(0);\n\n const [isGenerating, setIsGenerating] = useState(false);\n const [isValidate, setIsValidate] = useState(false);\n\n const relatedPosts = useMemo(() => {\n if (!post) return [];\n return posts\n .filter(\n (p) =>\n p.id !== post.id &&\n p.categories.some((c) => post.categories.includes(c)),\n )\n .slice(0, 3);\n }, [post]);\n\n if (!post) {\n return (\n <Layout>\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"BookOpen\"\n title=\"Post not found\"\n description=\"This post may have been removed or the link is incorrect.\"\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n const handleLike = () => {\n if (!isLoggedIn) return;\n if (hasLiked) {\n setLikes((l) => l - 1);\n setHasLiked(false);\n } else {\n setLikes((l) => l + 1);\n setHasLiked(true);\n }\n };\n\n const handleGenerateImage = () => {\n setIsGenerating(true);\n setTimeout(() => {\n setIsGenerating(false);\n }, 1000);\n toast.message(\"Generating image…\", {\n description: \"A new AI image is being created for this post.\",\n dismissible: true,\n icon: <Icon icon=\"ImagePlus\" />,\n });\n };\n\n const handleValidate = () => {\n setIsValidate(true);\n setTimeout(() => {\n setIsValidate(false);\n }, 1000);\n toast.message(\"Post validated\", {\n description: \"Content has been reviewed.\",\n icon: <Icon icon=\"Check\" />,\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim() || !text.trim()) return;\n setComments((prev) => [\n ...prev,\n {\n id: Date.now().toString(),\n author: name,\n text,\n date: new Date().toISOString().slice(0, 10),\n },\n ]);\n setName(\"\");\n setText(\"\");\n };\n\n // Simple markdown-ish rendering for content\n const renderBody = (body: string) => {\n return body.split(\"\\n\\n\").map((block, i) => {\n if (block.startsWith(\"## \")) {\n return (\n <Heading variant=\"h2\" key={i} className=\"sg:mt-8 sg:mb-4\">\n {block.replace(\"## \", \"\")}\n </Heading>\n );\n }\n if (block.startsWith(\"1. \") || block.startsWith(\"- \")) {\n const items = block\n .split(\"\\n\")\n .map((line) => line.replace(/^(\\d+\\.\\s|-\\s)/, \"\"));\n const isOrdered = block.startsWith(\"1.\");\n const Tag = isOrdered ? \"ol\" : \"ul\";\n\n return (\n <Tag\n key={i}\n className={`sg:my-4 sg:ml-6 sg:space-y-2 ${isOrdered ? \"sg:list-decimal\" : \"sg:list-disc\"}`}\n >\n {items.map((item, j) => (\n <li\n key={j}\n className=\"sg:text-muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n item.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n ))}\n </Tag>\n );\n }\n\n return (\n <Text\n key={i}\n className=\"sg:my-4\"\n foreground=\"muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n block.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"sg:text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n );\n });\n };\n\n // Using a mocked author for now as PostGetDto lacks author info\n const mockedAuthor = authors[0];\n\n return (\n <>\n <Layout type=\"container\">\n <Layout.Col1 className=\"sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden\">\n <UiImage\n src={post.primaryImage || post.originalImage || \"\"}\n alt={post.title}\n className=\"sg:w-full sg:h-full sg:object-cover\"\n />\n </Layout.Col1>\n </Layout>\n <Layout type=\"col\" className=\"sg:py-12\" as=\"article\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:flex sg:flex-col sg:gap-6 sg:items-start\">\n <Link to=\"/posts\" iconStart=\"ArrowLeft\">\n Back to all posts\n </Link>\n\n <TextTime size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author}\n </TextTime>\n </div>\n\n <Heading variant=\"h1\" className=\"sg:mt-2\">\n {post.title}\n </Heading>\n\n {post.categories.length > 0 && (\n <Badges className=\"sg:mt-4\">\n {post.categories.map((cat) => (\n <Badge key={cat} variant=\"secondary\">\n {cat}\n </Badge>\n ))}\n </Badges>\n )}\n\n <Text\n size=\"lg\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-4 sg:italic\"\n >\n {post.excerpt}\n </Text>\n\n {/* Like + admin actions bar */}\n <div className=\"sg:mt-6 sg:flex sg:items-center sg:gap-3 sg:flex-wrap\">\n <Button\n onClick={handleLike}\n disabled={!isLoggedIn}\n variant=\"outline\"\n size=\"sm\"\n iconStart=\"Heart\"\n iconStartFill={hasLiked}\n title={\n isLoggedIn\n ? hasLiked\n ? \"Unlike\"\n : \"Like this post\"\n : \"Log in to like\"\n }\n >\n {likes}\n </Button>\n\n {isLoggedIn && (\n <>\n <Button\n variant=\"outline\"\n iconStart=\"ImagePlus\"\n size=\"sm\"\n onClick={handleGenerateImage}\n loading={isGenerating}\n >\n Generate image\n </Button>\n <Button\n variant=\"outline\"\n iconStart=\"ShieldCheck\"\n size=\"sm\"\n onClick={handleValidate}\n loading={isValidate}\n >\n Validate\n </Button>\n </>\n )}\n </div>\n\n <div className=\"sg:mt-8 sg:border-t sg:pt-8\">\n {renderBody(post.content || \"\")}\n </div>\n\n {/* Secondary image */}\n <div className=\"sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden\">\n <UiImage\n src={(post.originalImage || \"\") + \"&q=80&crop=entropy\"}\n alt={`Visual for ${post.title}`}\n className=\"sg:w-full sg:h-56 sg:md:h-72 sg:object-cover\"\n loading=\"lazy\"\n />\n </div>\n\n {/* Author card */}\n {(() => {\n const authorData = mockedAuthor;\n return (\n <div className=\"sg:mt-12 sg:border-t sg:pt-8\">\n <Link\n variant=\"no-decoration\"\n to={`/extras/authors/${authorData.slug}`}\n className=\"sg:group sg:flex sg:items-start sg:gap-4 sg:rounded-lg sg:border sg:bg-card sg:p-5 sg:hover:shadow-md sg:hover:border-primary/40 sg:transition-all\"\n >\n <Avatar className=\"sg:h-14 sg:w-14 sg:mt-0.5\">\n <AvatarImage\n src={authorData.avatar}\n alt={authorData.name}\n />\n <AvatarFallback>\n {authorData.name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")}\n </AvatarFallback>\n </Avatar>\n <div className=\"sg:min-w-0\">\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:uppercase sg:tracking-wider sg:mb-1\"\n >\n Written by\n </TextSpan>\n <Text\n size=\"sm\"\n fontweight=\"semibold\"\n className=\"sg:font-display sg:group-hover:text-primary sg:transition-colors\"\n >\n {authorData.name}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-0.5\"\n >\n {authorData.role} · {authorData.location}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-2 sg:line-clamp-2\"\n >\n {authorData.bio}\n </Text>\n </div>\n </Link>\n </div>\n );\n })()}\n\n {/* Prev / Next navigation */}\n <nav className=\"sg:mt-12 sg:border-t sg:pt-8 sg:grid sg:grid-cols-2 sg:gap-4\">\n {prevPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${prevPost.id}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Previous post: ${prevPost.title}`}\n title={`Previous post: ${prevPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n <ArrowLeft className=\"h-3 w-3\" /> Previous\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1 sg:text-start\"\n >\n {prevPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n {nextPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${nextPost.id}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Next post: ${nextPost.title}`}\n title={`Next post: ${nextPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n Next <ArrowRight className=\"h-3 w-3\" />\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1\"\n >\n {nextPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n </nav>\n\n {/* Comments */}\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Comments ({comments.length})\n </Heading>\n\n <div className=\"sg:space-y-4 sg:mb-8\">\n {comments.map((c) => (\n <div key={c.id} className=\"sg:rounded-lg sg:bg-accent sg:p-4\">\n <div className=\"sg:flex sg:items-center sg:gap-2 sg:mb-1\">\n <TextSpan fontweight=\"medium\" size=\"sm\">\n {c.author}\n </TextSpan>\n <TextSpan\n fontweight=\"medium\"\n size=\"xs\"\n foreground=\"muted-foreground\"\n >\n {c.date}\n </TextSpan>\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {c.text}\n </Text>\n </div>\n ))}\n </div>\n\n <form onSubmit={handleSubmit} className=\"sg:space-y-4\">\n <Heading variant=\"h4\">Leave a comment</Heading>\n <Input\n placeholder=\"Your name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n aria-label=\"Your name\"\n />\n <Textarea\n placeholder=\"Write your comment...\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n required\n aria-label=\"Your comment\"\n />\n <Button type=\"submit\">Post comment</Button>\n </form>\n </section>\n\n {/* Related Posts */}\n {relatedPosts.length > 0 && (\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Related Posts\n </Heading>\n <div className=\"sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3\">\n {relatedPosts.map((p) => (\n <BlogPostCard\n key={p.id}\n id={post.id}\n image={post.primaryImage}\n categories={post.categories}\n date={post.date}\n title={post.title}\n excerpt={post.excerpt}\n />\n ))}\n </div>\n </section>\n )}\n </Layout.Col1>\n </Layout>\n </>\n );\n}\n\nexport { BlogPost };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/blogpost/blogpost.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo, useState } from \"react\";\nimport DOMPurify from \"isomorphic-dompurify\";\nimport { mockComments, posts, type Comment } from \"../../../data/posts\";\nimport {\n Button,\n Layout,\n Link,\n TextTime,\n UiImage,\n Badges,\n Icon,\n Textarea,\n Input,\n Heading,\n Text,\n TextSpan,\n} from \"../../primitives/index\";\nimport { ArrowLeft, ArrowRight } from \"lucide-react\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\nimport CategoryBadge from \"../../blocks/badges/category-badge\";\nimport ImageGallery from \"../../blocks/gallery/image-gallery\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\nimport { authors } from \"../../../data/authors\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"../../primitives/avatar/avatar\";\nimport BlogPostCard from \"../../blocks/cards/blogpost-card\";\n\nfunction getReadTime(text: string): number {\n const words = text.trim().split(/\\s+/).length;\n return Math.max(1, Math.ceil(words / 200));\n}\n\ninterface BlogPostProps {\n slug?: string;\n}\n\nfunction BlogPost({ slug: propSlug = \"the-art-of-slow-living\" }: BlogPostProps) {\n const post = posts.find((p) => p.slug === propSlug);\n const postIndex = posts.findIndex((p) => p.slug === propSlug);\n const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;\n const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;\n\n const isLoggedIn = true;\n const { toast } = useToast();\n\n const [comments, setComments] = useState<Comment[]>(mockComments);\n const [name, setName] = useState(\"\");\n const [text, setText] = useState(\"\");\n\n const [hasLiked, setHasLiked] = useState(false);\n const [likes, setLikes] = useState(0);\n\n const [isGenerating, setIsGenerating] = useState(false);\n const [isValidate, setIsValidate] = useState(false);\n\n const readTime = useMemo(\n () => (post ? getReadTime(post.content) : 0),\n [post],\n );\n\n const relatedPosts = useMemo(() => {\n if (!post) return [];\n return posts\n .filter(\n (p) =>\n p.id !== post.id &&\n p.categories.some((c) => post.categories.includes(c)),\n )\n .slice(0, 3);\n }, [post]);\n\n if (!post) {\n return (\n <Layout>\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"BookOpen\"\n title=\"Post not found\"\n description=\"This post may have been removed or the link is incorrect.\"\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n const handleLike = () => {\n if (!isLoggedIn) return;\n if (hasLiked) {\n setLikes((l) => l - 1);\n setHasLiked(false);\n } else {\n setLikes((l) => l + 1);\n setHasLiked(true);\n }\n };\n\n const handleGenerateImage = () => {\n setIsGenerating(true);\n setTimeout(() => {\n setIsGenerating(false);\n }, 1000);\n toast.message(\"Generating image…\", {\n description: \"A new AI image is being created for this post.\",\n dismissible: true,\n icon: <Icon icon=\"ImagePlus\" />,\n });\n };\n\n const handleValidate = () => {\n setIsValidate(true);\n setTimeout(() => {\n setIsValidate(false);\n }, 1000);\n toast.message(\"Post validated\", {\n description: \"Content has been reviewed.\",\n icon: <Icon icon=\"Check\" />,\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim() || !text.trim()) return;\n setComments((prev) => [\n ...prev,\n {\n id: Date.now().toString(),\n author: name,\n text,\n date: new Date().toISOString().slice(0, 10),\n },\n ]);\n setName(\"\");\n setText(\"\");\n };\n\n // Simple markdown-ish rendering for content\n const renderBody = (body: string) => {\n return body.split(\"\\n\\n\").map((block, i) => {\n if (block.startsWith(\"## \")) {\n return (\n <Heading variant=\"h2\" key={i} className=\"sg:mt-8 sg:mb-4\">\n {block.replace(\"## \", \"\")}\n </Heading>\n );\n }\n if (block.startsWith(\"1. \") || block.startsWith(\"- \")) {\n const items = block\n .split(\"\\n\")\n .map((line) => line.replace(/^(\\d+\\.\\s|-\\s)/, \"\"));\n const isOrdered = block.startsWith(\"1.\");\n const Tag = isOrdered ? \"ol\" : \"ul\";\n\n return (\n <Tag\n key={i}\n className={`sg:my-4 sg:ml-6 sg:space-y-2 ${isOrdered ? \"sg:list-decimal\" : \"sg:list-disc\"}`}\n >\n {items.map((item, j) => (\n <li\n key={j}\n className=\"sg:text-muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n item.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n ))}\n </Tag>\n );\n }\n\n return (\n <Text\n key={i}\n className=\"sg:my-4\"\n foreground=\"muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n block.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"sg:text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n );\n });\n };\n\n // Resolve author from post data\n const authorData = authors.find((a) => a.name === post?.author) ?? authors[0];\n\n return (\n <>\n <Layout type=\"container\">\n <Layout.Col1 className=\"sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden\">\n <UiImage\n src={post.primaryImage || post.originalImage || \"\"}\n alt={post.title}\n className=\"sg:w-full sg:h-full sg:object-cover\"\n />\n </Layout.Col1>\n </Layout>\n <Layout type=\"col\" className=\"sg:py-12\" as=\"article\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:flex sg:flex-col sg:gap-6 sg:items-start\">\n <Link to=\"/posts\" iconStart=\"ArrowLeft\">\n Back to all posts\n </Link>\n\n <TextTime size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author} · {readTime} min read\n </TextTime>\n </div>\n\n <Heading variant=\"h1\" className=\"sg:mt-2\">\n {post.title}\n </Heading>\n\n {post.categories.length > 0 && (\n <Badges className=\"sg:mt-4\">\n {post.categories.map((cat) => (\n <CategoryBadge key={cat} category={cat} clickable />\n ))}\n </Badges>\n )}\n\n <Text\n size=\"lg\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-4 sg:italic\"\n >\n {post.excerpt}\n </Text>\n\n {/* Like + admin actions bar */}\n <div className=\"sg:mt-6 sg:flex sg:items-center sg:gap-3 sg:flex-wrap\">\n <Button\n onClick={handleLike}\n disabled={!isLoggedIn}\n variant=\"outline\"\n size=\"sm\"\n iconStart=\"Heart\"\n iconStartFill={hasLiked}\n title={\n isLoggedIn\n ? hasLiked\n ? \"Unlike\"\n : \"Like this post\"\n : \"Log in to like\"\n }\n >\n {likes}\n </Button>\n\n {isLoggedIn && (\n <>\n <Button\n variant=\"outline\"\n iconStart=\"ImagePlus\"\n size=\"sm\"\n onClick={handleGenerateImage}\n loading={isGenerating}\n >\n Generate image\n </Button>\n <Button\n variant=\"outline\"\n iconStart=\"ShieldCheck\"\n size=\"sm\"\n onClick={handleValidate}\n loading={isValidate}\n >\n Validate\n </Button>\n </>\n )}\n </div>\n\n <div className=\"sg:mt-8 sg:border-t sg:pt-8\">\n {renderBody(post.content || \"\")}\n </div>\n\n {post.gallery && post.gallery.length > 0 && (\n <div className=\"sg:mt-12\">\n <ImageGallery images={post.gallery} />\n </div>\n )}\n\n {/* Secondary image */}\n <div className=\"sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden\">\n <UiImage\n src={(post.originalImage || \"\") + \"&q=80&crop=entropy\"}\n alt={`Visual for ${post.title}`}\n className=\"sg:w-full sg:h-56 sg:md:h-72 sg:object-cover\"\n loading=\"lazy\"\n />\n </div>\n\n {/* Author card */}\n {(() => {\n return (\n <div className=\"sg:mt-12 sg:border-t sg:pt-8\">\n <Link\n variant=\"no-decoration\"\n to={`/extras/authors/${authorData.slug}`}\n className=\"sg:group sg:flex sg:items-start sg:gap-4 sg:rounded-lg sg:border sg:bg-card sg:p-5 sg:hover:shadow-md sg:hover:border-primary/40 sg:transition-all\"\n >\n <Avatar className=\"sg:h-14 sg:w-14 sg:mt-0.5\">\n <AvatarImage\n src={authorData.avatar}\n alt={authorData.name}\n />\n <AvatarFallback>\n {authorData.name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")}\n </AvatarFallback>\n </Avatar>\n <div className=\"sg:min-w-0\">\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:uppercase sg:tracking-wider sg:mb-1\"\n >\n Written by\n </TextSpan>\n <Text\n size=\"sm\"\n fontweight=\"semibold\"\n className=\"sg:font-display sg:group-hover:text-primary sg:transition-colors\"\n >\n {authorData.name}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-0.5\"\n >\n {authorData.role} · {authorData.location}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-2 sg:line-clamp-2\"\n >\n {authorData.bio}\n </Text>\n </div>\n </Link>\n </div>\n );\n })()}\n\n {/* Prev / Next navigation */}\n <nav className=\"sg:mt-12 sg:border-t sg:pt-8 sg:grid sg:grid-cols-2 sg:gap-4\">\n {prevPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${prevPost.slug}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Previous post: ${prevPost.title}`}\n title={`Previous post: ${prevPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n <ArrowLeft className=\"h-3 w-3\" /> Previous\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1 sg:text-start\"\n >\n {prevPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n {nextPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${nextPost.slug}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Next post: ${nextPost.title}`}\n title={`Next post: ${nextPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n Next <ArrowRight className=\"h-3 w-3\" />\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1\"\n >\n {nextPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n </nav>\n\n {/* Comments */}\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Comments ({comments.length})\n </Heading>\n\n <div className=\"sg:space-y-4 sg:mb-8\">\n {comments.length === 0 ? (\n <EmptyState\n icon=\"MessageSquare\"\n title=\"No comments yet\"\n description=\"Be the first to share your thoughts on this post.\"\n />\n ) : (\n comments.map((c) => (\n <div key={c.id} className=\"sg:rounded-lg sg:bg-accent sg:p-4\">\n <div className=\"sg:flex sg:items-center sg:gap-2 sg:mb-1\">\n <TextSpan fontweight=\"medium\" size=\"sm\">\n {c.author}\n </TextSpan>\n <TextSpan\n fontweight=\"medium\"\n size=\"xs\"\n foreground=\"muted-foreground\"\n >\n {c.date}\n </TextSpan>\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {c.text}\n </Text>\n </div>\n ))\n )}\n </div>\n\n <form onSubmit={handleSubmit} className=\"sg:space-y-4\">\n <Heading variant=\"h4\">Leave a comment</Heading>\n <Input\n placeholder=\"Your name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n aria-label=\"Your name\"\n />\n <Textarea\n placeholder=\"Write your comment...\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n required\n aria-label=\"Your comment\"\n />\n <Button type=\"submit\">Post comment</Button>\n </form>\n </section>\n\n {/* Related Posts */}\n {relatedPosts.length > 0 && (\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Related Posts\n </Heading>\n <div className=\"sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3\">\n {relatedPosts.map((p) => (\n <BlogPostCard\n key={p.id}\n id={p.id}\n slug={p.slug}\n image={p.primaryImage}\n categories={p.categories}\n date={p.date}\n title={p.title}\n excerpt={p.excerpt}\n clickableCategories\n />\n ))}\n </div>\n </section>\n )}\n </Layout.Col1>\n </Layout>\n </>\n );\n}\n\nexport { BlogPost };\n"],"mappings":";AA+EU,SA4LI,UA5LJ,KA8IE,YA9IF;AA9EV,SAAS,SAAS,gBAAgB;AAClC,OAAO,eAAe;AACtB,SAAS,cAAc,aAA2B;AAClD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW,kBAAkB;AACtC,OAAO,gBAAgB;AACvB,OAAO,mBAAmB;AAC1B,OAAO,kBAAkB;AACzB,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,kBAAkB;AAEzB,SAAS,YAAY,MAAsB;AACzC,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE;AACvC,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,GAAG,CAAC;AAC3C;AAMA,SAAS,SAAS,EAAE,MAAM,WAAW,yBAAyB,GAAkB;AAC9E,QAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAClD,QAAM,YAAY,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC5D,QAAM,WAAW,YAAY,IAAI,MAAM,YAAY,CAAC,IAAI;AACxD,QAAM,WAAW,YAAY,MAAM,SAAS,IAAI,MAAM,YAAY,CAAC,IAAI;AAEvE,QAAM,aAAa;AACnB,QAAM,EAAE,MAAM,IAAI,SAAS;AAE3B,QAAM,CAAC,UAAU,WAAW,IAAI,SAAoB,YAAY;AAChE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AAEnC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,CAAC;AAEpC,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,QAAM,WAAW;AAAA,IACf,MAAO,OAAO,YAAY,KAAK,OAAO,IAAI;AAAA,IAC1C,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,eAAe,QAAQ,MAAM;AACjC,QAAI,CAAC,KAAM,QAAO,CAAC;AACnB,WAAO,MACJ;AAAA,MACC,CAAC,MACC,EAAE,OAAO,KAAK,MACd,EAAE,WAAW,KAAK,CAAC,MAAM,KAAK,WAAW,SAAS,CAAC,CAAC;AAAA,IACxD,EACC,MAAM,GAAG,CAAC;AAAA,EACf,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,CAAC,MAAM;AACT,WACE,oBAAC,UACC,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,aAAY;AAAA,QACZ,UAAS;AAAA,QACT,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,EAEJ;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,CAAC,WAAY;AACjB,QAAI,UAAU;AACZ,eAAS,CAAC,MAAM,IAAI,CAAC;AACrB,kBAAY,KAAK;AAAA,IACnB,OAAO;AACL,eAAS,CAAC,MAAM,IAAI,CAAC;AACrB,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,sBAAsB,MAAM;AAChC,oBAAgB,IAAI;AACpB,eAAW,MAAM;AACf,sBAAgB,KAAK;AAAA,IACvB,GAAG,GAAI;AACP,UAAM,QAAQ,0BAAqB;AAAA,MACjC,aAAa;AAAA,MACb,aAAa;AAAA,MACb,MAAM,oBAAC,QAAK,MAAK,aAAY;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB,MAAM;AAC3B,kBAAc,IAAI;AAClB,eAAW,MAAM;AACf,oBAAc,KAAK;AAAA,IACrB,GAAG,GAAI;AACP,UAAM,QAAQ,kBAAkB;AAAA,MAC9B,aAAa;AAAA,MACb,MAAM,oBAAC,QAAK,MAAK,SAAQ;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,CAAC,MAAuB;AAC3C,MAAE,eAAe;AACjB,QAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,EAAG;AAClC,gBAAY,CAAC,SAAS;AAAA,MACpB,GAAG;AAAA,MACH;AAAA,QACE,IAAI,KAAK,IAAI,EAAE,SAAS;AAAA,QACxB,QAAQ;AAAA,QACR;AAAA,QACA,OAAM,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,MAC5C;AAAA,IACF,CAAC;AACD,YAAQ,EAAE;AACV,YAAQ,EAAE;AAAA,EACZ;AAGA,QAAM,aAAa,CAAC,SAAiB;AACnC,WAAO,KAAK,MAAM,MAAM,EAAE,IAAI,CAAC,OAAO,MAAM;AAC1C,UAAI,MAAM,WAAW,KAAK,GAAG;AAC3B,eACE,oBAAC,WAAQ,SAAQ,MAAa,WAAU,mBACrC,gBAAM,QAAQ,OAAO,EAAE,KADC,CAE3B;AAAA,MAEJ;AACA,UAAI,MAAM,WAAW,KAAK,KAAK,MAAM,WAAW,IAAI,GAAG;AACrD,cAAM,QAAQ,MACX,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,kBAAkB,EAAE,CAAC;AACnD,cAAM,YAAY,MAAM,WAAW,IAAI;AACvC,cAAM,MAAM,YAAY,OAAO;AAE/B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,gCAAgC,YAAY,oBAAoB,cAAc;AAAA,YAExF,gBAAM,IAAI,CAAC,MAAM,MAChB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAU;AAAA,gBACV,yBAAyB;AAAA,kBACvB,QAAQ,UAAU;AAAA,oBAChB,KAAK;AAAA,sBACH;AAAA,sBACA;AAAA,oBACF;AAAA,kBACF;AAAA,gBACF;AAAA;AAAA,cATK;AAAA,YAUP,CACD;AAAA;AAAA,UAhBI;AAAA,QAiBP;AAAA,MAEJ;AAEA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UACV,YAAW;AAAA,UACX,yBAAyB;AAAA,YACvB,QAAQ,UAAU;AAAA,cAChB,MAAM;AAAA,gBACJ;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA;AAAA,QAVK;AAAA,MAWP;AAAA,IAEJ,CAAC;AAAA,EACH;AAGA,QAAM,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,MAAM,KAAK,QAAQ,CAAC;AAE5E,SACE,iCACE;AAAA,wBAAC,UAAO,MAAK,aACX,8BAAC,OAAO,MAAP,EAAY,WAAU,mDACrB;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,KAAK,gBAAgB,KAAK,iBAAiB;AAAA,QAChD,KAAK,KAAK;AAAA,QACV,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,IACA,oBAAC,UAAO,MAAK,OAAM,WAAU,YAAW,IAAG,WACzC,+BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,2BAAC,SAAI,WAAU,+CACb;AAAA,4BAAC,QAAK,IAAG,UAAS,WAAU,aAAY,+BAExC;AAAA,QAEA,qBAAC,YAAS,MAAK,MAAK,YAAW,oBAC5B;AAAA,eAAK;AAAA,UAAK;AAAA,UAAI,KAAK;AAAA,UAAO;AAAA,UAAI;AAAA,UAAS;AAAA,WAC1C;AAAA,SACF;AAAA,MAEA,oBAAC,WAAQ,SAAQ,MAAK,WAAU,WAC7B,eAAK,OACR;AAAA,MAEC,KAAK,WAAW,SAAS,KACxB,oBAAC,UAAO,WAAU,WACf,eAAK,WAAW,IAAI,CAAC,QACpB,oBAAC,iBAAwB,UAAU,KAAK,WAAS,QAA7B,GAA8B,CACnD,GACH;AAAA,MAGF;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,YAAW;AAAA,UACX,WAAU;AAAA,UAET,eAAK;AAAA;AAAA,MACR;AAAA,MAGA,qBAAC,SAAI,WAAU,yDACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,UAAU,CAAC;AAAA,YACX,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,eAAe;AAAA,YACf,OACE,aACI,WACE,WACA,mBACF;AAAA,YAGL;AAAA;AAAA,QACH;AAAA,QAEC,cACC,iCACE;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,MAAK;AAAA,cACL,SAAS;AAAA,cACT,SAAS;AAAA,cACV;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,MAAK;AAAA,cACL,SAAS;AAAA,cACT,SAAS;AAAA,cACV;AAAA;AAAA,UAED;AAAA,WACF;AAAA,SAEJ;AAAA,MAEA,oBAAC,SAAI,WAAU,+BACZ,qBAAW,KAAK,WAAW,EAAE,GAChC;AAAA,MAEC,KAAK,WAAW,KAAK,QAAQ,SAAS,KACrC,oBAAC,SAAI,WAAU,YACb,8BAAC,gBAAa,QAAQ,KAAK,SAAS,GACtC;AAAA,MAIF,oBAAC,SAAI,WAAU,kEACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK,iBAAiB,MAAM;AAAA,UAClC,KAAK,cAAc,KAAK,KAAK;AAAA,UAC7B,WAAU;AAAA,UACV,SAAQ;AAAA;AAAA,MACV,GACF;AAAA,OAGE,MAAM;AACN,eACE,oBAAC,SAAI,WAAU,gCACb;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,IAAI,mBAAmB,WAAW,IAAI;AAAA,YACtC,WAAU;AAAA,YAEV;AAAA,mCAAC,UAAO,WAAU,6BAChB;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,KAAK,WAAW;AAAA,oBAChB,KAAK,WAAW;AAAA;AAAA,gBAClB;AAAA,gBACA,oBAAC,kBACE,qBAAW,KACT,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,KAAK,EAAE,GACZ;AAAA,iBACF;AAAA,cACA,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,YAAW;AAAA,oBACX,WAAU;AAAA,oBACX;AAAA;AAAA,gBAED;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,YAAW;AAAA,oBACX,WAAU;AAAA,oBAET,qBAAW;AAAA;AAAA,gBACd;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,YAAW;AAAA,oBACX,WAAU;AAAA,oBAET;AAAA,iCAAW;AAAA,sBAAK;AAAA,sBAAI,WAAW;AAAA;AAAA;AAAA,gBAClC;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,YAAW;AAAA,oBACX,WAAU;AAAA,oBAET,qBAAW;AAAA;AAAA,gBACd;AAAA,iBACF;AAAA;AAAA;AAAA,QACF,GACF;AAAA,MAEJ,GAAG;AAAA,MAGH,qBAAC,SAAI,WAAU,gEACZ;AAAA,mBACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,IAAI,UAAU,SAAS,IAAI;AAAA,YAC3B,WAAU;AAAA,YACV,cAAY,kBAAkB,SAAS,KAAK;AAAA,YAC5C,OAAO,kBAAkB,SAAS,KAAK;AAAA,YAEvC;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,YAAW;AAAA,kBACX,WAAU;AAAA,kBAEV;AAAA,wCAAC,aAAU,WAAU,WAAU;AAAA,oBAAE;AAAA;AAAA;AAAA,cACnC;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,YAAW;AAAA,kBACX,WAAU;AAAA,kBAET,mBAAS;AAAA;AAAA,cACZ;AAAA;AAAA;AAAA,QACF,IAEA,oBAAC,SAAI;AAAA,QAEN,WACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,IAAI,UAAU,SAAS,IAAI;AAAA,YAC3B,WAAU;AAAA,YACV,cAAY,cAAc,SAAS,KAAK;AAAA,YACxC,OAAO,cAAc,SAAS,KAAK;AAAA,YAEnC;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,YAAW;AAAA,kBACX,WAAU;AAAA,kBACX;AAAA;AAAA,oBACM,oBAAC,cAAW,WAAU,WAAU;AAAA;AAAA;AAAA,cACvC;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,YAAW;AAAA,kBACX,WAAU;AAAA,kBAET,mBAAS;AAAA;AAAA,cACZ;AAAA;AAAA;AAAA,QACF,IAEA,oBAAC,SAAI;AAAA,SAET;AAAA,MAGA,qBAAC,aAAQ,WAAU,gCACjB;AAAA,6BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU;AAAA;AAAA,UAC7B,SAAS;AAAA,UAAO;AAAA,WAC7B;AAAA,QAEA,oBAAC,SAAI,WAAU,wBACZ,mBAAS,WAAW,IACnB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAM;AAAA,YACN,aAAY;AAAA;AAAA,QACd,IAEA,SAAS,IAAI,CAAC,MACZ,qBAAC,SAAe,WAAU,qCACxB;AAAA,+BAAC,SAAI,WAAU,4CACb;AAAA,gCAAC,YAAS,YAAW,UAAS,MAAK,MAChC,YAAE,QACL;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,YAAW;AAAA,gBACX,MAAK;AAAA,gBACL,YAAW;AAAA,gBAEV,YAAE;AAAA;AAAA,YACL;AAAA,aACF;AAAA,UACA,oBAAC,QAAK,MAAK,MAAK,YAAW,oBACxB,YAAE,MACL;AAAA,aAfQ,EAAE,EAgBZ,CACD,GAEL;AAAA,QAEA,qBAAC,UAAK,UAAU,cAAc,WAAU,gBACtC;AAAA,8BAAC,WAAQ,SAAQ,MAAK,6BAAe;AAAA,UACrC;AAAA,YAAC;AAAA;AAAA,cACC,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,cACvC,UAAQ;AAAA,cACR,cAAW;AAAA;AAAA,UACb;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,cACvC,UAAQ;AAAA,cACR,cAAW;AAAA;AAAA,UACb;AAAA,UACA,oBAAC,UAAO,MAAK,UAAS,0BAAY;AAAA,WACpC;AAAA,SACF;AAAA,MAGC,aAAa,SAAS,KACrB,qBAAC,aAAQ,WAAU,gCACjB;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,2BAE1C;AAAA,QACA,oBAAC,SAAI,WAAU,qDACZ,uBAAa,IAAI,CAAC,MACjB;AAAA,UAAC;AAAA;AAAA,YAEC,IAAI,EAAE;AAAA,YACN,MAAM,EAAE;AAAA,YACR,OAAO,EAAE;AAAA,YACT,YAAY,EAAE;AAAA,YACd,MAAM,EAAE;AAAA,YACR,OAAO,EAAE;AAAA,YACT,SAAS,EAAE;AAAA,YACX,qBAAmB;AAAA;AAAA,UARd,EAAE;AAAA,QAST,CACD,GACH;AAAA,SACF;AAAA,OAEJ,GACF;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function CategoriesPage(): React.JSX.Element;
4
+
5
+ export { CategoriesPage };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { motion } from "framer-motion";
5
+ import { Layout, Heading, Text } from "../../primitives/index.js";
6
+ import { CategoryCard } from "../../blocks/directory/category-card.js";
7
+ import { posts } from "../../../data/posts.js";
8
+ function CategoriesPage() {
9
+ const categories = useMemo(() => {
10
+ const map = /* @__PURE__ */ new Map();
11
+ posts.forEach(
12
+ (p) => p.categories.forEach((c) => map.set(c, (map.get(c) || 0) + 1))
13
+ );
14
+ return Array.from(map.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count }));
15
+ }, []);
16
+ return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, className: "sg:max-w-3xl", children: /* @__PURE__ */ jsxs(
17
+ motion.div,
18
+ {
19
+ initial: { opacity: 0, y: 20 },
20
+ animate: { opacity: 1, y: 0 },
21
+ transition: { duration: 0.3 },
22
+ children: [
23
+ /* @__PURE__ */ jsx(Heading, { variant: "h1", className: "sg:mb-2", children: "Categories" }),
24
+ /* @__PURE__ */ jsx(Text, { foreground: "muted-foreground", className: "sg:mb-10", children: "Browse posts by topic." }),
25
+ /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-4 sm:sg:grid-cols-2", children: categories.map((cat) => /* @__PURE__ */ jsx(CategoryCard, { name: cat.name, count: cat.count }, cat.name)) })
26
+ ]
27
+ }
28
+ ) }) });
29
+ }
30
+ export {
31
+ CategoriesPage
32
+ };
33
+ //# sourceMappingURL=categories-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/pages/categories/categories-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useMemo } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport { Layout, Heading, Text } from \"../../primitives/index\";\r\nimport { CategoryCard } from \"../../blocks/directory/category-card\";\r\nimport { posts } from \"../../../data/posts\";\r\n\r\nexport function CategoriesPage() {\r\n\tconst categories = useMemo(() => {\r\n\t\tconst map = new Map<string, number>();\r\n\t\tposts.forEach((p) =>\r\n\t\t\tp.categories.forEach((c) => map.set(c, (map.get(c) || 0) + 1)),\r\n\t\t);\r\n\t\treturn Array.from(map.entries())\r\n\t\t\t.sort((a, b) => a[0].localeCompare(b[0]))\r\n\t\t\t.map(([name, count]) => ({ name, count }));\r\n\t}, []);\r\n\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-3xl\">\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Heading variant=\"h1\" className=\"sg:mb-2\">\r\n\t\t\t\t\t\tCategories\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<Text foreground=\"muted-foreground\" className=\"sg:mb-10\">\r\n\t\t\t\t\t\tBrowse posts by topic.\r\n\t\t\t\t\t</Text>\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-4 sm:sg:grid-cols-2\">\r\n\t\t\t\t\t\t{categories.map((cat) => (\r\n\t\t\t\t\t\t\t<CategoryCard key={cat.name} name={cat.name} count={cat.count} />\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAsBI,SAKC,KALD;AApBJ,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AAEf,SAAS,iBAAiB;AAChC,QAAM,aAAa,QAAQ,MAAM;AAChC,UAAM,MAAM,oBAAI,IAAoB;AACpC,UAAM;AAAA,MAAQ,CAAC,MACd,EAAE,WAAW,QAAQ,CAAC,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;AAAA,IAC9D;AACA,WAAO,MAAM,KAAK,IAAI,QAAQ,CAAC,EAC7B,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EACvC,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,gBAC9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACA,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,wBAE1C;AAAA,QACA,oBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW,oCAEzD;AAAA,QACA,oBAAC,SAAI,WAAU,sCACb,qBAAW,IAAI,CAAC,QAChB,oBAAC,gBAA4B,MAAM,IAAI,MAAM,OAAO,IAAI,SAArC,IAAI,IAAwC,CAC/D,GACF;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
@@ -49,7 +49,9 @@ function CategoryPage({ category: propCategory }) {
49
49
  /* @__PURE__ */ jsxs(Text, { foreground: "muted-foreground", className: "sg:mb-12", children: [
50
50
  "Explore ",
51
51
  categoryPosts.length,
52
- " story in this category."
52
+ " ",
53
+ categoryPosts.length === 1 ? "story" : "stories",
54
+ " in this category."
53
55
  ] }),
54
56
  /* @__PURE__ */ jsx(
55
57
  PostListWithFilters,