singularity-components 0.1.194 → 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 (203) 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 +4 -2
  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/cards/card.d.ts +7 -8
  8. package/dist/components/blocks/directory/author-card.d.ts +10 -0
  9. package/dist/components/blocks/directory/author-card.js +50 -0
  10. package/dist/components/blocks/directory/author-card.js.map +1 -0
  11. package/dist/components/blocks/directory/category-card.d.ts +10 -0
  12. package/dist/components/blocks/directory/category-card.js +26 -0
  13. package/dist/components/blocks/directory/category-card.js.map +1 -0
  14. package/dist/components/blocks/empty-state/EmptyState.d.ts +2 -2
  15. package/dist/components/blocks/extras/extras-hub-card.d.ts +16 -0
  16. package/dist/components/blocks/extras/extras-hub-card.js +21 -0
  17. package/dist/components/blocks/extras/extras-hub-card.js.map +1 -0
  18. package/dist/components/blocks/gallery/image-gallery.d.ts +14 -0
  19. package/dist/components/blocks/gallery/image-gallery.js +211 -0
  20. package/dist/components/blocks/gallery/image-gallery.js.map +1 -0
  21. package/dist/components/blocks/index.d.ts +12 -1
  22. package/dist/components/blocks/index.js +11 -0
  23. package/dist/components/blocks/index.js.map +1 -1
  24. package/dist/components/blocks/loading/loading-skeletons.d.ts +15 -0
  25. package/dist/components/blocks/loading/loading-skeletons.js +78 -0
  26. package/dist/components/blocks/loading/loading-skeletons.js.map +1 -0
  27. package/dist/components/blocks/login/login.d.ts +78 -0
  28. package/dist/components/blocks/login/login.js +95 -0
  29. package/dist/components/blocks/login/login.js.map +1 -0
  30. package/dist/components/blocks/marketing/page-hero.d.ts +13 -0
  31. package/dist/components/blocks/marketing/page-hero.js +37 -0
  32. package/dist/components/blocks/marketing/page-hero.js.map +1 -0
  33. package/dist/components/blocks/marketing/stats-grid.d.ts +16 -0
  34. package/dist/components/blocks/marketing/stats-grid.js +30 -0
  35. package/dist/components/blocks/marketing/stats-grid.js.map +1 -0
  36. package/dist/components/blocks/marketing/timeline.d.ts +17 -0
  37. package/dist/components/blocks/marketing/timeline.js +45 -0
  38. package/dist/components/blocks/marketing/timeline.js.map +1 -0
  39. package/dist/components/blocks/marketing/values-grid.d.ts +16 -0
  40. package/dist/components/blocks/marketing/values-grid.js +29 -0
  41. package/dist/components/blocks/marketing/values-grid.js.map +1 -0
  42. package/dist/components/blocks/post-list/post-filters.d.ts +2 -2
  43. package/dist/components/blocks/post-list/post-list-with-filters.d.ts +2 -2
  44. package/dist/components/index.d.ts +30 -3
  45. package/dist/components/pages/about/about-page.d.ts +5 -0
  46. package/dist/components/pages/about/about-page.js +161 -0
  47. package/dist/components/pages/about/about-page.js.map +1 -0
  48. package/dist/components/pages/admin/admin-page.d.ts +2 -2
  49. package/dist/components/pages/admin/admin-page.js +4 -1
  50. package/dist/components/pages/admin/admin-page.js.map +1 -1
  51. package/dist/components/pages/author/author-page.d.ts +8 -0
  52. package/dist/components/pages/author/author-page.js +107 -0
  53. package/dist/components/pages/author/author-page.js.map +1 -0
  54. package/dist/components/pages/authors/authors-page.d.ts +5 -0
  55. package/dist/components/pages/authors/authors-page.js +25 -0
  56. package/dist/components/pages/authors/authors-page.js.map +1 -0
  57. package/dist/components/pages/blogpost/blogpost.d.ts +5 -2
  58. package/dist/components/pages/blogpost/blogpost.js +38 -18
  59. package/dist/components/pages/blogpost/blogpost.js.map +1 -1
  60. package/dist/components/pages/categories/categories-page.d.ts +5 -0
  61. package/dist/components/pages/categories/categories-page.js +33 -0
  62. package/dist/components/pages/categories/categories-page.js.map +1 -0
  63. package/dist/components/pages/category/category-page.d.ts +2 -2
  64. package/dist/components/pages/category/category-page.js +3 -1
  65. package/dist/components/pages/category/category-page.js.map +1 -1
  66. package/dist/components/pages/chat/chat-page.d.ts +2 -2
  67. package/dist/components/pages/contact/contact-page.d.ts +5 -0
  68. package/dist/components/pages/contact/contact-page.js +173 -0
  69. package/dist/components/pages/contact/contact-page.js.map +1 -0
  70. package/dist/components/pages/content-blocks/content-blocks-page.d.ts +5 -0
  71. package/dist/components/pages/content-blocks/content-blocks-page.js +86 -0
  72. package/dist/components/pages/content-blocks/content-blocks-page.js.map +1 -0
  73. package/dist/components/pages/extras/extras-hub-page.d.ts +10 -0
  74. package/dist/components/pages/extras/extras-hub-page.js +110 -0
  75. package/dist/components/pages/extras/extras-hub-page.js.map +1 -0
  76. package/dist/components/pages/index.d.ts +15 -1
  77. package/dist/components/pages/index.js +12 -0
  78. package/dist/components/pages/index.js.map +1 -1
  79. package/dist/components/pages/login/login-page.d.ts +2 -2
  80. package/dist/components/pages/login/login-page.js +19 -69
  81. package/dist/components/pages/login/login-page.js.map +1 -1
  82. package/dist/components/pages/maintenance/maintenance-page.d.ts +2 -2
  83. package/dist/components/pages/membership/membership-page.d.ts +5 -0
  84. package/dist/components/pages/membership/membership-page.js +131 -0
  85. package/dist/components/pages/membership/membership-page.js.map +1 -0
  86. package/dist/components/pages/mosaic/mosaic-page.d.ts +5 -0
  87. package/dist/components/pages/mosaic/mosaic-page.js +81 -0
  88. package/dist/components/pages/mosaic/mosaic-page.js.map +1 -0
  89. package/dist/components/pages/newsletter/newsletter-page.d.ts +5 -0
  90. package/dist/components/pages/newsletter/newsletter-page.js +148 -0
  91. package/dist/components/pages/newsletter/newsletter-page.js.map +1 -0
  92. package/dist/components/pages/not-found/not-found.d.ts +2 -2
  93. package/dist/components/pages/privacy/privacy-page.d.ts +2 -2
  94. package/dist/components/pages/resources/resources-page.d.ts +5 -0
  95. package/dist/components/pages/resources/resources-page.js +24 -0
  96. package/dist/components/pages/resources/resources-page.js.map +1 -0
  97. package/dist/components/pages/search/search-page.d.ts +2 -2
  98. package/dist/components/pages/startpage/startpage.d.ts +2 -2
  99. package/dist/components/pages/startpage/startpage.js +6 -4
  100. package/dist/components/pages/startpage/startpage.js.map +1 -1
  101. package/dist/components/pages/terms/terms-page.d.ts +2 -2
  102. package/dist/components/primitives/accordion/accordion.d.ts +5 -5
  103. package/dist/components/primitives/accordion/accordion.js +14 -16
  104. package/dist/components/primitives/accordion/accordion.js.map +1 -1
  105. package/dist/components/primitives/alert/alert.d.ts +4 -5
  106. package/dist/components/primitives/avatar/avatar.d.ts +6 -7
  107. package/dist/components/primitives/badge/badge.js +1 -1
  108. package/dist/components/primitives/badge/badge.js.map +1 -1
  109. package/dist/components/primitives/badge/badges.d.ts +1 -2
  110. package/dist/components/primitives/buttons/button.d.ts +4 -4
  111. package/dist/components/primitives/buttons/icon-button.d.ts +3 -3
  112. package/dist/components/primitives/buttons/link-button.d.ts +2 -3
  113. package/dist/components/primitives/collapsible/collapsible.d.ts +3 -4
  114. package/dist/components/primitives/collapsible/collapsible.js +4 -1
  115. package/dist/components/primitives/collapsible/collapsible.js.map +1 -1
  116. package/dist/components/primitives/dropdown-menu/dropdown-menu.d.ts +1 -2
  117. package/dist/components/primitives/dropdown-menu/dropdown-menu.js +6 -1
  118. package/dist/components/primitives/dropdown-menu/dropdown-menu.js.map +1 -1
  119. package/dist/components/primitives/forms/checkbox.d.ts +2 -2
  120. package/dist/components/primitives/forms/checkbox.js +1 -1
  121. package/dist/components/primitives/forms/checkbox.js.map +1 -1
  122. package/dist/components/primitives/forms/field.d.ts +14 -13
  123. package/dist/components/primitives/forms/field.js +4 -2
  124. package/dist/components/primitives/forms/field.js.map +1 -1
  125. package/dist/components/primitives/forms/form-control.d.ts +28 -0
  126. package/dist/components/primitives/forms/form-control.js +40 -0
  127. package/dist/components/primitives/forms/form-control.js.map +1 -0
  128. package/dist/components/primitives/forms/form.d.ts +12 -0
  129. package/dist/components/primitives/forms/form.js +30 -0
  130. package/dist/components/primitives/forms/form.js.map +1 -0
  131. package/dist/components/primitives/forms/input.d.ts +1 -2
  132. package/dist/components/primitives/forms/select.d.ts +7 -8
  133. package/dist/components/primitives/forms/select.js +12 -12
  134. package/dist/components/primitives/forms/select.js.map +1 -1
  135. package/dist/components/primitives/forms/textarea.d.ts +1 -2
  136. package/dist/components/primitives/icon/icon.d.ts +4 -4
  137. package/dist/components/primitives/icon/icon.js +2 -1
  138. package/dist/components/primitives/icon/icon.js.map +1 -1
  139. package/dist/components/primitives/index.d.ts +6 -3
  140. package/dist/components/primitives/index.js +3 -0
  141. package/dist/components/primitives/index.js.map +1 -1
  142. package/dist/components/primitives/label/label.d.ts +1 -2
  143. package/dist/components/primitives/layout/layout.d.ts +5 -4
  144. package/dist/components/primitives/layout/layout.js.map +1 -1
  145. package/dist/components/primitives/link/link.d.ts +4 -5
  146. package/dist/components/primitives/separator/separator.d.ts +2 -2
  147. package/dist/components/primitives/sheet/sheet.d.ts +2 -3
  148. package/dist/components/primitives/sheet/sheet.js +1 -1
  149. package/dist/components/primitives/sheet/sheet.js.map +1 -1
  150. package/dist/components/primitives/skeleton/skeleton.d.ts +2 -2
  151. package/dist/components/primitives/sonner/sonner.d.ts +2 -2
  152. package/dist/components/primitives/spinner/spinner.d.ts +2 -2
  153. package/dist/components/primitives/stack/stack.d.ts +47 -3
  154. package/dist/components/primitives/stack/stack.js +44 -42
  155. package/dist/components/primitives/stack/stack.js.map +1 -1
  156. package/dist/components/primitives/text/heading.d.ts +2 -3
  157. package/dist/components/primitives/text/internal/text-element.d.ts +9 -4
  158. package/dist/components/primitives/text/internal/text-element.js +3 -0
  159. package/dist/components/primitives/text/internal/text-element.js.map +1 -1
  160. package/dist/components/primitives/text/text-code.d.ts +3 -3
  161. package/dist/components/primitives/text/text-div.d.ts +2 -3
  162. package/dist/components/primitives/text/text-span.d.ts +2 -3
  163. package/dist/components/primitives/text/text-time.d.ts +2 -3
  164. package/dist/components/primitives/text/text.d.ts +2 -3
  165. package/dist/components/primitives/ui-image/ui-image.d.ts +2 -3
  166. package/dist/components/primitives/ui-link/ui-link.d.ts +2 -3
  167. package/dist/components/providers/SingularityContext.d.ts +0 -1
  168. package/dist/components/providers/auth-provider.d.ts +2 -2
  169. package/dist/components/providers/index.d.ts +0 -1
  170. package/dist/components/providers/theme-provider.d.ts +1 -2
  171. package/dist/components/templates/container/container.d.ts +4 -5
  172. package/dist/components/templates/footer/footer.d.ts +2 -2
  173. package/dist/components/templates/form/form.d.ts +2 -2
  174. package/dist/components/templates/hero/hero.d.ts +2 -2
  175. package/dist/components/templates/index.d.ts +1 -1
  176. package/dist/components/templates/index.js +1 -0
  177. package/dist/components/templates/index.js.map +1 -1
  178. package/dist/components/templates/loading-screen/loading-screen.d.ts +10 -0
  179. package/dist/components/templates/loading-screen/loading-screen.js +39 -0
  180. package/dist/components/templates/loading-screen/loading-screen.js.map +1 -0
  181. package/dist/components/templates/navigation/header.d.ts +1 -2
  182. package/dist/components/templates/navigation/index.d.ts +0 -1
  183. package/dist/css/variables.css +2 -0
  184. package/dist/css/variables.css.map +1 -1
  185. package/dist/data/posts.d.ts +5 -0
  186. package/dist/data/posts.js +37 -4
  187. package/dist/data/posts.js.map +1 -1
  188. package/dist/index.d.ts +30 -3
  189. package/dist/lib/forms/field-props.d.ts +60 -0
  190. package/dist/lib/forms/field-props.js +60 -0
  191. package/dist/lib/forms/field-props.js.map +1 -0
  192. package/dist/lib/forms/index.d.ts +11 -0
  193. package/dist/lib/forms/index.js +3 -0
  194. package/dist/lib/forms/index.js.map +1 -0
  195. package/dist/lib/forms/tanstack-field.d.ts +56 -0
  196. package/dist/lib/forms/tanstack-field.js +114 -0
  197. package/dist/lib/forms/tanstack-field.js.map +1 -0
  198. package/dist/lib/index.d.ts +11 -0
  199. package/dist/lib/index.js +1 -0
  200. package/dist/lib/index.js.map +1 -1
  201. package/dist/main.css +442 -117
  202. package/dist/main.css.map +1 -1
  203. package/package.json +64 -39
