stackpatch 1.1.1 → 1.1.4

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 (36) hide show
  1. package/README.md +123 -114
  2. package/bin/stackpatch.ts +2 -2370
  3. package/boilerplate/auth/app/auth/login/page.tsx +50 -24
  4. package/boilerplate/auth/app/auth/signup/page.tsx +69 -56
  5. package/boilerplate/auth/app/stackpatch/page.tsx +269 -0
  6. package/boilerplate/auth/components/auth-wrapper.tsx +61 -0
  7. package/package.json +4 -2
  8. package/src/auth/generator.ts +569 -0
  9. package/src/auth/index.ts +372 -0
  10. package/src/auth/setup.ts +293 -0
  11. package/src/commands/add.ts +112 -0
  12. package/src/commands/create.ts +128 -0
  13. package/src/commands/revert.ts +389 -0
  14. package/src/config.ts +52 -0
  15. package/src/fileOps/copy.ts +224 -0
  16. package/src/fileOps/layout.ts +304 -0
  17. package/src/fileOps/protected.ts +67 -0
  18. package/src/index.ts +215 -0
  19. package/src/manifest.ts +87 -0
  20. package/src/ui/logo.ts +24 -0
  21. package/src/ui/progress.ts +82 -0
  22. package/src/utils/dependencies.ts +114 -0
  23. package/src/utils/deps-check.ts +45 -0
  24. package/src/utils/files.ts +58 -0
  25. package/src/utils/paths.ts +217 -0
  26. package/src/utils/scanner.ts +109 -0
  27. package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +0 -124
  28. package/boilerplate/auth/app/api/auth/signup/route.ts +0 -45
  29. package/boilerplate/auth/app/dashboard/page.tsx +0 -82
  30. package/boilerplate/auth/app/login/page.tsx +0 -136
  31. package/boilerplate/auth/app/page.tsx +0 -48
  32. package/boilerplate/auth/components/auth-button.tsx +0 -43
  33. package/boilerplate/auth/components/auth-navbar.tsx +0 -118
  34. package/boilerplate/auth/components/protected-route.tsx +0 -74
  35. package/boilerplate/auth/components/session-provider.tsx +0 -11
  36. package/boilerplate/auth/middleware.ts +0 -51
@@ -1,50 +1,59 @@
1
1
  "use client";
2
2
 
