@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arch-cadre/blog-module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "A sample module for Kryo framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"src",
|
|
13
14
|
"locales",
|
|
14
15
|
"manifest.json"
|
|
15
16
|
],
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"build": "pnpm clean && tsc --module esnext"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"@arch-cadre/modules": "^0.0.
|
|
27
|
+
"@arch-cadre/modules": "^0.0.78",
|
|
27
28
|
"@hookform/resolvers": "^3.10.0",
|
|
28
29
|
"@radix-ui/react-slot": "^1.2.3",
|
|
29
30
|
"class-variance-authority": "^0.7.1",
|
|
@@ -46,9 +47,9 @@
|
|
|
46
47
|
"unbuild": "^3.6.1"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"@arch-cadre/core": "^0.0.
|
|
50
|
-
"@arch-cadre/intl": "^0.0.
|
|
51
|
-
"@arch-cadre/ui": "^0.0.
|
|
50
|
+
"@arch-cadre/core": "^0.0.52",
|
|
51
|
+
"@arch-cadre/intl": "^0.0.52",
|
|
52
|
+
"@arch-cadre/ui": "^0.0.52",
|
|
52
53
|
"react": "^19.0.0"
|
|
53
54
|
},
|
|
54
55
|
"main": "./dist/index.mjs"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { commentSchema, postSchema } from "../lib/validation.js";
|
|
3
|
+
export declare function createPost(data: z.infer<typeof postSchema>): Promise<{
|
|
4
|
+
success: boolean;
|
|
5
|
+
error?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
success: boolean;
|
|
8
|
+
error: any;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function getPosts(): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
content: string;
|
|
15
|
+
createdAt: Date;
|
|
16
|
+
author: {
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
}[]>;
|
|
20
|
+
export declare function getPostBySlug(slug: string): Promise<{
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
slug: string;
|
|
24
|
+
content: string;
|
|
25
|
+
createdAt: Date;
|
|
26
|
+
author: {
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
export declare function getPostById(id: string): Promise<{
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
slug: string;
|
|
34
|
+
content: string;
|
|
35
|
+
authorId: string;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function getComments(postId: string): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
content: string;
|
|
41
|
+
createdAt: Date;
|
|
42
|
+
author: {
|
|
43
|
+
name: string;
|
|
44
|
+
image: string | null;
|
|
45
|
+
};
|
|
46
|
+
}[]>;
|
|
47
|
+
export declare function createComment(data: z.infer<typeof commentSchema>): Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
error?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
success: boolean;
|
|
52
|
+
error: any;
|
|
53
|
+
}>;
|
|
54
|
+
export declare function deletePost(id: string): Promise<{
|
|
55
|
+
success: boolean;
|
|
56
|
+
error?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
success: boolean;
|
|
59
|
+
error: any;
|
|
60
|
+
}>;
|
|
61
|
+
export declare function updatePost(id: string, data: z.infer<typeof postSchema>): Promise<{
|
|
62
|
+
success: boolean;
|
|
63
|
+
error?: undefined;
|
|
64
|
+
} | {
|
|
65
|
+
success: boolean;
|
|
66
|
+
error: any;
|
|
67
|
+
}>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { db, getCurrentSession, userTable } from "@arch-cadre/core/server";
|
|
3
|
+
import { desc, eq } from "drizzle-orm";
|
|
4
|
+
import { revalidatePath } from "next/cache";
|
|
5
|
+
import { commentSchema, postSchema } from "../lib/validation.js";
|
|
6
|
+
import { commentsTable, postsTable } from "../schema.js";
|
|
7
|
+
// --- Posts ---
|
|
8
|
+
export async function createPost(data) {
|
|
9
|
+
try {
|
|
10
|
+
const { user } = await getCurrentSession();
|
|
11
|
+
if (!user)
|
|
12
|
+
throw new Error("Unauthorized");
|
|
13
|
+
const validated = postSchema.parse(data);
|
|
14
|
+
await db.insert(postsTable).values({
|
|
15
|
+
...validated,
|
|
16
|
+
authorId: user.id,
|
|
17
|
+
});
|
|
18
|
+
revalidatePath("/blog");
|
|
19
|
+
return { success: true };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("[BlogModule:Actions] createPost failed:", error);
|
|
23
|
+
return { success: false, error: error.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function getPosts() {
|
|
27
|
+
try {
|
|
28
|
+
return await db
|
|
29
|
+
.select({
|
|
30
|
+
id: postsTable.id,
|
|
31
|
+
title: postsTable.title,
|
|
32
|
+
slug: postsTable.slug,
|
|
33
|
+
content: postsTable.content,
|
|
34
|
+
createdAt: postsTable.createdAt,
|
|
35
|
+
author: {
|
|
36
|
+
name: userTable.name,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
.from(postsTable)
|
|
40
|
+
.innerJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
41
|
+
.orderBy(desc(postsTable.createdAt));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error("[BlogModule:Actions] getPosts failed:", error);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function getPostBySlug(slug) {
|
|
49
|
+
try {
|
|
50
|
+
const [post] = await db
|
|
51
|
+
.select({
|
|
52
|
+
id: postsTable.id,
|
|
53
|
+
title: postsTable.title,
|
|
54
|
+
slug: postsTable.slug,
|
|
55
|
+
content: postsTable.content,
|
|
56
|
+
createdAt: postsTable.createdAt,
|
|
57
|
+
author: {
|
|
58
|
+
name: userTable.name,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
.from(postsTable)
|
|
62
|
+
.innerJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
63
|
+
.where(eq(postsTable.slug, slug));
|
|
64
|
+
return post || null;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error("[BlogModule:Actions] getPostBySlug failed:", error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function getPostById(id) {
|
|
72
|
+
try {
|
|
73
|
+
const [post] = await db
|
|
74
|
+
.select()
|
|
75
|
+
.from(postsTable)
|
|
76
|
+
.where(eq(postsTable.id, id));
|
|
77
|
+
return post || null;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error("[BlogModule:Actions] getPostById failed:", error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// --- Comments ---
|
|
85
|
+
export async function getComments(postId) {
|
|
86
|
+
try {
|
|
87
|
+
return await db
|
|
88
|
+
.select({
|
|
89
|
+
id: commentsTable.id,
|
|
90
|
+
content: commentsTable.content,
|
|
91
|
+
createdAt: commentsTable.createdAt,
|
|
92
|
+
author: {
|
|
93
|
+
name: userTable.name,
|
|
94
|
+
image: userTable.image,
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
.from(commentsTable)
|
|
98
|
+
.innerJoin(userTable, eq(commentsTable.authorId, userTable.id))
|
|
99
|
+
.where(eq(commentsTable.postId, postId))
|
|
100
|
+
.orderBy(desc(commentsTable.createdAt));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error("[BlogModule:Actions] getComments failed:", error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function createComment(data) {
|
|
108
|
+
try {
|
|
109
|
+
const { user } = await getCurrentSession();
|
|
110
|
+
if (!user)
|
|
111
|
+
throw new Error("Must be logged in to comment");
|
|
112
|
+
const validated = commentSchema.parse(data);
|
|
113
|
+
await db.insert(commentsTable).values({
|
|
114
|
+
content: validated.content,
|
|
115
|
+
postId: validated.postId,
|
|
116
|
+
authorId: user.id,
|
|
117
|
+
});
|
|
118
|
+
revalidatePath(`/blog`);
|
|
119
|
+
return { success: true };
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error("[BlogModule:Actions] createComment failed:", error);
|
|
123
|
+
return { success: false, error: error.message };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function deletePost(id) {
|
|
127
|
+
try {
|
|
128
|
+
await db.delete(postsTable).where(eq(postsTable.id, id));
|
|
129
|
+
revalidatePath("/blog");
|
|
130
|
+
return { success: true };
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("[BlogModule:Actions] deletePost failed:", error);
|
|
134
|
+
return { success: false, error: error.message };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export async function updatePost(id, data) {
|
|
138
|
+
try {
|
|
139
|
+
const validated = postSchema.parse(data);
|
|
140
|
+
await db.update(postsTable).set(validated).where(eq(postsTable.id, id));
|
|
141
|
+
revalidatePath("/blog");
|
|
142
|
+
revalidatePath(`/blog/${validated.slug}`);
|
|
143
|
+
return { success: true };
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error("[BlogModule:Actions] updatePost failed:", error);
|
|
147
|
+
return { success: false, error: error.message };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { db, getCurrentSession, userTable } from "@arch-cadre/core/server";
|
|
4
|
+
import { desc, eq } from "drizzle-orm";
|
|
5
|
+
import { revalidatePath } from "next/cache";
|
|
6
|
+
import type { z } from "zod";
|
|
7
|
+
import { commentSchema, postSchema } from "../lib/validation.js";
|
|
8
|
+
import { commentsTable, postsTable } from "../schema.js";
|
|
9
|
+
|
|
10
|
+
// --- Posts ---
|
|
11
|
+
export async function createPost(data: z.infer<typeof postSchema>) {
|
|
12
|
+
try {
|
|
13
|
+
const { user } = await getCurrentSession();
|
|
14
|
+
if (!user) throw new Error("Unauthorized");
|
|
15
|
+
|
|
16
|
+
const validated = postSchema.parse(data);
|
|
17
|
+
|
|
18
|
+
await db.insert(postsTable).values({
|
|
19
|
+
...validated,
|
|
20
|
+
authorId: user.id,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
revalidatePath("/blog");
|
|
24
|
+
return { success: true };
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
console.error("[BlogModule:Actions] createPost failed:", error);
|
|
27
|
+
return { success: false, error: error.message };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getPosts() {
|
|
32
|
+
try {
|
|
33
|
+
return await db
|
|
34
|
+
.select({
|
|
35
|
+
id: postsTable.id,
|
|
36
|
+
title: postsTable.title,
|
|
37
|
+
slug: postsTable.slug,
|
|
38
|
+
content: postsTable.content,
|
|
39
|
+
createdAt: postsTable.createdAt,
|
|
40
|
+
author: {
|
|
41
|
+
name: userTable.name,
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.from(postsTable)
|
|
45
|
+
.innerJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
46
|
+
.orderBy(desc(postsTable.createdAt));
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
console.error("[BlogModule:Actions] getPosts failed:", error);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function getPostBySlug(slug: string) {
|
|
54
|
+
try {
|
|
55
|
+
const [post] = await db
|
|
56
|
+
.select({
|
|
57
|
+
id: postsTable.id,
|
|
58
|
+
title: postsTable.title,
|
|
59
|
+
slug: postsTable.slug,
|
|
60
|
+
content: postsTable.content,
|
|
61
|
+
createdAt: postsTable.createdAt,
|
|
62
|
+
author: {
|
|
63
|
+
name: userTable.name,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
.from(postsTable)
|
|
67
|
+
.innerJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
68
|
+
.where(eq(postsTable.slug, slug));
|
|
69
|
+
|
|
70
|
+
return post || null;
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
console.error("[BlogModule:Actions] getPostBySlug failed:", error);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function getPostById(id: string) {
|
|
78
|
+
try {
|
|
79
|
+
const [post] = await db
|
|
80
|
+
.select()
|
|
81
|
+
.from(postsTable)
|
|
82
|
+
.where(eq(postsTable.id, id));
|
|
83
|
+
return post || null;
|
|
84
|
+
} catch (error: any) {
|
|
85
|
+
console.error("[BlogModule:Actions] getPostById failed:", error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- Comments ---
|
|
91
|
+
export async function getComments(postId: string) {
|
|
92
|
+
try {
|
|
93
|
+
return await db
|
|
94
|
+
.select({
|
|
95
|
+
id: commentsTable.id,
|
|
96
|
+
content: commentsTable.content,
|
|
97
|
+
createdAt: commentsTable.createdAt,
|
|
98
|
+
author: {
|
|
99
|
+
name: userTable.name,
|
|
100
|
+
image: userTable.image,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
.from(commentsTable)
|
|
104
|
+
.innerJoin(userTable, eq(commentsTable.authorId, userTable.id))
|
|
105
|
+
.where(eq(commentsTable.postId, postId))
|
|
106
|
+
.orderBy(desc(commentsTable.createdAt));
|
|
107
|
+
} catch (error: any) {
|
|
108
|
+
console.error("[BlogModule:Actions] getComments failed:", error);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function createComment(data: z.infer<typeof commentSchema>) {
|
|
114
|
+
try {
|
|
115
|
+
const { user } = await getCurrentSession();
|
|
116
|
+
if (!user) throw new Error("Must be logged in to comment");
|
|
117
|
+
|
|
118
|
+
const validated = commentSchema.parse(data);
|
|
119
|
+
|
|
120
|
+
await db.insert(commentsTable).values({
|
|
121
|
+
content: validated.content,
|
|
122
|
+
postId: validated.postId,
|
|
123
|
+
authorId: user.id,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
revalidatePath(`/blog`);
|
|
127
|
+
return { success: true };
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
console.error("[BlogModule:Actions] createComment failed:", error);
|
|
130
|
+
return { success: false, error: error.message };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function deletePost(id: string) {
|
|
135
|
+
try {
|
|
136
|
+
await db.delete(postsTable).where(eq(postsTable.id, id));
|
|
137
|
+
revalidatePath("/blog");
|
|
138
|
+
return { success: true };
|
|
139
|
+
} catch (error: any) {
|
|
140
|
+
console.error("[BlogModule:Actions] deletePost failed:", error);
|
|
141
|
+
return { success: false, error: error.message };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function updatePost(id: string, data: z.infer<typeof postSchema>) {
|
|
146
|
+
try {
|
|
147
|
+
const validated = postSchema.parse(data);
|
|
148
|
+
|
|
149
|
+
await db.update(postsTable).set(validated).where(eq(postsTable.id, id));
|
|
150
|
+
revalidatePath("/blog");
|
|
151
|
+
revalidatePath(`/blog/${validated.slug}`);
|
|
152
|
+
return { success: true };
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
console.error("[BlogModule:Actions] updatePost failed:", error);
|
|
155
|
+
return { success: false, error: error.message };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function BlogStatsWidget(): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { db } from "@arch-cadre/core/server";
|
|
3
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@arch-cadre/ui";
|
|
5
|
+
import { sql } from "drizzle-orm";
|
|
6
|
+
import { FileText, MessageSquare } from "lucide-react";
|
|
7
|
+
import { commentsTable, postsTable } from "../schema.js";
|
|
8
|
+
export default async function BlogStatsWidget() {
|
|
9
|
+
const { t } = await getTranslation();
|
|
10
|
+
const [postsCount] = await db
|
|
11
|
+
.select({ count: sql `count(*)` })
|
|
12
|
+
.from(postsTable);
|
|
13
|
+
const [commentsCount] = await db
|
|
14
|
+
.select({ count: sql `count(*)` })
|
|
15
|
+
.from(commentsTable);
|
|
16
|
+
return (_jsxs("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-2", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [_jsx(CardTitle, { className: "text-sm font-medium", children: t("Total Posts") }), _jsx(FileText, { className: "h-4 w-4 text-muted-foreground" })] }), _jsx(CardContent, { children: _jsx("div", { className: "text-2xl font-bold", children: postsCount?.count || 0 }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [_jsx(CardTitle, { className: "text-sm font-medium", children: t("Total Comments") }), _jsx(MessageSquare, { className: "h-4 w-4 text-muted-foreground" })] }), _jsx(CardContent, { children: _jsx("div", { className: "text-2xl font-bold", children: commentsCount?.count || 0 }) })] })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { db } from "@arch-cadre/core/server";
|
|
2
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@arch-cadre/ui";
|
|
4
|
+
import { sql } from "drizzle-orm";
|
|
5
|
+
import { FileText, MessageSquare } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { commentsTable, postsTable } from "../schema.js";
|
|
8
|
+
|
|
9
|
+
export default async function BlogStatsWidget() {
|
|
10
|
+
const { t } = await getTranslation();
|
|
11
|
+
|
|
12
|
+
const [postsCount] = await db
|
|
13
|
+
.select({ count: sql<number>`count(*)` })
|
|
14
|
+
.from(postsTable);
|
|
15
|
+
|
|
16
|
+
const [commentsCount] = await db
|
|
17
|
+
.select({ count: sql<number>`count(*)` })
|
|
18
|
+
.from(commentsTable);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-2">
|
|
22
|
+
<Card>
|
|
23
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
24
|
+
<CardTitle className="text-sm font-medium">
|
|
25
|
+
{t("Total Posts")}
|
|
26
|
+
</CardTitle>
|
|
27
|
+
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
28
|
+
</CardHeader>
|
|
29
|
+
<CardContent>
|
|
30
|
+
<div className="text-2xl font-bold">{postsCount?.count || 0}</div>
|
|
31
|
+
</CardContent>
|
|
32
|
+
</Card>
|
|
33
|
+
<Card>
|
|
34
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
35
|
+
<CardTitle className="text-sm font-medium">
|
|
36
|
+
{t("Total Comments")}
|
|
37
|
+
</CardTitle>
|
|
38
|
+
<MessageSquare className="h-4 w-4 text-muted-foreground" />
|
|
39
|
+
</CardHeader>
|
|
40
|
+
<CardContent>
|
|
41
|
+
<div className="text-2xl font-bold">{commentsCount?.count || 0}</div>
|
|
42
|
+
</CardContent>
|
|
43
|
+
</Card>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function RecentCommentsWidget(): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { db, userTable } from "@arch-cadre/core/server";
|
|
3
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@arch-cadre/ui";
|
|
5
|
+
import { desc, eq } from "drizzle-orm";
|
|
6
|
+
import { commentsTable } from "../schema.js";
|
|
7
|
+
export default async function RecentCommentsWidget() {
|
|
8
|
+
const { t } = await getTranslation();
|
|
9
|
+
const comments = await db
|
|
10
|
+
.select({
|
|
11
|
+
id: commentsTable.id,
|
|
12
|
+
content: commentsTable.content,
|
|
13
|
+
createdAt: commentsTable.createdAt,
|
|
14
|
+
author: {
|
|
15
|
+
name: userTable.name,
|
|
16
|
+
image: userTable.image,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
.from(commentsTable)
|
|
20
|
+
.leftJoin(userTable, eq(commentsTable.authorId, userTable.id))
|
|
21
|
+
.orderBy(desc(commentsTable.createdAt))
|
|
22
|
+
.limit(5);
|
|
23
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t("Recent Comments") }), _jsx(CardDescription, { children: t("What readers are saying about your posts.") })] }), _jsxs(CardContent, { className: "grid gap-8", children: [comments.map((comment) => (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Avatar, { className: "h-9 w-9", children: [_jsx(AvatarImage, { src: comment.author?.image || "", alt: "Avatar" }), _jsx(AvatarFallback, { children: comment.author?.name?.substring(0, 2).toUpperCase() || "CM" })] }), _jsxs("div", { className: "grid gap-1", children: [_jsx("p", { className: "text-sm font-medium leading-none line-clamp-1", children: comment.content }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [comment.author?.name || t("Anonymous"), " \u2022", " ", new Date(comment.createdAt).toLocaleDateString()] })] })] }, comment.id))), comments.length === 0 && (_jsx("div", { className: "text-center py-4 text-muted-foreground", children: t("No comments yet.") }))] })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { db, userTable } from "@arch-cadre/core/server";
|
|
2
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
AvatarFallback,
|
|
6
|
+
AvatarImage,
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@arch-cadre/ui";
|
|
13
|
+
import { desc, eq } from "drizzle-orm";
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import { commentsTable } from "../schema.js";
|
|
16
|
+
|
|
17
|
+
export default async function RecentCommentsWidget() {
|
|
18
|
+
const { t } = await getTranslation();
|
|
19
|
+
|
|
20
|
+
const comments = await db
|
|
21
|
+
.select({
|
|
22
|
+
id: commentsTable.id,
|
|
23
|
+
content: commentsTable.content,
|
|
24
|
+
createdAt: commentsTable.createdAt,
|
|
25
|
+
author: {
|
|
26
|
+
name: userTable.name,
|
|
27
|
+
image: userTable.image,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.from(commentsTable)
|
|
31
|
+
.leftJoin(userTable, eq(commentsTable.authorId, userTable.id))
|
|
32
|
+
.orderBy(desc(commentsTable.createdAt))
|
|
33
|
+
.limit(5);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Card>
|
|
37
|
+
<CardHeader>
|
|
38
|
+
<CardTitle>{t("Recent Comments")}</CardTitle>
|
|
39
|
+
<CardDescription>
|
|
40
|
+
{t("What readers are saying about your posts.")}
|
|
41
|
+
</CardDescription>
|
|
42
|
+
</CardHeader>
|
|
43
|
+
<CardContent className="grid gap-8">
|
|
44
|
+
{comments.map((comment) => (
|
|
45
|
+
<div key={comment.id} className="flex items-center gap-4">
|
|
46
|
+
<Avatar className="h-9 w-9">
|
|
47
|
+
<AvatarImage src={comment.author?.image || ""} alt="Avatar" />
|
|
48
|
+
<AvatarFallback>
|
|
49
|
+
{comment.author?.name?.substring(0, 2).toUpperCase() || "CM"}
|
|
50
|
+
</AvatarFallback>
|
|
51
|
+
</Avatar>
|
|
52
|
+
<div className="grid gap-1">
|
|
53
|
+
<p className="text-sm font-medium leading-none line-clamp-1">
|
|
54
|
+
{comment.content}
|
|
55
|
+
</p>
|
|
56
|
+
<p className="text-sm text-muted-foreground">
|
|
57
|
+
{comment.author?.name || t("Anonymous")} •{" "}
|
|
58
|
+
{new Date(comment.createdAt).toLocaleDateString()}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
{comments.length === 0 && (
|
|
64
|
+
<div className="text-center py-4 text-muted-foreground">
|
|
65
|
+
{t("No comments yet.")}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</CardContent>
|
|
69
|
+
</Card>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function RecentPostsWidget(): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { db, userTable } from "@arch-cadre/core/server";
|
|
3
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@arch-cadre/ui";
|
|
5
|
+
import { desc, eq } from "drizzle-orm";
|
|
6
|
+
import { postsTable } from "../schema.js";
|
|
7
|
+
export default async function RecentPostsWidget() {
|
|
8
|
+
const { t } = await getTranslation();
|
|
9
|
+
const posts = await db
|
|
10
|
+
.select({
|
|
11
|
+
id: postsTable.id,
|
|
12
|
+
title: postsTable.title,
|
|
13
|
+
createdAt: postsTable.createdAt,
|
|
14
|
+
author: {
|
|
15
|
+
name: userTable.name,
|
|
16
|
+
image: userTable.image,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
.from(postsTable)
|
|
20
|
+
.leftJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
21
|
+
.orderBy(desc(postsTable.createdAt))
|
|
22
|
+
.limit(5);
|
|
23
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t("Recent Posts") }), _jsx(CardDescription, { children: t("The latest articles published on your blog.") })] }), _jsxs(CardContent, { className: "grid gap-8", children: [posts.map((post) => (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Avatar, { className: "h-9 w-9", children: [_jsx(AvatarImage, { src: post.author?.image || "", alt: "Avatar" }), _jsx(AvatarFallback, { children: post.author?.name?.substring(0, 2).toUpperCase() || "AU" })] }), _jsxs("div", { className: "grid gap-1", children: [_jsx("p", { className: "text-sm font-medium leading-none", children: post.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: new Date(post.createdAt).toLocaleDateString() })] })] }, post.id))), posts.length === 0 && (_jsx("div", { className: "text-center py-4 text-muted-foreground", children: t("No posts found.") }))] })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { db, userTable } from "@arch-cadre/core/server";
|
|
2
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
AvatarFallback,
|
|
6
|
+
AvatarImage,
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@arch-cadre/ui";
|
|
13
|
+
import { desc, eq } from "drizzle-orm";
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import { postsTable } from "../schema.js";
|
|
16
|
+
|
|
17
|
+
export default async function RecentPostsWidget() {
|
|
18
|
+
const { t } = await getTranslation();
|
|
19
|
+
|
|
20
|
+
const posts = await db
|
|
21
|
+
.select({
|
|
22
|
+
id: postsTable.id,
|
|
23
|
+
title: postsTable.title,
|
|
24
|
+
createdAt: postsTable.createdAt,
|
|
25
|
+
author: {
|
|
26
|
+
name: userTable.name,
|
|
27
|
+
image: userTable.image,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.from(postsTable)
|
|
31
|
+
.leftJoin(userTable, eq(postsTable.authorId, userTable.id))
|
|
32
|
+
.orderBy(desc(postsTable.createdAt))
|
|
33
|
+
.limit(5);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Card>
|
|
37
|
+
<CardHeader>
|
|
38
|
+
<CardTitle>{t("Recent Posts")}</CardTitle>
|
|
39
|
+
<CardDescription>
|
|
40
|
+
{t("The latest articles published on your blog.")}
|
|
41
|
+
</CardDescription>
|
|
42
|
+
</CardHeader>
|
|
43
|
+
<CardContent className="grid gap-8">
|
|
44
|
+
{posts.map((post) => (
|
|
45
|
+
<div key={post.id} className="flex items-center gap-4">
|
|
46
|
+
<Avatar className="h-9 w-9">
|
|
47
|
+
<AvatarImage src={post.author?.image || ""} alt="Avatar" />
|
|
48
|
+
<AvatarFallback>
|
|
49
|
+
{post.author?.name?.substring(0, 2).toUpperCase() || "AU"}
|
|
50
|
+
</AvatarFallback>
|
|
51
|
+
</Avatar>
|
|
52
|
+
<div className="grid gap-1">
|
|
53
|
+
<p className="text-sm font-medium leading-none">{post.title}</p>
|
|
54
|
+
<p className="text-sm text-muted-foreground">
|
|
55
|
+
{new Date(post.createdAt).toLocaleDateString()}
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
{posts.length === 0 && (
|
|
61
|
+
<div className="text-center py-4 text-muted-foreground">
|
|
62
|
+
{t("No posts found.")}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
);
|
|
68
|
+
}
|