singularity-components 0.1.196 → 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.
Files changed (71) hide show
  1. package/dist/components/blocks/cards/blogpost-card.js +1 -1
  2. package/dist/components/blocks/cards/blogpost-card.js.map +1 -1
  3. package/dist/components/blocks/directory/category-card.js +3 -2
  4. package/dist/components/blocks/directory/category-card.js.map +1 -1
  5. package/dist/components/blocks/extras/extras-hub-card.js +4 -3
  6. package/dist/components/blocks/extras/extras-hub-card.js.map +1 -1
  7. package/dist/components/blocks/login/login.js +76 -47
  8. package/dist/components/blocks/login/login.js.map +1 -1
  9. package/dist/components/blocks/marketing/timeline.js +2 -1
  10. package/dist/components/blocks/marketing/timeline.js.map +1 -1
  11. package/dist/components/blocks/post-list/post-list-with-filters.js +4 -4
  12. package/dist/components/blocks/post-list/post-list-with-filters.js.map +1 -1
  13. package/dist/components/pages/about/about-page.js +2 -2
  14. package/dist/components/pages/about/about-page.js.map +1 -1
  15. package/dist/components/pages/admin/admin-page.js +159 -105
  16. package/dist/components/pages/admin/admin-page.js.map +1 -1
  17. package/dist/components/pages/author/author-page.js +1 -1
  18. package/dist/components/pages/author/author-page.js.map +1 -1
  19. package/dist/components/pages/authors/authors-page.js +1 -1
  20. package/dist/components/pages/authors/authors-page.js.map +1 -1
  21. package/dist/components/pages/blogpost/blogpost.js +72 -44
  22. package/dist/components/pages/blogpost/blogpost.js.map +1 -1
  23. package/dist/components/pages/categories/categories-page.js +1 -1
  24. package/dist/components/pages/categories/categories-page.js.map +1 -1
  25. package/dist/components/pages/category/category-page.js +1 -1
  26. package/dist/components/pages/category/category-page.js.map +1 -1
  27. package/dist/components/pages/chat/chat-page.js +4 -4
  28. package/dist/components/pages/chat/chat-page.js.map +1 -1
  29. package/dist/components/pages/contact/contact-page.js +104 -97
  30. package/dist/components/pages/contact/contact-page.js.map +1 -1
  31. package/dist/components/pages/content-blocks/content-blocks-page.js +3 -2
  32. package/dist/components/pages/content-blocks/content-blocks-page.js.map +1 -1
  33. package/dist/components/pages/extras/extras-hub-page.js +1 -1
  34. package/dist/components/pages/extras/extras-hub-page.js.map +1 -1
  35. package/dist/components/pages/maintenance/maintenance-page.js +1 -1
  36. package/dist/components/pages/maintenance/maintenance-page.js.map +1 -1
  37. package/dist/components/pages/membership/membership-page.js +1 -1
  38. package/dist/components/pages/membership/membership-page.js.map +1 -1
  39. package/dist/components/pages/mosaic/mosaic-page.js +1 -1
  40. package/dist/components/pages/mosaic/mosaic-page.js.map +1 -1
  41. package/dist/components/pages/newsletter/newsletter-page.js +56 -39
  42. package/dist/components/pages/newsletter/newsletter-page.js.map +1 -1
  43. package/dist/components/pages/not-found/not-found.js +2 -2
  44. package/dist/components/pages/not-found/not-found.js.map +1 -1
  45. package/dist/components/pages/privacy/privacy-page.js +2 -2
  46. package/dist/components/pages/privacy/privacy-page.js.map +1 -1
  47. package/dist/components/pages/resources/resources-page.js +1 -1
  48. package/dist/components/pages/resources/resources-page.js.map +1 -1
  49. package/dist/components/pages/terms/terms-page.js +2 -2
  50. package/dist/components/pages/terms/terms-page.js.map +1 -1
  51. package/dist/components/primitives/forms/form.d.ts +1 -1
  52. package/dist/components/primitives/forms/form.js.map +1 -1
  53. package/dist/components/templates/form/form.d.ts +2 -2
  54. package/dist/components/templates/form/form.js +133 -87
  55. package/dist/components/templates/form/form.js.map +1 -1
  56. package/dist/components/templates/hero/hero.js +1 -0
  57. package/dist/components/templates/hero/hero.js.map +1 -1
  58. package/dist/components/templates/loading-screen/loading-screen.js +1 -1
  59. package/dist/components/templates/loading-screen/loading-screen.js.map +1 -1
  60. package/dist/css/variables.css +4 -3
  61. package/dist/css/variables.css.map +1 -1
  62. package/dist/data/posts.js +4 -4
  63. package/dist/data/posts.js.map +1 -1
  64. package/dist/lib/forms/index.d.ts +1 -1
  65. package/dist/lib/forms/tanstack-field.d.ts +26 -11
  66. package/dist/lib/forms/tanstack-field.js +13 -6
  67. package/dist/lib/forms/tanstack-field.js.map +1 -1
  68. package/dist/lib/index.d.ts +1 -1
  69. package/dist/main.css +16 -22
  70. package/dist/main.css.map +1 -1
  71. package/package.json +25 -30
@@ -1,23 +1,35 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState, useMemo, useEffect } from "react";
3
+ import { useForm } from "@tanstack/react-form";
4
+ import { useState, useMemo, useEffect, useRef } from "react";
5
+ import {
6
+ TanStackInputField,
7
+ TanStackTextareaField
8
+ } from "../../../lib/forms/tanstack-field.js";
4
9
  import {
5
10
  Layout,
6
11
  Button,
7
12
  Input,
8
- Textarea,
9
- Label,
10
13
  Heading,
11
14
  Text,
12
15
  Icon
13
16
  } from "../../primitives/index.js";
17
+ import { FieldGroup } from "../../primitives/forms/field.js";
18
+ import { Form, FormActions } from "../../primitives/forms/form.js";
14
19
  import { Card, CardContent } from "../../blocks/index.js";
15
20
  import { posts as initialPosts } from "../../../data/posts.js";
16
21
  import { useToast } from "../../primitives/sonner/use-toast.js";
17
22
  const POSTS_PER_PAGE = 5;