3
- import { signIn } from "next-auth/react";
4
- import { useState } from "react";
5
- import { useRouter } from "next/navigation";
3
+ import { authClient } from "@/lib/auth-client";
4
+ import { useState, useEffect } from "react";
5
+ import { useRouter, useSearchParams } 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
+ */
8
17
  export default function LoginPage() {
9
18
  const router = useRouter();
19
+ const searchParams = useSearchParams();
20
+ const { data: session, isPending: sessionLoading } = authClient.useSession();
10
21
  const [email, setEmail] = useState("");
11
22
  const [password, setPassword] = useState("");
12
23
  const [error, setError] = useState("");
13
24
  const [loading, setLoading] = useState(false);
14
25
 
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
+
15
39
  const handleSubmit = async (e: React.FormEvent) => {
16
40
  e.preventDefault();
17
41
  setError("");
18
42
  setLoading(true);
19
43
 
20
44
  try {
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", {
45
+ const result = await authClient.signIn.email({
36
46
  email,
37
47
  password,
38
- redirect: false,
39
48
  });
40
49
 
41
- if (result?.error) {
42
- setError("Invalid email or password");
43
- toast.error("Invalid email or password");
50
+ if (result.error) {
51
+ setError(result.error.message || "Invalid email or password");
52
+ toast.error(result.error.message || "Invalid email or password");
44
53
  } else {
45
54
  toast.success("Login successful! Redirecting...");
46
55
  setTimeout(() => {
47
- router.push("/");
56
+ router.push(redirectTo);
48
57
  router.refresh();
49
58
  }, 1000);
50
59
  }
@@ -58,12 +67,29 @@ export default function LoginPage() {
58
67
 
59
68
  const handleOAuthSignIn = async (provider: "google" | "github") => {
60
69
  try {
61
- await signIn(provider, { callbackUrl: "/" });
70
+ await authClient.signIn.social({ provider });
62
71
  } catch (error) {
63
72
  toast.error(`Failed to sign in with ${provider}`);
64
73
  }
65
74
  };
66
75
 
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
+
67
93
  return (
68
94
  <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">
69
95
  <div className="w-full max-w-md space-y-8">
@@ -1,12 +1,23 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
4
- import { useRouter } from "next/navigation";
5
- import { signIn } from "next-auth/react";
3
+ import React, { useState, useEffect } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { authClient } from "@/lib/auth-client";
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
+ */
8
17
  export default function SignupPage() {
9
18
  const router = useRouter();
19
+ const searchParams = useSearchParams();
20
+ const { data: session, isPending: sessionLoading } = authClient.useSession();
10
21
  const [email, setEmail] = useState("");
11
22
  const [password, setPassword] = useState("");
12
23
  const [confirmPassword, setConfirmPassword] = useState("");
@@ -14,6 +25,19 @@ export default function SignupPage() {
14
25
  const [error, setError] = useState("");
15
26
  const [loading, setLoading] = useState(false);
16
27
 
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
+
17
41
  const handleSubmit = async (e: React.FormEvent) => {
18
42
  e.preventDefault();
19
43
  setError("");
@@ -33,63 +57,35 @@ export default function SignupPage() {
33
57
  setLoading(true);
34
58
 
35
59
  try {
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", {
60
+ // Better Auth handles signup natively
61
+ const result = await authClient.signUp.email({
79
62
  email,
80
63
  password,
81
- redirect: false,
64
+ name,
82
65
  });
83
66
 
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.");
67
+ if (result.error) {
68
+ setError(result.error.message || "Failed to create account");
69
+ toast.error(result.error.message || "Failed to create account");
87
70
  } else {
88
- toast.success("Welcome! Redirecting...");
89
- setTimeout(() => {
90
- router.push("/");
91
- router.refresh();
92
- }, 1000);
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
+ }
93
89
  }
94
90
  } catch (err) {
95
91
  setError("Something went wrong. Please try again.");
@@ -101,12 +97,29 @@ export default function SignupPage() {
101
97
 
102
98
  const handleOAuthSignIn = async (provider: "google" | "github") => {
103
99
  try {
104
- await signIn(provider, { callbackUrl: "/" });
100
+ await authClient.signIn.social({ provider });
105
101
  } catch (error) {
106
102
  toast.error(`Failed to sign in with ${provider}`);
107
103
  }
108
104
  };
109
105
 
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
+
110
123
  return (
111
124
  <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">
112
125
  <div className="w-full max-w-md space-y-8">
@@ -0,0 +1,269 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { authClient } from "@/lib/auth-client";
5
+
6
+ /**
7
+ * StackPatch Landing Page
8
+ *
9
+ * Landing page with ProductHunt, GitHub links, user session info, and sign out button
10
+ *
11
+ * 📝 TO CHANGE THIS ROUTE:
12
+ * 1. Rename this file/folder: app/stackpatch/page.tsx → app/YOUR_ROUTE/page.tsx
13
+ * 2. Update redirects in:
14
+ * - app/page.tsx (line 21)
15
+ * - app/auth/login/page.tsx (line 19, 40)
16
+ * - app/auth/signup/page.tsx (line 21, 69)
17
+ * - middleware.ts (if protecting this route)
18
+ */
19
+ export default function StackPatchPage() {
20
+ const { data: session, isPending } = authClient.useSession();
21
+
22
+ const handleSignOut = async () => {
23
+ await authClient.signOut({ callbackUrl: "/" });
24
+ };
25
+
26
+ if (isPending) {
27
+ return (
28
+ <div className="flex min-h-screen items-center justify-center bg-gradient-to-b from-zinc-50 to-white dark:from-black dark:to-zinc-900">
29
+ <div className="text-center">
30
+ <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
31
+ <p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading session...</p>
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ if (!session?.user) {
38
+ return (
39
+ <div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-black dark:to-zinc-900">
40
+ <div className="flex min-h-screen flex-col items-center justify-center px-4 py-16 sm:px-6 lg:px-8">
41
+ <div className="mx-auto max-w-2xl text-center">
42
+ {/* Logo/Brand */}
43
+ <div className="mb-8">
44
+ <h1 className="text-6xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 sm:text-7xl">
45
+ StackPatch
46
+ </h1>
47
+ <p className="mt-4 text-xl leading-8 text-zinc-600 dark:text-zinc-400">
48
+ Composable frontend features for modern React & Next.js apps
49
+ </p>
50
+ </div>
51
+
52
+ {/* Authentication Status Card */}
53
+ <div className="mx-auto mt-8 max-w-md rounded-lg border border-zinc-200 bg-white p-8 shadow-lg dark:border-zinc-800 dark:bg-zinc-900">
54
+ <div className="mb-4">
55
+ <div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800">
56
+ <svg
57
+ className="h-8 w-8 text-zinc-600 dark:text-zinc-400"
58
+ fill="none"
59
+ viewBox="0 0 24 24"
60
+ stroke="currentColor"
61
+ >
62
+ <path
63
+ strokeLinecap="round"
64
+ strokeLinejoin="round"
65
+ strokeWidth={2}
66
+ d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
67
+ />
68
+ </svg>
69
+ </div>
70
+ </div>
71
+ <h2 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50">
72
+ Authentication Required
73
+ </h2>
74
+ <p className="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
75
+ Please sign in to access the StackPatch dashboard and manage your account.
76
+ </p>
77
+ <div className="mt-6">
78
+ <a
79
+ href="/auth/login"
80
+ className="inline-flex w-full items-center justify-center rounded-md bg-zinc-900 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
81
+ >
82
+ Sign In
83
+ </a>
84
+ </div>
85
+ <p className="mt-4 text-xs text-zinc-500 dark:text-zinc-500">
86
+ Don't have an account?{" "}
87
+ <a
88
+ href="/auth/signup"
89
+ className="font-medium text-zinc-900 hover:underline dark:text-zinc-50"
90
+ >
91
+ Sign up
92
+ </a>
93
+ </p>
94
+ </div>
95
+
96
+ {/* Additional Info */}
97
+ <div className="mt-12 flex flex-col items-center gap-6 sm:flex-row sm:justify-center">
98
+ <a
99
+ href="https://www.producthunt.com/posts/stackpatch"
100
+ target="_blank"
101
+ rel="noopener noreferrer"
102
+ className="inline-flex items-center gap-2 rounded-md bg-orange-500 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-orange-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-600"
103
+ >
104
+ 🚀 Support us on ProductHunt
105
+ </a>
106
+ <a
107
+ href="https://github.com/Darshh09/StackPatch"
108
+ target="_blank"
109
+ rel="noopener noreferrer"
110
+ className="inline-flex items-center gap-2 rounded-md bg-zinc-900 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
111
+ >
112
+ <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
113
+ <path
114
+ fillRule="evenodd"
115
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482C19.138 20.197 22 16.425 22 12.017 22 6.484 17.522 2 12 2z"
116
+ clipRule="evenodd"
117
+ />
118
+ </svg>
119
+ ⭐ Star on GitHub
120
+ </a>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ return (
129
+ <div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-black dark:to-zinc-900">
130
+ {/* Header with Sign Out */}
131
+ <header className="border-b border-zinc-200 bg-white/80 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-900/80">
132
+ <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
133
+ <div className="flex h-16 items-center justify-between">
134
+ <h1 className="text-xl font-bold text-zinc-900 dark:text-zinc-50">StackPatch</h1>
135
+ {session?.user && (
136
+ <div className="flex items-center gap-4">
137
+ <div className="hidden sm:flex sm:flex-col sm:items-end">
138
+ <span className="text-sm font-medium text-zinc-900 dark:text-zinc-50">
139
+ {session.user.name || session.user.email}
140
+ </span>
141
+ <span className="text-xs text-zinc-500 dark:text-zinc-400">
142
+ {session.user.email}
143
+ </span>
144
+ </div>
145
+ <button
146
+ onClick={handleSignOut}
147
+ className="rounded-md bg-zinc-900 px-4 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 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
148
+ >
149
+ Sign Out
150
+ </button>
151
+ </div>
152
+ )}
153
+ </div>
154
+ </div>
155
+ </header>
156
+
157
+ <main className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
158
+ <div className="text-center">
159
+ <h1 className="text-5xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 sm:text-7xl">
160
+ StackPatch
161
+ </h1>
162
+ <p className="mt-6 text-xl leading-8 text-zinc-600 dark:text-zinc-400">
163
+ Composable frontend features for modern React & Next.js apps
164
+ </p>
165
+
166
+ {/* User Session Info */}
167
+ {session?.user && (
168
+ <div className="mx-auto mt-8 max-w-md rounded-lg bg-white p-6 shadow-sm dark:bg-zinc-800">
169
+ <h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
170
+ Welcome back! 👋
171
+ </h2>
172
+ <div className="mt-4 space-y-2 text-left text-sm text-zinc-600 dark:text-zinc-400">
173
+ <p>
174
+ <span className="font-medium">Name:</span> {session.user.name || "Not provided"}
175
+ </p>
176
+ <p>
177
+ <span className="font-medium">Email:</span> {session.user.email}
178
+ </p>
179
+ {session.user.image && (
180
+ <div className="mt-3">
181
+ <img
182
+ src={session.user.image}
183
+ alt={session.user.name || "User"}
184
+ className="mx-auto h-16 w-16 rounded-full"
185
+ />
186
+ </div>
187
+ )}
188
+ </div>
189
+ </div>
190
+ )}
191
+
192
+ {/* CTA Buttons */}
193
+ <div className="mt-10 flex items-center justify-center gap-x-6">
194
+ <a
195
+ href="https://www.producthunt.com/posts/stackpatch"
196
+ target="_blank"
197
+ rel="noopener noreferrer"
198
+ className="rounded-md bg-orange-500 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-orange-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-600"
199
+ >
200
+ 🚀 Support us on ProductHunt
201
+ </a>
202
+ <a
203
+ href="https://github.com/Darshh09/StackPatch"
204
+ target="_blank"
205
+ rel="noopener noreferrer"
206
+ className="flex items-center gap-2 rounded-md bg-zinc-900 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
207
+ >
208
+ <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
209
+ <path
210
+ fillRule="evenodd"
211
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482C19.138 20.197 22 16.425 22 12.017 22 6.484 17.522 2 12 2z"
212
+ clipRule="evenodd"
213
+ />
214
+ </svg>
215
+ ⭐ Star on GitHub
216
+ </a>
217
+ </div>
218
+
219
+ {/* Setup Instructions */}
220
+ {session?.user && (
221
+ <div className="mx-auto mt-12 max-w-2xl rounded-lg border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-800 dark:bg-zinc-900">
222
+ <h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
223
+ 🎉 Authentication Setup Complete!
224
+ </h2>
225
+ <div className="mt-4 space-y-3 text-left text-sm text-zinc-600 dark:text-zinc-400">
226
+ <div>
227
+ <p className="font-medium text-zinc-900 dark:text-zinc-50">Next Steps:</p>
228
+ <ol className="mt-2 ml-4 list-decimal space-y-2">
229
+ <li>Create <code className="rounded bg-zinc-200 px-1.5 py-0.5 text-xs dark:bg-zinc-800">.env.local</code> from <code className="rounded bg-zinc-200 px-1.5 py-0.5 text-xs dark:bg-zinc-800">.env.example</code></li>
230
+ <li>Add your OAuth credentials (Google/GitHub) to <code className="rounded bg-zinc-200 px-1.5 py-0.5 text-xs dark:bg-zinc-800">.env.local</code></li>
231
+ <li>Configure your database (if using database mode)</li>
232
+ <li>Start building your app!</li>
233
+ </ol>
234
+ </div>
235
+ <div className="mt-4 pt-4 border-t border-zinc-300 dark:border-zinc-700">
236
+ <p className="font-medium text-zinc-900 dark:text-zinc-50 mb-2">Protected Routes:</p>
237
+ <div className="text-xs space-y-1 text-zinc-600 dark:text-zinc-400">
238
+ <p>Routes are automatically protected. Use <code className="rounded bg-zinc-200 px-1 py-0.5 dark:bg-zinc-800">/*</code> to protect all sub-routes:</p>
239
+ <ul className="ml-4 mt-1 space-y-0.5 list-disc">
240
+ <li><code className="rounded bg-zinc-200 px-1 py-0.5 dark:bg-zinc-800">/dashboard</code> → Protects only /dashboard</li>
241
+ <li><code className="rounded bg-zinc-200 px-1 py-0.5 dark:bg-zinc-800">/dashboard/*</code> → Protects /dashboard and all sub-routes</li>
242
+ <li><code className="rounded bg-zinc-200 px-1 py-0.5 dark:bg-zinc-800">/admin/*</code> → Protects /admin and all sub-routes</li>
243
+ </ul>
244
+ <p className="mt-2">Edit <code className="rounded bg-zinc-200 px-1 py-0.5 dark:bg-zinc-800">lib/protected-routes.ts</code> to modify protected routes.</p>
245
+ </div>
246
+ </div>
247
+ <div className="mt-4 pt-4 border-t border-zinc-300 dark:border-zinc-700">
248
+ <p className="font-medium text-zinc-900 dark:text-zinc-50">Documentation:</p>
249
+ <ul className="mt-2 space-y-1">
250
+ <li>
251
+ <a href="https://better-auth.com/docs" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline dark:text-blue-400">
252
+ Better Auth Docs
253
+ </a>
254
+ </li>
255
+ <li>
256
+ <a href="https://stackpatch.darshitdev.in/docs" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline dark:text-blue-400">
257
+ StackPatch Auth Guide
258
+ </a>
259
+ </li>
260
+ </ul>
261
+ </div>
262
+ </div>
263
+ </div>
264
+ )}
265
+ </div>
266
+ </main>
267
+ </div>
268
+ );
269
+ }
@@ -0,0 +1,61 @@
1
+ "use client";
2
+
3
+ import { ReactNode, useEffect } from "react";
4
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
+ import { authClient } from "@/lib/auth-client";
6
+ import { isProtectedRoute } from "@/lib/protected-routes";
7
+
8
+ /**
9
+ * Auth Wrapper Component
10
+ *
11
+ * This wrapper checks authentication for protected routes and handles redirects.
12
+ * It works alongside middleware to ensure routes are properly protected.
13
+ */
14
+ export function AuthWrapper({ children }: { children: ReactNode }) {
15
+ const pathname = usePathname();
16
+ const router = useRouter();
17
+ const searchParams = useSearchParams();
18
+ const { data: session, isPending } = authClient.useSession();
19
+
20
+ useEffect(() => {
21
+ // Don't do anything while loading
22
+ if (isPending) return;
23
+
24
+ // Handle auth pages (login/signup)
25
+ if (pathname === "/auth/login" || pathname === "/auth/signup") {
26
+ if (session?.user) {
27
+ // Already authenticated - redirect away from auth pages
28
+ const redirectParam = searchParams.get("redirect");
29
+ const redirectTo = redirectParam || "/stackpatch";
30
+ router.push(redirectTo);
31
+ }
32
+ return; // Allow access to auth pages if not authenticated
33
+ }
34
+
35
+ // Handle protected routes
36
+ if (isProtectedRoute(pathname)) {
37
+ if (!session?.user) {
38
+ // Not authenticated - redirect to login with return URL
39
+ const loginUrl = `/auth/login?redirect=${encodeURIComponent(pathname)}`;
40
+ router.push(loginUrl);
41
+ }
42
+ return; // Allow access if authenticated
43
+ }
44
+
45
+ // For all other routes, allow access
46
+ }, [pathname, session, isPending, router, searchParams]);
47
+
48
+ // Show loading state while checking session (only on protected routes)
49
+ if (isPending && isProtectedRoute(pathname) && !session?.user) {
50
+ return (
51
+ <div className="flex min-h-screen items-center justify-center">
52
+ <div className="text-center">
53
+ <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
54
+ <p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading...</p>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ return <>{children}</>;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackpatch",
3
- "version": "1.1.1",
3
+ "version": "1.1.4",
4
4
  "description": "Composable frontend features for modern React & Next.js apps - Add authentication, UI components, and more with zero configuration",
5
5
  "main": "bin/stackpatch.ts",
6
6
  "type": "module",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "bin",
13
+ "src",
13
14
  "boilerplate",
14
15
  "README.md"
15
16
  ],
@@ -51,9 +52,10 @@
51
52
  },
52
53
  "packageManager": "pnpm@10.28.0",
53
54
  "dependencies": {
55
+ "@inquirer/select": "^5.0.4",
54
56
  "chalk": "^5.6.2",
55
57
  "fs-extra": "^11.3.3",
56
- "inquirer": "^13.1.0",
58
+ "inquirer": "^13.2.0",
57
59
  "jimp": "^0.22.10",
58
60
  "tsx": "^4.19.2"
59
61
  },