singularity-components 0.1.195 β 0.1.197
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 +3 -1
- package/dist/components/blocks/cards/blogpost-card.js +10 -5
- package/dist/components/blocks/cards/blogpost-card.js.map +1 -1
- 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 +27 -0
- package/dist/components/blocks/directory/category-card.js.map +1 -0
- package/dist/components/blocks/extras/extras-hub-card.d.ts +16 -0
- package/dist/components/blocks/extras/extras-hub-card.js +22 -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 +11 -0
- package/dist/components/blocks/index.js +10 -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.js +76 -47
- package/dist/components/blocks/login/login.js.map +1 -1
- 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 +46 -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-list-with-filters.js +4 -4
- package/dist/components/blocks/post-list/post-list-with-filters.js.map +1 -1
- package/dist/components/index.d.ts +28 -1
- 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.js +160 -103
- 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 +4 -1
- package/dist/components/pages/blogpost/blogpost.js +110 -62
- 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.js +4 -2
- package/dist/components/pages/category/category-page.js.map +1 -1
- package/dist/components/pages/chat/chat-page.js +4 -4
- package/dist/components/pages/chat/chat-page.js.map +1 -1
- package/dist/components/pages/contact/contact-page.d.ts +5 -0
- package/dist/components/pages/contact/contact-page.js +180 -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 +87 -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 +14 -0
- package/dist/components/pages/index.js +12 -0
- package/dist/components/pages/index.js.map +1 -1
- package/dist/components/pages/maintenance/maintenance-page.js +1 -1
- package/dist/components/pages/maintenance/maintenance-page.js.map +1 -1
- 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 +165 -0
- package/dist/components/pages/newsletter/newsletter-page.js.map +1 -0
- package/dist/components/pages/not-found/not-found.js +2 -2
- package/dist/components/pages/not-found/not-found.js.map +1 -1
- package/dist/components/pages/privacy/privacy-page.js +2 -2
- package/dist/components/pages/privacy/privacy-page.js.map +1 -1
- 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/startpage/startpage.js +6 -4
- package/dist/components/pages/startpage/startpage.js.map +1 -1
- package/dist/components/pages/terms/terms-page.js +2 -2
- package/dist/components/pages/terms/terms-page.js.map +1 -1
- package/dist/components/primitives/accordion/accordion.js +14 -16
- package/dist/components/primitives/accordion/accordion.js.map +1 -1
- package/dist/components/primitives/badge/badge.js +1 -1
- package/dist/components/primitives/badge/badge.js.map +1 -1
- package/dist/components/primitives/buttons/button.d.ts +2 -2
- package/dist/components/primitives/buttons/icon-button.d.ts +1 -1
- 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.js +6 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu.js.map +1 -1
- 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 +4 -2
- 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/select.js +12 -12
- package/dist/components/primitives/forms/select.js.map +1 -1
- package/dist/components/primitives/icon/icon.d.ts +3 -2
- 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 +4 -0
- package/dist/components/primitives/index.js +3 -0
- package/dist/components/primitives/index.js.map +1 -1
- package/dist/components/primitives/layout/layout.d.ts +1 -1
- package/dist/components/primitives/link/link.d.ts +2 -2
- package/dist/components/primitives/sheet/sheet.js +1 -1
- package/dist/components/primitives/sheet/sheet.js.map +1 -1
- package/dist/components/primitives/stack/stack.d.ts +2 -2
- package/dist/components/primitives/text/internal/text-element.d.ts +8 -2
- 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 +1 -1
- package/dist/components/templates/form/form.d.ts +2 -2
- package/dist/components/templates/form/form.js +133 -87
- package/dist/components/templates/form/form.js.map +1 -1
- package/dist/components/templates/hero/hero.js +1 -0
- package/dist/components/templates/hero/hero.js.map +1 -1
- package/dist/components/templates/index.d.ts +1 -0
- 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/css/variables.css +6 -3
- package/dist/css/variables.css.map +1 -1
- package/dist/data/posts.d.ts +5 -0
- package/dist/data/posts.js +41 -8
- package/dist/data/posts.js.map +1 -1
- package/dist/index.d.ts +28 -1
- 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 +71 -0
- package/dist/lib/forms/tanstack-field.js +121 -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 +393 -90
- package/dist/main.css.map +1 -1
- package/package.json +30 -23
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useForm } from "@tanstack/react-form";
|
|
3
4
|
import { useMemo, useState } from "react";
|
|
4
5
|
import DOMPurify from "isomorphic-dompurify";
|
|
5
6
|
import { mockComments, posts } from "../../../data/posts.js";
|
|
7
|
+
import {
|
|
8
|
+
TanStackInputField,
|
|
9
|
+
TanStackTextareaField
|
|
10
|
+
} from "../../../lib/forms/tanstack-field.js";
|
|
6
11
|
import {
|
|
7
12
|
Button,
|
|
8
13
|
Layout,
|
|
9
14
|
Link,
|
|
10
15
|
TextTime,
|
|
11
16
|
UiImage,
|
|
12
|
-
Badge,
|
|
13
17
|
Badges,
|
|
14
18
|
Icon,
|
|
15
|
-
Textarea,
|
|
16
|
-
Input,
|
|
17
19
|
Heading,
|
|
18
20
|
Text,
|
|
19
21
|
TextSpan
|
|
20
22
|
} from "../../primitives/index.js";
|
|
23
|
+
import { FieldGroup } from "../../primitives/forms/field.js";
|
|
24
|
+
import { Form, FormActions } from "../../primitives/forms/form.js";
|
|
21
25
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
22
26
|
import EmptyState from "../../blocks/empty-state/EmptyState.js";
|
|
27
|
+
import CategoryBadge from "../../blocks/badges/category-badge.js";
|
|
28
|
+
import ImageGallery from "../../blocks/gallery/image-gallery.js";
|
|
23
29
|
import { useToast } from "../../primitives/sonner/use-toast.js";
|
|
24
30
|
import { authors } from "../../../data/authors.js";
|
|
25
31
|
import {
|
|
@@ -28,21 +34,41 @@ import {
|
|
|
28
34
|
AvatarImage
|
|
29
35
|
} from "../../primitives/avatar/avatar.js";
|
|
30
36
|
import BlogPostCard from "../../blocks/cards/blogpost-card.js";
|
|
31
|
-
function
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
function getReadTime(text) {
|
|
38
|
+
const words = text.trim().split(/\s+/).length;
|
|
39
|
+
return Math.max(1, Math.ceil(words / 200));
|
|
40
|
+
}
|
|
41
|
+
function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
|
|
42
|
+
const post = posts.find((p) => p.slug === propSlug);
|
|
43
|
+
const postIndex = posts.findIndex((p) => p.slug === propSlug);
|
|
35
44
|
const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;
|
|
36
45
|
const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;
|
|
37
46
|
const isLoggedIn = true;
|
|
38
47
|
const { toast } = useToast();
|
|
39
48
|
const [comments, setComments] = useState(mockComments);
|
|
40
|
-
const
|
|
41
|
-
|
|
49
|
+
const commentForm = useForm({
|
|
50
|
+
defaultValues: { name: "", text: "" },
|
|
51
|
+
onSubmit: ({ value, formApi }) => {
|
|
52
|
+
setComments((prev) => [
|
|
53
|
+
...prev,
|
|
54
|
+
{
|
|
55
|
+
id: Date.now().toString(),
|
|
56
|
+
author: value.name,
|
|
57
|
+
text: value.text,
|
|
58
|
+
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
59
|
+
}
|
|
60
|
+
]);
|
|
61
|
+
formApi.reset();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
42
64
|
const [hasLiked, setHasLiked] = useState(false);
|
|
43
65
|
const [likes, setLikes] = useState(0);
|
|
44
66
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
45
67
|
const [isValidate, setIsValidate] = useState(false);
|
|
68
|
+
const readTime = useMemo(
|
|
69
|
+
() => post ? getReadTime(post.content) : 0,
|
|
70
|
+
[post]
|
|
71
|
+
);
|
|
46
72
|
const relatedPosts = useMemo(() => {
|
|
47
73
|
if (!post) return [];
|
|
48
74
|
return posts.filter(
|
|
@@ -93,21 +119,6 @@ function BlogPost() {
|
|
|
93
119
|
icon: /* @__PURE__ */ jsx(Icon, { icon: "Check" })
|
|
94
120
|
});
|
|
95
121
|
};
|
|
96
|
-
const handleSubmit = (e) => {
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
if (!name.trim() || !text.trim()) return;
|
|
99
|
-
setComments((prev) => [
|
|
100
|
-
...prev,
|
|
101
|
-
{
|
|
102
|
-
id: Date.now().toString(),
|
|
103
|
-
author: name,
|
|
104
|
-
text,
|
|
105
|
-
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
106
|
-
}
|
|
107
|
-
]);
|
|
108
|
-
setName("");
|
|
109
|
-
setText("");
|
|
110
|
-
};
|
|
111
122
|
const renderBody = (body) => {
|
|
112
123
|
return body.split("\n\n").map((block, i) => {
|
|
113
124
|
if (block.startsWith("## ")) {
|
|
@@ -158,7 +169,7 @@ function BlogPost() {
|
|
|
158
169
|
);
|
|
159
170
|
});
|
|
160
171
|
};
|
|
161
|
-
const
|
|
172
|
+
const authorData = authors.find((a) => a.name === post?.author) ?? authors[0];
|
|
162
173
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
163
174
|
/* @__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
175
|
UiImage,
|
|
@@ -174,11 +185,14 @@ function BlogPost() {
|
|
|
174
185
|
/* @__PURE__ */ jsxs(TextTime, { size: "sm", foreground: "muted-foreground", children: [
|
|
175
186
|
post.date,
|
|
176
187
|
" \xB7 ",
|
|
177
|
-
post.author
|
|
188
|
+
post.author,
|
|
189
|
+
" \xB7 ",
|
|
190
|
+
readTime,
|
|
191
|
+
" min read"
|
|
178
192
|
] })
|
|
179
193
|
] }),
|
|
180
194
|
/* @__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(
|
|
195
|
+
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
196
|
/* @__PURE__ */ jsx(
|
|
183
197
|
Text,
|
|
184
198
|
{
|
|
@@ -228,6 +242,7 @@ function BlogPost() {
|
|
|
228
242
|
] })
|
|
229
243
|
] }),
|
|
230
244
|
/* @__PURE__ */ jsx("div", { className: "sg:mt-8 sg:border-t sg:pt-8", children: renderBody(post.content || "") }),
|
|
245
|
+
post.gallery && post.gallery.length > 0 && /* @__PURE__ */ jsx("div", { className: "sg:mt-12", children: /* @__PURE__ */ jsx(ImageGallery, { images: post.gallery }) }),
|
|
231
246
|
/* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
232
247
|
UiImage,
|
|
233
248
|
{
|
|
@@ -238,7 +253,6 @@ function BlogPost() {
|
|
|
238
253
|
}
|
|
239
254
|
) }),
|
|
240
255
|
(() => {
|
|
241
|
-
const authorData = mockedAuthor;
|
|
242
256
|
return /* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:border-t sg:pt-8", children: /* @__PURE__ */ jsxs(
|
|
243
257
|
Link,
|
|
244
258
|
{
|
|
@@ -307,7 +321,7 @@ function BlogPost() {
|
|
|
307
321
|
Link,
|
|
308
322
|
{
|
|
309
323
|
variant: "no-decoration",
|
|
310
|
-
to: `/posts/${prevPost.
|
|
324
|
+
to: `/posts/${prevPost.slug}`,
|
|
311
325
|
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
326
|
"aria-label": `Previous post: ${prevPost.title}`,
|
|
313
327
|
title: `Previous post: ${prevPost.title}`,
|
|
@@ -340,7 +354,7 @@ function BlogPost() {
|
|
|
340
354
|
Link,
|
|
341
355
|
{
|
|
342
356
|
variant: "no-decoration",
|
|
343
|
-
to: `/posts/${nextPost.
|
|
357
|
+
to: `/posts/${nextPost.slug}`,
|
|
344
358
|
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
359
|
"aria-label": `Next post: ${nextPost.title}`,
|
|
346
360
|
title: `Next post: ${nextPost.title}`,
|
|
@@ -376,7 +390,14 @@ function BlogPost() {
|
|
|
376
390
|
comments.length,
|
|
377
391
|
")"
|
|
378
392
|
] }),
|
|
379
|
-
/* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.
|
|
393
|
+
/* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.length === 0 ? /* @__PURE__ */ jsx(
|
|
394
|
+
EmptyState,
|
|
395
|
+
{
|
|
396
|
+
icon: "MessageSquare",
|
|
397
|
+
title: "No comments yet",
|
|
398
|
+
description: "Be the first to share your thoughts on this post."
|
|
399
|
+
}
|
|
400
|
+
) : comments.map((c) => /* @__PURE__ */ jsxs("div", { className: "sg:rounded-lg sg:bg-accent sg:p-4", children: [
|
|
380
401
|
/* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-center sg:gap-2 sg:mb-1", children: [
|
|
381
402
|
/* @__PURE__ */ jsx(TextSpan, { fontweight: "medium", size: "sm", children: c.author }),
|
|
382
403
|
/* @__PURE__ */ jsx(
|
|
@@ -391,42 +412,69 @@ function BlogPost() {
|
|
|
391
412
|
] }),
|
|
392
413
|
/* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: c.text })
|
|
393
414
|
] }, c.id)) }),
|
|
394
|
-
/* @__PURE__ */ jsxs(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
415
|
+
/* @__PURE__ */ jsxs(
|
|
416
|
+
Form,
|
|
417
|
+
{
|
|
418
|
+
className: "sg:gap-4",
|
|
419
|
+
onSubmit: (event) => {
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
event.stopPropagation();
|
|
422
|
+
void commentForm.handleSubmit();
|
|
423
|
+
},
|
|
424
|
+
children: [
|
|
425
|
+
/* @__PURE__ */ jsx(Heading, { variant: "h4", children: "Leave a comment" }),
|
|
426
|
+
/* @__PURE__ */ jsxs(FieldGroup, { className: "sg:gap-4", children: [
|
|
427
|
+
/* @__PURE__ */ jsx(
|
|
428
|
+
TanStackInputField,
|
|
429
|
+
{
|
|
430
|
+
formApi: commentForm,
|
|
431
|
+
name: "name",
|
|
432
|
+
label: "Your name",
|
|
433
|
+
labelClassName: "sg:sr-only",
|
|
434
|
+
placeholder: "Your name",
|
|
435
|
+
validators: {
|
|
436
|
+
onChange: ({ value }) => value.trim() ? void 0 : "Name is required"
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
/* @__PURE__ */ jsx(
|
|
441
|
+
TanStackTextareaField,
|
|
442
|
+
{
|
|
443
|
+
formApi: commentForm,
|
|
444
|
+
name: "text",
|
|
445
|
+
label: "Your comment",
|
|
446
|
+
labelClassName: "sg:sr-only",
|
|
447
|
+
placeholder: "Write your comment...",
|
|
448
|
+
validators: {
|
|
449
|
+
onChange: ({ value }) => value.trim() ? void 0 : "Comment is required"
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
] }),
|
|
454
|
+
/* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(
|
|
455
|
+
commentForm.Subscribe,
|
|
456
|
+
{
|
|
457
|
+
selector: (state) => [state.canSubmit, state.isSubmitting],
|
|
458
|
+
children: ([canSubmit, isSubmitting]) => /* @__PURE__ */ jsx(Button, { type: "submit", disabled: !canSubmit || isSubmitting, children: "Post comment" })
|
|
459
|
+
}
|
|
460
|
+
) })
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
)
|
|
418
464
|
] }),
|
|
419
465
|
relatedPosts.length > 0 && /* @__PURE__ */ jsxs("section", { className: "sg:mt-16 sg:border-t sg:pt-8", children: [
|
|
420
466
|
/* @__PURE__ */ jsx(Heading, { variant: "h3", className: "sg:mb-6", children: "Related Posts" }),
|
|
421
|
-
/* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3", children: relatedPosts.map((p) => /* @__PURE__ */ jsx(
|
|
467
|
+
/* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:sg:grid-cols-3", children: relatedPosts.map((p) => /* @__PURE__ */ jsx(
|
|
422
468
|
BlogPostCard,
|
|
423
469
|
{
|
|
424
|
-
id:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
470
|
+
id: p.id,
|
|
471
|
+
slug: p.slug,
|
|
472
|
+
image: p.primaryImage,
|
|
473
|
+
categories: p.categories,
|
|
474
|
+
date: p.date,
|
|
475
|
+
title: p.title,
|
|
476
|
+
excerpt: p.excerpt,
|
|
477
|
+
clickableCategories: true
|
|
430
478
|
},
|
|
431
479
|
p.id
|
|
432
480
|
)) })
|
|
@@ -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 { useForm } from \"@tanstack/react-form\";\nimport { useMemo, useState } from \"react\";\nimport DOMPurify from \"isomorphic-dompurify\";\nimport { mockComments, posts, type Comment } from \"../../../data/posts\";\nimport {\n\tTanStackInputField,\n\tTanStackTextareaField,\n} from \"../../../lib/forms/tanstack-field\";\nimport {\n Button,\n Layout,\n Link,\n TextTime,\n UiImage,\n Badges,\n Icon,\n Heading,\n Text,\n TextSpan,\n} from \"../../primitives/index\";\nimport { FieldGroup } from \"../../primitives/forms/field\";\nimport { Form, FormActions } from \"../../primitives/forms/form\";\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\n const commentForm = useForm({\n defaultValues: { name: \"\", text: \"\" },\n onSubmit: ({ value, formApi }) => {\n setComments((prev) => [\n ...prev,\n {\n id: Date.now().toString(),\n author: value.name,\n text: value.text,\n date: new Date().toISOString().slice(0, 10),\n },\n ]);\n formApi.reset();\n },\n });\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 // 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\n className=\"sg:gap-4\"\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void commentForm.handleSubmit();\n }}\n >\n <Heading variant=\"h4\">Leave a comment</Heading>\n <FieldGroup className=\"sg:gap-4\">\n <TanStackInputField\n formApi={commentForm}\n name=\"name\"\n label=\"Your name\"\n labelClassName=\"sg:sr-only\"\n placeholder=\"Your name\"\n validators={{\n onChange: ({ value }: { value: string }) =>\n value.trim() ? undefined : \"Name is required\",\n }}\n />\n <TanStackTextareaField\n formApi={commentForm}\n name=\"text\"\n label=\"Your comment\"\n labelClassName=\"sg:sr-only\"\n placeholder=\"Write your comment...\"\n validators={{\n onChange: ({ value }: { value: string }) =>\n value.trim() ? undefined : \"Comment is required\",\n }}\n />\n </FieldGroup>\n <FormActions>\n <commentForm.Subscribe\n selector={(state) => [state.canSubmit, state.isSubmitting]}\n >\n {([canSubmit, isSubmitting]) => (\n <Button type=\"submit\" disabled={!canSubmit || isSubmitting}>\n Post comment\n </Button>\n )}\n </commentForm.Subscribe>\n </FormActions>\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:sg: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: 1, 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: 1, 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":[]}
|
|
@@ -30,7 +30,7 @@ function CategoryPage({ category: propCategory }) {
|
|
|
30
30
|
return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", as: "main", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, children: /* @__PURE__ */ jsxs(
|
|
31
31
|
motion.div,
|
|
32
32
|
{
|
|
33
|
-
initial: { opacity:
|
|
33
|
+
initial: { opacity: 1, y: 20 },
|
|
34
34
|
animate: { opacity: 1, y: 0 },
|
|
35
35
|
transition: { duration: 0.3 },
|
|
36
36
|
children: [
|
|
@@ -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:
|
|
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: 1, 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":[]}
|
|
@@ -74,9 +74,9 @@ function ChatPage() {
|
|
|
74
74
|
return /* @__PURE__ */ jsx(Layout, { type: "container", bgColor: "background", className: "sg:min-h-screen", children: /* @__PURE__ */ jsx(Layout.Col1, { className: "sg:flex sg:flex-col sg:items-center", children: /* @__PURE__ */ jsxs(
|
|
75
75
|
motion.div,
|
|
76
76
|
{
|
|
77
|
-
initial: { opacity:
|
|
77
|
+
initial: { opacity: 1, y: 20 },
|
|
78
78
|
animate: { opacity: 1, y: 0 },
|
|
79
|
-
exit: { opacity:
|
|
79
|
+
exit: { opacity: 1, y: -20 },
|
|
80
80
|
transition: { duration: 0.3 },
|
|
81
81
|
className: "sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full",
|
|
82
82
|
style: { height: "calc(100vh - 2rem)" },
|
|
@@ -97,7 +97,7 @@ function ChatPage() {
|
|
|
97
97
|
messages.map((msg) => /* @__PURE__ */ jsxs(
|
|
98
98
|
motion.div,
|
|
99
99
|
{
|
|
100
|
-
initial: { opacity:
|
|
100
|
+
initial: { opacity: 1, y: 10 },
|
|
101
101
|
animate: { opacity: 1, y: 0 },
|
|
102
102
|
transition: { duration: 0.2 },
|
|
103
103
|
className: cn(
|
|
@@ -147,7 +147,7 @@ function ChatPage() {
|
|
|
147
147
|
isTyping && /* @__PURE__ */ jsxs(
|
|
148
148
|
motion.div,
|
|
149
149
|
{
|
|
150
|
-
initial: { opacity:
|
|
150
|
+
initial: { opacity: 1, y: 10 },
|
|
151
151
|
animate: { opacity: 1, y: 0 },
|
|
152
152
|
className: "sg:flex sg:gap-3",
|
|
153
153
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/pages/chat/chat-page.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Send, Bot, User, Sparkles } from \"lucide-react\";\nimport { Layout, Button, Input, Heading, Text } from \"../../primitives/index\";\nimport { cn } from \"../../../utils/index\";\n\ninterface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: Date;\n}\n\nconst mockResponses: Record<string, string> = {\n hello:\n \"Hey there! π I'm **Storied Bot**, your friendly AI assistant. I can help you explore blog posts, find resources, or just chat. What's on your mind?\",\n help: \"Sure! Here's what I can help with:\\n\\n- π **Search posts** β Ask me about any topic\\n- π **Recommend reads** β I'll suggest posts based on your interests\\n- π¨ **Design tips** β Questions about typography, color, layout\\n- π‘ **General chat** β I'm happy to talk about anything!\",\n posts:\n \"We have some great posts! Here are a few highlights:\\n\\n1. **The Art of Minimalism** β A deep dive into less-is-more design\\n2. **Color Theory Fundamentals** β Understanding how colors work together\\n3. **Typography Best Practices** β Making your text shine\\n\\nWant me to go deeper on any of these?\",\n design:\n \"Great question about design! Here are some key principles I follow:\\n\\n- **Hierarchy** β Guide the eye with size, weight, and contrast\\n- **Whitespace** β Give elements room to breathe\\n- **Consistency** β Stick to your design system\\n- **Accessibility** β Make sure everyone can use your work\\n\\nWould you like to explore our **Design System** page for live examples?\",\n};\n\nconst defaultResponse =\n \"That's an interesting thought! π€\\n\\nI'm currently running in demo mode, so my responses are limited. In a full implementation, I'd be powered by an AI model and could have much richer conversations.\\n\\nTry asking me about **posts**, **design**, or type **help** to see what I can do!\";\n\nfunction getResponse(input: string): string {\n const lower = input.toLowerCase().trim();\n for (const [key, response] of Object.entries(mockResponses)) {\n if (lower.includes(key)) return response;\n }\n return defaultResponse;\n}\n\nconst initialMessages: Message[] = [\n {\n id: \"welcome\",\n role: \"assistant\",\n content:\n \"Welcome to **Storied Bot**! π€β¨\\n\\nI'm your AI assistant β here to help you explore content, answer questions, and chat. This is a demo showcasing how a conversational interface can look and feel.\\n\\nTry saying **hello**, ask about **posts**, or type **help** to get started!\",\n timestamp: new Date(Date.now() - 60000),\n },\n];\n\nexport function ChatPage() {\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n }, [messages, isTyping]);\n\n const sendMessage = () => {\n const text = input.trim();\n if (!text || isTyping) return;\n\n const userMsg: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n timestamp: new Date(),\n };\n\n setMessages((prev) => [...prev, userMsg]);\n setInput(\"\");\n setIsTyping(true);\n\n // Simulate typing delay\n setTimeout(\n () => {\n const botMsg: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: getResponse(text),\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, botMsg]);\n setIsTyping(false);\n },\n 800 + Math.random() * 1200,\n );\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n };\n\n return (\n <Layout type=\"container\" bgColor=\"background\" className=\"sg:min-h-screen\">\n <Layout.Col1 className=\"sg:flex sg:flex-col sg:items-center\">\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -20 }}\n transition={{ duration: 0.3 }}\n className=\"sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full\"\n style={{ height: \"calc(100vh - 2rem)\" }}\n >\n {/* Header */}\n <div className=\"sg:flex sg:items-center sg:gap-3 sg:mb-4 sg:pb-4 sg:border-b sg:border-border\">\n <div className=\"sg:h-10 sg:w-10 sg:rounded-full sg:bg-primary/10 sg:flex sg:items-center sg:justify-center\">\n <Sparkles className=\"sg:h-5 sg:w-5 sg:text-primary\" />\n </div>\n <div>\n <Heading variant=\"h4\" className=\"sg:font-bold sg:leading-tight\">\n Storied Bot\n </Heading>\n <Text className=\"sg:text-xs sg:text-muted-foreground\">\n AI assistant Β· Demo mode\n </Text>\n </div>\n </div>\n\n {/* Messages area */}\n <div\n ref={scrollRef}\n className=\"chat-scroll sg:flex-1 sg:overflow-y-auto sg:space-y-4 sg:pr-2 sg:min-h-0\"\n >\n {messages.map((msg) => (\n <motion.div\n key={msg.id}\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2 }}\n className={cn(\n \"sg:flex sg:gap-3 sg:max-w-[85%]\",\n msg.role === \"user\" ? \"sg:ml-auto sg:flex-row-reverse\" : \"\",\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"sg:h-8 sg:w-8 sg:rounded-full sg:flex sg:items-center sg:justify-center sg:shrink-0 sg:mt-0.5\",\n msg.role === \"assistant\"\n ? \"sg:bg-primary/10 sg:text-primary\"\n : \"sg:bg-secondary sg:text-secondary-foreground\",\n )}\n >\n {msg.role === \"assistant\" ? (\n <Bot className=\"sg:h-4 sg:w-4\" />\n ) : (\n <User className=\"sg:h-4 sg:w-4\" />\n )}\n </div>\n\n {/* Bubble */}\n <div\n className={cn(\n \"sg:rounded-2xl sg:px-4 sg:py-2.5 sg:text-sm sg:leading-relaxed\",\n msg.role === \"assistant\"\n ? \"sg:bg-muted sg:text-foreground sg:rounded-tl-sm\"\n : \"sg:bg-primary sg:text-primary-foreground sg:rounded-tr-sm\",\n )}\n >\n <MessageContent content={msg.content} />\n <p\n className={cn(\n \"sg:text-[10px] sg:mt-1.5\",\n msg.role === \"assistant\"\n ? \"sg:text-muted-foreground\"\n : \"sg:text-primary-foreground/60\",\n )}\n >\n {msg.timestamp.toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}\n </p>\n </div>\n </motion.div>\n ))}\n\n {/* Typing indicator */}\n {isTyping && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n className=\"sg:flex sg:gap-3\"\n >\n <div className=\"sg:h-8 sg:w-8 sg:rounded-full sg:bg-primary/10 sg:text-primary sg:flex sg:items-center sg:justify-center sg:shrink-0\">\n <Bot className=\"sg:h-4 sg:w-4\" />\n </div>\n <div className=\"sg:bg-muted sg:rounded-2xl sg:rounded-tl-sm sg:px-4 sg:py-3 sg:flex sg:items-center sg:gap-1\">\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:0ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:150ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:300ms]\" />\n </div>\n </motion.div>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"sg:pt-4 sg:mt-4 sg:border-t sg:border-border\">\n <div className=\"sg:flex sg:gap-2\">\n <Input\n ref={inputRef}\n placeholder=\"Type a messageβ¦\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={isTyping}\n className=\"sg:flex-1\"\n />\n <Button\n onClick={sendMessage}\n disabled={!input.trim() || isTyping}\n aria-label=\"Send message\"\n >\n <Send className=\"sg:h-4 sg:w-4\" />\n </Button>\n </div>\n <Text className=\"sg:text-[11px] sg:text-muted-foreground sg:text-center sg:mt-2\">\n Demo mode β responses are pre-written. Try: hello, help, posts,\n design\n </Text>\n </div>\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n\n/** Simple markdown-ish renderer for bold and line breaks */\nfunction MessageContent({ content }: { content: string }) {\n const lines = content.split(\"\\n\");\n return (\n <div className=\"sg:space-y-1.5\">\n {lines.map((line, i) => {\n if (line.trim() === \"\") return <br key={i} />;\n // Bold: **text**\n const parts = line.split(/(\\*\\*[^*]+\\*\\*)/g);\n return (\n <p key={i}>\n {parts.map((part, j) =>\n part.startsWith(\"**\") && part.endsWith(\"**\") ? (\n <strong key={j} className=\"sg:font-semibold\">\n {part.slice(2, -2)}\n </strong>\n ) : (\n <span key={j}>{part}</span>\n ),\n )}\n </p>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/pages/chat/chat-page.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Send, Bot, User, Sparkles } from \"lucide-react\";\nimport { Layout, Button, Input, Heading, Text } from \"../../primitives/index\";\nimport { cn } from \"../../../utils/index\";\n\ninterface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: Date;\n}\n\nconst mockResponses: Record<string, string> = {\n hello:\n \"Hey there! π I'm **Storied Bot**, your friendly AI assistant. I can help you explore blog posts, find resources, or just chat. What's on your mind?\",\n help: \"Sure! Here's what I can help with:\\n\\n- π **Search posts** β Ask me about any topic\\n- π **Recommend reads** β I'll suggest posts based on your interests\\n- π¨ **Design tips** β Questions about typography, color, layout\\n- π‘ **General chat** β I'm happy to talk about anything!\",\n posts:\n \"We have some great posts! Here are a few highlights:\\n\\n1. **The Art of Minimalism** β A deep dive into less-is-more design\\n2. **Color Theory Fundamentals** β Understanding how colors work together\\n3. **Typography Best Practices** β Making your text shine\\n\\nWant me to go deeper on any of these?\",\n design:\n \"Great question about design! Here are some key principles I follow:\\n\\n- **Hierarchy** β Guide the eye with size, weight, and contrast\\n- **Whitespace** β Give elements room to breathe\\n- **Consistency** β Stick to your design system\\n- **Accessibility** β Make sure everyone can use your work\\n\\nWould you like to explore our **Design System** page for live examples?\",\n};\n\nconst defaultResponse =\n \"That's an interesting thought! π€\\n\\nI'm currently running in demo mode, so my responses are limited. In a full implementation, I'd be powered by an AI model and could have much richer conversations.\\n\\nTry asking me about **posts**, **design**, or type **help** to see what I can do!\";\n\nfunction getResponse(input: string): string {\n const lower = input.toLowerCase().trim();\n for (const [key, response] of Object.entries(mockResponses)) {\n if (lower.includes(key)) return response;\n }\n return defaultResponse;\n}\n\nconst initialMessages: Message[] = [\n {\n id: \"welcome\",\n role: \"assistant\",\n content:\n \"Welcome to **Storied Bot**! π€β¨\\n\\nI'm your AI assistant β here to help you explore content, answer questions, and chat. This is a demo showcasing how a conversational interface can look and feel.\\n\\nTry saying **hello**, ask about **posts**, or type **help** to get started!\",\n timestamp: new Date(Date.now() - 60000),\n },\n];\n\nexport function ChatPage() {\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n }, [messages, isTyping]);\n\n const sendMessage = () => {\n const text = input.trim();\n if (!text || isTyping) return;\n\n const userMsg: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n timestamp: new Date(),\n };\n\n setMessages((prev) => [...prev, userMsg]);\n setInput(\"\");\n setIsTyping(true);\n\n // Simulate typing delay\n setTimeout(\n () => {\n const botMsg: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: getResponse(text),\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, botMsg]);\n setIsTyping(false);\n },\n 800 + Math.random() * 1200,\n );\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n };\n\n return (\n <Layout type=\"container\" bgColor=\"background\" className=\"sg:min-h-screen\">\n <Layout.Col1 className=\"sg:flex sg:flex-col sg:items-center\">\n <motion.div\n initial={{ opacity: 1, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 1, y: -20 }}\n transition={{ duration: 0.3 }}\n className=\"sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full\"\n style={{ height: \"calc(100vh - 2rem)\" }}\n >\n {/* Header */}\n <div className=\"sg:flex sg:items-center sg:gap-3 sg:mb-4 sg:pb-4 sg:border-b sg:border-border\">\n <div className=\"sg:h-10 sg:w-10 sg:rounded-full sg:bg-primary/10 sg:flex sg:items-center sg:justify-center\">\n <Sparkles className=\"sg:h-5 sg:w-5 sg:text-primary\" />\n </div>\n <div>\n <Heading variant=\"h4\" className=\"sg:font-bold sg:leading-tight\">\n Storied Bot\n </Heading>\n <Text className=\"sg:text-xs sg:text-muted-foreground\">\n AI assistant Β· Demo mode\n </Text>\n </div>\n </div>\n\n {/* Messages area */}\n <div\n ref={scrollRef}\n className=\"chat-scroll sg:flex-1 sg:overflow-y-auto sg:space-y-4 sg:pr-2 sg:min-h-0\"\n >\n {messages.map((msg) => (\n <motion.div\n key={msg.id}\n initial={{ opacity: 1, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2 }}\n className={cn(\n \"sg:flex sg:gap-3 sg:max-w-[85%]\",\n msg.role === \"user\" ? \"sg:ml-auto sg:flex-row-reverse\" : \"\",\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"sg:h-8 sg:w-8 sg:rounded-full sg:flex sg:items-center sg:justify-center sg:shrink-0 sg:mt-0.5\",\n msg.role === \"assistant\"\n ? \"sg:bg-primary/10 sg:text-primary\"\n : \"sg:bg-secondary sg:text-secondary-foreground\",\n )}\n >\n {msg.role === \"assistant\" ? (\n <Bot className=\"sg:h-4 sg:w-4\" />\n ) : (\n <User className=\"sg:h-4 sg:w-4\" />\n )}\n </div>\n\n {/* Bubble */}\n <div\n className={cn(\n \"sg:rounded-2xl sg:px-4 sg:py-2.5 sg:text-sm sg:leading-relaxed\",\n msg.role === \"assistant\"\n ? \"sg:bg-muted sg:text-foreground sg:rounded-tl-sm\"\n : \"sg:bg-primary sg:text-primary-foreground sg:rounded-tr-sm\",\n )}\n >\n <MessageContent content={msg.content} />\n <p\n className={cn(\n \"sg:text-[10px] sg:mt-1.5\",\n msg.role === \"assistant\"\n ? \"sg:text-muted-foreground\"\n : \"sg:text-primary-foreground/60\",\n )}\n >\n {msg.timestamp.toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}\n </p>\n </div>\n </motion.div>\n ))}\n\n {/* Typing indicator */}\n {isTyping && (\n <motion.div\n initial={{ opacity: 1, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n className=\"sg:flex sg:gap-3\"\n >\n <div className=\"sg:h-8 sg:w-8 sg:rounded-full sg:bg-primary/10 sg:text-primary sg:flex sg:items-center sg:justify-center sg:shrink-0\">\n <Bot className=\"sg:h-4 sg:w-4\" />\n </div>\n <div className=\"sg:bg-muted sg:rounded-2xl sg:rounded-tl-sm sg:px-4 sg:py-3 sg:flex sg:items-center sg:gap-1\">\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:0ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:150ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:300ms]\" />\n </div>\n </motion.div>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"sg:pt-4 sg:mt-4 sg:border-t sg:border-border\">\n <div className=\"sg:flex sg:gap-2\">\n <Input\n ref={inputRef}\n placeholder=\"Type a messageβ¦\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={isTyping}\n className=\"sg:flex-1\"\n />\n <Button\n onClick={sendMessage}\n disabled={!input.trim() || isTyping}\n aria-label=\"Send message\"\n >\n <Send className=\"sg:h-4 sg:w-4\" />\n </Button>\n </div>\n <Text className=\"sg:text-[11px] sg:text-muted-foreground sg:text-center sg:mt-2\">\n Demo mode β responses are pre-written. Try: hello, help, posts,\n design\n </Text>\n </div>\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n\n/** Simple markdown-ish renderer for bold and line breaks */\nfunction MessageContent({ content }: { content: string }) {\n const lines = content.split(\"\\n\");\n return (\n <div className=\"sg:space-y-1.5\">\n {lines.map((line, i) => {\n if (line.trim() === \"\") return <br key={i} />;\n // Bold: **text**\n const parts = line.split(/(\\*\\*[^*]+\\*\\*)/g);\n return (\n <p key={i}>\n {parts.map((part, j) =>\n part.startsWith(\"**\") && part.endsWith(\"**\") ? (\n <strong key={j} className=\"sg:font-semibold\">\n {part.slice(2, -2)}\n </strong>\n ) : (\n <span key={j}>{part}</span>\n ),\n )}\n </p>\n );\n })}\n </div>\n );\n}\n"],"mappings":";AAgHc,cAEF,YAFE;AA9Gd,SAAS,UAAU,QAAQ,iBAAiB;AAC5C,SAAS,cAAc;AACvB,SAAS,MAAM,KAAK,MAAM,gBAAgB;AAC1C,SAAS,QAAQ,QAAQ,OAAO,SAAS,YAAY;AACrD,SAAS,UAAU;AASnB,MAAM,gBAAwC;AAAA,EAC5C,OACE;AAAA,EACF,MAAM;AAAA,EACN,OACE;AAAA,EACF,QACE;AACJ;AAEA,MAAM,kBACJ;AAEF,SAAS,YAAY,OAAuB;AAC1C,QAAM,QAAQ,MAAM,YAAY,EAAE,KAAK;AACvC,aAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,aAAa,GAAG;AAC3D,QAAI,MAAM,SAAS,GAAG,EAAG,QAAO;AAAA,EAClC;AACA,SAAO;AACT;AAEA,MAAM,kBAA6B;AAAA,EACjC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SACE;AAAA,IACF,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,GAAK;AAAA,EACxC;AACF;AAEO,SAAS,WAAW;AACzB,QAAM,CAAC,UAAU,WAAW,IAAI,SAAoB,eAAe;AACnE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,WAAW,OAAyB,IAAI;AAE9C,YAAU,MAAM;AACd,cAAU,SAAS,SAAS;AAAA,MAC1B,KAAK,UAAU,QAAQ;AAAA,MACvB,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,QAAM,cAAc,MAAM;AACxB,UAAM,OAAO,MAAM,KAAK;AACxB,QAAI,CAAC,QAAQ,SAAU;AAEvB,UAAM,UAAmB;AAAA,MACvB,IAAI,OAAO,WAAW;AAAA,MACtB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW,oBAAI,KAAK;AAAA,IACtB;AAEA,gBAAY,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC;AACxC,aAAS,EAAE;AACX,gBAAY,IAAI;AAGhB;AAAA,MACE,MAAM;AACJ,cAAM,SAAkB;AAAA,UACtB,IAAI,OAAO,WAAW;AAAA,UACtB,MAAM;AAAA,UACN,SAAS,YAAY,IAAI;AAAA,UACzB,WAAW,oBAAI,KAAK;AAAA,QACtB;AACA,oBAAY,CAAC,SAAS,CAAC,GAAG,MAAM,MAAM,CAAC;AACvC,oBAAY,KAAK;AAAA,MACnB;AAAA,MACA,MAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,MAA2B;AAChD,QAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,QAAE,eAAe;AACjB,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,SACE,oBAAC,UAAO,MAAK,aAAY,SAAQ,cAAa,WAAU,mBACtD,8BAAC,OAAO,MAAP,EAAY,WAAU,uCACrB;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,MAAM,EAAE,SAAS,GAAG,GAAG,IAAI;AAAA,MAC3B,YAAY,EAAE,UAAU,IAAI;AAAA,MAC5B,WAAU;AAAA,MACV,OAAO,EAAE,QAAQ,qBAAqB;AAAA,MAGtC;AAAA,6BAAC,SAAI,WAAU,iFACb;AAAA,8BAAC,SAAI,WAAU,8FACb,8BAAC,YAAS,WAAU,iCAAgC,GACtD;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,WAAQ,SAAQ,MAAK,WAAU,iCAAgC,yBAEhE;AAAA,YACA,oBAAC,QAAK,WAAU,uCAAsC,yCAEtD;AAAA,aACF;AAAA,WACF;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAU;AAAA,YAET;AAAA,uBAAS,IAAI,CAAC,QACb;AAAA,gBAAC,OAAO;AAAA,gBAAP;AAAA,kBAEC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,kBAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,kBAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,kBAC5B,WAAW;AAAA,oBACT;AAAA,oBACA,IAAI,SAAS,SAAS,mCAAmC;AAAA,kBAC3D;AAAA,kBAGA;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW;AAAA,0BACT;AAAA,0BACA,IAAI,SAAS,cACT,qCACA;AAAA,wBACN;AAAA,wBAEC,cAAI,SAAS,cACZ,oBAAC,OAAI,WAAU,iBAAgB,IAE/B,oBAAC,QAAK,WAAU,iBAAgB;AAAA;AAAA,oBAEpC;AAAA,oBAGA;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW;AAAA,0BACT;AAAA,0BACA,IAAI,SAAS,cACT,oDACA;AAAA,wBACN;AAAA,wBAEA;AAAA,8CAAC,kBAAe,SAAS,IAAI,SAAS;AAAA,0BACtC;AAAA,4BAAC;AAAA;AAAA,8BACC,WAAW;AAAA,gCACT;AAAA,gCACA,IAAI,SAAS,cACT,6BACA;AAAA,8BACN;AAAA,8BAEC,cAAI,UAAU,mBAAmB,CAAC,GAAG;AAAA,gCACpC,MAAM;AAAA,gCACN,QAAQ;AAAA,8BACV,CAAC;AAAA;AAAA,0BACH;AAAA;AAAA;AAAA,oBACF;AAAA;AAAA;AAAA,gBAhDK,IAAI;AAAA,cAiDX,CACD;AAAA,cAGA,YACC;AAAA,gBAAC,OAAO;AAAA,gBAAP;AAAA,kBACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,kBAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,kBAC5B,WAAU;AAAA,kBAEV;AAAA,wCAAC,SAAI,WAAU,wHACb,8BAAC,OAAI,WAAU,iBAAgB,GACjC;AAAA,oBACA,qBAAC,SAAI,WAAU,gGACb;AAAA,0CAAC,UAAK,WAAU,mGAAkG;AAAA,sBAClH,oBAAC,UAAK,WAAU,qGAAoG;AAAA,sBACpH,oBAAC,UAAK,WAAU,qGAAoG;AAAA,uBACtH;AAAA;AAAA;AAAA,cACF;AAAA;AAAA;AAAA,QAEJ;AAAA,QAGA,qBAAC,SAAI,WAAU,gDACb;AAAA,+BAAC,SAAI,WAAU,oBACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK;AAAA,gBACL,aAAY;AAAA,gBACZ,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,gBACxC,WAAW;AAAA,gBACX,UAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS;AAAA,gBACT,UAAU,CAAC,MAAM,KAAK,KAAK;AAAA,gBAC3B,cAAW;AAAA,gBAEX,8BAAC,QAAK,WAAU,iBAAgB;AAAA;AAAA,YAClC;AAAA,aACF;AAAA,UACA,oBAAC,QAAK,WAAU,kEAAiE,yFAGjF;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF,GACF;AAEJ;AAGA,SAAS,eAAe,EAAE,QAAQ,GAAwB;AACxD,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,SACE,oBAAC,SAAI,WAAU,kBACZ,gBAAM,IAAI,CAAC,MAAM,MAAM;AACtB,QAAI,KAAK,KAAK,MAAM,GAAI,QAAO,oBAAC,UAAQ,CAAG;AAE3C,UAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,WACE,oBAAC,OACE,gBAAM;AAAA,MAAI,CAAC,MAAM,MAChB,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS,IAAI,IACzC,oBAAC,YAAe,WAAU,oBACvB,eAAK,MAAM,GAAG,EAAE,KADN,CAEb,IAEA,oBAAC,UAAc,kBAAJ,CAAS;AAAA,IAExB,KATM,CAUR;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
|