stackpatch 1.1.4 → 1.1.6

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 +76 -69
  2. package/bin/stackpatch.js +79 -0
  3. package/bin/stackpatch.ts +2445 -3
  4. package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +124 -0
  5. package/boilerplate/auth/app/api/auth/signup/route.ts +45 -0
  6. package/boilerplate/auth/app/auth/login/page.tsx +24 -50
  7. package/boilerplate/auth/app/auth/signup/page.tsx +56 -69
  8. package/boilerplate/auth/app/dashboard/page.tsx +82 -0
  9. package/boilerplate/auth/app/login/page.tsx +136 -0
  10. package/boilerplate/auth/app/page.tsx +48 -0
  11. package/boilerplate/auth/components/auth-button.tsx +43 -0
  12. package/boilerplate/auth/components/auth-navbar.tsx +118 -0
  13. package/boilerplate/auth/components/protected-route.tsx +74 -0
  14. package/boilerplate/auth/components/session-provider.tsx +11 -0
  15. package/boilerplate/auth/middleware.ts +51 -0
  16. package/package.json +5 -6
  17. package/boilerplate/auth/app/stackpatch/page.tsx +0 -269
  18. package/boilerplate/auth/components/auth-wrapper.tsx +0 -61
  19. package/src/auth/generator.ts +0 -569
  20. package/src/auth/index.ts +0 -372
  21. package/src/auth/setup.ts +0 -293
  22. package/src/commands/add.ts +0 -112
  23. package/src/commands/create.ts +0 -128
  24. package/src/commands/revert.ts +0 -389
  25. package/src/config.ts +0 -52
  26. package/src/fileOps/copy.ts +0 -224
  27. package/src/fileOps/layout.ts +0 -304
  28. package/src/fileOps/protected.ts +0 -67
  29. package/src/index.ts +0 -215
  30. package/src/manifest.ts +0 -87
  31. package/src/ui/logo.ts +0 -24
  32. package/src/ui/progress.ts +0 -82
  33. package/src/utils/dependencies.ts +0 -114
  34. package/src/utils/deps-check.ts +0 -45
  35. package/src/utils/files.ts +0 -58
  36. package/src/utils/paths.ts +0 -217
  37. package/src/utils/scanner.ts +0 -109
