nextworks 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (37) hide show
  1. package/README.md +61 -2
  2. package/dist/cli_manifests/auth_manifest.json +7 -1
  3. package/dist/cli_manifests/blocks_manifest.json +7 -6
  4. package/dist/cli_manifests/data_manifest.json +4 -2
  5. package/dist/cli_manifests/forms_manifest.json +6 -3
  6. package/dist/commands/blocks.d.ts +1 -0
  7. package/dist/commands/blocks.d.ts.map +1 -1
  8. package/dist/commands/blocks.js +82 -10
  9. package/dist/commands/blocks.js.map +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/kits/auth-core/{README.md → .nextworks/docs/AUTH_CORE_README.md} +35 -0
  13. package/dist/kits/auth-core/.nextworks/docs/AUTH_QUICKSTART.md +244 -0
  14. package/dist/kits/auth-core/LICENSE +21 -0
  15. package/dist/kits/auth-core/app/(protected)/admin/posts/page.tsx +29 -0
  16. package/dist/kits/auth-core/app/(protected)/admin/users/page.tsx +29 -0
  17. package/dist/kits/auth-core/app/api/users/[id]/route.ts +127 -0
  18. package/dist/kits/auth-core/components/ui/button.tsx +84 -17
  19. package/dist/kits/auth-core/components/ui/input.tsx +5 -3
  20. package/dist/kits/auth-core/components/ui/label.tsx +10 -4
  21. package/dist/kits/auth-core/lib/prisma.ts +10 -0
  22. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +193 -0
  23. package/dist/kits/blocks/{README.md → .nextworks/docs/BLOCKS_README.md} +12 -0
  24. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +223 -0
  25. package/dist/kits/blocks/LICENSE +21 -0
  26. package/dist/kits/data/.nextworks/docs/DATA_QUICKSTART.md +112 -0
  27. package/dist/kits/data/{README.md → .nextworks/docs/DATA_README.md} +37 -0
  28. package/dist/kits/data/LICENSE +21 -0
  29. package/dist/kits/data/lib/prisma.ts +10 -0
  30. package/dist/kits/forms/.nextworks/docs/FORMS_QUICKSTART.md +85 -0
  31. package/dist/kits/forms/{README.md → .nextworks/docs/FORMS_README.md} +12 -0
  32. package/dist/kits/forms/LICENSE +21 -0
  33. package/package.json +4 -1
  34. package/dist/.gitkeep +0 -0
  35. package/dist/kits/blocks/components/ui/button_bck.tsx +0 -93
  36. package/dist/kits/blocks/lib/themes_old.ts +0 -37
  37. package/dist/kits/blocks/notes/THEMING_CONVERSION_SUMMARY.md +0 -14
