@arch-cadre/blog-module 1.0.8 → 1.0.9
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/package.json +6 -5
- package/src/actions/index.d.ts +67 -0
- package/src/actions/index.js +149 -0
- package/src/actions/index.ts +157 -0
- package/src/components/BlogStatsWidget.d.ts +1 -0
- package/src/components/BlogStatsWidget.js +17 -0
- package/src/components/BlogStatsWidget.tsx +46 -0
- package/src/components/RecentCommentsWidget.d.ts +1 -0
- package/src/components/RecentCommentsWidget.js +24 -0
- package/src/components/RecentCommentsWidget.tsx +71 -0
- package/src/components/RecentPostsWidget.d.ts +1 -0
- package/src/components/RecentPostsWidget.js +24 -0
- package/src/components/RecentPostsWidget.tsx +68 -0
- package/src/components/ui/button.d.ts +11 -0
- package/src/components/ui/button.js +33 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.d.ts +6 -0
- package/src/components/ui/card.js +12 -0
- package/src/components/ui/card.tsx +51 -0
- package/src/components/ui/input.d.ts +5 -0
- package/src/components/ui/input.js +8 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/table.d.ts +8 -0
- package/src/components/ui/table.js +16 -0
- package/src/components/ui/table.tsx +83 -0
- package/src/components/ui/textarea.d.ts +5 -0
- package/src/components/ui/textarea.js +8 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +98 -0
- package/src/index.ts +121 -0
- package/src/intl.d.ts +7 -0
- package/src/lib/utils.d.ts +2 -0
- package/src/lib/utils.js +5 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.d.ts +24 -0
- package/src/lib/validation.js +11 -0
- package/src/lib/validation.ts +13 -0
- package/src/navigation.d.ts +2 -0
- package/src/navigation.js +21 -0
- package/src/navigation.ts +23 -0
- package/src/routes.d.ts +3 -0
- package/src/routes.js +55 -0
- package/src/routes.tsx +74 -0
- package/src/schema.d.ts +736 -0
- package/src/schema.js +60 -0
- package/src/schema.ts +67 -0
- package/src/styles/globals.css +123 -0
- package/src/ui/views.d.ts +15 -0
- package/src/ui/views.js +119 -0
- package/src/ui/views.tsx +538 -0
package/src/ui/views.tsx
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/** biome-ignore-all lint/a11y/noLabelWithoutControl: <explanation> */
|
|
2
|
+
"use client";
|
|
3
|
+
import { useTranslation } from "@arch-cadre/intl/client";
|
|
4
|
+
|
|
5
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
|
+
import {
|
|
7
|
+
ArrowLeft,
|
|
8
|
+
Eye,
|
|
9
|
+
MessageSquare,
|
|
10
|
+
Pencil,
|
|
11
|
+
Plus,
|
|
12
|
+
Trash2,
|
|
13
|
+
User as UserIcon,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import Link from "next/link";
|
|
16
|
+
import { useRouter } from "next/navigation";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import { useForm } from "react-hook-form";
|
|
19
|
+
import { toast } from "sonner";
|
|
20
|
+
import { createComment, createPost, deletePost, updatePost } from "../actions/index.js";
|
|
21
|
+
import { Button } from "../components/ui/button.js";
|
|
22
|
+
import {
|
|
23
|
+
Card,
|
|
24
|
+
CardContent,
|
|
25
|
+
CardHeader,
|
|
26
|
+
CardTitle,
|
|
27
|
+
} from "../components/ui/card.js";
|
|
28
|
+
import { Input } from "../components/ui/input.js";
|
|
29
|
+
import {
|
|
30
|
+
Table,
|
|
31
|
+
TableBody,
|
|
32
|
+
TableCell,
|
|
33
|
+
TableHead,
|
|
34
|
+
TableHeader,
|
|
35
|
+
TableRow,
|
|
36
|
+
} from "../components/ui/table.js";
|
|
37
|
+
import { Textarea } from "../components/ui/textarea.js";
|
|
38
|
+
import { commentSchema, postSchema } from "../lib/validation.js";
|
|
39
|
+
|
|
40
|
+
export function BlogListPage({ posts = [] }: { posts?: any[] }) {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
return (
|
|
43
|
+
<div className="max-w-4xl mx-auto p-6 space-y-8">
|
|
44
|
+
<header className="border-b pb-6">
|
|
45
|
+
<h1 className="text-4xl font-black tracking-tighter text-primary">
|
|
46
|
+
{t("Blog Posts")}
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="text-muted-foreground mt-2">
|
|
49
|
+
{t("Reading your blog posts")}
|
|
50
|
+
</p>
|
|
51
|
+
</header>
|
|
52
|
+
<div className="grid gap-6">
|
|
53
|
+
{posts.map((post) => (
|
|
54
|
+
<Link href={`/blog/${post.slug}`} key={post.id}>
|
|
55
|
+
<Card className="group hover:shadow-lg transition-all cursor-pointer overflow-hidden border-primary/5">
|
|
56
|
+
<CardHeader>
|
|
57
|
+
<CardTitle className="text-2xl group-hover:text-primary transition-colors">
|
|
58
|
+
{post.title}
|
|
59
|
+
</CardTitle>
|
|
60
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-1">
|
|
61
|
+
<UserIcon className="size-3" />
|
|
62
|
+
<span>{post.author?.name}</span>
|
|
63
|
+
<span>•</span>
|
|
64
|
+
<span>{new Date(post.createdAt).toLocaleDateString()}</span>
|
|
65
|
+
</div>
|
|
66
|
+
</CardHeader>
|
|
67
|
+
<CardContent>
|
|
68
|
+
<p className="text-secondary-foreground line-clamp-2">
|
|
69
|
+
{post.content}
|
|
70
|
+
</p>
|
|
71
|
+
</CardContent>
|
|
72
|
+
</Card>
|
|
73
|
+
</Link>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Comment Form Component ---
|
|
81
|
+
function CommentForm({ postId }: { postId: string }) {
|
|
82
|
+
const router = useRouter();
|
|
83
|
+
const { t } = useTranslation();
|
|
84
|
+
const {
|
|
85
|
+
register,
|
|
86
|
+
handleSubmit,
|
|
87
|
+
reset,
|
|
88
|
+
formState: { errors, isSubmitting },
|
|
89
|
+
} = useForm({
|
|
90
|
+
resolver: zodResolver(commentSchema.omit({ postId: true })),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const onSubmit = async (data: any) => {
|
|
94
|
+
const result = await createComment({ ...data, postId });
|
|
95
|
+
if (result.success) {
|
|
96
|
+
toast.success(t("Comment added"));
|
|
97
|
+
reset();
|
|
98
|
+
router.refresh();
|
|
99
|
+
} else {
|
|
100
|
+
toast.error(result.error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
106
|
+
<div className="space-y-1">
|
|
107
|
+
<Textarea
|
|
108
|
+
{...register("content")}
|
|
109
|
+
placeholder={t("Add your comment here...")}
|
|
110
|
+
className={errors.content ? "border-destructive" : ""}
|
|
111
|
+
/>
|
|
112
|
+
{errors.content && (
|
|
113
|
+
<p className="text-xs text-destructive">
|
|
114
|
+
{errors.content.message as string}
|
|
115
|
+
</p>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
<Button type="submit" disabled={isSubmitting} size="sm">
|
|
119
|
+
{isSubmitting ? t("Please wait...") : t("Create Comment")}
|
|
120
|
+
</Button>
|
|
121
|
+
</form>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- Detail View ---
|
|
126
|
+
export function PostDetailPage({
|
|
127
|
+
post,
|
|
128
|
+
comments = [],
|
|
129
|
+
currentUser,
|
|
130
|
+
}: {
|
|
131
|
+
post: any;
|
|
132
|
+
comments: any[];
|
|
133
|
+
currentUser?: any;
|
|
134
|
+
}) {
|
|
135
|
+
const { t } = useTranslation();
|
|
136
|
+
|
|
137
|
+
if (!post)
|
|
138
|
+
return <div className="p-20 text-center">{t("Post not found")}</div>;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div className="max-w-3xl mx-auto p-6 space-y-10 pb-20">
|
|
142
|
+
<Button asChild variant="ghost" size="sm" className="gap-2">
|
|
143
|
+
<Link href="/blog">
|
|
144
|
+
<ArrowLeft className="size-4" /> {t("Back to posts")}
|
|
145
|
+
</Link>
|
|
146
|
+
</Button>
|
|
147
|
+
|
|
148
|
+
<article className="space-y-6">
|
|
149
|
+
<div className="space-y-2">
|
|
150
|
+
<h1 className="text-5xl font-black tracking-tighter leading-tight">
|
|
151
|
+
{post.title}
|
|
152
|
+
</h1>
|
|
153
|
+
<div className="flex items-center gap-3 text-muted-foreground">
|
|
154
|
+
<div className="size-8 rounded-full bg-primary/10 flex items-center justify-center">
|
|
155
|
+
<UserIcon className="size-4 text-primary" />
|
|
156
|
+
</div>
|
|
157
|
+
<div className="text-sm">
|
|
158
|
+
<p className="font-bold text-foreground leading-none">
|
|
159
|
+
{post.author?.name}
|
|
160
|
+
</p>
|
|
161
|
+
<p className="text-xs">
|
|
162
|
+
{new Date(post.createdAt).toLocaleDateString()}
|
|
163
|
+
</p>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="prose prose-lg dark:prose-invert max-w-none whitespace-pre-wrap pt-4 border-t">
|
|
168
|
+
{post.content}
|
|
169
|
+
</div>
|
|
170
|
+
</article>
|
|
171
|
+
|
|
172
|
+
<section className="pt-10 border-t space-y-8">
|
|
173
|
+
<div className="flex items-center gap-2">
|
|
174
|
+
<MessageSquare className="size-5" />
|
|
175
|
+
<h3 className="text-2xl font-bold tracking-tight">
|
|
176
|
+
{t("Comments")} ({comments.length})
|
|
177
|
+
</h3>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{currentUser ? (
|
|
181
|
+
<div className="bg-muted/30 p-6 rounded-2xl border">
|
|
182
|
+
<p className="text-sm font-bold mb-4">{t("Leave a comment")}</p>
|
|
183
|
+
<CommentForm postId={post.id} />
|
|
184
|
+
</div>
|
|
185
|
+
) : (
|
|
186
|
+
<div className="p-6 bg-amber-50 border border-amber-100 rounded-2xl text-center">
|
|
187
|
+
<p className="text-sm font-medium text-amber-800">
|
|
188
|
+
Please{" "}
|
|
189
|
+
<Link href="/signin" className="underline font-bold">
|
|
190
|
+
{t("Sign in")}
|
|
191
|
+
</Link>{" "}
|
|
192
|
+
{t("to join the conversation.")}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
<div className="space-y-6">
|
|
198
|
+
{comments.map((comment) => (
|
|
199
|
+
<div key={comment.id} className="flex gap-4">
|
|
200
|
+
<div className="size-10 rounded-full bg-muted flex items-center justify-center shrink-0">
|
|
201
|
+
<UserIcon className="size-5 opacity-40" />
|
|
202
|
+
</div>
|
|
203
|
+
<div className="space-y-1">
|
|
204
|
+
<div className="flex items-center gap-2">
|
|
205
|
+
<span className="font-bold text-sm">
|
|
206
|
+
{comment.author?.name}
|
|
207
|
+
</span>
|
|
208
|
+
<span className="text-[10px] opacity-50">
|
|
209
|
+
{new Date(comment.createdAt).toLocaleDateString()}
|
|
210
|
+
</span>
|
|
211
|
+
</div>
|
|
212
|
+
<p className="text-sm text-secondary-foreground leading-relaxed">
|
|
213
|
+
{comment.content}
|
|
214
|
+
</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
))}
|
|
218
|
+
{comments.length === 0 && (
|
|
219
|
+
<p className="text-center text-muted-foreground text-sm py-10 italic">
|
|
220
|
+
{t("No comments yet. Be the first to comment!")}
|
|
221
|
+
</p>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
</section>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Admin View Components ---
|
|
230
|
+
|
|
231
|
+
export function BlogAdminPage({ posts = [] }: { posts: any[] }) {
|
|
232
|
+
const router = useRouter();
|
|
233
|
+
const { t } = useTranslation();
|
|
234
|
+
|
|
235
|
+
const handleDelete = async (id: string) => {
|
|
236
|
+
if (!confirm(t("Confirm deletion of this post?"))) return;
|
|
237
|
+
|
|
238
|
+
const result = await deletePost(id);
|
|
239
|
+
if (result.success) {
|
|
240
|
+
toast.success(t("Post deleted"));
|
|
241
|
+
router.refresh();
|
|
242
|
+
} else {
|
|
243
|
+
toast.error(t("UnexpectedError") + result.error);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div className="p-6 space-y-6">
|
|
249
|
+
<div className="flex justify-between items-center">
|
|
250
|
+
<h1 className="text-3xl font-black tracking-tight text-foreground">
|
|
251
|
+
{t("Blog posts")}
|
|
252
|
+
</h1>
|
|
253
|
+
<Button asChild size="sm" className="gap-2">
|
|
254
|
+
<Link href="/kryo/blog/new">
|
|
255
|
+
<Plus className="size-4" /> {t("New Post")}
|
|
256
|
+
</Link>
|
|
257
|
+
</Button>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<Card className="border-primary/10 shadow-sm overflow-hidden">
|
|
261
|
+
<Table>
|
|
262
|
+
<TableHeader>
|
|
263
|
+
<TableRow>
|
|
264
|
+
<TableHead className="w-[40%]">{t("Title")}</TableHead>
|
|
265
|
+
<TableHead>{t("Author")}</TableHead>
|
|
266
|
+
<TableHead>{t("Date")}</TableHead>
|
|
267
|
+
<TableHead className="text-right">{t("Actions")}</TableHead>
|
|
268
|
+
</TableRow>
|
|
269
|
+
</TableHeader>
|
|
270
|
+
<TableBody>
|
|
271
|
+
{posts.length === 0 && (
|
|
272
|
+
<TableRow>
|
|
273
|
+
<TableCell
|
|
274
|
+
colSpan={4}
|
|
275
|
+
className="h-24 text-center text-muted-foreground italic"
|
|
276
|
+
>
|
|
277
|
+
{t("No posts found. Create your first post!")}
|
|
278
|
+
</TableCell>
|
|
279
|
+
</TableRow>
|
|
280
|
+
)}
|
|
281
|
+
{posts.map((post) => (
|
|
282
|
+
<TableRow key={post.id} className="transition-colors">
|
|
283
|
+
<TableCell className="font-bold">{post.title}</TableCell>
|
|
284
|
+
<TableCell className="text-xs">{post.author?.name}</TableCell>
|
|
285
|
+
<TableCell className="text-muted-foreground text-xs">
|
|
286
|
+
{new Date(post.createdAt).toLocaleDateString()}
|
|
287
|
+
</TableCell>
|
|
288
|
+
<TableCell className="text-right">
|
|
289
|
+
<div className="flex justify-end gap-2">
|
|
290
|
+
<Button
|
|
291
|
+
asChild
|
|
292
|
+
variant="ghost"
|
|
293
|
+
size="icon"
|
|
294
|
+
className="size-8"
|
|
295
|
+
>
|
|
296
|
+
<Link href={`/blog/${post.slug}`} target="_blank">
|
|
297
|
+
<Eye className="size-4" />
|
|
298
|
+
</Link>
|
|
299
|
+
</Button>
|
|
300
|
+
<Button
|
|
301
|
+
asChild
|
|
302
|
+
variant="ghost"
|
|
303
|
+
size="icon"
|
|
304
|
+
className="size-8"
|
|
305
|
+
>
|
|
306
|
+
<Link href={`/kryo/blog/edit/${post.id}`}>
|
|
307
|
+
<Pencil className="size-4" />
|
|
308
|
+
</Link>
|
|
309
|
+
</Button>
|
|
310
|
+
<Button
|
|
311
|
+
variant="ghost"
|
|
312
|
+
size="icon"
|
|
313
|
+
className="size-8 text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
314
|
+
onClick={() => handleDelete(post.id)}
|
|
315
|
+
>
|
|
316
|
+
<Trash2 className="size-4" />
|
|
317
|
+
</Button>
|
|
318
|
+
</div>
|
|
319
|
+
</TableCell>
|
|
320
|
+
</TableRow>
|
|
321
|
+
))}
|
|
322
|
+
</TableBody>
|
|
323
|
+
</Table>
|
|
324
|
+
</Card>
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function CreatePostForm() {
|
|
330
|
+
const router = useRouter();
|
|
331
|
+
const { t } = useTranslation();
|
|
332
|
+
const {
|
|
333
|
+
register,
|
|
334
|
+
handleSubmit,
|
|
335
|
+
formState: { errors, isSubmitting },
|
|
336
|
+
} = useForm({
|
|
337
|
+
resolver: zodResolver(postSchema.omit({ slug: true })),
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const onSubmit = async (data: any) => {
|
|
341
|
+
const slug = data.title
|
|
342
|
+
.toLowerCase()
|
|
343
|
+
.replace(/ /g, "-")
|
|
344
|
+
.replace(/[^\w-]+/g, "");
|
|
345
|
+
const result = await createPost({ ...data, slug });
|
|
346
|
+
|
|
347
|
+
if (result.success) {
|
|
348
|
+
toast.success(t("Post created"));
|
|
349
|
+
router.push("/kryo/blog");
|
|
350
|
+
router.refresh();
|
|
351
|
+
} else {
|
|
352
|
+
toast.error(t("UnexpectedError") + result.error);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div className="max-w-2xl mx-auto p-6 space-y-6">
|
|
358
|
+
<div className="flex items-center gap-4">
|
|
359
|
+
<Button asChild variant="outline" size="icon" className="rounded-lg">
|
|
360
|
+
<Link href="/kryo/blog">
|
|
361
|
+
<ArrowLeft className="size-4" />
|
|
362
|
+
</Link>
|
|
363
|
+
</Button>
|
|
364
|
+
<h1 className="text-2xl font-black text-foreground">{t("New Post")}</h1>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<Card className="border-primary/10 shadow-lg">
|
|
368
|
+
<CardContent className="pt-6">
|
|
369
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
370
|
+
<div className="space-y-2">
|
|
371
|
+
<label className="text-xs font-black uppercase tracking-widest opacity-50 px-1">
|
|
372
|
+
{t("Title")}
|
|
373
|
+
</label>
|
|
374
|
+
<Input
|
|
375
|
+
{...register("title")}
|
|
376
|
+
className={
|
|
377
|
+
errors.title ? "border-destructive font-bold" : "font-bold"
|
|
378
|
+
}
|
|
379
|
+
placeholder={t("Title of your post")}
|
|
380
|
+
/>
|
|
381
|
+
{errors.title && (
|
|
382
|
+
<p className="text-xs text-destructive font-bold">
|
|
383
|
+
{errors.title.message as string}
|
|
384
|
+
</p>
|
|
385
|
+
)}
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<div className="space-y-2">
|
|
389
|
+
<label className="text-xs font-black uppercase tracking-widest opacity-50 px-1">
|
|
390
|
+
{t("Content")}
|
|
391
|
+
</label>
|
|
392
|
+
<Textarea
|
|
393
|
+
{...register("content")}
|
|
394
|
+
rows={12}
|
|
395
|
+
className={
|
|
396
|
+
errors.content
|
|
397
|
+
? "border-destructive resize-none"
|
|
398
|
+
: "resize-none"
|
|
399
|
+
}
|
|
400
|
+
placeholder={t("Content of your post")}
|
|
401
|
+
/>
|
|
402
|
+
{errors.content && (
|
|
403
|
+
<p className="text-xs text-destructive font-bold">
|
|
404
|
+
{errors.content.message as string}
|
|
405
|
+
</p>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<Button
|
|
410
|
+
type="submit"
|
|
411
|
+
disabled={isSubmitting}
|
|
412
|
+
className="w-full text-sm uppercase tracking-widest transition-all"
|
|
413
|
+
>
|
|
414
|
+
{isSubmitting ? t("Please wait...") : t("Publish")}
|
|
415
|
+
</Button>
|
|
416
|
+
</form>
|
|
417
|
+
</CardContent>
|
|
418
|
+
</Card>
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function EditPostForm({ post }: { post: any }) {
|
|
424
|
+
const router = useRouter();
|
|
425
|
+
const { t } = useTranslation();
|
|
426
|
+
const {
|
|
427
|
+
register,
|
|
428
|
+
handleSubmit,
|
|
429
|
+
formState: { errors, isSubmitting },
|
|
430
|
+
} = useForm({
|
|
431
|
+
resolver: zodResolver(postSchema),
|
|
432
|
+
defaultValues: {
|
|
433
|
+
title: post.title,
|
|
434
|
+
content: post.content,
|
|
435
|
+
slug: post.slug,
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const onSubmit = async (data: any) => {
|
|
440
|
+
const result = await updatePost(post.id, data);
|
|
441
|
+
|
|
442
|
+
if (result.success) {
|
|
443
|
+
toast.success(t("Post updated"));
|
|
444
|
+
router.push("/kryo/blog");
|
|
445
|
+
router.refresh();
|
|
446
|
+
} else {
|
|
447
|
+
toast.error(t("UnexpectedError") + result.error);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<div className="max-w-2xl mx-auto p-6 space-y-6">
|
|
453
|
+
<div className="flex items-center gap-4">
|
|
454
|
+
<Button asChild variant="outline" size="icon" className="rounded-lg">
|
|
455
|
+
<Link href="/kryo/blog">
|
|
456
|
+
<ArrowLeft className="size-4" />
|
|
457
|
+
</Link>
|
|
458
|
+
</Button>
|
|
459
|
+
<h1 className="text-2xl font-black text-foreground">
|
|
460
|
+
{t("Edit Post")}
|
|
461
|
+
</h1>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<Card className="border-primary/10 shadow-lg">
|
|
465
|
+
<CardContent className="pt-6">
|
|
466
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
467
|
+
<div className="space-y-2">
|
|
468
|
+
<label className="text-xs font-black uppercase tracking-widest opacity-50 px-1">
|
|
469
|
+
{t("Title")}
|
|
470
|
+
</label>
|
|
471
|
+
<Input
|
|
472
|
+
{...register("title")}
|
|
473
|
+
className={
|
|
474
|
+
errors.title ? "border-destructive font-bold" : "font-bold"
|
|
475
|
+
}
|
|
476
|
+
placeholder={t("Title of your post")}
|
|
477
|
+
/>
|
|
478
|
+
{errors.title && (
|
|
479
|
+
<p className="text-xs text-destructive font-bold">
|
|
480
|
+
{errors.title.message as string}
|
|
481
|
+
</p>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<div className="space-y-2">
|
|
486
|
+
<label className="text-xs font-black uppercase tracking-widest opacity-50 px-1">
|
|
487
|
+
{t("Slug")}
|
|
488
|
+
</label>
|
|
489
|
+
<Input
|
|
490
|
+
{...register("slug")}
|
|
491
|
+
className={
|
|
492
|
+
errors.slug
|
|
493
|
+
? "border-destructive font-mono text-sm"
|
|
494
|
+
: "font-mono text-sm"
|
|
495
|
+
}
|
|
496
|
+
placeholder={t("Slug URL")}
|
|
497
|
+
/>
|
|
498
|
+
{errors.slug && (
|
|
499
|
+
<p className="text-xs text-destructive font-bold">
|
|
500
|
+
{errors.slug.message as string}
|
|
501
|
+
</p>
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div className="space-y-2">
|
|
506
|
+
<label className="text-xs font-black uppercase tracking-widest opacity-50 px-1">
|
|
507
|
+
{t("Content")}
|
|
508
|
+
</label>
|
|
509
|
+
<Textarea
|
|
510
|
+
{...register("content")}
|
|
511
|
+
rows={12}
|
|
512
|
+
className={
|
|
513
|
+
errors.content
|
|
514
|
+
? "border-destructive resize-none"
|
|
515
|
+
: "resize-none"
|
|
516
|
+
}
|
|
517
|
+
placeholder={t("Content of your post")}
|
|
518
|
+
/>
|
|
519
|
+
{errors.content && (
|
|
520
|
+
<p className="text-xs text-destructive font-bold">
|
|
521
|
+
{errors.content.message as string}
|
|
522
|
+
</p>
|
|
523
|
+
)}
|
|
524
|
+
</div>
|
|
525
|
+
|
|
526
|
+
<Button
|
|
527
|
+
type="submit"
|
|
528
|
+
disabled={isSubmitting}
|
|
529
|
+
className="w-full text-sm uppercase tracking-widest transition-all"
|
|
530
|
+
>
|
|
531
|
+
{isSubmitting ? t("Please wait...") : t("Update")}
|
|
532
|
+
</Button>
|
|
533
|
+
</form>
|
|
534
|
+
</CardContent>
|
|
535
|
+
</Card>
|
|
536
|
+
</div>
|
|
537
|
+
);
|
|
538
|
+
}
|