23
+ const emptyEditorValues = {
24
+ title: "",
25
+ excerpt: "",
26
+ primaryImage: "",
27
+ content: ""
28
+ };
18
29
  function AdminPage({ initialEditId }) {
19
30
  const [postsList, setPostsList] = useState(initialPosts);
20
31
  const [editing, setEditing] = useState(null);
32
+ const editingRef = useRef(null);
21
33
  const [showForm, setShowForm] = useState(false);
22
34
  const [searchQuery, setSearchQuery] = useState("");
23
35
  const { toast } = useToast();
@@ -33,52 +45,82 @@ function AdminPage({ initialEditId }) {
33
45
  author: "Admin",
34
46
  categories: []
35
47
  };
36
- const [form, setForm] = useState(emptyPost);
48
+ useEffect(() => {
49
+ editingRef.current = editing;
50
+ }, [editing]);
51
+ const editorForm = useForm({
52
+ defaultValues: emptyEditorValues,
53
+ onSubmit: ({ value, formApi }) => {
54
+ if (!value.title.trim()) {
55
+ return;
56
+ }
57
+ const currentEditing = editingRef.current;
58
+ if (currentEditing !== null) {
59
+ setPostsList(
60
+ (prev) => prev.map(
61
+ (p) => p.id === currentEditing ? {
62
+ ...p,
63
+ title: value.title,
64
+ excerpt: value.excerpt,
65
+ primaryImage: value.primaryImage,
66
+ content: value.content
67
+ } : p
68
+ )
69
+ );
70
+ toast.success("Post updated", {
71
+ description: `"${value.title}" has been saved.`
72
+ });
73
+ setEditing(null);
74
+ } else {
75
+ setPostsList((prev) => {
76
+ const nextId = Math.max(...prev.map((p) => p.id), 0) + 1;
77
+ const slug = value.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || `post-${nextId}`;
78
+ return [
79
+ {
80
+ ...emptyPost,
81
+ id: nextId,
82
+ slug,
83
+ title: value.title,
84
+ excerpt: value.excerpt,
85
+ primaryImage: value.primaryImage,
86
+ content: value.content
87
+ },
88
+ ...prev
89
+ ];
90
+ });
91
+ toast.success("Post created", {
92
+ description: `"${value.title}" has been published.`
93
+ });
94
+ }
95
+ formApi.reset(emptyEditorValues);
96
+ setShowForm(false);
97
+ }
98
+ });
99
+ const resetEditor = (values = emptyEditorValues) => {
100
+ editorForm.reset(values);
101
+ };
37
102
  useEffect(() => {
38
103
  if (initialEditId) {
39
104
  const post = postsList.find((p) => p.id === initialEditId);
40
105
  if (post) {
41
- setForm({
42
- id: post.id,
43
- slug: post.slug,
106
+ resetEditor({
44
107
  title: post.title,
45
- excerpt: post.excerpt,
46
- content: post.content,
108
+ excerpt: post.excerpt || "",
47
109
  primaryImage: post.primaryImage,
48
- originalImage: post.originalImage,
49
- date: post.date,
50
- author: post.author,
51
- categories: post.categories
110
+ content: post.content || ""
52
111
  });
53
112
  setEditing(post.id);
54
113
  setShowForm(true);
55
114
  }
56
115
  }
57
116
  }, [initialEditId, postsList]);
58
- const handleSave = (e) => {
59
- e.preventDefault();
60
- if (!form.title.trim()) return;
61
- if (editing !== null) {
62
- setPostsList(
63
- (prev) => prev.map((p) => p.id === editing ? { ...p, ...form } : p)
64
- );
65
- toast.success("Post updated", {
66
- description: `"${form.title}" has been saved.`
67
- });
68
- setEditing(null);
69
- } else {
70
- const nextId = Math.max(...postsList.map((p) => p.id), 0) + 1;
71
- const slug = form.slug || form.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || `post-${nextId}`;
72
- setPostsList((prev) => [{ ...form, id: nextId, slug }, ...prev]);
73
- toast.success("Post created", {
74
- description: `"${form.title}" has been published.`
75
- });
76
- }
77
- setForm(emptyPost);
78
- setShowForm(false);
79
- };
80
117
  const handleEdit = (post) => {
81
- setForm(post);
118
+ resetEditor({
119
+ title: post.title,
120
+ excerpt: post.excerpt || "",
121
+ primaryImage: post.primaryImage,
122
+ content: post.content || ""
123
+ });
82
124
  setEditing(post.id);
83
125
  setShowForm(true);
84
126
  };
@@ -114,7 +156,7 @@ function AdminPage({ initialEditId }) {
114
156
  onClick: () => {
115
157
  setShowForm(!showForm);
116
158
  setEditing(null);
117
- setForm(emptyPost);
159
+ resetEditor();
118
160
  },
119
161
  children: [
120
162
  /* @__PURE__ */ jsx(Icon, { icon: "Plus", size: "xs", className: "sg:mr-1" }),
@@ -148,74 +190,86 @@ function AdminPage({ initialEditId }) {
148
190
  ] }),
149
191
  showForm && /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "sg:p-6 sg:mt-6", children: [
150
192
  /* @__PURE__ */ jsx(Heading, { variant: "h3", className: "sg:mb-4", children: editing !== null ? "Edit Post" : "New Post" }),
151
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, className: "sg:space-y-4", children: [
152
- /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
153
- /* @__PURE__ */ jsx(Label, { htmlFor: "title", children: "Title" }),
154
- /* @__PURE__ */ jsx(
155
- Input,
156
- {
157
- id: "title",
158
- value: form.title,
159
- onChange: (e) => setForm({ ...form, title: e.target.value }),
160
- required: true
161
- }
162
- )
163
- ] }),
164
- /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
165
- /* @__PURE__ */ jsx(Label, { htmlFor: "excerpt", children: "Excerpt" }),
166
- /* @__PURE__ */ jsx(
167
- Input,
168
- {
169
- id: "excerpt",
170
- value: form.excerpt || "",
171
- onChange: (e) => setForm({ ...form, excerpt: e.target.value })
172
- }
173
- )
174
- ] }),
175
- /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
176
- /* @__PURE__ */ jsx(Label, { htmlFor: "image", children: "Image URL" }),
177
- /* @__PURE__ */ jsx(
178
- Input,
179
- {
180
- id: "image",
181
- value: form.primaryImage,
182
- onChange: (e) => setForm({ ...form, primaryImage: e.target.value }),
183
- placeholder: "https://..."
184
- }
185
- )
186
- ] }),
187
- /* @__PURE__ */ jsxs("div", { className: "sg:space-y-2", children: [
188
- /* @__PURE__ */ jsx(Label, { htmlFor: "content", children: "Content" }),
189
- /* @__PURE__ */ jsx(
190
- Textarea,
191
- {
192
- id: "content",
193
- value: form.content || "",
194
- onChange: (e) => setForm({
195
- ...form,
196
- content: e.target.value
197
- }),
198
- className: "sg:min-h-[200px]"
199
- }
200
- )
201
- ] }),
202
- /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:gap-2 sg:pt-2", children: [
203
- /* @__PURE__ */ jsx(Button, { type: "submit", children: editing !== null ? "Update" : "Publish" }),
204
- /* @__PURE__ */ jsx(
205
- Button,
206
- {
207
- type: "button",
208
- variant: "ghost",
209
- onClick: () => {
210
- setShowForm(false);
211
- setEditing(null);
212
- setForm(emptyPost);
213
- },
214
- children: "Cancel"
215
- }
216
- )
217
- ] })
218
- ] })
193
+ /* @__PURE__ */ jsxs(
194
+ Form,
195
+ {
196
+ onSubmit: (event) => {
197
+ event.preventDefault();
198
+ event.stopPropagation();
199
+ void editorForm.handleSubmit();
200
+ },
201
+ children: [
202
+ /* @__PURE__ */ jsxs(FieldGroup, { className: "sg:gap-4", children: [
203
+ /* @__PURE__ */ jsx(
204
+ TanStackInputField,
205
+ {
206
+ formApi: editorForm,
207
+ name: "title",
208
+ label: "Title",
209
+ validators: {
210
+ onChange: ({ value }) => value.trim() ? void 0 : "Title is required"
211
+ }
212
+ }
213
+ ),
214
+ /* @__PURE__ */ jsx(
215
+ TanStackInputField,
216
+ {
217
+ formApi: editorForm,
218
+ name: "excerpt",
219
+ label: "Excerpt"
220
+ }
221
+ ),
222
+ /* @__PURE__ */ jsx(
223
+ TanStackInputField,
224
+ {
225
+ formApi: editorForm,
226
+ name: "primaryImage",
227
+ label: "Image URL",
228
+ placeholder: "https://..."
229
+ }
230
+ ),
231
+ /* @__PURE__ */ jsx(
232
+ TanStackTextareaField,
233
+ {
234
+ formApi: editorForm,
235
+ name: "content",
236
+ label: "Content",
237
+ className: "sg:min-h-[200px]"
238
+ }
239
+ )
240
+ ] }),
241
+ /* @__PURE__ */ jsxs(FormActions, { className: "sg:pt-2", children: [
242
+ /* @__PURE__ */ jsx(
243
+ editorForm.Subscribe,
244
+ {
245
+ selector: (state) => [state.canSubmit, state.isSubmitting],
246
+ children: ([canSubmit, isSubmitting]) => /* @__PURE__ */ jsx(
247
+ Button,
248
+ {
249
+ type: "submit",
250
+ disabled: !canSubmit || isSubmitting,
251
+ children: editing !== null ? "Update" : "Publish"
252
+ }
253
+ )
254
+ }
255
+ ),
256
+ /* @__PURE__ */ jsx(
257
+ Button,
258
+ {
259
+ type: "button",
260
+ variant: "ghost",
261
+ onClick: () => {
262
+ setShowForm(false);
263
+ setEditing(null);
264
+ resetEditor();
265
+ },
266
+ children: "Cancel"
267
+ }
268
+ )
269
+ ] })
270
+ ]
271
+ }
272
+ )
219
273
  ] }) }),
