stackpatch 1.1.2 → 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.
- package/README.md +123 -114
- package/bin/stackpatch.ts +2 -2441
- 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/package.json +4 -2
- package/src/auth/generator.ts +569 -0
- package/src/auth/index.ts +372 -0
- package/src/auth/setup.ts +293 -0
- package/src/commands/add.ts +112 -0
- package/src/commands/create.ts +128 -0
- package/src/commands/revert.ts +389 -0
- package/src/config.ts +52 -0
- package/src/fileOps/copy.ts +224 -0
- package/src/fileOps/layout.ts +304 -0
- package/src/fileOps/protected.ts +67 -0
- package/src/index.ts +215 -0
- package/src/manifest.ts +87 -0
- package/src/ui/logo.ts +24 -0
- package/src/ui/progress.ts +82 -0
- package/src/utils/dependencies.ts +114 -0
- package/src/utils/deps-check.ts +45 -0
- package/src/utils/files.ts +58 -0
- package/src/utils/paths.ts +217 -0
- package/src/utils/scanner.ts +109 -0
- 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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility functions for detecting project structure and paths
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect the app directory location (app/ or src/app/)
|
|
10
|
+
*/
|
|
11
|
+
export function detectAppDirectory(target: string): string {
|
|
12
|
+
// Check for src/app first (more common in modern Next.js projects)
|
|
13
|
+
if (fs.existsSync(path.join(target, "src", "app"))) {
|
|
14
|
+
return "src/app";
|
|
15
|
+
}
|
|
16
|
+
// Check for app directory
|
|
17
|
+
if (fs.existsSync(path.join(target, "app"))) {
|
|
18
|
+
return "app";
|
|
19
|
+
}
|
|
20
|
+
// Check for src/pages (legacy)
|
|
21
|
+
if (fs.existsSync(path.join(target, "src", "pages"))) {
|
|
22
|
+
return "src/pages";
|
|
23
|
+
}
|
|
24
|
+
// Check for pages (legacy)
|
|
25
|
+
if (fs.existsSync(path.join(target, "pages"))) {
|
|
26
|
+
return "pages";
|
|
27
|
+
}
|
|
28
|
+
// Default to app if nothing found (will fail gracefully later)
|
|
29
|
+
return "app";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect the components directory location (components/ or src/components/)
|
|
34
|
+
*/
|
|
35
|
+
export function detectComponentsDirectory(target: string): string {
|
|
36
|
+
const appDir = detectAppDirectory(target);
|
|
37
|
+
|
|
38
|
+
// If app is in src/app, components should be in src/components
|
|
39
|
+
if (appDir.startsWith("src/")) {
|
|
40
|
+
// Check if src/components exists
|
|
41
|
+
if (fs.existsSync(path.join(target, "src", "components"))) {
|
|
42
|
+
return "src/components";
|
|
43
|
+
}
|
|
44
|
+
// Even if it doesn't exist yet, return src/components to match app structure
|
|
45
|
+
return "src/components";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If app is in root, components should be in root
|
|
49
|
+
if (fs.existsSync(path.join(target, "components"))) {
|
|
50
|
+
return "components";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Default to components
|
|
54
|
+
return "components";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect path aliases from tsconfig.json
|
|
59
|
+
*/
|
|
60
|
+
export function detectPathAliases(target: string): { alias: string; path: string } | null {
|
|
61
|
+
const tsconfigPath = path.join(target, "tsconfig.json");
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const tsconfigContent = fs.readFileSync(tsconfigPath, "utf-8");
|
|
69
|
+
const tsconfig = JSON.parse(tsconfigContent);
|
|
70
|
+
|
|
71
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
72
|
+
if (!paths || typeof paths !== "object") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Look for common aliases like @/*, ~/*, etc.
|
|
77
|
+
for (const [alias, pathsArray] of Object.entries(paths)) {
|
|
78
|
+
if (Array.isArray(pathsArray) && pathsArray.length > 0) {
|
|
79
|
+
// Remove the /* from alias (e.g., "@/*" -> "@")
|
|
80
|
+
const cleanAlias = alias.replace(/\/\*$/, "");
|
|
81
|
+
// Get the first path and remove /* from it
|
|
82
|
+
const cleanPath = pathsArray[0].replace(/\/\*$/, "");
|
|
83
|
+
return { alias: cleanAlias, path: cleanPath };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// If parsing fails, return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate import path for components
|
|
95
|
+
*/
|
|
96
|
+
export function generateComponentImportPath(
|
|
97
|
+
target: string,
|
|
98
|
+
componentName: string,
|
|
99
|
+
fromFile: string
|
|
100
|
+
): string {
|
|
101
|
+
const pathAlias = detectPathAliases(target);
|
|
102
|
+
const componentsDir = detectComponentsDirectory(target);
|
|
103
|
+
|
|
104
|
+
// If we have a path alias, use it
|
|
105
|
+
if (pathAlias) {
|
|
106
|
+
// Check if the alias path matches components directory
|
|
107
|
+
const aliasPath = pathAlias.path.replace(/^\.\//, ""); // Remove leading ./
|
|
108
|
+
|
|
109
|
+
// If alias points to root and components is in root, use alias
|
|
110
|
+
if (aliasPath === "" && componentsDir === "components") {
|
|
111
|
+
return `${pathAlias.alias}/components/${componentName}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If alias points to src and components is in src/components, use alias
|
|
115
|
+
if (aliasPath === "src" && componentsDir === "src/components") {
|
|
116
|
+
return `${pathAlias.alias}/components/${componentName}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Try to match the alias path structure
|
|
120
|
+
if (componentsDir.startsWith(aliasPath)) {
|
|
121
|
+
const relativeFromAlias = componentsDir.replace(new RegExp(`^${aliasPath}/?`), "");
|
|
122
|
+
return `${pathAlias.alias}/${relativeFromAlias}/${componentName}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If alias path is "./" (root), components should be accessible via alias
|
|
126
|
+
if (aliasPath === "" || aliasPath === ".") {
|
|
127
|
+
return `${pathAlias.alias}/components/${componentName}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fallback: calculate relative path
|
|
132
|
+
// fromFile is the full path to the file we're importing into
|
|
133
|
+
const fromDir = path.dirname(fromFile);
|
|
134
|
+
const toComponents = path.join(target, componentsDir);
|
|
135
|
+
|
|
136
|
+
// Calculate relative path from the file's directory to components directory
|
|
137
|
+
const relativePath = path.relative(fromDir, toComponents).replace(/\\/g, "/");
|
|
138
|
+
const normalizedPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
139
|
+
|
|
140
|
+
return `${normalizedPath}/${componentName}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get all parent directories of a file path
|
|
145
|
+
*/
|
|
146
|
+
export function getParentDirectories(filePath: string, rootPath: string): string[] {
|
|
147
|
+
const dirs: string[] = [];
|
|
148
|
+
let current = path.dirname(filePath);
|
|
149
|
+
const root = path.resolve(rootPath);
|
|
150
|
+
|
|
151
|
+
while (current !== root && current !== path.dirname(current)) {
|
|
152
|
+
dirs.push(current);
|
|
153
|
+
current = path.dirname(current);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return dirs;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Auto-detect target directory for Next.js app
|
|
161
|
+
*/
|
|
162
|
+
export function detectTargetDirectory(startDir: string = process.cwd()): string {
|
|
163
|
+
let target = startDir;
|
|
164
|
+
|
|
165
|
+
// Check if we're in a Next.js app (has app/, src/app/, pages/, or src/pages/ directory)
|
|
166
|
+
const hasAppDir =
|
|
167
|
+
fs.existsSync(path.join(target, "app")) || fs.existsSync(path.join(target, "src", "app"));
|
|
168
|
+
const hasPagesDir =
|
|
169
|
+
fs.existsSync(path.join(target, "pages")) || fs.existsSync(path.join(target, "src", "pages"));
|
|
170
|
+
|
|
171
|
+
if (!hasAppDir && !hasPagesDir) {
|
|
172
|
+
// Try parent directory
|
|
173
|
+
const parent = path.resolve(target, "..");
|
|
174
|
+
if (
|
|
175
|
+
fs.existsSync(path.join(parent, "app")) ||
|
|
176
|
+
fs.existsSync(path.join(parent, "src", "app")) ||
|
|
177
|
+
fs.existsSync(path.join(parent, "pages")) ||
|
|
178
|
+
fs.existsSync(path.join(parent, "src", "pages"))
|
|
179
|
+
) {
|
|
180
|
+
target = parent;
|
|
181
|
+
} else {
|
|
182
|
+
// Try common monorepo locations: apps/, packages/, or root
|
|
183
|
+
const possiblePaths = [
|
|
184
|
+
path.join(target, "apps"),
|
|
185
|
+
path.join(parent, "apps"),
|
|
186
|
+
path.join(target, "packages"),
|
|
187
|
+
path.join(parent, "packages"),
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
let foundApp = false;
|
|
191
|
+
for (const possiblePath of possiblePaths) {
|
|
192
|
+
if (fs.existsSync(possiblePath)) {
|
|
193
|
+
// Look for Next.js apps in this directory
|
|
194
|
+
const entries = fs.readdirSync(possiblePath, { withFileTypes: true });
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (entry.isDirectory()) {
|
|
197
|
+
const appPath = path.join(possiblePath, entry.name);
|
|
198
|
+
if (
|
|
199
|
+
fs.existsSync(path.join(appPath, "app")) ||
|
|
200
|
+
fs.existsSync(path.join(appPath, "src", "app")) ||
|
|
201
|
+
fs.existsSync(path.join(appPath, "pages")) ||
|
|
202
|
+
fs.existsSync(path.join(appPath, "src", "pages"))
|
|
203
|
+
) {
|
|
204
|
+
target = appPath;
|
|
205
|
+
foundApp = true;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (foundApp) break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return target;
|
|
217
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Project scanner to detect project configuration
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ProjectScan {
|
|
9
|
+
framework: "nextjs" | "unknown";
|
|
10
|
+
router: "app" | "pages" | "unknown";
|
|
11
|
+
typescript: boolean;
|
|
12
|
+
packageManager: "pnpm" | "npm" | "yarn" | "bun" | "unknown";
|
|
13
|
+
runtime: "node" | "bun" | "unknown";
|
|
14
|
+
hasSrcDir: boolean;
|
|
15
|
+
existingAuth: "better-auth" | "next-auth" | "none";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Scan project to detect configuration
|
|
20
|
+
*/
|
|
21
|
+
export function scanProject(target: string): ProjectScan {
|
|
22
|
+
const scan: ProjectScan = {
|
|
23
|
+
framework: "unknown",
|
|
24
|
+
router: "unknown",
|
|
25
|
+
typescript: false,
|
|
26
|
+
packageManager: "unknown",
|
|
27
|
+
runtime: "node",
|
|
28
|
+
hasSrcDir: false,
|
|
29
|
+
existingAuth: "none",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Check for Next.js
|
|
33
|
+
const packageJsonPath = path.join(target, "package.json");
|
|
34
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
37
|
+
const deps = {
|
|
38
|
+
...packageJson.dependencies,
|
|
39
|
+
...packageJson.devDependencies,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (deps.next || deps["next"]) {
|
|
43
|
+
scan.framework = "nextjs";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Detect package manager from lock files
|
|
47
|
+
if (fs.existsSync(path.join(target, "pnpm-lock.yaml"))) {
|
|
48
|
+
scan.packageManager = "pnpm";
|
|
49
|
+
} else if (fs.existsSync(path.join(target, "yarn.lock"))) {
|
|
50
|
+
scan.packageManager = "yarn";
|
|
51
|
+
} else if (fs.existsSync(path.join(target, "package-lock.json"))) {
|
|
52
|
+
scan.packageManager = "npm";
|
|
53
|
+
} else if (fs.existsSync(path.join(target, "bun.lockb"))) {
|
|
54
|
+
scan.packageManager = "bun";
|
|
55
|
+
scan.runtime = "bun";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for existing auth
|
|
59
|
+
if (deps["better-auth"]) {
|
|
60
|
+
scan.existingAuth = "better-auth";
|
|
61
|
+
} else if (deps["next-auth"]) {
|
|
62
|
+
scan.existingAuth = "next-auth";
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Ignore errors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check for TypeScript
|
|
70
|
+
if (
|
|
71
|
+
fs.existsSync(path.join(target, "tsconfig.json")) ||
|
|
72
|
+
fs.existsSync(path.join(target, "src", "tsconfig.json"))
|
|
73
|
+
) {
|
|
74
|
+
scan.typescript = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for src directory
|
|
78
|
+
if (fs.existsSync(path.join(target, "src"))) {
|
|
79
|
+
scan.hasSrcDir = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Detect router type
|
|
83
|
+
if (fs.existsSync(path.join(target, "app")) || fs.existsSync(path.join(target, "src", "app"))) {
|
|
84
|
+
scan.router = "app";
|
|
85
|
+
} else if (
|
|
86
|
+
fs.existsSync(path.join(target, "pages")) ||
|
|
87
|
+
fs.existsSync(path.join(target, "src", "pages"))
|
|
88
|
+
) {
|
|
89
|
+
scan.router = "pages";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return scan;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format scan results for display
|
|
97
|
+
*/
|
|
98
|
+
export function formatScanResults(scan: ProjectScan): string[] {
|
|
99
|
+
const results: string[] = [];
|
|
100
|
+
|
|
101
|
+
results.push(`✔ Framework: ${scan.framework === "nextjs" ? "Next.js" : "Unknown"} ${scan.router !== "unknown" ? `(${scan.router === "app" ? "App Router" : "Pages Router"})` : ""}`);
|
|
102
|
+
results.push(`✔ TypeScript: ${scan.typescript ? "Yes" : "No"}`);
|
|
103
|
+
results.push(`✔ Package Manager: ${scan.packageManager !== "unknown" ? scan.packageManager : "Unknown"}`);
|
|
104
|
+
results.push(`✔ Runtime: ${scan.runtime === "bun" ? "Bun" : "Node"}`);
|
|
105
|
+
results.push(`✔ src directory: ${scan.hasSrcDir ? "Yes" : "No"}`);
|
|
106
|
+
results.push(`✔ Existing auth: ${scan.existingAuth === "none" ? "None" : scan.existingAuth === "better-auth" ? "Better Auth" : "NextAuth"}`);
|
|
107
|
+
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
@@ -1,124 +0,0 @@
|
|
|
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 };
|
|
@@ -1,45 +0,0 @@
|
|
|
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,82 +0,0 @@
|
|
|
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
|
-
}
|