@@ -0,0 +1,244 @@
1
+ # Auth Quickstart
2
+
3
+ Follow these steps to get email/password auth (and optional GitHub OAuth) running in under 5 minutes.
4
+
5
+ If you are using the `nextworks` CLI in your own app, the recommended alpha setup is to install Blocks with sections and templates first, then Auth Core:
6
+
7
+ ```bash
8
+ npx nextworks add blocks --sections --templates
9
+ npx nextworks add auth-core
10
+ ```
11
+
12
+ Then follow the steps below inside your app.
13
+
14
+ ## 1) Copy environment variables
15
+
16
+ Create a `.env` file based on `.env.example`:
17
+
18
+ ```
19
+ cp .env.example .env
20
+ ```
21
+
22
+ Fill in values for the following environment variables used by the Auth kit:
23
+
24
+ - DATABASE_URL — PostgreSQL connection string
25
+ - NEXTAUTH_URL — URL of your app (e.g. http://localhost:3000 in dev)
26
+ - NEXTAUTH_SECRET — a strong random string (used to sign NextAuth tokens)
27
+
28
+ Optional / password reset / email provider vars:
29
+
30
+ - GITHUB_ID — (optional) GitHub OAuth client id
31
+ - GITHUB_SECRET — (optional) GitHub OAuth client secret
32
+ - NEXTWORKS_ENABLE_PASSWORD_RESET — set to `1` to enable the dev password reset scaffold (default: disabled)
33
+ - NEXTWORKS_USE_DEV_EMAIL — set to `1` to enable Ethereal dev email transport (for local testing only)
34
+ - SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, NOREPLY_EMAIL — when using a real SMTP provider (required in production if enable password reset)
35
+ - NODE_ENV — (production/dev) used to guard password-reset behavior
36
+
37
+ Notes:
38
+
39
+ - The Auth kit will only enable the GitHub provider when both GITHUB_ID and GITHUB_SECRET are present.
40
+ - Password reset remains disabled by default; enable cautiously and only after configuring a real mail provider in production.
41
+
42
+ ## 2) Install and generate Prisma
43
+
44
+ If you are using the monorepo directly:
45
+
46
+ ```bash
47
+ npm install
48
+ npx prisma generate
49
+ npx prisma migrate dev -n init
50
+ ```
51
+
52
+ If you installed Auth via the CLI (`npx nextworks add auth-core`), the schema and scripts are already in place in your app — you still need to run:
53
+
54
+ ```bash
55
+ npm install @prisma/client prisma
56
+ npx prisma generate
57
+ npx prisma migrate dev -n init
58
+ ```
59
+
60
+ This applies the Prisma schema and generates the Prisma client.
61
+
62
+ ## 3) Start the dev server
63
+
64
+ ```
65
+ npm run dev
66
+ ```
67
+
68
+ Visit:
69
+
70
+ - http://localhost:3000/auth/signup to create a user
71
+ - http://localhost:3000/auth/login to sign in
72
+ - http://localhost:3000/dashboard after login
73
+
74
+ ## 4) Optional: GitHub OAuth
75
+
76
+ If you provided GITHUB_ID and GITHUB_SECRET in `.env`, the GitHub provider will be enabled and the GitHub button will appear on the forms. If not provided, the button will be hidden and only email/password is available.
77
+
78
+ ## 5) Roles (optional)
79
+
80
+ The `User` model has a `role` field (default: `user`). Role is propagated to the JWT and session so you can gate pages/components. You can later add admin tooling or seed scripts as needed.
81
+
82
+ ## 6) Seed and promote admin scripts
83
+
84
+ - `scripts/seed-demo.mjs` (already in the repo) creates a demo admin user and sample posts for quick demos.
85
+ - `scripts/promote-admin.mjs` (new) is idempotent and promotes an existing user to `admin`:
86
+
87
+ ```
88
+ node ./scripts/promote-admin.mjs admin@example.com
89
+ ```
90
+
91
+ ## 7) Forgot password (development scaffold)
92
+
93
+ This is a development scaffold that is disabled by default. To enable the password reset feature, set:
94
+
95
+ ```
96
+ NEXTWORKS_ENABLE_PASSWORD_RESET=1
97
+ ```
98
+
99
+ - POST `/api/auth/forgot-password` accepts `{ email }`. When enabled the endpoint generates a one-time token stored in the database and (in development) prints a reset link to the server console. The endpoint always returns a generic success message to avoid user enumeration.
100
+ - GET `/api/auth/reset-password?token=...` validates a token (only when enabled).
101
+ - POST `/api/auth/reset-password` accepts `{ token, password, confirmPassword }` and updates the user password while invalidating the token (only when enabled).
102
+
103
+ Security notes:
104
+
105
+ - The scaffold is intentionally disabled by default; enable it only for local testing or after hardening.
106
+ - The forgot-password scaffold uses an in-memory rate limiter for demo purposes — replace it with a centralized rate limiter (Redis, API gateway) in production.
107
+ - Always use a real email provider (SMTP, SendGrid, etc.) in production and never log reset tokens in server logs.
108
+
109
+ Migration & upgrade notes
110
+
111
+ - A recent schema migration changed PasswordReset to store tokenHash (SHA-256) instead of the raw token. Steps we executed:
112
+ 1. Added `tokenHash` (nullable) to the PasswordReset model and made `token` nullable in Prisma.
113
+ 2. Generated a migration that adds the tokenHash column and a unique index on it.
114
+ 3. Ran a one-off script `scripts/populate-tokenhash.mjs` to compute SHA-256 hashes for previous tokens and fill `tokenHash`.
115
+ 4. Applied the migration and regenerated the Prisma client (`npx prisma generate`).
116
+
117
+ - To enable password reset behavior in your environment, set:
118
+
119
+ ```
120
+ NEXTWORKS_ENABLE_PASSWORD_RESET=1
121
+ ```
122
+
123
+ - Dev email helper (optional): there is an optional dev email helper (nodemailer + Ethereal) that can be enabled with `NEXTWORKS_USE_DEV_EMAIL=1`. When enabled (and NEXTWORKS_ENABLE_PASSWORD_RESET=1), forgot-password will send an Ethereal email and the server will log only the Ethereal preview URL (no plaintext token in logs). This helper is strictly opt-in for development only. Use a real email provider in production.
124
+
125
+ - Production hardening: Password reset should only be enabled in production when a mail provider is configured. To harden the scaffold in production, ensure the following environment variables are set for SMTP (or equivalent for other providers):
126
+
127
+ ```
128
+ SMTP_HOST=smtp.example.com
129
+ SMTP_PORT=587
130
+ SMTP_USER=user@example.com
131
+ SMTP_PASS=supersecret
132
+ NOREPLY_EMAIL=no-reply@example.com
133
+ NEXTWORKS_ENABLE_PASSWORD_RESET=1
134
+ ```
135
+
136
+ When NEXTWORKS_ENABLE_PASSWORD_RESET=1 and NODE_ENV=production the server will refuse to enable password reset unless a mail provider is configured. The server will not log reset tokens or URLs in production logs.
137
+
138
+ Security checklist before enabling reset in production:
139
+
140
+ - Set a strong NEXTAUTH_SECRET
141
+ - Use HTTPS/TLS
142
+ - Replace in-memory rate limiting with a centralized store (Redis/API Gateway)
143
+ - Configure and test a real mail provider (SMTP, SendGrid, etc.)
144
+ - Ensure password reset tokens are expired/cleaned up periodically
145
+ - Review logging to avoid token leakage
146
+
147
+ - Promotion script: to promote an existing user to admin, run:
148
+
149
+ ```
150
+ node ./scripts/promote-admin.mjs user@example.com
151
+ ```
152
+
153
+ - Important: Before turning password reset on in production, ensure you:
154
+ - Replace the in-memory rate limiter with a centralized solution (Redis or API Gateway).
155
+ - Configure a real email provider and remove console logging of tokens.
156
+ - Ensure tokens are cleaned up periodically (see scripts/prune-password-resets.mjs stub).
157
+ - Set a secure NEXTAUTH_SECRET and use HTTPS in production.
158
+
159
+ ## Files & kit manifest (what to look at)
160
+
161
+ If you want to inspect or package the Auth kit, the primary files and routes are listed below. This is a minimal manifest intended for documentation and CLI packaging reference — adjust paths if you extract the kit into another project.
162
+
163
+ Key files
164
+
165
+ - NextAuth handler & callbacks: lib/auth.ts
166
+ - Prisma client: lib/prisma.ts
167
+ - Auth helper utilities: lib/auth-helpers.ts, lib/hash.ts
168
+ - Email providers: lib/email/index.ts, lib/email/dev-transport.ts, lib/email/provider-smtp.ts
169
+ - Validation helpers: lib/validation/forms.ts
170
+ - Form error mapping: lib/forms/map-errors.ts
171
+ - UI components used by Auth: components/auth/_ and components/ui/_ (login-form.tsx, signup-form.tsx, button/input/label etc.)
172
+ - Pages & API routes:
173
+ - app/auth/login/page.tsx
174
+ - app/auth/signup/page.tsx
175
+ - app/auth/forgot-password/page.tsx
176
+ - app/auth/reset-password/page.tsx
177
+ - app/auth/verify-email/page.tsx
178
+ - app/api/auth/[...nextauth]/route.ts
179
+ - app/api/auth/forgot-password/route.ts
180
+ - app/api/auth/reset-password/route.ts
181
+ - app/api/auth/send-verify-email/route.ts
182
+ - app/api/auth/providers/route.ts
183
+ - app/api/signup/route.ts
184
+ - Prisma schema & auth models: prisma/schema.prisma and prisma/auth-models.prisma
185
+ - Seed & maintenance scripts: scripts/seed-demo.mjs, scripts/promote-admin.mjs, scripts/populate-tokenhash.mjs
186
+
187
+ Minimal manifest (JSON)
188
+
189
+ {
190
+ "name": "auth-core",
191
+ "files": [
192
+ "lib/auth.ts",
193
+ "lib/prisma.ts",
194
+ "lib/auth-helpers.ts",
195
+ "lib/hash.ts",
196
+ "lib/forms/map-errors.ts",
197
+ "lib/validation/forms.ts",
198
+ "lib/email/index.ts",
199
+ "components/auth/login-form.tsx",
200
+ "components/auth/signup-form.tsx",
201
+ "components/auth/logout-button.tsx",
202
+ "components/session-provider.tsx",
203
+ "app/auth/login/page.tsx",
204
+ "app/auth/signup/page.tsx",
205
+ "app/api/auth/[...nextauth]/route.ts",
206
+ "app/api/signup/route.ts",
207
+ "prisma/schema.prisma",
208
+ "scripts/seed-demo.mjs",
209
+ "scripts/promote-admin.mjs"
210
+ ]
211
+ }
212
+
213
+ ## Where to customize NextAuth behavior
214
+
215
+ - The NextAuth configuration and handlers live in lib/auth.ts. Common customizations include:
216
+ - Adjusting providers (add/remove OAuth providers).
217
+ - Modifying session and jwt callbacks to include or remove fields on the token/session.
218
+ - Customizing pages (e.g., pages.signIn) or redirect logic.
219
+
220
+ - Notes: lib/auth.ts already persists user.id and role into the JWT and exposes them on session.user for server components. If you change what is exposed on the session, ensure server-side checks (RBAC) are updated accordingly.
221
+
222
+ ## CLI kit source (if packaging later)
223
+
224
+ - A kit skeleton for packaging already exists in the repo at: `cli/kits/auth-core/` — it contains a compact copy of the core auth files (components, lib, prisma snippets) used by the CLI during development. Use that folder as a starting point when you implement the CLI packaging step.
225
+
226
+ ## Post-install checklist (Auth)
227
+
228
+ 1. Copy `.env.example` → `.env` and set DATABASE_URL, NEXTAUTH_URL, NEXTAUTH_SECRET (and GITHUB_ID/GITHUB_SECRET if using GitHub OAuth).
229
+ 2. Generate Prisma client and run migrations:
230
+
231
+ npx prisma generate
232
+ npx prisma migrate dev -n init
233
+
234
+ 3. (Optional) Seed demo data for quick testing:
235
+
236
+ SEED_ADMIN_EMAIL=admin@example.com SEED_ADMIN_PASSWORD=password123 node ./scripts/seed-demo.mjs
237
+
238
+ 4. Start dev server:
239
+
240
+ npm run dev
241
+
242
+ 5. Visit: `/auth/signup`, `/auth/login` and `/dashboard` for protected pages.
243
+
244
+ If you want, I can also create a short docs/AUTH_MANIFEST.json file containing the JSON manifest above in the repo (useful for CLI packaging). I will not create CLI code or copy files unless you ask.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jakob Bro Liebe Hansen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+
5
+ export default function AdminPostsPlaceholderPage() {
6
+ return (
7
+ <main className="mx-auto max-w-2xl px-4 py-12">
8
+ <h1 className="text-2xl font-semibold text-foreground">
9
+ Posts admin requires the Data kit
10
+ </h1>
11
+ <p className="mt-2 text-sm text-muted-foreground">
12
+ This project has the auth-core kit installed, which provides
13
+ authentication and basic admin scaffolding.
14
+ <br />
15
+ To create and manage posts, install the Data kit and wire up its admin
16
+ routes into your app.
17
+ </p>
18
+
19
+ <div className="mt-6 flex items-center gap-3 text-sm">
20
+ <Link
21
+ href="/dashboard"
22
+ className="text-primary underline-offset-4 hover:underline"
23
+ >
24
+ Back to dashboard
25
+ </Link>
26
+ </div>
27
+ </main>
28
+ );
29
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+
5
+ export default function AdminUsersPlaceholderPage() {
6
+ return (
7
+ <main className="mx-auto max-w-2xl px-4 py-12">
8
+ <h1 className="text-2xl font-semibold text-foreground">
9
+ Users admin requires the Data kit
10
+ </h1>
11
+ <p className="mt-2 text-sm text-muted-foreground">
12
+ This project has the auth-core kit installed, which provides
13
+ authentication and basic admin scaffolding.
14
+ <br />
15
+ To manage users (list, filter, edit, etc.), install the Data kit and
16
+ add its admin routes to your app.
17
+ </p>
18
+
19
+ <div className="mt-6 flex items-center gap-3 text-sm">
20
+ <Link
21
+ href="/dashboard"
22
+ className="text-primary underline-offset-4 hover:underline"
23
+ >
24
+ Back to dashboard
25
+ </Link>
26
+ </div>
27
+ </main>
28
+ );
29
+ }
@@ -0,0 +1,127 @@
1
+ import { NextRequest } from "next/server";
2
+ import { prisma } from "@/lib/prisma";
3
+ import type { Prisma } from "@prisma/client";
4
+ import { jsonOk, jsonFail } from "@/lib/server/result";
5
+ import { getServerSession } from "next-auth";
6
+ import { authOptions } from "@/lib/auth";
7
+
8
+ // Ensure Prisma runs in Node.js (not Edge)
9
+ export const runtime = "nodejs";
10
+
11
+ type RouteContext = { params: Promise<{ id: string }> };
12
+
13
+ // Type guards/helpers (kept minimal here)
14
+ const hasErrorCode = (e: unknown, code: string): boolean =>
15
+ typeof e === "object" &&
16
+ e !== null &&
17
+ "code" in e &&
18
+ typeof (e as { code?: unknown }).code === "string" &&
19
+ (e as { code: string }).code === code;
20
+
21
+ export async function GET(_req: NextRequest, { params }: RouteContext) {
22
+ try {
23
+ const { id } = await params; // async params in Next.js 15
24
+
25
+ const user = await prisma.user.findUnique({
26
+ where: { id },
27
+ select: { id: true, name: true, email: true, role: true },
28
+ });
29
+
30
+ if (!user) {
31
+ return jsonFail("Not found", { status: 404 });
32
+ }
33
+
34
+ return jsonOk(user);
35
+ } catch (e) {
36
+ console.error("GET /api/users/[id] error:", e);
37
+ return jsonFail("Failed to fetch user", { status: 500 });
38
+ }
39
+ }
40
+
41
+ export async function PUT(req: NextRequest, { params }: RouteContext) {
42
+ try {
43
+ // Only admins may update other users. Allow self-update as well.
44
+ const session = await getServerSession(authOptions);
45
+ if (!session?.user) return jsonFail("Unauthorized", { status: 401 });
46
+
47
+ const { id } = await params;
48
+ const isAdmin = (session.user as { role?: string }).role === "admin";
49
+ const isSelf = (session.user as { id?: string }).id === id;
50
+ if (!isAdmin && !isSelf) return jsonFail("Forbidden", { status: 403 });
51
+
52
+ const body: unknown = await req.json();
53
+ if (typeof body !== "object" || body === null) {
54
+ return jsonFail("Body must be a JSON object", { status: 400 });
55
+ }
56
+
57
+ // Validate with Zod (imported lazily to keep this route light)
58
+ const { userUpdateSchema } = await import("@/lib/validation/forms");
59
+ try {
60
+ const parsed = userUpdateSchema.parse(body);
61
+
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;
76
+
77
+ const updated = await prisma.user.update({
78
+ where: { id },
79
+ data,
80
+ });
81
+
82
+ return jsonOk(updated, { status: 200, message: "User updated" });
83
+ } catch (err) {
84
+ // Zod errors
85
+ if (err && typeof err === "object" && "issues" in (err as any)) {
86
+ const { jsonFromZod } = await import("@/lib/server/result");
87
+ return jsonFromZod(err as any, {
88
+ status: 400,
89
+ message: "Validation failed",
90
+ });
91
+ }
92
+ throw err;
93
+ }
94
+ } catch (e) {
95
+ console.error("PUT /api/users/[id] error:", e);
96
+ if (hasErrorCode(e, "P2025")) {
97
+ return jsonFail("Not found", { status: 404 });
98
+ }
99
+ return jsonFail("Failed to update user", { status: 500 });
100
+ }
101
+ }
102
+
103
+ export async function DELETE(_req: NextRequest, { params }: RouteContext) {
104
+ try {
105
+ const { requireAdminApi } = await import("@/lib/auth-helpers");
106
+ const session = await requireAdminApi();
107
+ if (!session) return jsonFail("Forbidden", { status: 403 });
108
+
109
+ const { id } = await params;
110
+
111
+ // Clean up dependent records first to avoid FK violations
112
+ await prisma.$transaction([
113
+ prisma.account.deleteMany({ where: { userId: id } }),
114
+ prisma.session.deleteMany({ where: { userId: id } }),
115
+ prisma.post.deleteMany({ where: { authorId: id } }),
116
+ prisma.user.delete({ where: { id } }),
117
+ ]);
118
+
119
+ return jsonOk({ ok: true }, { status: 200, message: "User deleted" });
120
+ } catch (e) {
121
+ console.error("DELETE /api/users/[id] error:", e);
122
+ if (hasErrorCode(e, "P2025")) {
123
+ return jsonFail("Not found", { status: 404 });
124
+ }
125
+ return jsonFail("Failed to delete user", { status: 500 });
126
+ }
127
+ }
@@ -5,24 +5,27 @@ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  const buttonVariants = cva(
8
- "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
8
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
9
  {
10
10
  variants: {
11
11
  variant: {
12
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ default:
13
+ "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs",
13
14
  destructive:
14
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 shadow-xs",
15
16
  outline:
16
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
- ghost: "hover:bg-accent hover:text-accent-foreground",
17
+ "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
19
22
  link: "text-primary underline-offset-4 hover:underline",
20
23
  },
21
24
  size: {
22
- default: "h-10 px-4 py-2",
23
- sm: "h-9 rounded-md px-3",
24
- lg: "h-11 rounded-md px-8",
25
- icon: "h-10 w-10",
25
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
26
29
  },
27
30
  },
28
31
  defaultVariants: {
@@ -32,24 +35,88 @@ const buttonVariants = cva(
32
35
  },
33
36
  );
34
37
 
35
- export interface ButtonProps
36
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
37
- VariantProps<typeof buttonVariants> {
38
- asChild?: boolean;
39
- }
38
+ type ButtonProps = React.ComponentProps<"button"> &
39
+ VariantProps<typeof buttonVariants> & {
40
+ asChild?: boolean;
41
+ /** When true, bypasses tokenized buttonVariants so callers fully control classes */
42
+ unstyled?: boolean;
43
+ /** Opt-in: force inline CSS var styles for color/bg/border/ring */
44
+ forceInlineVars?: boolean;
45
+ };
40
46
 
41
47
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
42
- ({ className, variant, size, asChild = false, ...props }, ref) => {
48
+ (
49
+ {
50
+ className,
51
+ variant,
52
+ size,
53
+ asChild = false,
54
+ unstyled = false,
55
+ forceInlineVars = false,
56
+ ...props
57
+ },
58
+ ref,
59
+ ) => {
43
60
  const Comp = asChild ? Slot : "button";
61
+
62
+ // Use caller-provided style; only inject inline var-driven colors when explicitly requested
63
+ const incomingStyle =
64
+ (props.style as React.CSSProperties | undefined) ?? undefined;
65
+ const finalStyle =
66
+ forceInlineVars && !unstyled
67
+ ? {
68
+ ...incomingStyle,
69
+ color: "var(--btn-fg)",
70
+ backgroundColor: "var(--btn-bg)",
71
+ borderColor: "var(--btn-border)",
72
+ "--tw-ring-color": "var(--btn-ring)",
73
+ }
74
+ : incomingStyle;
75
+
76
+ // Only enable CSS variable hooks when explicitly requested via inline vars
77
+ // or when the caller sets any [--btn-*] classes in className.
78
+ const wantsVarHooks =
79
+ !unstyled &&
80
+ (forceInlineVars ||
81
+ (typeof className === "string" && className.includes("[--btn-")));
82
+
44
83
  return (
45
84
  <Comp
46
- className={cn(buttonVariants({ variant, size, className }))}
47
85
  ref={ref}
86
+ data-slot="button"
87
+ className={
88
+ unstyled
89
+ ? cn(
90
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50",
91
+ className,
92
+ )
93
+ : cn(
94
+ buttonVariants({ variant, size }),
95
+ wantsVarHooks && [
96
+ // Color var hooks (apply only when CSS vars are provided)
97
+ "text-[var(--btn-fg)]",
98
+ "bg-[var(--btn-bg)]",
99
+ "hover:bg-[var(--btn-hover-bg)]",
100
+ "hover:text-[var(--btn-hover-fg)]",
101
+ // explicit dark variants to compete with dark: utilities from variants like outline
102
+ "dark:bg-[var(--btn-bg)]",
103
+ "dark:hover:bg-[var(--btn-hover-bg)]",
104
+ "dark:hover:text-[var(--btn-hover-fg)]",
105
+ // Focus ring and border hooks
106
+ "focus-visible:ring-[var(--btn-ring)]",
107
+ "border-[var(--btn-border)]",
108
+ "dark:border-[var(--btn-border)]",
109
+ ],
110
+ className,
111
+ )
112
+ }
113
+ style={finalStyle}
48
114
  {...props}
49
115
  />
50
116
  );
51
117
  },
52
118
  );
119
+
53
120
  Button.displayName = "Button";
54
121
 
55
122
  export { Button, buttonVariants };
@@ -2,8 +2,7 @@ import * as React from "react";
2
2
 
3
3
  import { cn } from "@/lib/utils";
4
4
 
5
- export interface InputProps
6
- extends React.InputHTMLAttributes<HTMLInputElement> {}
5
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
7
6
 
8
7
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
8
  ({ className, type, ...props }, ref) => {
@@ -11,7 +10,10 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
11
10
  <input
12
11
  type={type}
13
12
  className={cn(
14
- "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
13
+ // Base structural + token fallbacks
14
+ "border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/30 dark:aria-invalid:focus-visible:ring-destructive/40 flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
15
+ // CSS variable hooks (preset-first). When vars are unset, these are ignored and tokens above apply.
16
+ "focus-visible:ring-offset-background border-[var(--input-border)] bg-[var(--input-bg)] text-[var(--input-fg)] placeholder:text-[var(--input-placeholder)] focus-visible:ring-[var(--input-focus-ring,var(--ring))] focus-visible:ring-offset-2",
15
17
  className,
16
18
  )}
17
19
  ref={ref}
@@ -1,18 +1,24 @@
1
- "use client";
2
-
3
1
  import * as React from "react";
4
2
  import * as LabelPrimitive from "@radix-ui/react-label";
3
+ import { cva, type VariantProps } from "class-variance-authority";
5
4
 
6
5
  import { cn } from "@/lib/utils";
7
6
 
7
+ const labelVariants = cva(
8
+ "text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
9
+ );
10
+
8
11
  const Label = React.forwardRef<
9
12
  React.ElementRef<typeof LabelPrimitive.Root>,
10
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
11
15
  >(({ className, ...props }, ref) => (
12
16
  <LabelPrimitive.Root
13
17
  ref={ref}
14
18
  className={cn(
15
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
19
+ labelVariants(),
20
+ // Optional color override via var; falls back to inherited color when unset
21
+ "text-[var(--label-fg)]",
16
22
  className,
17
23
  )}
18
24
  {...props}
@@ -13,3 +13,13 @@ export const prisma =
13
13
  });
14
14
 
15
15
  if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
16
+
17
+ if (process.env.NODE_ENV === "development" && process.platform === "win32") {
18
+ // Dev-only hint for Windows users hitting Prisma + Turbopack symlink issues
19
+ // (e.g. "create symlink to ../../../../node_modules/@prisma/client" with os error 1314).
20
+ // This is intentionally a console.warn so it surfaces clearly but only in dev.
21
+ // See docs/AUTH_QUICKSTART.md for full details.
22
+ console.warn(
23
+ "[nextworks][auth-core] On Windows with Next 16+ and Prisma, enable Windows Developer Mode or run your dev server from an elevated terminal to avoid Prisma symlink errors (os error 1314). See AUTH_QUICKSTART.md for details."
24
+ );
25
+ }