220
274
  /* @__PURE__ */ jsxs("div", { className: "sg:space-y-3", children: [
221
275
  paginatedPosts.map((post) => /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "sg:p-4 sg:flex sg:items-center sg:justify-between sg:pt-4", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/admin/admin-page.tsx"],"sourcesContent":["\"use client\";\nimport { useState, useMemo, useEffect } from \"react\";\nimport {\n Layout,\n Button,\n Input,\n Textarea,\n Label,\n Heading,\n Text,\n Icon,\n} from \"../../primitives/index\";\nimport { Card, CardContent } from \"../../blocks/index\";\nimport { posts as initialPosts, type BlogPost } from \"../../../data/posts\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\n\nconst POSTS_PER_PAGE = 5;\n\nexport interface AdminPageProps {\n initialEditId?: number;\n}\n\nexport function AdminPage({ initialEditId }: AdminPageProps) {\n const [postsList, setPostsList] = useState<BlogPost[]>(initialPosts);\n const [editing, setEditing] = useState<number | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const { toast } = useToast();\n\n const emptyPost: BlogPost = {\n id: 0,\n slug: \"\",\n title: \"\",\n excerpt: \"\",\n content: \"\",\n primaryImage: \"\",\n originalImage: \"\",\n date: new Date().toISOString().slice(0, 10),\n author: \"Admin\",\n categories: [],\n };\n\n const [form, setForm] = useState(emptyPost);\n\n useEffect(() => {\n if (initialEditId) {\n const post = postsList.find((p) => p.id === initialEditId);\n if (post) {\n setForm({\n id: post.id,\n slug: post.slug,\n title: post.title,\n excerpt: post.excerpt,\n content: post.content,\n primaryImage: post.primaryImage,\n originalImage: post.originalImage,\n date: post.date,\n author: post.author,\n categories: post.categories,\n });\n setEditing(post.id);\n setShowForm(true);\n }\n }\n }, [initialEditId, postsList]);\n\n const handleSave = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.title.trim()) return;\n\n if (editing !== null) {\n setPostsList((prev) =>\n prev.map((p) => (p.id === editing ? { ...p, ...form } : p)),\n );\n toast.success(\"Post updated\", {\n description: `\"${form.title}\" has been saved.`,\n });\n setEditing(null);\n } else {\n const nextId = Math.max(...postsList.map((p) => p.id), 0) + 1;\n const slug =\n form.slug ||\n form.title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\") ||\n `post-${nextId}`;\n setPostsList((prev) => [{ ...form, id: nextId, slug }, ...prev]);\n toast.success(\"Post created\", {\n description: `\"${form.title}\" has been published.`,\n });\n }\n\n setForm(emptyPost);\n setShowForm(false);\n };\n\n const handleEdit = (post: BlogPost) => {\n setForm(post);\n setEditing(post.id);\n setShowForm(true);\n };\n\n const [page, setPage] = useState(1);\n\n const handleDelete = (id: number) => {\n setPostsList((prev) => {\n const updated = prev.filter((p) => p.id !== id);\n const newTotalPages = Math.ceil(updated.length / POSTS_PER_PAGE);\n if (page > newTotalPages) setPage(Math.max(1, newTotalPages));\n return updated;\n });\n toast.message(\"Post deleted\");\n };\n\n const filteredPosts = useMemo(() => {\n if (!searchQuery.trim()) return postsList;\n const q = searchQuery.toLowerCase();\n return postsList.filter((p) => p.title.toLowerCase().includes(q));\n }, [postsList, searchQuery]);\n\n const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE);\n const paginatedPosts = useMemo(\n () =>\n filteredPosts.slice((page - 1) * POSTS_PER_PAGE, page * POSTS_PER_PAGE),\n [filteredPosts, page],\n );\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:space-y-8\">\n <div className=\"sg:flex sg:items-center sg:justify-between\">\n <div>\n <Heading variant=\"h1\">Admin</Heading>\n <Text className=\"sg:mt-1\" foreground=\"muted-foreground\">\n Manage your blog posts.\n </Text>\n </div>\n <Button\n onClick={() => {\n setShowForm(!showForm);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n <Icon icon=\"Plus\" size=\"xs\" className=\"sg:mr-1\" /> New post\n </Button>\n </div>\n\n {/* Search */}\n <div className=\"sg:relative\">\n <Icon\n icon=\"Search\"\n size=\"xs\"\n color=\"muted-foreground\"\n className=\"sg:absolute sg:left-3 sg:top-1/2 sg:-translate-y-1/2\"\n />\n <Input\n placeholder=\"Search posts by title...\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setPage(1);\n }}\n className=\"sg:pl-9\"\n />\n </div>\n\n {/* Form */}\n {showForm && (\n <Card>\n <CardContent className=\"sg:p-6 sg:mt-6\">\n <Heading variant=\"h3\" className=\"sg:mb-4\">\n {editing !== null ? \"Edit Post\" : \"New Post\"}\n </Heading>\n <form onSubmit={handleSave} className=\"sg:space-y-4\">\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"title\">Title</Label>\n <Input\n id=\"title\"\n value={form.title}\n onChange={(e) =>\n setForm({ ...form, title: e.target.value })\n }\n required\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"excerpt\">Excerpt</Label>\n <Input\n id=\"excerpt\"\n value={form.excerpt || \"\"}\n onChange={(e) =>\n setForm({ ...form, excerpt: e.target.value })\n }\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"image\">Image URL</Label>\n <Input\n id=\"image\"\n value={form.primaryImage}\n onChange={(e) =>\n setForm({ ...form, primaryImage: e.target.value })\n }\n placeholder=\"https://...\"\n />\n </div>\n <div className=\"sg:space-y-2\">\n <Label htmlFor=\"content\">Content</Label>\n <Textarea\n id=\"content\"\n value={(form.content || \"\") as string}\n onChange={(e) =>\n setForm({\n ...form,\n content: e.target.value,\n })\n }\n className=\"sg:min-h-[200px]\"\n />\n </div>\n <div className=\"sg:flex sg:gap-2 sg:pt-2\">\n <Button type=\"submit\">\n {editing !== null ? \"Update\" : \"Publish\"}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setShowForm(false);\n setEditing(null);\n setForm(emptyPost);\n }}\n >\n Cancel\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n )}\n\n {/* Posts list */}\n <div className=\"sg:space-y-3\">\n {paginatedPosts.map((post) => (\n <Card key={post.id}>\n <CardContent className=\"sg:p-4 sg:flex sg:items-center sg:justify-between sg:pt-4\">\n <div className=\"sg:flex-1 sg:min-w-0\">\n <div className=\"sg:font-medium sg:truncate\">\n {post.title}\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author}\n </Text>\n </div>\n <div className=\"sg:flex sg:gap-2 sg:ml-4\">\n <Button\n variant=\"ghost\"\n onClick={() => handleEdit(post)}\n aria-label={`Edit ${post.title}`}\n className=\"sg:px-2\"\n >\n <Icon icon=\"Pencil\" size=\"xs\" />\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => handleDelete(post.id)}\n aria-label={`Delete ${post.title}`}\n className=\"sg:px-2 sg:text-destructive sg:hover:text-destructive sg:hover:bg-destructive/10\"\n >\n <Icon icon=\"Trash2\" size=\"xs\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n {paginatedPosts.length === 0 && (\n <Text\n foreground=\"muted-foreground\"\n className=\"sg:text-center sg:py-8\"\n >\n No posts found.\n </Text>\n )}\n </div>\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"sg:flex sg:items-center sg:justify-center sg:gap-2 sg:pt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === 1}\n onClick={() => setPage(page - 1)}\n >\n <Icon icon=\"ChevronLeft\" size=\"xs\" className=\"sg:mr-1\" />{\" \"}\n Previous\n </Button>\n <Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:px-3\">\n Page {page} of {totalPages}\n </Text>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === totalPages}\n onClick={() => setPage(page + 1)}\n >\n Next <Icon icon=\"ChevronRight\" size=\"xs\" className=\"sg:ml-1\" />\n </Button>\n </div>\n )}\n </div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/admin/admin-page.tsx"],"sourcesContent":["\"use client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState, useMemo, useEffect, useRef } from \"react\";\nimport {\n TanStackInputField,\n TanStackTextareaField,\n} from \"../../../lib/forms/tanstack-field\";\nimport {\n Layout,\n Button,\n Input,\n Heading,\n Text,\n Icon,\n} from \"../../primitives/index\";\nimport { FieldGroup } from \"../../primitives/forms/field\";\nimport { Form, FormActions } from \"../../primitives/forms/form\";\nimport { Card, CardContent } from \"../../blocks/index\";\nimport { posts as initialPosts, type BlogPost } from \"../../../data/posts\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\n\nconst POSTS_PER_PAGE = 5;\n\ntype PostEditorValues = {\n title: string;\n excerpt: string;\n primaryImage: string;\n content: string;\n};\n\nconst emptyEditorValues: PostEditorValues = {\n title: \"\",\n excerpt: \"\",\n primaryImage: \"\",\n content: \"\",\n};\n\nexport interface AdminPageProps {\n initialEditId?: number;\n}\n\nexport function AdminPage({ initialEditId }: AdminPageProps) {\n const [postsList, setPostsList] = useState<BlogPost[]>(initialPosts);\n const [editing, setEditing] = useState<number | null>(null);\n const editingRef = useRef<number | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const { toast } = useToast();\n\n const emptyPost: BlogPost = {\n id: 0,\n slug: \"\",\n title: \"\",\n excerpt: \"\",\n content: \"\",\n primaryImage: \"\",\n originalImage: \"\",\n date: new Date().toISOString().slice(0, 10),\n author: \"Admin\",\n categories: [],\n };\n\n useEffect(() => {\n editingRef.current = editing;\n }, [editing]);\n\n const editorForm = useForm({\n defaultValues: emptyEditorValues,\n onSubmit: ({ value, formApi }) => {\n if (!value.title.trim()) {\n return;\n }\n\n const currentEditing = editingRef.current;\n\n if (currentEditing !== null) {\n setPostsList((prev) =>\n prev.map((p) =>\n p.id === currentEditing\n ? {\n ...p,\n title: value.title,\n excerpt: value.excerpt,\n primaryImage: value.primaryImage,\n content: value.content,\n }\n : p,\n ),\n );\n toast.success(\"Post updated\", {\n description: `\"${value.title}\" has been saved.`,\n });\n setEditing(null);\n } else {\n setPostsList((prev) => {\n const nextId = Math.max(...prev.map((p) => p.id), 0) + 1;\n const slug =\n value.title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\") || `post-${nextId}`;\n return [\n {\n ...emptyPost,\n id: nextId,\n slug,\n title: value.title,\n excerpt: value.excerpt,\n primaryImage: value.primaryImage,\n content: value.content,\n },\n ...prev,\n ];\n });\n toast.success(\"Post created\", {\n description: `\"${value.title}\" has been published.`,\n });\n }\n\n formApi.reset(emptyEditorValues);\n setShowForm(false);\n },\n });\n\n const resetEditor = (values: PostEditorValues = emptyEditorValues) => {\n editorForm.reset(values);\n };\n\n useEffect(() => {\n if (initialEditId) {\n const post = postsList.find((p) => p.id === initialEditId);\n if (post) {\n resetEditor({\n title: post.title,\n excerpt: post.excerpt || \"\",\n primaryImage: post.primaryImage,\n content: (post.content || \"\") as string,\n });\n setEditing(post.id);\n setShowForm(true);\n }\n }\n }, [initialEditId, postsList]);\n\n const handleEdit = (post: BlogPost) => {\n resetEditor({\n title: post.title,\n excerpt: post.excerpt || \"\",\n primaryImage: post.primaryImage,\n content: (post.content || \"\") as string,\n });\n setEditing(post.id);\n setShowForm(true);\n };\n\n const [page, setPage] = useState(1);\n\n const handleDelete = (id: number) => {\n setPostsList((prev) => {\n const updated = prev.filter((p) => p.id !== id);\n const newTotalPages = Math.ceil(updated.length / POSTS_PER_PAGE);\n if (page > newTotalPages) setPage(Math.max(1, newTotalPages));\n return updated;\n });\n toast.message(\"Post deleted\");\n };\n\n const filteredPosts = useMemo(() => {\n if (!searchQuery.trim()) return postsList;\n const q = searchQuery.toLowerCase();\n return postsList.filter((p) => p.title.toLowerCase().includes(q));\n }, [postsList, searchQuery]);\n\n const totalPages = Math.ceil(filteredPosts.length / POSTS_PER_PAGE);\n const paginatedPosts = useMemo(\n () =>\n filteredPosts.slice((page - 1) * POSTS_PER_PAGE, page * POSTS_PER_PAGE),\n [filteredPosts, page],\n );\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:space-y-8\">\n <div className=\"sg:flex sg:items-center sg:justify-between\">\n <div>\n <Heading variant=\"h1\">Admin</Heading>\n <Text className=\"sg:mt-1\" foreground=\"muted-foreground\">\n Manage your blog posts.\n </Text>\n </div>\n <Button\n onClick={() => {\n setShowForm(!showForm);\n setEditing(null);\n resetEditor();\n }}\n >\n <Icon icon=\"Plus\" size=\"xs\" className=\"sg:mr-1\" /> New post\n </Button>\n </div>\n\n <div className=\"sg:relative\">\n <Icon\n icon=\"Search\"\n size=\"xs\"\n color=\"muted-foreground\"\n className=\"sg:absolute sg:left-3 sg:top-1/2 sg:-translate-y-1/2\"\n />\n <Input\n placeholder=\"Search posts by title...\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setPage(1);\n }}\n className=\"sg:pl-9\"\n />\n </div>\n\n {showForm && (\n <Card>\n <CardContent className=\"sg:p-6 sg:mt-6\">\n <Heading variant=\"h3\" className=\"sg:mb-4\">\n {editing !== null ? \"Edit Post\" : \"New Post\"}\n </Heading>\n <Form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void editorForm.handleSubmit();\n }}\n >\n <FieldGroup className=\"sg:gap-4\">\n <TanStackInputField\n formApi={editorForm}\n name=\"title\"\n label=\"Title\"\n validators={{\n onChange: ({ value }: { value: string }) =>\n value.trim() ? undefined : \"Title is required\",\n }}\n />\n <TanStackInputField\n formApi={editorForm}\n name=\"excerpt\"\n label=\"Excerpt\"\n />\n <TanStackInputField\n formApi={editorForm}\n name=\"primaryImage\"\n label=\"Image URL\"\n placeholder=\"https://...\"\n />\n <TanStackTextareaField\n formApi={editorForm}\n name=\"content\"\n label=\"Content\"\n className=\"sg:min-h-[200px]\"\n />\n </FieldGroup>\n <FormActions className=\"sg:pt-2\">\n <editorForm.Subscribe\n selector={(state) => [state.canSubmit, state.isSubmitting]}\n >\n {([canSubmit, isSubmitting]) => (\n <Button\n type=\"submit\"\n disabled={!canSubmit || isSubmitting}\n >\n {editing !== null ? \"Update\" : \"Publish\"}\n </Button>\n )}\n </editorForm.Subscribe>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setShowForm(false);\n setEditing(null);\n resetEditor();\n }}\n >\n Cancel\n </Button>\n </FormActions>\n </Form>\n </CardContent>\n </Card>\n )}\n\n <div className=\"sg:space-y-3\">\n {paginatedPosts.map((post) => (\n <Card key={post.id}>\n <CardContent className=\"sg:p-4 sg:flex sg:items-center sg:justify-between sg:pt-4\">\n <div className=\"sg:flex-1 sg:min-w-0\">\n <div className=\"sg:font-medium sg:truncate\">\n {post.title}\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {post.date} · {post.author}\n </Text>\n </div>\n <div className=\"sg:flex sg:gap-2 sg:ml-4\">\n <Button\n variant=\"ghost\"\n onClick={() => handleEdit(post)}\n aria-label={`Edit ${post.title}`}\n className=\"sg:px-2\"\n >\n <Icon icon=\"Pencil\" size=\"xs\" />\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => handleDelete(post.id)}\n aria-label={`Delete ${post.title}`}\n className=\"sg:px-2 sg:text-destructive sg:hover:text-destructive sg:hover:bg-destructive/10\"\n >\n <Icon icon=\"Trash2\" size=\"xs\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n {paginatedPosts.length === 0 && (\n <Text\n foreground=\"muted-foreground\"\n className=\"sg:text-center sg:py-8\"\n >\n No posts found.\n </Text>\n )}\n </div>\n\n {totalPages > 1 && (\n <div className=\"sg:flex sg:items-center sg:justify-center sg:gap-2 sg:pt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === 1}\n onClick={() => setPage(page - 1)}\n >\n <Icon icon=\"ChevronLeft\" size=\"xs\" className=\"sg:mr-1\" />{\" \"}\n Previous\n </Button>\n <Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:px-3\">\n Page {page} of {totalPages}\n </Text>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page === totalPages}\n onClick={() => setPage(page + 1)}\n >\n Next <Icon icon=\"ChevronRight\" size=\"xs\" className=\"sg:ml-1\" />\n </Button>\n </div>\n )}\n </div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":"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","names":[]}
@@ -35,7 +35,7 @@ function AuthorPage({ slug = "elena-marsh" }) {
35
35
  return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, children: /* @__PURE__ */ jsxs(
36
36
  motion.div,
37
37
  {
38
- initial: { opacity: 0, y: 20 },
38
+ initial: { opacity: 1, y: 20 },
39
39
  animate: { opacity: 1, y: 0 },
40
40
  transition: { duration: 0.3 },
41
41
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/author/author-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useMemo } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport {\r\n\tLayout,\r\n\tHeading,\r\n\tText,\r\n\tTextSpan,\r\n\tIcon,\r\n\tBadges,\r\n} from \"../../primitives/index\";\r\nimport {\r\n\tAvatar,\r\n\tAvatarFallback,\r\n\tAvatarImage,\r\n} from \"../../primitives/avatar/avatar\";\r\nimport CategoryBadge from \"../../blocks/badges/category-badge\";\r\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\r\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\r\nimport { authors } from \"../../../data/authors\";\r\nimport { posts } from \"../../../data/posts\";\r\n\r\ntype Props = {\r\n\tslug?: string;\r\n};\r\n\r\nexport function AuthorPage({ slug = \"elena-marsh\" }: Props) {\r\n\tconst author = authors.find((a) => a.slug === slug);\r\n\r\n\tconst authorPosts = useMemo(\r\n\t\t() => (author ? posts.filter((p) => p.author === author.name) : []),\r\n\t\t[author],\r\n\t);\r\n\r\n\tif (!author) {\r\n\t\treturn (\r\n\t\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t\t<Layout.Col1 hideDiv className=\"sg:text-center\">\r\n\t\t\t\t\t<Heading variant=\"h2\" className=\"sg:mb-4\">\r\n\t\t\t\t\t\tAuthor not found\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<LinkButton variant=\"outline\" to=\"/extras/authors\" iconStart=\"ArrowLeft\">\r\n\t\t\t\t\t\tBack to Authors\r\n\t\t\t\t\t</LinkButton>\r\n\t\t\t\t</Layout.Col1>\r\n\t\t\t</Layout>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv>\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<LinkButton\r\n\t\t\t\t\t\tvariant=\"ghost\"\r\n\t\t\t\t\t\tsize=\"sm\"\r\n\t\t\t\t\t\tto=\"/extras/authors\"\r\n\t\t\t\t\t\ticonStart=\"ArrowLeft\"\r\n\t\t\t\t\t\tclassName=\"sg:mb-6\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\tAll Authors\r\n\t\t\t\t\t</LinkButton>\r\n\r\n\t\t\t\t\t<div className=\"sg:flex sg:flex-col sm:sg:flex-row sg:items-start sg:gap-6 sg:mb-12\">\r\n\t\t\t\t\t\t<Avatar className=\"sg:h-24 sg:w-24\">\r\n\t\t\t\t\t\t\t<AvatarImage src={author.avatar} alt={author.name} />\r\n\t\t\t\t\t\t\t<AvatarFallback className=\"sg:text-2xl\">\r\n\t\t\t\t\t\t\t\t{author.name\r\n\t\t\t\t\t\t\t\t\t.split(\" \")\r\n\t\t\t\t\t\t\t\t\t.map((n) => n[0])\r\n\t\t\t\t\t\t\t\t\t.join(\"\")}\r\n\t\t\t\t\t\t\t</AvatarFallback>\r\n\t\t\t\t\t\t</Avatar>\r\n\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t<Heading variant=\"h1\">{author.name}</Heading>\r\n\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-center sg:gap-4 sg:mt-2\">\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"Briefcase\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.role}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"MapPin\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.location}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t<Text\r\n\t\t\t\t\t\t\t\tforeground=\"muted-foreground\"\r\n\t\t\t\t\t\t\t\tclassName=\"sg:mt-4 sg:leading-relaxed sg:max-w-2xl\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t{author.bio}\r\n\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t<Badges className=\"sg:mt-4\">\r\n\t\t\t\t\t\t\t\t{author.interests.map((interest) => (\r\n\t\t\t\t\t\t\t\t\t<CategoryBadge key={interest} category={interest} clickable />\r\n\t\t\t\t\t\t\t\t))}\r\n\t\t\t\t\t\t\t</Badges>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t<Heading variant=\"h3\" className=\"sg:mb-6\">\r\n\t\t\t\t\t\tPosts by {author.name}{\" \"}\r\n\t\t\t\t\t\t<TextSpan foreground=\"muted-foreground\" size=\"lg\" className=\"sg:font-normal\">\r\n\t\t\t\t\t\t\t({authorPosts.length})\r\n\t\t\t\t\t\t</TextSpan>\r\n\t\t\t\t\t</Heading>\r\n\r\n\t\t\t\t\t<PostListWithFilters\r\n\t\t\t\t\t\tposts={authorPosts}\r\n\t\t\t\t\t\thideSearch\r\n\t\t\t\t\t\thideAuthorFilter\r\n\t\t\t\t\t\tfilterMode=\"drawer\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAsCI,SACC,KADD;AApCJ,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,OAAO,mBAAmB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AACpC,SAAS,eAAe;AACxB,SAAS,aAAa;AAMf,SAAS,WAAW,EAAE,OAAO,cAAc,GAAU;AAC3D,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAElD,QAAM,cAAc;AAAA,IACnB,MAAO,SAAS,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,IAAI,IAAI,CAAC;AAAA,IACjE,CAAC,MAAM;AAAA,EACR;AAEA,MAAI,CAAC,QAAQ;AACZ,WACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,+BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,kBAC9B;AAAA,0BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,8BAE1C;AAAA,MACA,oBAAC,cAAW,SAAQ,WAAU,IAAG,mBAAkB,WAAU,aAAY,6BAEzE;AAAA,OACD,GACD;AAAA,EAEF;AAEA,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MACnB;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;AAAA,UAAC;AAAA;AAAA,YACA,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,IAAG;AAAA,YACH,WAAU;AAAA,YACV,WAAU;AAAA,YACV;AAAA;AAAA,QAED;AAAA,QAEA,qBAAC,SAAI,WAAU,uEACd;AAAA,+BAAC,UAAO,WAAU,mBACjB;AAAA,gCAAC,eAAY,KAAK,OAAO,QAAQ,KAAK,OAAO,MAAM;AAAA,YACnD,oBAAC,kBAAe,WAAU,eACxB,iBAAO,KACN,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,KAAK,EAAE,GACV;AAAA,aACD;AAAA,UACA,qBAAC,SACA;AAAA,gCAAC,WAAQ,SAAQ,MAAM,iBAAO,MAAK;AAAA,YACnC,qBAAC,SAAI,WAAU,4CACd;AAAA,mCAAC,QAAK,MAAK,MAAK,YAAW,oBAAmB,WAAU,oCACvD;AAAA,oCAAC,QAAK,MAAK,aAAY,WAAU,qBAAoB;AAAA,gBACpD,OAAO;AAAA,iBACT;AAAA,cACA,qBAAC,QAAK,MAAK,MAAK,YAAW,oBAAmB,WAAU,oCACvD;AAAA,oCAAC,QAAK,MAAK,UAAS,WAAU,qBAAoB;AAAA,gBACjD,OAAO;AAAA,iBACT;AAAA,eACD;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACA,YAAW;AAAA,gBACX,WAAU;AAAA,gBAET,iBAAO;AAAA;AAAA,YACT;AAAA,YACA,oBAAC,UAAO,WAAU,WAChB,iBAAO,UAAU,IAAI,CAAC,aACtB,oBAAC,iBAA6B,UAAU,UAAU,WAAS,QAAvC,QAAwC,CAC5D,GACF;AAAA,aACD;AAAA,WACD;AAAA,QAEA,qBAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU;AAAA;AAAA,UAC/B,OAAO;AAAA,UAAM;AAAA,UACvB,qBAAC,YAAS,YAAW,oBAAmB,MAAK,MAAK,WAAU,kBAAiB;AAAA;AAAA,YAC1E,YAAY;AAAA,YAAO;AAAA,aACtB;AAAA,WACD;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACA,OAAO;AAAA,YACP,YAAU;AAAA,YACV,kBAAgB;AAAA,YAChB,YAAW;AAAA;AAAA,QACZ;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/author/author-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useMemo } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport {\r\n\tLayout,\r\n\tHeading,\r\n\tText,\r\n\tTextSpan,\r\n\tIcon,\r\n\tBadges,\r\n} from \"../../primitives/index\";\r\nimport {\r\n\tAvatar,\r\n\tAvatarFallback,\r\n\tAvatarImage,\r\n} from \"../../primitives/avatar/avatar\";\r\nimport CategoryBadge from \"../../blocks/badges/category-badge\";\r\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\r\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\r\nimport { authors } from \"../../../data/authors\";\r\nimport { posts } from \"../../../data/posts\";\r\n\r\ntype Props = {\r\n\tslug?: string;\r\n};\r\n\r\nexport function AuthorPage({ slug = \"elena-marsh\" }: Props) {\r\n\tconst author = authors.find((a) => a.slug === slug);\r\n\r\n\tconst authorPosts = useMemo(\r\n\t\t() => (author ? posts.filter((p) => p.author === author.name) : []),\r\n\t\t[author],\r\n\t);\r\n\r\n\tif (!author) {\r\n\t\treturn (\r\n\t\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t\t<Layout.Col1 hideDiv className=\"sg:text-center\">\r\n\t\t\t\t\t<Heading variant=\"h2\" className=\"sg:mb-4\">\r\n\t\t\t\t\t\tAuthor not found\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<LinkButton variant=\"outline\" to=\"/extras/authors\" iconStart=\"ArrowLeft\">\r\n\t\t\t\t\t\tBack to Authors\r\n\t\t\t\t\t</LinkButton>\r\n\t\t\t\t</Layout.Col1>\r\n\t\t\t</Layout>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv>\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 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<LinkButton\r\n\t\t\t\t\t\tvariant=\"ghost\"\r\n\t\t\t\t\t\tsize=\"sm\"\r\n\t\t\t\t\t\tto=\"/extras/authors\"\r\n\t\t\t\t\t\ticonStart=\"ArrowLeft\"\r\n\t\t\t\t\t\tclassName=\"sg:mb-6\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\tAll Authors\r\n\t\t\t\t\t</LinkButton>\r\n\r\n\t\t\t\t\t<div className=\"sg:flex sg:flex-col sm:sg:flex-row sg:items-start sg:gap-6 sg:mb-12\">\r\n\t\t\t\t\t\t<Avatar className=\"sg:h-24 sg:w-24\">\r\n\t\t\t\t\t\t\t<AvatarImage src={author.avatar} alt={author.name} />\r\n\t\t\t\t\t\t\t<AvatarFallback className=\"sg:text-2xl\">\r\n\t\t\t\t\t\t\t\t{author.name\r\n\t\t\t\t\t\t\t\t\t.split(\" \")\r\n\t\t\t\t\t\t\t\t\t.map((n) => n[0])\r\n\t\t\t\t\t\t\t\t\t.join(\"\")}\r\n\t\t\t\t\t\t\t</AvatarFallback>\r\n\t\t\t\t\t\t</Avatar>\r\n\t\t\t\t\t\t<div>\r\n\t\t\t\t\t\t\t<Heading variant=\"h1\">{author.name}</Heading>\r\n\t\t\t\t\t\t\t<div className=\"sg:flex sg:items-center sg:gap-4 sg:mt-2\">\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"Briefcase\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.role}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t\t<Text size=\"sm\" foreground=\"muted-foreground\" className=\"sg:flex sg:items-center sg:gap-1\">\r\n\t\t\t\t\t\t\t\t\t<Icon icon=\"MapPin\" className=\"sg:h-3.5 sg:w-3.5\" />\r\n\t\t\t\t\t\t\t\t\t{author.location}\r\n\t\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t<Text\r\n\t\t\t\t\t\t\t\tforeground=\"muted-foreground\"\r\n\t\t\t\t\t\t\t\tclassName=\"sg:mt-4 sg:leading-relaxed sg:max-w-2xl\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t{author.bio}\r\n\t\t\t\t\t\t\t</Text>\r\n\t\t\t\t\t\t\t<Badges className=\"sg:mt-4\">\r\n\t\t\t\t\t\t\t\t{author.interests.map((interest) => (\r\n\t\t\t\t\t\t\t\t\t<CategoryBadge key={interest} category={interest} clickable />\r\n\t\t\t\t\t\t\t\t))}\r\n\t\t\t\t\t\t\t</Badges>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t<Heading variant=\"h3\" className=\"sg:mb-6\">\r\n\t\t\t\t\t\tPosts by {author.name}{\" \"}\r\n\t\t\t\t\t\t<TextSpan foreground=\"muted-foreground\" size=\"lg\" className=\"sg:font-normal\">\r\n\t\t\t\t\t\t\t({authorPosts.length})\r\n\t\t\t\t\t\t</TextSpan>\r\n\t\t\t\t\t</Heading>\r\n\r\n\t\t\t\t\t<PostListWithFilters\r\n\t\t\t\t\t\tposts={authorPosts}\r\n\t\t\t\t\t\thideSearch\r\n\t\t\t\t\t\thideAuthorFilter\r\n\t\t\t\t\t\tfilterMode=\"drawer\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":"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","names":[]}
@@ -8,7 +8,7 @@ function AuthorsPage() {
8
8
  return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, className: "sg:max-w-3xl", children: /* @__PURE__ */ jsxs(
9
9
  motion.div,
10
10
  {
11
- initial: { opacity: 0, y: 20 },
11
+ initial: { opacity: 1, y: 20 },
12
12
  animate: { opacity: 1, y: 0 },
13
13
  transition: { duration: 0.3 },
14
14
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/authors/authors-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { motion } from \"framer-motion\";\r\nimport { Layout, Heading, Text } from \"../../primitives/index\";\r\nimport { AuthorCard } from \"../../blocks/directory/author-card\";\r\nimport { authors } from \"../../../data/authors\";\r\n\r\nexport function AuthorsPage() {\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-3xl\">\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Heading variant=\"h1\" className=\"sg:mb-2\">\r\n\t\t\t\t\t\tAuthors\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<Text foreground=\"muted-foreground\" className=\"sg:mb-10\">\r\n\t\t\t\t\t\tMeet the voices behind the stories.\r\n\t\t\t\t\t</Text>\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-6\">\r\n\t\t\t\t\t\t{authors.map((author) => (\r\n\t\t\t\t\t\t\t<AuthorCard key={author.slug} author={author} />\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAWI,SAKC,KALD;AATJ,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAEjB,SAAS,cAAc;AAC7B,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,gBAC9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACA,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,qBAE1C;AAAA,QACA,oBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW,iDAEzD;AAAA,QACA,oBAAC,SAAI,WAAU,oBACb,kBAAQ,IAAI,CAAC,WACb,oBAAC,cAA6B,UAAb,OAAO,IAAsB,CAC9C,GACF;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/authors/authors-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { motion } from \"framer-motion\";\r\nimport { Layout, Heading, Text } from \"../../primitives/index\";\r\nimport { AuthorCard } from \"../../blocks/directory/author-card\";\r\nimport { authors } from \"../../../data/authors\";\r\n\r\nexport function AuthorsPage() {\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-3xl\">\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 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\tAuthors\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<Text foreground=\"muted-foreground\" className=\"sg:mb-10\">\r\n\t\t\t\t\t\tMeet the voices behind the stories.\r\n\t\t\t\t\t</Text>\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-6\">\r\n\t\t\t\t\t\t{authors.map((author) => (\r\n\t\t\t\t\t\t\t<AuthorCard key={author.slug} author={author} />\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAWI,SAKC,KALD;AATJ,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAEjB,SAAS,cAAc;AAC7B,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,gBAC9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACA,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,qBAE1C;AAAA,QACA,oBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW,iDAEzD;AAAA,QACA,oBAAC,SAAI,WAAU,oBACb,kBAAQ,IAAI,CAAC,WACb,oBAAC,cAA6B,UAAb,OAAO,IAAsB,CAC9C,GACF;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
@@ -1,8 +1,13 @@
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,
@@ -11,12 +16,12 @@ import {
11
16
  UiImage,
12
17
  Badges,
13
18
  Icon,
14
- Textarea,
15
- Input,
16
19
  Heading,
17
20
  Text,
18
21
  TextSpan
19
22
  } from "../../primitives/index.js";
23
+ import { FieldGroup } from "../../primitives/forms/field.js";
24
+ import { Form, FormActions } from "../../primitives/forms/form.js";
20
25
  import { ArrowLeft, ArrowRight } from "lucide-react";
21
26
  import EmptyState from "../../blocks/empty-state/EmptyState.js";
22
27
  import CategoryBadge from "../../blocks/badges/category-badge.js";
@@ -41,8 +46,21 @@ function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
41
46
  const isLoggedIn = true;
42
47
  const { toast } = useToast();
43
48
  const [comments, setComments] = useState(mockComments);
44
- const [name, setName] = useState("");
45
- const [text, setText] = useState("");
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
+ });
46
64
  const [hasLiked, setHasLiked] = useState(false);
47
65
  const [likes, setLikes] = useState(0);
48
66
  const [isGenerating, setIsGenerating] = useState(false);
@@ -101,21 +119,6 @@ function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
101
119
  icon: /* @__PURE__ */ jsx(Icon, { icon: "Check" })
102
120
  });