@@ -0,0 +1,124 @@
1
+ import NextAuth from "next-auth";
2
+ import type { NextAuthOptions } from "next-auth";
3
+ import GoogleProvider from "next-auth/providers/google";
4
+ import GitHubProvider from "next-auth/providers/github";
5
+ import CredentialsProvider from "next-auth/providers/credentials";
6
+
7
+ export const authOptions: NextAuthOptions = {
8
+ providers: [
9
+ CredentialsProvider({
10
+ name: "Credentials",
11
+ credentials: {
12
+ email: { label: "Email", type: "email" },
13
+ password: { label: "Password", type: "password" },
14
+ },
15
+ async authorize(credentials) {
16
+ // ⚠️ DEMO MODE: This is a placeholder implementation
17
+ //
18
+ // TO IMPLEMENT REAL AUTHENTICATION:
19
+ // 1. Set up a database (PostgreSQL, MongoDB, Prisma, etc.)
20
+ // 2. Install bcrypt: npm install bcryptjs @types/bcryptjs
21
+ // 3. Replace this function with database lookup:
22
+ //
23
+ // Example implementation:
24
+ // ```ts
25
+ // import bcrypt from "bcryptjs";
26
+ // import { db } from "@/lib/db"; // Your database connection
27
+ //
28
+ // async authorize(credentials) {
29
+ // if (!credentials?.email || !credentials?.password) {
30
+ // return null;
31
+ // }
32
+ //
33
+ // // Find user in database
34
+ // const user = await db.user.findUnique({
35
+ // where: { email: credentials.email },
36
+ // });
37
+ //
38
+ // if (!user) {
39
+ // return null;
40
+ // }
41
+ //
42
+ // // Verify password
43
+ // const isValid = await bcrypt.compare(
44
+ // credentials.password,
45
+ // user.password
46
+ // );
47
+ //
48
+ // if (!isValid) {
49
+ // return null;
50
+ // }
51
+ //
52
+ // return {
53
+ // id: user.id,
54
+ // email: user.email,
55
+ // name: user.name,
56
+ // };
57
+ // }
58
+ // ```
59
+ //
60
+ // Current demo credentials (REMOVE IN PRODUCTION):
61
+ // Email: demo@example.com
62
+ // Password: demo123
63
+
64
+ if (!credentials?.email || !credentials?.password) {
65
+ return null;
66
+ }
67
+
68
+ // Demo check - REMOVE THIS IN PRODUCTION
69
+ if (
70
+ credentials.email === "demo@example.com" &&
71
+ credentials.password === "demo123"
72
+ ) {
73
+ return {
74
+ id: "1",
75
+ email: credentials.email,
76
+ name: "Demo User",
77
+ };
78
+ }
79
+
80
+ return null;
81
+ },
82
+ }),
83
+ GoogleProvider({
84
+ clientId: process.env.GOOGLE_CLIENT_ID!,
85
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
86
+ }),
87
+ GitHubProvider({
88
+ clientId: process.env.GITHUB_CLIENT_ID!,
89
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
90
+ }),
91
+ ],
92
+ pages: {
93
+ signIn: "/auth/login",
94
+ error: "/auth/error",
95
+ },
96
+ session: {
97
+ strategy: "jwt",
98
+ },
99
+ callbacks: {
100
+ async jwt({ token, user, account }) {
101
+ if (user) {
102
+ token.id = user.id;
103
+ token.email = user.email;
104
+ token.name = user.name;
105
+ }
106
+ if (account) {
107
+ token.accessToken = account.access_token;
108
+ token.provider = account.provider;
109
+ }
110
+ return token;
111
+ },
112
+ async session({ session, token }) {
113
+ if (session.user) {
114
+ session.user.id = token.id as string;
115
+ session.accessToken = token.accessToken as string;
116
+ }
117
+ return session;
118
+ },
119
+ },
120
+ };
121
+
122
+ const handler = NextAuth(authOptions);
123
+
124
+ export { handler as GET, handler as POST };
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST(request: Request) {
4
+ try {
5
+ const { email, password, name } = await request.json();
6
+
7
+ // TODO: Replace with your actual signup logic
8
+ // This is a placeholder - you should:
9
+ // 1. Validate input
10
+ // 2. Check if user already exists
11
+ // 3. Hash password (use bcrypt or similar)
12
+ // 4. Save user to database
13
+ // 5. Return success or error
14
+
15
+ // Example validation
16
+ if (!email || !password || !name) {
17
+ return NextResponse.json(
18
+ { error: "Missing required fields" },
19
+ { status: 400 }
20
+ );
21
+ }
22
+
23
+ if (password.length < 6) {
24
+ return NextResponse.json(
25
+ { error: "Password must be at least 6 characters" },
26
+ { status: 400 }
27
+ );
28
+ }
29
+
30
+ // Placeholder: In production, save to database here
31
+ // const hashedPassword = await bcrypt.hash(password, 10);
32
+ // const user = await db.user.create({ email, password: hashedPassword, name });
33
+
34
+ return NextResponse.json(
35
+ { message: "Account created successfully" },
36
+ { status: 201 }
37
+ );
38
+ } catch (error) {
39
+ console.error("Signup error:", error);
40
+ return NextResponse.json(
41
+ { error: "Failed to create account" },
42
+ { status: 500 }
43
+ );
44
+ }
45
+ }
@@ -1,59 +1,50 @@
1
1
  "use client";
2
2
 
3
- import { authClient } from "@/lib/auth-client";
4
- import { useState, useEffect } from "react";
5
- import { useRouter, useSearchParams } from "next/navigation";
3
+ import { signIn } from "next-auth/react";
4
+ import { useState } from "react";
5
+ import { useRouter } from "next/navigation";
6
6
  import toast from "react-hot-toast";
7
7
 