@@ -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":"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","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":[]}
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as React from 'react';
2
2
 
3
3
  interface Props {
4
4
  category?: string;
5
5
  }
6
- declare function CategoryPage({ category: propCategory }: Props): react_jsx_runtime.JSX.Element;
6
+ declare function CategoryPage({ category: propCategory }: Props): React.JSX.Element;
7
7
 
8
8
  export { CategoryPage };
@@ -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,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/category/category-page.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Layout, Heading, Text } from \"../../primitives/index\";\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\nimport { posts } from \"../../../data/posts\";\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\n\ninterface Props {\n category?: string;\n}\n\nexport function CategoryPage({ category: propCategory }: Props) {\n // In a real app, this would come from useParams()\n // For the library/storybook, we allow passing it as a prop\n const category = propCategory || \"Design\";\n const decodedCategory = decodeURIComponent(category);\n\n const categoryPosts = useMemo(\n () => posts.filter((p) => p.categories.includes(decodedCategory)),\n [decodedCategory],\n );\n\n if (!decodedCategory || categoryPosts.length === 0) {\n return (\n <Layout type=\"col\" className=\"sg:py-16\">\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"Tag\"\n title=\"Category not found\"\n description={\n decodedCategory\n ? `No posts found in \"${decodedCategory}\". It may have been removed or renamed.`\n : \"This category doesn't exist.\"\n }\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"sg:min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.3 }}\n >\n <LinkButton\n variant=\"ghost\"\n size=\"sm\"\n to=\"/posts\"\n iconStart=\"ArrowLeft\"\n className=\"sg:mb-6\"\n >\n All Posts\n </LinkButton>\n\n <Heading variant=\"h1\" className=\"sg:mb-2 sg:capitalize\">\n {decodedCategory}\n </Heading>\n <Text foreground=\"muted-foreground\" className=\"sg:mb-12\">\n Explore {categoryPosts.length} story in this category.\n </Text>\n\n <PostListWithFilters\n posts={categoryPosts}\n hideSearch\n filterMode=\"drawer\"\n />\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":";AA4BU,cAsCA,YAtCA;AA3BV,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,2BAA2B;AACpC,OAAO,gBAAgB;AAMhB,SAAS,aAAa,EAAE,UAAU,aAAa,GAAU;AAG9D,QAAM,WAAW,gBAAgB;AACjC,QAAM,kBAAkB,mBAAmB,QAAQ;AAEnD,QAAM,gBAAgB;AAAA,IACpB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,eAAe,CAAC;AAAA,IAChE,CAAC,eAAe;AAAA,EAClB;AAEA,MAAI,CAAC,mBAAmB,cAAc,WAAW,GAAG;AAClD,WACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC3B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aACE,kBACI,sBAAsB,eAAe,4CACrC;AAAA,QAEN,aAAY;AAAA,QACZ,UAAS;AAAA,QACT,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAAW,IAAG,QACzC,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,IAAG;AAAA,YACH,WAAU;AAAA,YACV,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QAEA,oBAAC,WAAQ,SAAQ,MAAK,WAAU,yBAC7B,2BACH;AAAA,QACA,qBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW;AAAA;AAAA,UAC9C,cAAc;AAAA,UAAO;AAAA,WAChC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,YAAU;AAAA,YACV,YAAW;AAAA;AAAA,QACb;AAAA;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/category/category-page.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Layout, Heading, Text } from \"../../primitives/index\";\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\nimport { posts } from \"../../../data/posts\";\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\n\ninterface Props {\n category?: string;\n}\n\nexport function CategoryPage({ category: propCategory }: Props) {\n // In a real app, this would come from useParams()\n // For the library/storybook, we allow passing it as a prop\n const category = propCategory || \"Design\";\n const decodedCategory = decodeURIComponent(category);\n\n const categoryPosts = useMemo(\n () => posts.filter((p) => p.categories.includes(decodedCategory)),\n [decodedCategory],\n );\n\n if (!decodedCategory || categoryPosts.length === 0) {\n return (\n <Layout type=\"col\" className=\"sg:py-16\">\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"Tag\"\n title=\"Category not found\"\n description={\n decodedCategory\n ? `No posts found in \"${decodedCategory}\". It may have been removed or renamed.`\n : \"This category doesn't exist.\"\n }\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"sg:min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.3 }}\n >\n <LinkButton\n variant=\"ghost\"\n size=\"sm\"\n to=\"/posts\"\n iconStart=\"ArrowLeft\"\n className=\"sg:mb-6\"\n >\n All Posts\n </LinkButton>\n\n <Heading variant=\"h1\" className=\"sg:mb-2 sg:capitalize\">\n {decodedCategory}\n </Heading>\n <Text foreground=\"muted-foreground\" className=\"sg:mb-12\">\n Explore {categoryPosts.length}{\" \"}\n {categoryPosts.length === 1 ? \"story\" : \"stories\"} in this category.\n </Text>\n\n <PostListWithFilters\n posts={categoryPosts}\n hideSearch\n filterMode=\"drawer\"\n />\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":";AA4BU,cAsCA,YAtCA;AA3BV,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,2BAA2B;AACpC,OAAO,gBAAgB;AAMhB,SAAS,aAAa,EAAE,UAAU,aAAa,GAAU;AAG9D,QAAM,WAAW,gBAAgB;AACjC,QAAM,kBAAkB,mBAAmB,QAAQ;AAEnD,QAAM,gBAAgB;AAAA,IACpB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,eAAe,CAAC;AAAA,IAChE,CAAC,eAAe;AAAA,EAClB;AAEA,MAAI,CAAC,mBAAmB,cAAc,WAAW,GAAG;AAClD,WACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC3B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aACE,kBACI,sBAAsB,eAAe,4CACrC;AAAA,QAEN,aAAY;AAAA,QACZ,UAAS;AAAA,QACT,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAAW,IAAG,QACzC,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,IAAG;AAAA,YACH,WAAU;AAAA,YACV,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QAEA,oBAAC,WAAQ,SAAQ,MAAK,WAAU,yBAC7B,2BACH;AAAA,QACA,qBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW;AAAA;AAAA,UAC9C,cAAc;AAAA,UAAQ;AAAA,UAC9B,cAAc,WAAW,IAAI,UAAU;AAAA,UAAU;AAAA,WACpD;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,YAAU;AAAA,YACV,YAAW;AAAA;AAAA,QACb;AAAA;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
@@ -1,5 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as React from 'react';
2
2
 