103
121
  };
104
- const handleSubmit = (e) => {
105
- e.preventDefault();
106
- if (!name.trim() || !text.trim()) return;
107
- setComments((prev) => [
108
- ...prev,
109
- {
110
- id: Date.now().toString(),
111
- author: name,
112
- text,
113
- date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
114
- }
115
- ]);
116
- setName("");
117
- setText("");
118
- };
119
122
  const renderBody = (body) => {
120
123
  return body.split("\n\n").map((block, i) => {
121
124
  if (block.startsWith("## ")) {
@@ -409,34 +412,59 @@ function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
409
412
  ] }),
410
413
  /* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: c.text })
411
414
  ] }, c.id)) }),
412
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "sg:space-y-4", children: [
413
- /* @__PURE__ */ jsx(Heading, { variant: "h4", children: "Leave a comment" }),
414
- /* @__PURE__ */ jsx(
415
- Input,
416
- {
417
- placeholder: "Your name",
418
- value: name,
419
- onChange: (e) => setName(e.target.value),
420
- required: true,
421
- "aria-label": "Your name"
422
- }
423
- ),
424
- /* @__PURE__ */ jsx(
425
- Textarea,
426
- {
427
- placeholder: "Write your comment...",
428
- value: text,
429
- onChange: (e) => setText(e.target.value),
430
- required: true,
431
- "aria-label": "Your comment"
432
- }
433
- ),
434
- /* @__PURE__ */ jsx(Button, { type: "submit", children: "Post comment" })
435
- ] })
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
+ )
436
464
  ] }),
437
465
  relatedPosts.length > 0 && /* @__PURE__ */ jsxs("section", { className: "sg:mt-16 sg:border-t sg:pt-8", children: [
438
466
  /* @__PURE__ */ jsx(Heading, { variant: "h3", className: "sg:mb-6", children: "Related Posts" }),
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(
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(
440
468
  BlogPostCard,
441
469
  {
442
470
  id: p.id,