8
- /**
9
- * Login Page
10
- *
11
- * 📝 TO CHANGE THE REDIRECT ROUTE AFTER LOGIN:
12
- * Change "/stackpatch" below to your desired route (e.g., "/dashboard", "/home")
13
- * Also update the same route in:
14
- * - app/page.tsx
15
- * - app/auth/signup/page.tsx
16
- */
17
8
  export default function LoginPage() {
18
9
  const router = useRouter();
19
- const searchParams = useSearchParams();
20
- const { data: session, isPending: sessionLoading } = authClient.useSession();
21
10
  const [email, setEmail] = useState("");
22
11
  const [password, setPassword] = useState("");
23
12
  const [error, setError] = useState("");
24
13
  const [loading, setLoading] = useState(false);
25
14
 
26
- // 🔧 CHANGE THIS ROUTE: Update "/stackpatch" to your desired landing page route
27
- const LANDING_PAGE_ROUTE = "/stackpatch";
28
-
29
- // Get redirect URL from query params (set by middleware when protecting routes)
30
- const redirectTo = searchParams.get("redirect") || LANDING_PAGE_ROUTE;
31
-
32
- // Redirect if already signed in
33
- useEffect(() => {
34
- if (!sessionLoading && session?.user) {
35
- router.push(redirectTo);
36
- }
37
- }, [session, sessionLoading, router, redirectTo]);
38
-
39
15
  const handleSubmit = async (e: React.FormEvent) => {
40
16
  e.preventDefault();
41
17
  setError("");
42
18
  setLoading(true);
43
19
 
44
20
  try {
45
- const result = await authClient.signIn.email({
21
+ // ⚠️ DEMO MODE: This is a placeholder implementation
22
+ //
23
+ // TO IMPLEMENT REAL AUTHENTICATION:
24
+ // 1. Set up a database (PostgreSQL, MongoDB, etc.)
25
+ // 2. Create a user registration API route (app/api/auth/signup/route.ts)
26
+ // 3. Hash passwords using bcrypt or similar
27
+ // 4. Update the authorize function in app/api/auth/[...nextauth]/route.ts
28
+ // to check credentials against your database
29
+ // 5. Remove this demo check and implement proper database lookup
30
+ //
31
+ // Current demo credentials (REMOVE IN PRODUCTION):
32
+ // Email: demo@example.com
33
+ // Password: demo123
34
+
35
+ const result = await signIn("credentials", {
46
36
  email,
47
37
  password,
38
+ redirect: false,
48
39
  });
49
40
 
50
- if (result.error) {
51
- setError(result.error.message || "Invalid email or password");
52
- toast.error(result.error.message || "Invalid email or password");
41
+ if (result?.error) {
42
+ setError("Invalid email or password");
43
+ toast.error("Invalid email or password");
53
44
  } else {
54
45
  toast.success("Login successful! Redirecting...");
55
46
  setTimeout(() => {
56
- router.push(redirectTo);
47
+ router.push("/");
57
48
  router.refresh();
58
49
  }, 1000);
59
50
  }
@@ -67,29 +58,12 @@ export default function LoginPage() {
67
58
 
68
59
  const handleOAuthSignIn = async (provider: "google" | "github") => {
69
60
  try {
70
- await authClient.signIn.social({ provider });
61
+ await signIn(provider, { callbackUrl: "/" });
71
62
  } catch (error) {
72
63
  toast.error(`Failed to sign in with ${provider}`);
73
64
  }
74
65
  };
75
66
 
76
- // Show loading state while checking session
77
- if (sessionLoading) {
78
- return (
79
- <div className="flex min-h-screen items-center justify-center">
80
- <div className="text-center">
81
- <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent"></div>
82
- <p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading...</p>
83
- </div>
84
- </div>
85
- );
86
- }
87
-
88
- // Don't show login form if already signed in (will redirect)
89
- if (session?.user) {
90
- return null;
91
- }
92
-
93
67
  return (
94
68
  <div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
95
69
  <div className="w-full max-w-md space-y-8">
@@ -1,23 +1,12 @@
1
1
  "use client";
2
2
 
3
- import React, { useState, useEffect } from "react";
4
- import { useRouter, useSearchParams } from "next/navigation";
5
- import { authClient } from "@/lib/auth-client";
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { signIn } from "next-auth/react";
6
6
  import toast from "react-hot-toast";
7
7
 
8
- /**
9
- * Signup Page
10
- *
11
- * 📝 TO CHANGE THE REDIRECT ROUTE AFTER SIGNUP:
12
- * Change "/stackpatch" below to your desired route (e.g., "/dashboard", "/home")
13
- * Also update the same route in:
14
- * - app/page.tsx
15
- * - app/auth/login/page.tsx
16
- */
17
8
  export default function SignupPage() {
18
9
  const router = useRouter();
19
- const searchParams = useSearchParams();
20
- const { data: session, isPending: sessionLoading } = authClient.useSession();
21
10
  const [email, setEmail] = useState("");
22
11
  const [password, setPassword] = useState("");
23
12
  const [confirmPassword, setConfirmPassword] = useState("");
@@ -25,19 +14,6 @@ export default function SignupPage() {
25
14
  const [error, setError] = useState("");
26
15
  const [loading, setLoading] = useState(false);
27
16
 
28
- // 🔧 CHANGE THIS ROUTE: Update "/stackpatch" to your desired landing page route
29
- const LANDING_PAGE_ROUTE = "/stackpatch";
30
-
31
- // Get redirect URL from query params (set by middleware when protecting routes)
32
- const redirectTo = searchParams.get("redirect") || LANDING_PAGE_ROUTE;
33
-
34
- // Redirect if already signed in
35
- useEffect(() => {
36
- if (!sessionLoading && session?.user) {
37
- router.push(redirectTo);
38
- }
39
- }, [session, sessionLoading, router, redirectTo]);
40
-
41
17
  const handleSubmit = async (e: React.FormEvent) => {
42
18
  e.preventDefault();
43
19
  setError("");
@@ -57,35 +33,63 @@ export default function SignupPage() {
57
33
  setLoading(true);
58
34
 
59
35
  try {
60
- // Better Auth handles signup natively
61
- const result = await authClient.signUp.email({
36
+ // ⚠️ DEMO MODE: This is a placeholder implementation
37
+ //
38
+ // TO IMPLEMENT REAL SIGNUP:
39
+ // 1. Set up a database (PostgreSQL, MongoDB, etc.)
40
+ // 2. Create app/api/auth/signup/route.ts with:
41
+ // - Password hashing (use bcrypt)
42
+ // - User creation in database
43
+ // - Email validation
44
+ // - Duplicate user checking
45
+ // 3. Update this fetch call to use your actual signup endpoint
46
+ // 4. Handle errors properly (duplicate email, weak password, etc.)
47
+ //
48
+ // Example signup route structure:
49
+ // ```ts
50
+ // // app/api/auth/signup/route.ts
51
+ // import bcrypt from "bcryptjs";
52
+ // import { db } from "@/lib/db"; // Your database connection
53
+ //
54
+ // export async function POST(req: Request) {
55
+ // const { email, password, name } = await req.json();
56
+ // const hashedPassword = await bcrypt.hash(password, 10);
57
+ // const user = await db.user.create({ data: { email, password: hashedPassword, name } });
58
+ // return Response.json({ user });
59
+ // }
60
+ // ```
61
+
62
+ const response = await fetch("/api/auth/signup", {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ email, password, name }),
66
+ });
67
+
68
+ if (!response.ok) {
69
+ const data = await response.json();
70
+ setError(data.error || "Failed to create account");
71
+ toast.error(data.error || "Failed to create account");
72
+ return;
73
+ }
74
+
75
+ toast.success("Account created successfully! Signing you in...");
76
+
77
+ // Auto sign in after successful signup
78
+ const result = await signIn("credentials", {
62
79
  email,
63
80
  password,
64
- name,
81
+ redirect: false,
65
82
  });
66
83
 
67
- if (result.error) {
68
- setError(result.error.message || "Failed to create account");
69
- toast.error(result.error.message || "Failed to create account");
84
+ if (result?.error) {
85
+ setError("Account created but sign in failed. Please try logging in.");
86
+ toast.error("Account created but sign in failed. Please try logging in.");
70
87
  } else {
71
- toast.success("Account created successfully! Signing you in...");
72
-
73
- // Auto sign in after successful signup
74
- const signInResult = await authClient.signIn.email({
75
- email,
76
- password,
77
- });
78
-
79
- if (signInResult.error) {
80
- setError("Account created but sign in failed. Please try logging in.");
81
- toast.error("Account created but sign in failed. Please try logging in.");
82
- } else {
83
- toast.success("Welcome! Redirecting...");
84
- setTimeout(() => {
85
- router.push(redirectTo);
86
- router.refresh();
87
- }, 1000);
88
- }
88
+ toast.success("Welcome! Redirecting...");
89
+ setTimeout(() => {
90
+ router.push("/");
91
+ router.refresh();
92
+ }, 1000);
89
93
  }
90
94
  } catch (err) {
91
95
  setError("Something went wrong. Please try again.");
@@ -97,29 +101,12 @@ export default function SignupPage() {
97
101
 
98
102
  const handleOAuthSignIn = async (provider: "google" | "github") => {
99
103
  try {
100
- await authClient.signIn.social({ provider });
104
+ await signIn(provider, { callbackUrl: "/" });
101
105
  } catch (error) {
102
106
  toast.error(`Failed to sign in with ${provider}`);
103
107
  }
104
108
  };
105
109
 
106
- // Show loading state while checking session
107
- if (sessionLoading) {
108
- return (
109
- <div className="flex min-h-screen items-center justify-center">
110
- <div className="text-center">
111
- <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent"></div>
112
- <p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading...</p>
113
- </div>
114
- </div>
115
- );
116
- }
117
-
118
- // Don't show signup form if already signed in (will redirect)
119
- if (session?.user) {
120
- return null;
121
- }
122
-
123
110
  return (
124
111
  <div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
125
112
  <div className="w-full max-w-md space-y-8">
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useSession } from "next-auth/react";
5
+ import { ProtectedRoute } from "@/components/protected-route";
6
+ import { AuthNavbar } from "@/components/auth-navbar";
7
+
8
+ /**
9
+ * Dashboard Page Example
10
+ *
11
+ * This is an example protected dashboard page.
12
+ *
13
+ * To use this:
14
+ * 1. Copy this file to your app/dashboard/page.tsx
15
+ * 2. The page is automatically protected using ProtectedRoute
16
+ * 3. The AuthNavbar shows session status and sign out button
17
+ *
18
+ * You can customize this page to show your dashboard content.
19
+ * If you have an existing navbar, replace AuthNavbar with your own.
20
+ */
21
+ export default function DashboardPage() {
22
+ const { data: session } = useSession();
23
+
24
+ return (
25
+ <ProtectedRoute>
26
+ <div className="min-h-screen bg-zinc-50 dark:bg-black">
27
+ <AuthNavbar />
28
+ <main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
29
+ <div className="rounded-lg bg-white p-8 shadow dark:bg-zinc-900">
30
+ <h1 className="text-3xl font-bold text-zinc-900 dark:text-zinc-50">
31
+ Dashboard
32
+ </h1>
33
+ <p className="mt-2 text-zinc-600 dark:text-zinc-400">
34
+ Welcome to your protected dashboard!
35
+ </p>
36
+
37
+ {session && (
38
+ <div className="mt-6 rounded-md bg-zinc-100 p-4 dark:bg-zinc-800">
39
+ <h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
40
+ Session Information
41
+ </h2>
42
+ <div className="mt-2 space-y-1 text-sm text-zinc-600 dark:text-zinc-400">
43
+ <p>
44
+ <span className="font-medium">Name:</span>{" "}
45
+ {session.user?.name || "Not provided"}
46
+ </p>
47
+ <p>
48
+ <span className="font-medium">Email:</span>{" "}
49
+ {session.user?.email || "Not provided"}
50
+ </p>
51
+ {session.user?.image && (
52
+ <p>
53
+ <span className="font-medium">Image:</span>{" "}
54
+ <img
55
+ src={session.user.image}
56
+ alt={session.user.name || "User"}
57
+ className="mt-2 h-16 w-16 rounded-full"
58
+ />
59
+ </p>
60
+ )}
61
+ </div>
62
+ </div>
63
+ )}
64
+
65
+ <div className="mt-8">
66
+ <h2 className="text-xl font-semibold text-zinc-900 dark:text-zinc-50">
67
+ Getting Started
68
+ </h2>
69
+ <p className="mt-2 text-zinc-600 dark:text-zinc-400">
70
+ This is a protected page. Only authenticated users can see this
71
+ content.
72
+ </p>
73
+ <p className="mt-2 text-zinc-600 dark:text-zinc-400">
74
+ Customize this page to add your dashboard features.
75
+ </p>
76
+ </div>
77
+ </div>
78
+ </main>
79
+ </div>
80
+ </ProtectedRoute>
81
+ );
82
+ }
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ import { signIn } from "next-auth/react";
4
+ import { useState } from "react";
5
+ import { useRouter } from "next/navigation";
6
+
7
+ export default function LoginPage() {
8
+ const router = useRouter();
9
+ const [email, setEmail] = useState("");
10
+ const [password, setPassword] = useState("");
11
+ const [error, setError] = useState("");
12
+ const [loading, setLoading] = useState(false);
13
+
14
+ const handleSubmit = async (e: React.FormEvent) => {
15
+ e.preventDefault();
16
+ setError("");
17
+ setLoading(true);
18
+
19
+ try {
20
+ const result = await signIn("credentials", {
21
+ email,
22
+ password,
23
+ redirect: false,
24
+ });
25
+
26
+ if (result?.error) {
27
+ setError("Invalid email or password");
28
+ } else {
29
+ router.push("/");
30
+ router.refresh();
31
+ }
32
+ } catch (err) {
33
+ setError("Something went wrong. Please try again.");
34
+ } finally {
35
+ setLoading(false);
36
+ }
37
+ };
38
+
39
+ return (
40
+ <div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
41
+ <div className="w-full max-w-md space-y-8">
42
+ <div>
43
+ <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">
44
+ Sign in to your account
45
+ </h2>
46
+ <p className="mt-2 text-center text-sm text-zinc-600 dark:text-zinc-400">
47
+ Or{" "}
48
+ <a
49
+ href="/auth/signup"
50
+ className="font-medium text-zinc-950 hover:text-zinc-700 dark:text-zinc-50 dark:hover:text-zinc-300"
51
+ >
52
+ create a new account
53
+ </a>
54
+ </p>
55
+ </div>
56
+ <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
57
+ {error && (
58
+ <div className="rounded-md bg-red-50 p-4 dark:bg-red-900/20">
59
+ <p className="text-sm text-red-800 dark:text-red-200">{error}</p>
60
+ </div>
61
+ )}
62
+ <div className="-space-y-px rounded-md shadow-sm">
63
+ <div>
64
+ <label htmlFor="email" className="sr-only">
65
+ Email address
66
+ </label>
67
+ <input
68
+ id="email"
69
+ name="email"
70
+ type="email"
71
+ autoComplete="email"
72
+ required
73
+ value={email}
74
+ onChange={(e) => setEmail(e.target.value)}
75
+ className="relative block w-full rounded-t-md border-0 px-3 py-2 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-800 dark:text-zinc-50 dark:ring-zinc-700 dark:placeholder:text-zinc-500 dark:focus:ring-zinc-400 sm:text-sm sm:leading-6"
76
+ placeholder="Email address"
77
+ />
78
+ </div>
79
+ <div>
80
+ <label htmlFor="password" className="sr-only">
81
+ Password
82
+ </label>
83
+ <input
84
+ id="password"
85
+ name="password"
86
+ type="password"
87
+ autoComplete="current-password"
88
+ required
89
+ value={password}
90
+ onChange={(e) => setPassword(e.target.value)}
91
+ className="relative block w-full rounded-b-md border-0 px-3 py-2 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-800 dark:text-zinc-50 dark:ring-zinc-700 dark:placeholder:text-zinc-500 dark:focus:ring-zinc-400 sm:text-sm sm:leading-6"
92
+ placeholder="Password"
93
+ />
94
+ </div>
95
+ </div>
96
+
97
+ <div className="flex items-center justify-between">
98
+ <div className="flex items-center">
99
+ <input
100
+ id="remember-me"
101
+ name="remember-me"
102
+ type="checkbox"
103
+ className="h-4 w-4 rounded border-zinc-300 text-zinc-600 focus:ring-zinc-600 dark:border-zinc-700 dark:bg-zinc-800"
104
+ />
105
+ <label
106
+ htmlFor="remember-me"
107
+ className="ml-2 block text-sm text-zinc-900 dark:text-zinc-50"
108
+ >
109
+ Remember me
110
+ </label>
111
+ </div>
112
+
113
+ <div className="text-sm">
114
+ <a
115
+ href="/auth/forgot-password"
116
+ className="font-medium text-zinc-600 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
117
+ >
118
+ Forgot your password?
119
+ </a>
120
+ </div>
121
+ </div>
122
+
123
+ <div>
124
+ <button
125
+ type="submit"
126
+ disabled={loading}
127
+ className="group relative flex w-full justify-center rounded-md bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600 disabled:opacity-50 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
128
+ >
129
+ {loading ? "Signing in..." : "Sign in"}
130
+ </button>
131
+ </div>
132
+ </form>
133
+ </div>
134
+ </div>
135
+ );
136
+ }