3
- declare function ChatPage(): react_jsx_runtime.JSX.Element;
3
+ declare function ChatPage(): React.JSX.Element;
4
4
 
5
5
  export { ChatPage };
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function ContactPage(): React.JSX.Element;
4
+
5
+ export { ContactPage };
@@ -0,0 +1,173 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { motion } from "framer-motion";
5
+ import {
6
+ Layout,
7
+ Heading,
8
+ Text,
9
+ Input,
10
+ Textarea,
11
+ Button,
12
+ Icon
13
+ } from "../../primitives/index.js";
14
+ import { Label } from "../../primitives/label/label.js";
15
+ import { PageHero } from "../../blocks/marketing/page-hero.js";
16
+ import { Card, CardContent } from "../../blocks/cards/card.js";
17
+ import { useToast } from "../../primitives/sonner/use-toast.js";
18
+ const initialForm = {
19
+ name: "",
20
+ email: "",
21
+ subject: "",
22
+ message: ""
23
+ };
24
+ function validateContact(form) {
25
+ const errors = {};
26
+ if (!form.name.trim()) errors.name = "Name is required";
27
+ if (!form.email.trim() || !form.email.includes("@")) {
28
+ errors.email = "Please enter a valid email";
29
+ }
30
+ if (!form.subject.trim()) errors.subject = "Subject is required";
31
+ if (!form.message.trim()) errors.message = "Message is required";
32
+ return errors;
33
+ }
34
+ function ContactPage() {
35
+ const { toast } = useToast();
36
+ const [form, setForm] = useState(initialForm);
37
+ const [errors, setErrors] = useState({});
38
+ const [sending, setSending] = useState(false);
39
+ const handleChange = (field, value) => {
40
+ setForm((prev) => ({ ...prev, [field]: value }));
41
+ if (errors[field]) setErrors((prev) => ({ ...prev, [field]: void 0 }));
42
+ };
43
+ const handleSubmit = (e) => {
44
+ e.preventDefault();
45
+ const fieldErrors = validateContact(form);
46
+ if (Object.keys(fieldErrors).length > 0) {
47
+ setErrors(fieldErrors);
48
+ return;
49
+ }
50
+ setSending(true);
51
+ setTimeout(() => {
52
+ setSending(false);
53
+ toast.message("Message sent!", {
54
+ description: "Thanks for reaching out. We'll get back to you soon."
55
+ });
56
+ setForm(initialForm);
57
+ setErrors({});
58
+ }, 1200);
59
+ };
60
+ return /* @__PURE__ */ jsxs(
61
+ motion.div,
62
+ {
63
+ initial: { opacity: 0, y: 20 },
64
+ animate: { opacity: 1, y: 0 },
65
+ transition: { duration: 0.3 },
66
+ children: [
67
+ /* @__PURE__ */ jsx(
68
+ PageHero,
69
+ {
70
+ icon: "Mail",
71
+ title: "Get in Touch",
72
+ description: "Have a question, idea, or just want to say hello? We'd love to hear from you."
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, className: "sg:max-w-5xl", children: /* @__PURE__ */ jsxs("div", { className: "sg:grid sg:gap-12 lg:sg:grid-cols-5", children: [
76
+ /* @__PURE__ */ jsxs("div", { className: "lg:sg:col-span-3", children: [
77
+ /* @__PURE__ */ jsx(Heading, { variant: "h3", className: "sg:mb-6", children: "Send a Message" }),
78
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "sg:space-y-5", noValidate: true, children: [
79
+ /* @__PURE__ */ jsxs("div", { className: "sg:grid sg:gap-5 sm:sg:grid-cols-2", children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
81
+ /* @__PURE__ */ jsx(Label, { htmlFor: "contact-name", children: "Name" }),
82
+ /* @__PURE__ */ jsx(
83
+ Input,
84
+ {
85
+ id: "contact-name",
86
+ value: form.name,
87
+ onChange: (e) => handleChange("name", e.target.value),
88
+ placeholder: "Your name",
89
+ "aria-invalid": !!errors.name
90
+ }
91
+ ),
92
+ errors.name && /* @__PURE__ */ jsx(Text, { size: "sm", className: "sg:text-destructive", children: errors.name })
93
+ ] }),
94
+ /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
95
+ /* @__PURE__ */ jsx(Label, { htmlFor: "contact-email", children: "Email" }),
96
+ /* @__PURE__ */ jsx(
97
+ Input,
98
+ {
99
+ id: "contact-email",
100
+ type: "email",
101
+ value: form.email,
102
+ onChange: (e) => handleChange("email", e.target.value),
103
+ placeholder: "you@example.com",
104
+ "aria-invalid": !!errors.email
105
+ }
106
+ ),
107
+ errors.email && /* @__PURE__ */ jsx(Text, { size: "sm", className: "sg:text-destructive", children: errors.email })
108
+ ] })
109
+ ] }),
110
+ /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
111
+ /* @__PURE__ */ jsx(Label, { htmlFor: "contact-subject", children: "Subject" }),
112
+ /* @__PURE__ */ jsx(
113
+ Input,
114
+ {
115
+ id: "contact-subject",
116
+ value: form.subject,
117
+ onChange: (e) => handleChange("subject", e.target.value),
118
+ placeholder: "What's this about?",
119
+ "aria-invalid": !!errors.subject
120
+ }
121
+ ),
122
+ errors.subject && /* @__PURE__ */ jsx(Text, { size: "sm", className: "sg:text-destructive", children: errors.subject })
123
+ ] }),
124
+ /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
125
+ /* @__PURE__ */ jsx(Label, { htmlFor: "contact-message", children: "Message" }),
126
+ /* @__PURE__ */ jsx(
127
+ Textarea,
128
+ {
129
+ id: "contact-message",
130
+ value: form.message,
131
+ onChange: (e) => handleChange("message", e.target.value),
132
+ placeholder: "Your message...",
133
+ rows: 6,
134
+ "aria-invalid": !!errors.message
135
+ }
136
+ ),
137
+ errors.message && /* @__PURE__ */ jsx(Text, { size: "sm", className: "sg:text-destructive", children: errors.message })
138
+ ] }),
139
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: sending, iconStart: "Send", children: "Send message" })
140
+ ] })
141
+ ] }),
142
+ /* @__PURE__ */ jsx("div", { className: "lg:sg:col-span-2 sg:space-y-6", children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "sg:pt-6 sg:space-y-4", children: [
143
+ /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-start sg:gap-3", children: [
144
+ /* @__PURE__ */ jsx(Icon, { icon: "Mail", className: "sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5" }),
145
+ /* @__PURE__ */ jsxs("div", { children: [
146
+ /* @__PURE__ */ jsx(Text, { fontweight: "medium", children: "Email" }),
147
+ /* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: "hello@filion.se" })
148
+ ] })
149
+ ] }),
150
+ /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-start sg:gap-3", children: [
151
+ /* @__PURE__ */ jsx(Icon, { icon: "MapPin", className: "sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5" }),
152
+ /* @__PURE__ */ jsxs("div", { children: [
153
+ /* @__PURE__ */ jsx(Text, { fontweight: "medium", children: "Location" }),
154
+ /* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: "Stockholm, Sweden" })
155
+ ] })
156
+ ] }),
157
+ /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-start sg:gap-3", children: [
158
+ /* @__PURE__ */ jsx(Icon, { icon: "Clock", className: "sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5" }),
159
+ /* @__PURE__ */ jsxs("div", { children: [
160
+ /* @__PURE__ */ jsx(Text, { fontweight: "medium", children: "Hours" }),
161
+ /* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: "Mon\u2013Fri, 9am\u20135pm CET" })
162
+ ] })
163
+ ] })
164
+ ] }) }) })
165
+ ] }) }) })
166
+ ]
167
+ }
168
+ );
169
+ }
170
+ export {
171
+ ContactPage
172
+ };
173
+ //# sourceMappingURL=contact-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/pages/contact/contact-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport {\r\n\tLayout,\r\n\tHeading,\r\n\tText,\r\n\tInput,\r\n\tTextarea,\r\n\tButton,\r\n\tIcon,\r\n} from \"../../primitives/index\";\r\nimport { Label } from \"../../primitives/label/label\";\r\nimport { PageHero } from \"../../blocks/marketing/page-hero\";\r\nimport { Card, CardContent } from \"../../blocks/cards/card\";\r\nimport { useToast } from \"../../primitives/sonner/use-toast\";\r\n\r\ntype FormState = {\r\n\tname: string;\r\n\temail: string;\r\n\tsubject: string;\r\n\tmessage: string;\r\n};\r\n\r\nconst initialForm: FormState = {\r\n\tname: \"\",\r\n\temail: \"\",\r\n\tsubject: \"\",\r\n\tmessage: \"\",\r\n};\r\n\r\n/** Validates a simple contact form without external dependencies. */\r\nfunction validateContact(form: FormState): Partial<Record<keyof FormState, string>> {\r\n\tconst errors: Partial<Record<keyof FormState, string>> = {};\r\n\tif (!form.name.trim()) errors.name = \"Name is required\";\r\n\tif (!form.email.trim() || !form.email.includes(\"@\")) {\r\n\t\terrors.email = \"Please enter a valid email\";\r\n\t}\r\n\tif (!form.subject.trim()) errors.subject = \"Subject is required\";\r\n\tif (!form.message.trim()) errors.message = \"Message is required\";\r\n\treturn errors;\r\n}\r\n\r\nexport function ContactPage() {\r\n\tconst { toast } = useToast();\r\n\tconst [form, setForm] = useState<FormState>(initialForm);\r\n\tconst [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\r\n\tconst [sending, setSending] = useState(false);\r\n\r\n\tconst handleChange = (field: keyof FormState, value: string) => {\r\n\t\tsetForm((prev) => ({ ...prev, [field]: value }));\r\n\t\tif (errors[field]) setErrors((prev) => ({ ...prev, [field]: undefined }));\r\n\t};\r\n\r\n\tconst handleSubmit = (e: React.FormEvent) => {\r\n\t\te.preventDefault();\r\n\t\tconst fieldErrors = validateContact(form);\r\n\t\tif (Object.keys(fieldErrors).length > 0) {\r\n\t\t\tsetErrors(fieldErrors);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tsetSending(true);\r\n\t\tsetTimeout(() => {\r\n\t\t\tsetSending(false);\r\n\t\t\ttoast.message(\"Message sent!\", {\r\n\t\t\t\tdescription: \"Thanks for reaching out. We'll get back to you soon.\",\r\n\t\t\t});\r\n\t\t\tsetForm(initialForm);\r\n\t\t\tsetErrors({});\r\n\t\t}, 1200);\r\n\t};\r\n\r\n\treturn (\r\n\t\t<motion.div\r\n\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t>\r\n\t\t\t<PageHero\r\n\t\t\t\ticon=\"Mail\"\r\n\t\t\t\ttitle=\"Get in Touch\"\r\n\t\t\t\tdescription=\"Have a question, idea, or just want to say hello? We'd love to hear from you.\"\r\n\t\t\t/>\r\n\r\n\t\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-5xl\">\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-12 lg:sg:grid-cols-5\">\r\n\t\t\t\t\t\t<div className=\"lg:sg:col-span-3\">\r\n\t\t\t\t\t\t\t<Heading variant=\"h3\" className=\"sg:mb-6\">\r\n\t\t\t\t\t\t\t\tSend a Message\r\n\t\t\t\t\t\t\t</Heading>\r\n\t\t\t\t\t\t\t<form onSubmit={handleSubmit} className=\"sg:space-y-5\" noValidate>\r\n\t\t\t\t\t\t\t\t<div className=\"sg:grid sg:gap-5 sm:sg:grid-cols-2\">\r\n\t\t\t\t\t\t\t\t\t<div className=\"sg:space-y-2\">\r\n\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"contact-name\">Name</Label>\r\n\t\t\t\t\t\t\t\t\t\t<Input\r\n\t\t\t\t\t\t\t\t\t\t\tid=\"contact-name\"\r\n\t\t\t\t\t\t\t\t\t\t\tvalue={form.name}\r\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => handleChange(\"name\", e.target.value)}\r\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"Your name\"\r\n\t\t\t\t\t\t\t\t\t\t\taria-invalid={!!errors.name}\r\n\t\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t\t{errors.name && (\r\n\t\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" className=\"sg:text-destructive\">\r\n\t\t\t\t\t\t\t\t\t\t\t\t{errors.name}\r\n\t\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div className=\"sg:space-y-2\">\r\n\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"contact-email\">Email</Label>\r\n\t\t\t\t\t\t\t\t\t\t<Input\r\n\t\t\t\t\t\t\t\t\t\t\tid=\"contact-email\"\r\n\t\t\t\t\t\t\t\t\t\t\ttype=\"email\"\r\n\t\t\t\t\t\t\t\t\t\t\tvalue={form.email}\r\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => handleChange(\"email\", e.target.value)}\r\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"you@example.com\"\r\n\t\t\t\t\t\t\t\t\t\t\taria-invalid={!!errors.email}\r\n\t\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t\t{errors.email && (\r\n\t\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" className=\"sg:text-destructive\">\r\n\t\t\t\t\t\t\t\t\t\t\t\t{errors.email}\r\n\t\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t<div className=\"sg:space-y-2\">\r\n\t\t\t\t\t\t\t\t\t<Label htmlFor=\"contact-subject\">Subject</Label>\r\n\t\t\t\t\t\t\t\t\t<Input\r\n\t\t\t\t\t\t\t\t\t\tid=\"contact-subject\"\r\n\t\t\t\t\t\t\t\t\t\tvalue={form.subject}\r\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => handleChange(\"subject\", e.target.value)}\r\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"What's this about?\"\r\n\t\t\t\t\t\t\t\t\t\taria-invalid={!!errors.subject}\r\n\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t{errors.subject && (\r\n\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" className=\"sg:text-destructive\">\r\n\t\t\t\t\t\t\t\t\t\t\t{errors.subject}\r\n\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t<div className=\"sg:space-y-2\">\r\n\t\t\t\t\t\t\t\t\t<Label htmlFor=\"contact-message\">Message</Label>\r\n\t\t\t\t\t\t\t\t\t<Textarea\r\n\t\t\t\t\t\t\t\t\t\tid=\"contact-message\"\r\n\t\t\t\t\t\t\t\t\t\tvalue={form.message}\r\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => handleChange(\"message\", e.target.value)}\r\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"Your message...\"\r\n\t\t\t\t\t\t\t\t\t\trows={6}\r\n\t\t\t\t\t\t\t\t\t\taria-invalid={!!errors.message}\r\n\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t{errors.message && (\r\n\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" className=\"sg:text-destructive\">\r\n\t\t\t\t\t\t\t\t\t\t\t{errors.message}\r\n\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t<Button type=\"submit\" loading={sending} iconStart=\"Send\">\r\n\t\t\t\t\t\t\t\t\tSend message\r\n\t\t\t\t\t\t\t\t</Button>\r\n\t\t\t\t\t\t\t</form>\r\n\t\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t\t<div className=\"lg:sg:col-span-2 sg:space-y-6\">\r\n\t\t\t\t\t\t\t<Card>\r\n\t\t\t\t\t\t\t\t<CardContent className=\"sg:pt-6 sg:space-y-4\">\r\n\t\t\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-start sg:gap-3\">\r\n\t\t\t\t\t\t\t\t\t\t<Icon icon=\"Mail\" className=\"sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5\" />\r\n\t\t\t\t\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text fontweight=\"medium\">Email</Text>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\">\r\n\t\t\t\t\t\t\t\t\t\t\t\thello@filion.se\r\n\t\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-start sg:gap-3\">\r\n\t\t\t\t\t\t\t\t\t\t<Icon icon=\"MapPin\" className=\"sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5\" />\r\n\t\t\t\t\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text fontweight=\"medium\">Location</Text>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\">\r\n\t\t\t\t\t\t\t\t\t\t\t\tStockholm, Sweden\r\n\t\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-start sg:gap-3\">\r\n\t\t\t\t\t\t\t\t\t\t<Icon icon=\"Clock\" className=\"sg:h-5 sg:w-5 sg:text-primary sg:mt-0.5\" />\r\n\t\t\t\t\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text fontweight=\"medium\">Hours</Text>\r\n\t\t\t\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\">\r\n\t\t\t\t\t\t\t\t\t\t\t\tMon–Fri, 9am–5pm CET\r\n\t\t\t\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</CardContent>\r\n\t\t\t\t\t\t\t</Card>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</Layout.Col1>\r\n\t\t\t</Layout>\r\n\t\t</motion.div>\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 ContentBlocksPage(): React.JSX.Element;
4
+
5
+ export { ContentBlocksPage };