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