nextworks 0.1.0-alpha.13 → 0.1.0-alpha.14
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/README.md +20 -9
- package/dist/kits/auth-core/.nextworks/docs/AUTH_CORE_README.md +2 -2
- package/dist/kits/auth-core/.nextworks/docs/AUTH_QUICKSTART.md +264 -244
- package/dist/kits/auth-core/app/(protected)/settings/profile/profile-form.tsx +120 -114
- package/dist/kits/auth-core/app/api/auth/forgot-password/route.ts +116 -114
- package/dist/kits/auth-core/app/api/auth/reset-password/route.ts +66 -63
- package/dist/kits/auth-core/app/api/auth/send-verify-email/route.ts +1 -1
- package/dist/kits/auth-core/app/api/users/[id]/route.ts +134 -127
- package/dist/kits/auth-core/app/auth/reset-password/page.tsx +186 -187
- package/dist/kits/auth-core/components/auth/dashboard.tsx +25 -2
- package/dist/kits/auth-core/components/auth/forgot-password-form.tsx +90 -90
- package/dist/kits/auth-core/components/auth/login-form.tsx +492 -467
- package/dist/kits/auth-core/components/auth/signup-form.tsx +28 -29
- package/dist/kits/auth-core/lib/auth.ts +46 -15
- package/dist/kits/auth-core/lib/forms/map-errors.ts +37 -11
- package/dist/kits/auth-core/lib/server/result.ts +45 -45
- package/dist/kits/auth-core/lib/validation/forms.ts +1 -2
- package/dist/kits/auth-core/package-deps.json +1 -1
- package/dist/kits/auth-core/types/next-auth.d.ts +1 -1
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +2 -8
- package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +18 -1
- package/dist/kits/blocks/app/templates/productlaunch/page.tsx +0 -2
- package/dist/kits/blocks/components/sections/FAQ.tsx +0 -1
- package/dist/kits/blocks/components/sections/Newsletter.tsx +2 -2
- package/dist/kits/blocks/components/ui/switch.tsx +78 -78
- package/dist/kits/blocks/package-deps.json +3 -3
- package/dist/kits/data/.nextworks/docs/DATA_QUICKSTART.md +128 -112
- package/dist/kits/data/.nextworks/docs/DATA_README.md +2 -1
- package/dist/kits/data/app/api/posts/[id]/route.ts +83 -83
- package/dist/kits/data/app/api/posts/route.ts +136 -138
- package/dist/kits/data/app/api/seed-demo/route.ts +1 -2
- package/dist/kits/data/app/api/users/[id]/route.ts +29 -17
- package/dist/kits/data/app/api/users/check-email/route.ts +1 -1
- package/dist/kits/data/app/api/users/check-unique/route.ts +30 -27
- package/dist/kits/data/app/api/users/route.ts +0 -2
- package/dist/kits/data/app/examples/demo/create-post-form.tsx +108 -106
- package/dist/kits/data/app/examples/demo/page.tsx +2 -1
- package/dist/kits/data/app/examples/demo/seed-demo-button.tsx +1 -1
- package/dist/kits/data/components/admin/posts-manager.tsx +727 -719
- package/dist/kits/data/components/admin/users-manager.tsx +435 -432
- package/dist/kits/data/lib/server/result.ts +5 -2
- package/dist/kits/data/package-deps.json +1 -1
- package/dist/kits/data/scripts/seed-demo.mjs +1 -2
- package/dist/kits/forms/app/api/wizard/route.ts +76 -71
- package/dist/kits/forms/app/examples/forms/server-action/page.tsx +78 -71
- package/dist/kits/forms/components/hooks/useCheckUnique.ts +85 -79
- package/dist/kits/forms/components/ui/form/form-control.tsx +28 -28
- package/dist/kits/forms/components/ui/form/form-description.tsx +23 -22
- package/dist/kits/forms/components/ui/form/form-item.tsx +21 -21
- package/dist/kits/forms/components/ui/form/form-label.tsx +24 -24
- package/dist/kits/forms/components/ui/form/form-message.tsx +28 -29
- package/dist/kits/forms/components/ui/switch.tsx +78 -78
- package/dist/kits/forms/lib/forms/map-errors.ts +1 -1
- package/dist/kits/forms/lib/validation/forms.ts +1 -2
- package/package.json +1 -1
|
@@ -1,138 +1,136 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (publishedParam === "
|
|
36
|
-
where.published =
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
orderBy.
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* - If
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
}
|
|
1
|
+
import { ZodError, z } from "zod";
|
|
2
|
+
import { prisma } from "@/lib/prisma";
|
|
3
|
+
import { postSchema } from "@/lib/validation/forms";
|
|
4
|
+
import { getServerSession } from "next-auth";
|
|
5
|
+
import { authOptions } from "@/lib/auth";
|
|
6
|
+
import { jsonOk, jsonFail, jsonFromZod } from "@/lib/server/result";
|
|
7
|
+
|
|
8
|
+
// Ensure Prisma runs in Node.js (not Edge)
|
|
9
|
+
export const runtime = "nodejs";
|
|
10
|
+
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(req.url);
|
|
14
|
+
const page = Math.max(1, Number(url.searchParams.get("page") ?? "1"));
|
|
15
|
+
const perPage = Math.min(
|
|
16
|
+
200,
|
|
17
|
+
Math.max(1, Number(url.searchParams.get("perPage") ?? "8")),
|
|
18
|
+
);
|
|
19
|
+
const q = url.searchParams.get("q") ?? undefined;
|
|
20
|
+
const sortField = url.searchParams.get("sortField") ?? "createdAt";
|
|
21
|
+
const sortDir =
|
|
22
|
+
(url.searchParams.get("sortDir") as "asc" | "desc") ?? "desc";
|
|
23
|
+
|
|
24
|
+
const skip = (page - 1) * perPage;
|
|
25
|
+
|
|
26
|
+
const where: NonNullable<Parameters<typeof prisma.post.count>[0]>["where"] = {};
|
|
27
|
+
if (q) {
|
|
28
|
+
where.title = { contains: q, mode: "insensitive" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Filter by published state if requested: published=published|draft
|
|
32
|
+
const publishedParam = url.searchParams.get("published");
|
|
33
|
+
if (publishedParam === "published") {
|
|
34
|
+
where.published = true;
|
|
35
|
+
} else if (publishedParam === "draft") {
|
|
36
|
+
where.published = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const orderBy: NonNullable<Parameters<typeof prisma.post.findMany>[0]>["orderBy"] = {};
|
|
40
|
+
// Allow server to order by title or createdAt for now. Defaults to createdAt
|
|
41
|
+
if (sortField === "title") {
|
|
42
|
+
orderBy.title = sortDir === "asc" ? "asc" : "desc";
|
|
43
|
+
} else {
|
|
44
|
+
orderBy.createdAt = sortDir === "asc" ? "asc" : "desc";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [total, items] = await Promise.all([
|
|
48
|
+
prisma.post.count({ where }),
|
|
49
|
+
prisma.post.findMany({
|
|
50
|
+
where,
|
|
51
|
+
orderBy,
|
|
52
|
+
skip,
|
|
53
|
+
take: perPage,
|
|
54
|
+
include: { author: { select: { name: true, email: true } } },
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
return jsonOk({ items, total, page, perPage });
|
|
59
|
+
} catch (error: unknown) {
|
|
60
|
+
console.error("GET /api/posts error:", error);
|
|
61
|
+
return jsonFail("Failed to fetch posts", { status: 500 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a new post.
|
|
67
|
+
* DX: authorId is optional. If omitted, we default to the currently signed-in user.
|
|
68
|
+
* - If authorId is provided in the payload, it is used as-is.
|
|
69
|
+
* - Otherwise we read the NextAuth session and use session.user.id.
|
|
70
|
+
* - If neither is available (no session and no authorId), we return 400 to preserve data integrity.
|
|
71
|
+
*/
|
|
72
|
+
export async function POST(req: Request) {
|
|
73
|
+
try {
|
|
74
|
+
// Allow authenticated users to create posts. Admins may create on behalf of others.
|
|
75
|
+
const session = await getServerSession(authOptions);
|
|
76
|
+
if (!session?.user) {
|
|
77
|
+
return jsonFail("Not authenticated", { status: 401 });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 1) Parse and validate the request body. Server-side we allow authorId to be blank/omitted
|
|
81
|
+
// because we can infer it from the authenticated session.
|
|
82
|
+
const body = await req.json();
|
|
83
|
+
const serverPostSchema = postSchema.extend({
|
|
84
|
+
// Make authorId optional for this API (UI may still require it via postSchema)
|
|
85
|
+
authorId: postSchema.shape.authorId.optional().or(z.literal("")),
|
|
86
|
+
});
|
|
87
|
+
const data = serverPostSchema.parse(body);
|
|
88
|
+
|
|
89
|
+
// 2) Resolve the authorId: prefer explicit authorId (only if admin or same as session user), else default to current session user
|
|
90
|
+
const providedAuthorId =
|
|
91
|
+
data.authorId && data.authorId.length > 0 ? data.authorId : undefined;
|
|
92
|
+
|
|
93
|
+
const sessionUserId = (session.user as { id?: string } | undefined)?.id;
|
|
94
|
+
const sessionIsAdmin =
|
|
95
|
+
(session.user as { role?: string } | undefined)?.role === "admin";
|
|
96
|
+
|
|
97
|
+
if (providedAuthorId) {
|
|
98
|
+
// If a non-admin tries to set someone else as the author, reject for safety.
|
|
99
|
+
if (!sessionIsAdmin && providedAuthorId !== sessionUserId) {
|
|
100
|
+
return jsonFail("Forbidden: cannot set authorId to another user", {
|
|
101
|
+
status: 403,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const resolvedAuthorId = providedAuthorId ?? sessionUserId;
|
|
107
|
+
|
|
108
|
+
if (!resolvedAuthorId) {
|
|
109
|
+
return jsonFail(
|
|
110
|
+
"authorId is required. Provide an authorId or sign in so we can default to your user id.",
|
|
111
|
+
{
|
|
112
|
+
status: 400,
|
|
113
|
+
errors: {
|
|
114
|
+
authorId: "Author ID is required or sign in to use your user ID",
|
|
115
|
+
},
|
|
116
|
+
code: "MISSING_AUTHOR",
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const created = await prisma.post.create({
|
|
122
|
+
data: {
|
|
123
|
+
title: data.title,
|
|
124
|
+
content: data.content || null,
|
|
125
|
+
authorId: resolvedAuthorId,
|
|
126
|
+
published: data.published ?? false,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return jsonOk(created, { status: 201, message: "Post created" });
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
if (error instanceof ZodError) {
|
|
132
|
+
return jsonFromZod(error, { status: 400, message: "Validation failed" });
|
|
133
|
+
}
|
|
134
|
+
return jsonFail("Failed to create post", { status: 500 });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { NextRequest } from "next/server";
|
|
2
1
|
import { prisma } from "@/lib/prisma";
|
|
3
2
|
import { jsonOk, jsonFail } from "@/lib/server/result";
|
|
4
3
|
import { getServerSession } from "next-auth";
|
|
@@ -6,7 +5,7 @@ import { authOptions } from "@/lib/auth";
|
|
|
6
5
|
|
|
7
6
|
export const runtime = "nodejs";
|
|
8
7
|
|
|
9
|
-
export async function POST(
|
|
8
|
+
export async function POST() {
|
|
10
9
|
try {
|
|
11
10
|
const session = await getServerSession(authOptions);
|
|
12
11
|
if (!session?.user?.id) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NextRequest } from "next/server";
|
|
2
2
|
import { prisma } from "@/lib/prisma";
|
|
3
|
-
import type { Prisma } from "@prisma/client";
|
|
4
3
|
import { jsonOk, jsonFail } from "@/lib/server/result";
|
|
5
4
|
import { getServerSession } from "next-auth";
|
|
6
5
|
import { authOptions } from "@/lib/auth";
|
|
@@ -59,20 +58,28 @@ export async function PUT(req: NextRequest, { params }: RouteContext) {
|
|
|
59
58
|
try {
|
|
60
59
|
const parsed = userUpdateSchema.parse(body);
|
|
61
60
|
|
|
62
|
-
// Build Prisma-compatible update object
|
|
63
|
-
const data
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
// Build Prisma-compatible update object (avoid mutating `{}` with `satisfies`)
|
|
62
|
+
const data = {
|
|
63
|
+
...(parsed.name !== undefined
|
|
64
|
+
? { name: parsed.name === "" ? null : parsed.name }
|
|
65
|
+
: {}),
|
|
66
|
+
...(parsed.email !== undefined ? { email: parsed.email } : {}),
|
|
67
|
+
...(parsed.image !== undefined
|
|
68
|
+
? { image: parsed.image === "" ? null : parsed.image }
|
|
69
|
+
: {}),
|
|
70
|
+
...(parsed.password !== undefined
|
|
71
|
+
? {
|
|
72
|
+
password: await (async () => {
|
|
73
|
+
const { hashPassword } = await import("@/lib/hash");
|
|
74
|
+
// `password` is present in this branch; help TS narrow from `string | undefined`.
|
|
75
|
+
return hashPassword(parsed.password!);
|
|
76
|
+
})(),
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
79
|
+
...(parsed.emailVerified !== undefined
|
|
80
|
+
? { emailVerified: parsed.emailVerified }
|
|
81
|
+
: {}),
|
|
82
|
+
} satisfies Parameters<typeof prisma.user.update>[0]["data"];
|
|
76
83
|
|
|
77
84
|
const updated = await prisma.user.update({
|
|
78
85
|
where: { id },
|
|
@@ -82,9 +89,14 @@ export async function PUT(req: NextRequest, { params }: RouteContext) {
|
|
|
82
89
|
return jsonOk(updated, { status: 200, message: "User updated" });
|
|
83
90
|
} catch (err) {
|
|
84
91
|
// Zod errors
|
|
85
|
-
if (
|
|
92
|
+
if (
|
|
93
|
+
err &&
|
|
94
|
+
typeof err === "object" &&
|
|
95
|
+
"issues" in err &&
|
|
96
|
+
Array.isArray((err as { issues?: unknown }).issues)
|
|
97
|
+
) {
|
|
86
98
|
const { jsonFromZod } = await import("@/lib/server/result");
|
|
87
|
-
return jsonFromZod(err as
|
|
99
|
+
return jsonFromZod(err as never, {
|
|
88
100
|
status: 400,
|
|
89
101
|
message: "Validation failed",
|
|
90
102
|
});
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
-
import { NextRequest } from "next/server";
|
|
2
|
-
import { prisma } from "@/lib/prisma";
|
|
3
|
-
import { jsonOk, jsonFail } from "@/lib/server/result";
|
|
4
|
-
|
|
5
|
-
export const runtime = "nodejs";
|
|
6
|
-
|
|
7
|
-
export async function POST(req: NextRequest) {
|
|
8
|
-
try {
|
|
9
|
-
const body = await req.json().catch(() => null);
|
|
10
|
-
const field = body?.field;
|
|
11
|
-
const value = body?.value;
|
|
12
|
-
if (!field || !value) return jsonFail("Missing field or value", { status: 400 });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
where
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
1
|
+
import { NextRequest } from "next/server";
|
|
2
|
+
import { prisma } from "@/lib/prisma";
|
|
3
|
+
import { jsonOk, jsonFail } from "@/lib/server/result";
|
|
4
|
+
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export async function POST(req: NextRequest) {
|
|
8
|
+
try {
|
|
9
|
+
const body = await req.json().catch(() => null);
|
|
10
|
+
const field = body?.field;
|
|
11
|
+
const value = body?.value;
|
|
12
|
+
if (!field || !value) return jsonFail("Missing field or value", { status: 400 });
|
|
13
|
+
|
|
14
|
+
// Only allow specific fields for security
|
|
15
|
+
// Note: Prisma `findUnique` requires a UNIQUE field; by default this kit only supports `email`.
|
|
16
|
+
if (field !== "email") {
|
|
17
|
+
return jsonFail("Unsupported field", { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const where = {
|
|
21
|
+
email: value,
|
|
22
|
+
} satisfies Parameters<typeof prisma.user.findUnique>[0]["where"];
|
|
23
|
+
|
|
24
|
+
const user = await prisma.user.findUnique({ where });
|
|
25
|
+
|
|
26
|
+
return jsonOk({ unique: !user });
|
|
27
|
+
} catch {
|
|
28
|
+
return jsonFail("Failed to check uniqueness", { status: 500 });
|
|
29
|
+
}
|
|
30
|
+
}
|