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.
- package/dist/components/blocks/badges/category-badge.d.ts +11 -0
- package/dist/components/blocks/badges/category-badge.js +34 -0
- package/dist/components/blocks/badges/category-badge.js.map +1 -0
- package/dist/components/blocks/cards/blogpost-card.d.ts +4 -2
- package/dist/components/blocks/cards/blogpost-card.js +9 -4
- package/dist/components/blocks/cards/blogpost-card.js.map +1 -1
- package/dist/components/blocks/cards/card.d.ts +7 -8
- package/dist/components/blocks/directory/author-card.d.ts +10 -0
- package/dist/components/blocks/directory/author-card.js +50 -0
- package/dist/components/blocks/directory/author-card.js.map +1 -0
- package/dist/components/blocks/directory/category-card.d.ts +10 -0
- package/dist/components/blocks/directory/category-card.js +26 -0
- package/dist/components/blocks/directory/category-card.js.map +1 -0
- package/dist/components/blocks/empty-state/EmptyState.d.ts +2 -2
- package/dist/components/blocks/extras/extras-hub-card.d.ts +16 -0
- package/dist/components/blocks/extras/extras-hub-card.js +21 -0
- package/dist/components/blocks/extras/extras-hub-card.js.map +1 -0
- package/dist/components/blocks/gallery/image-gallery.d.ts +14 -0
- package/dist/components/blocks/gallery/image-gallery.js +211 -0
- package/dist/components/blocks/gallery/image-gallery.js.map +1 -0
- package/dist/components/blocks/index.d.ts +12 -1
- package/dist/components/blocks/index.js +11 -0
- package/dist/components/blocks/index.js.map +1 -1
- package/dist/components/blocks/loading/loading-skeletons.d.ts +15 -0
- package/dist/components/blocks/loading/loading-skeletons.js +78 -0
- package/dist/components/blocks/loading/loading-skeletons.js.map +1 -0
- package/dist/components/blocks/login/login.d.ts +78 -0
- package/dist/components/blocks/login/login.js +95 -0
- package/dist/components/blocks/login/login.js.map +1 -0
- package/dist/components/blocks/marketing/page-hero.d.ts +13 -0
- package/dist/components/blocks/marketing/page-hero.js +37 -0
- package/dist/components/blocks/marketing/page-hero.js.map +1 -0
- package/dist/components/blocks/marketing/stats-grid.d.ts +16 -0
- package/dist/components/blocks/marketing/stats-grid.js +30 -0
- package/dist/components/blocks/marketing/stats-grid.js.map +1 -0
- package/dist/components/blocks/marketing/timeline.d.ts +17 -0
- package/dist/components/blocks/marketing/timeline.js +45 -0
- package/dist/components/blocks/marketing/timeline.js.map +1 -0
- package/dist/components/blocks/marketing/values-grid.d.ts +16 -0
- package/dist/components/blocks/marketing/values-grid.js +29 -0
- package/dist/components/blocks/marketing/values-grid.js.map +1 -0
- package/dist/components/blocks/post-list/post-filters.d.ts +2 -2
- package/dist/components/blocks/post-list/post-list-with-filters.d.ts +2 -2
- package/dist/components/index.d.ts +30 -3
- package/dist/components/pages/about/about-page.d.ts +5 -0
- package/dist/components/pages/about/about-page.js +161 -0
- package/dist/components/pages/about/about-page.js.map +1 -0
- package/dist/components/pages/admin/admin-page.d.ts +2 -2
- package/dist/components/pages/admin/admin-page.js +4 -1
- package/dist/components/pages/admin/admin-page.js.map +1 -1
- package/dist/components/pages/author/author-page.d.ts +8 -0
- package/dist/components/pages/author/author-page.js +107 -0
- package/dist/components/pages/author/author-page.js.map +1 -0
- package/dist/components/pages/authors/authors-page.d.ts +5 -0
- package/dist/components/pages/authors/authors-page.js +25 -0
- package/dist/components/pages/authors/authors-page.js.map +1 -0
- package/dist/components/pages/blogpost/blogpost.d.ts +5 -2
- package/dist/components/pages/blogpost/blogpost.js +38 -18
- package/dist/components/pages/blogpost/blogpost.js.map +1 -1
- package/dist/components/pages/categories/categories-page.d.ts +5 -0
- package/dist/components/pages/categories/categories-page.js +33 -0
- package/dist/components/pages/categories/categories-page.js.map +1 -0
- package/dist/components/pages/category/category-page.d.ts +2 -2
- package/dist/components/pages/category/category-page.js +3 -1
- package/dist/components/pages/category/category-page.js.map +1 -1
- package/dist/components/pages/chat/chat-page.d.ts +2 -2
- package/dist/components/pages/contact/contact-page.d.ts +5 -0
- package/dist/components/pages/contact/contact-page.js +173 -0
- package/dist/components/pages/contact/contact-page.js.map +1 -0
- package/dist/components/pages/content-blocks/content-blocks-page.d.ts +5 -0
- package/dist/components/pages/content-blocks/content-blocks-page.js +86 -0
- package/dist/components/pages/content-blocks/content-blocks-page.js.map +1 -0
- package/dist/components/pages/extras/extras-hub-page.d.ts +10 -0
- package/dist/components/pages/extras/extras-hub-page.js +110 -0
- package/dist/components/pages/extras/extras-hub-page.js.map +1 -0
- package/dist/components/pages/index.d.ts +15 -1
- package/dist/components/pages/index.js +12 -0
- package/dist/components/pages/index.js.map +1 -1
- package/dist/components/pages/login/login-page.d.ts +2 -2
- package/dist/components/pages/login/login-page.js +19 -69
- package/dist/components/pages/login/login-page.js.map +1 -1
- package/dist/components/pages/maintenance/maintenance-page.d.ts +2 -2
- package/dist/components/pages/membership/membership-page.d.ts +5 -0
- package/dist/components/pages/membership/membership-page.js +131 -0
- package/dist/components/pages/membership/membership-page.js.map +1 -0
- package/dist/components/pages/mosaic/mosaic-page.d.ts +5 -0
- package/dist/components/pages/mosaic/mosaic-page.js +81 -0
- package/dist/components/pages/mosaic/mosaic-page.js.map +1 -0
- package/dist/components/pages/newsletter/newsletter-page.d.ts +5 -0
- package/dist/components/pages/newsletter/newsletter-page.js +148 -0
- package/dist/components/pages/newsletter/newsletter-page.js.map +1 -0
- package/dist/components/pages/not-found/not-found.d.ts +2 -2
- package/dist/components/pages/privacy/privacy-page.d.ts +2 -2
- package/dist/components/pages/resources/resources-page.d.ts +5 -0
- package/dist/components/pages/resources/resources-page.js +24 -0
- package/dist/components/pages/resources/resources-page.js.map +1 -0
- package/dist/components/pages/search/search-page.d.ts +2 -2
- package/dist/components/pages/startpage/startpage.d.ts +2 -2
- package/dist/components/pages/startpage/startpage.js +6 -4
- package/dist/components/pages/startpage/startpage.js.map +1 -1
- package/dist/components/pages/terms/terms-page.d.ts +2 -2
- package/dist/components/primitives/accordion/accordion.d.ts +5 -5
- package/dist/components/primitives/accordion/accordion.js +14 -16
- package/dist/components/primitives/accordion/accordion.js.map +1 -1
- package/dist/components/primitives/alert/alert.d.ts +4 -5
- package/dist/components/primitives/avatar/avatar.d.ts +6 -7
- package/dist/components/primitives/badge/badge.js +1 -1
- package/dist/components/primitives/badge/badge.js.map +1 -1
- package/dist/components/primitives/badge/badges.d.ts +1 -2
- package/dist/components/primitives/buttons/button.d.ts +4 -4
- package/dist/components/primitives/buttons/icon-button.d.ts +3 -3
- package/dist/components/primitives/buttons/link-button.d.ts +2 -3
- package/dist/components/primitives/collapsible/collapsible.d.ts +3 -4
- package/dist/components/primitives/collapsible/collapsible.js +4 -1
- package/dist/components/primitives/collapsible/collapsible.js.map +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu.d.ts +1 -2
- package/dist/components/primitives/dropdown-menu/dropdown-menu.js +6 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/primitives/forms/checkbox.d.ts +2 -2
- package/dist/components/primitives/forms/checkbox.js +1 -1
- package/dist/components/primitives/forms/checkbox.js.map +1 -1
- package/dist/components/primitives/forms/field.d.ts +14 -13
- package/dist/components/primitives/forms/field.js +4 -2
- package/dist/components/primitives/forms/field.js.map +1 -1
- package/dist/components/primitives/forms/form-control.d.ts +28 -0
- package/dist/components/primitives/forms/form-control.js +40 -0
- package/dist/components/primitives/forms/form-control.js.map +1 -0
- package/dist/components/primitives/forms/form.d.ts +12 -0
- package/dist/components/primitives/forms/form.js +30 -0
- package/dist/components/primitives/forms/form.js.map +1 -0
- package/dist/components/primitives/forms/input.d.ts +1 -2
- package/dist/components/primitives/forms/select.d.ts +7 -8
- package/dist/components/primitives/forms/select.js +12 -12
- package/dist/components/primitives/forms/select.js.map +1 -1
- package/dist/components/primitives/forms/textarea.d.ts +1 -2
- package/dist/components/primitives/icon/icon.d.ts +4 -4
- package/dist/components/primitives/icon/icon.js +2 -1
- package/dist/components/primitives/icon/icon.js.map +1 -1
- package/dist/components/primitives/index.d.ts +6 -3
- package/dist/components/primitives/index.js +3 -0
- package/dist/components/primitives/index.js.map +1 -1
- package/dist/components/primitives/label/label.d.ts +1 -2
- package/dist/components/primitives/layout/layout.d.ts +5 -4
- package/dist/components/primitives/layout/layout.js.map +1 -1
- package/dist/components/primitives/link/link.d.ts +4 -5
- package/dist/components/primitives/separator/separator.d.ts +2 -2
- package/dist/components/primitives/sheet/sheet.d.ts +2 -3
- package/dist/components/primitives/sheet/sheet.js +1 -1
- package/dist/components/primitives/sheet/sheet.js.map +1 -1
- package/dist/components/primitives/skeleton/skeleton.d.ts +2 -2
- package/dist/components/primitives/sonner/sonner.d.ts +2 -2
- package/dist/components/primitives/spinner/spinner.d.ts +2 -2
- package/dist/components/primitives/stack/stack.d.ts +47 -3
- package/dist/components/primitives/stack/stack.js +44 -42
- package/dist/components/primitives/stack/stack.js.map +1 -1
- package/dist/components/primitives/text/heading.d.ts +2 -3
- package/dist/components/primitives/text/internal/text-element.d.ts +9 -4
- package/dist/components/primitives/text/internal/text-element.js +3 -0
- package/dist/components/primitives/text/internal/text-element.js.map +1 -1
- package/dist/components/primitives/text/text-code.d.ts +3 -3
- package/dist/components/primitives/text/text-div.d.ts +2 -3
- package/dist/components/primitives/text/text-span.d.ts +2 -3
- package/dist/components/primitives/text/text-time.d.ts +2 -3
- package/dist/components/primitives/text/text.d.ts +2 -3
- package/dist/components/primitives/ui-image/ui-image.d.ts +2 -3
- package/dist/components/primitives/ui-link/ui-link.d.ts +2 -3
- package/dist/components/providers/SingularityContext.d.ts +0 -1
- package/dist/components/providers/auth-provider.d.ts +2 -2
- package/dist/components/providers/index.d.ts +0 -1
- package/dist/components/providers/theme-provider.d.ts +1 -2
- package/dist/components/templates/container/container.d.ts +4 -5
- package/dist/components/templates/footer/footer.d.ts +2 -2
- package/dist/components/templates/form/form.d.ts +2 -2
- package/dist/components/templates/hero/hero.d.ts +2 -2
- package/dist/components/templates/index.d.ts +1 -1
- package/dist/components/templates/index.js +1 -0
- package/dist/components/templates/index.js.map +1 -1
- package/dist/components/templates/loading-screen/loading-screen.d.ts +10 -0
- package/dist/components/templates/loading-screen/loading-screen.js +39 -0
- package/dist/components/templates/loading-screen/loading-screen.js.map +1 -0
- package/dist/components/templates/navigation/header.d.ts +1 -2
- package/dist/components/templates/navigation/index.d.ts +0 -1
- package/dist/css/variables.css +2 -0
- package/dist/css/variables.css.map +1 -1
- package/dist/data/posts.d.ts +5 -0
- package/dist/data/posts.js +37 -4
- package/dist/data/posts.js.map +1 -1
- package/dist/index.d.ts +30 -3
- package/dist/lib/forms/field-props.d.ts +60 -0
- package/dist/lib/forms/field-props.js +60 -0
- package/dist/lib/forms/field-props.js.map +1 -0
- package/dist/lib/forms/index.d.ts +11 -0
- package/dist/lib/forms/index.js +3 -0
- package/dist/lib/forms/index.js.map +1 -0
- package/dist/lib/forms/tanstack-field.d.ts +56 -0
- package/dist/lib/forms/tanstack-field.js +114 -0
- package/dist/lib/forms/tanstack-field.js.map +1 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/main.css +442 -117
- package/dist/main.css.map +1 -1
- 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
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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,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
|
|
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):
|
|
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
|
-
"
|
|
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,
|
|
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":[]}
|
|
@@ -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":[]}
|