stackpatch 1.2.3 → 1.2.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.
- package/README.md +1 -1
- package/boilerplate/auth/app/auth/login/page.tsx +50 -24
- package/boilerplate/auth/app/auth/signup/page.tsx +69 -56
- package/boilerplate/auth/app/stackpatch/page.tsx +269 -0
- package/boilerplate/auth/components/auth-wrapper.tsx +61 -0
- package/dist/stackpatch.js +2386 -28430
- package/package.json +6 -3
- package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +0 -124
- package/boilerplate/auth/app/api/auth/signup/route.ts +0 -45
- package/boilerplate/auth/app/dashboard/page.tsx +0 -82
- package/boilerplate/auth/app/login/page.tsx +0 -136
- package/boilerplate/auth/app/page.tsx +0 -48
- package/boilerplate/auth/components/auth-button.tsx +0 -43
- package/boilerplate/auth/components/auth-navbar.tsx +0 -118
- package/boilerplate/auth/components/protected-route.tsx +0 -74
- package/boilerplate/auth/components/session-provider.tsx +0 -11
- package/boilerplate/auth/middleware.ts +0 -51
package/README.md
CHANGED
|
@@ -1,50 +1,59 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 {
|
|
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
|
-
//
|
|
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
|
-
|
|
64
|
+
name,
|
|
82
65
|
});
|
|
83
66
|
|
|
84
|
-
if (result
|
|
85
|
-
setError(
|
|
86
|
-
toast.error(
|
|
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("
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
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
|
+
}
|