create-nextjs-stack 0.1.0
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/LICENSE +21 -0
- package/README.md +60 -0
- package/bin/cli.js +187 -0
- package/package.json +48 -0
- package/templates/admin/.env.example +11 -0
- package/templates/admin/README.md +82 -0
- package/templates/admin/app/(auth)/login/page.tsx +84 -0
- package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
- package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
- package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/layout.tsx +13 -0
- package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
- package/templates/admin/app/actions/resources.ts +46 -0
- package/templates/admin/app/actions/upload.ts +58 -0
- package/templates/admin/app/favicon.ico +0 -0
- package/templates/admin/app/globals.css +23 -0
- package/templates/admin/app/layout.tsx +23 -0
- package/templates/admin/app/page.tsx +5 -0
- package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
- package/templates/admin/components/admin/DeleteModal.tsx +90 -0
- package/templates/admin/components/admin/FormLayout.tsx +113 -0
- package/templates/admin/components/admin/ImageUpload.tsx +137 -0
- package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
- package/templates/admin/components/admin/Sidebar.tsx +74 -0
- package/templates/admin/components/admin/SubmitButton.tsx +34 -0
- package/templates/admin/components/admin/ToastProvider.tsx +8 -0
- package/templates/admin/components/categories/CategoryForm.tsx +24 -0
- package/templates/admin/components/categories/CategoryList.tsx +113 -0
- package/templates/admin/components/clients/ClientForm.tsx +24 -0
- package/templates/admin/components/clients/ClientList.tsx +113 -0
- package/templates/admin/components/products/ProductForm.tsx +24 -0
- package/templates/admin/components/products/ProductList.tsx +117 -0
- package/templates/admin/components/projects/ProjectForm.tsx +24 -0
- package/templates/admin/components/projects/ProjectList.tsx +121 -0
- package/templates/admin/components/users/UserForm.tsx +39 -0
- package/templates/admin/components/users/UserList.tsx +101 -0
- package/templates/admin/config/resources.ts +123 -0
- package/templates/admin/eslint.config.mjs +18 -0
- package/templates/admin/hooks/useResource.ts +86 -0
- package/templates/admin/lib/services/base.service.ts +106 -0
- package/templates/admin/lib/services/categories.service.ts +7 -0
- package/templates/admin/lib/services/clients.service.ts +7 -0
- package/templates/admin/lib/services/index.ts +27 -0
- package/templates/admin/lib/services/products.service.ts +9 -0
- package/templates/admin/lib/services/projects.service.ts +22 -0
- package/templates/admin/lib/services/resource.service.ts +26 -0
- package/templates/admin/lib/services/users.service.ts +9 -0
- package/templates/admin/lib/supabase/client.ts +9 -0
- package/templates/admin/lib/supabase/middleware.ts +57 -0
- package/templates/admin/lib/supabase/server.ts +29 -0
- package/templates/admin/middleware.ts +15 -0
- package/templates/admin/next.config.ts +10 -0
- package/templates/admin/package-lock.json +6768 -0
- package/templates/admin/package.json +33 -0
- package/templates/admin/postcss.config.mjs +7 -0
- package/templates/admin/public/file.svg +1 -0
- package/templates/admin/public/globe.svg +1 -0
- package/templates/admin/public/next.svg +1 -0
- package/templates/admin/public/vercel.svg +1 -0
- package/templates/admin/public/window.svg +1 -0
- package/templates/admin/supabase_mock_data.sql +57 -0
- package/templates/admin/supabase_schema.sql +93 -0
- package/templates/admin/tsconfig.json +34 -0
- package/templates/web/.env.example +21 -0
- package/templates/web/README.md +129 -0
- package/templates/web/components.json +22 -0
- package/templates/web/eslint.config.mjs +25 -0
- package/templates/web/next.config.ts +25 -0
- package/templates/web/package-lock.json +6778 -0
- package/templates/web/package.json +45 -0
- package/templates/web/postcss.config.mjs +5 -0
- package/templates/web/src/app/api/contact/route.ts +181 -0
- package/templates/web/src/app/api/revalidate/route.ts +95 -0
- package/templates/web/src/app/error.tsx +28 -0
- package/templates/web/src/app/globals.css +838 -0
- package/templates/web/src/app/layout.tsx +126 -0
- package/templates/web/src/app/loading.tsx +60 -0
- package/templates/web/src/app/not-found.tsx +68 -0
- package/templates/web/src/app/page.tsx +106 -0
- package/templates/web/src/app/robots.ts +12 -0
- package/templates/web/src/app/sitemap.ts +66 -0
- package/templates/web/src/components/home/StatsGrid.tsx +89 -0
- package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
- package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
- package/templates/web/src/lib/seo/index.ts +4 -0
- package/templates/web/src/lib/seo/metadata.ts +103 -0
- package/templates/web/src/lib/seo/seo.config.ts +161 -0
- package/templates/web/src/lib/seo/seo.types.ts +76 -0
- package/templates/web/src/lib/services/categories.service.ts +38 -0
- package/templates/web/src/lib/services/categoryService.ts +251 -0
- package/templates/web/src/lib/services/clientService.ts +132 -0
- package/templates/web/src/lib/services/clients.service.ts +20 -0
- package/templates/web/src/lib/services/productService.ts +261 -0
- package/templates/web/src/lib/services/products.service.ts +38 -0
- package/templates/web/src/lib/services/projectService.ts +234 -0
- package/templates/web/src/lib/services/projects.service.ts +38 -0
- package/templates/web/src/lib/services/users.service.ts +20 -0
- package/templates/web/src/lib/supabase/client.ts +42 -0
- package/templates/web/src/lib/supabase/constants.ts +25 -0
- package/templates/web/src/lib/supabase/server.ts +29 -0
- package/templates/web/src/lib/supabase/types.ts +112 -0
- package/templates/web/src/lib/utils/cache.ts +98 -0
- package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
- package/templates/web/src/store/actions/index.ts +2 -0
- package/templates/web/src/store/index.ts +13 -0
- package/templates/web/src/store/reducers/index.ts +13 -0
- package/templates/web/src/store/types/index.ts +2 -0
- package/templates/web/tsconfig.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mehmet Burak Altıparmak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Create Next.js Stack
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/create-nextjs-stack)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.npmjs.com/package/create-nextjs-stack)
|
|
6
|
+
|
|
7
|
+
A powerful CLI tool to scaffold production-ready Next.js applications and Supabase Admin panels.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **Multi-Template Support**: Choose between Web, Admin, or Full Stack.
|
|
12
|
+
- **Production Ready**: Includes Tailwind CSS 4, Redux Toolkit, Supabase, Cloudinary, and Resend.
|
|
13
|
+
- **Smart Scaffolding**: Automatically handles environment variables and cleans up dependencies.
|
|
14
|
+
|
|
15
|
+
## 📦 Usage
|
|
16
|
+
|
|
17
|
+
You can use this tool directly with `npx`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx create-nextjs-stack my-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Interactive Mode
|
|
24
|
+
|
|
25
|
+
The CLI will ask you:
|
|
26
|
+
|
|
27
|
+
1. **Project Name**: What to call your new project.
|
|
28
|
+
2. **Template Type**:
|
|
29
|
+
- `Full Stack`: Creates both `web` and `admin` projects.
|
|
30
|
+
- `Web Only`: Creates the Next.js Landing website.
|
|
31
|
+
- `Admin Only`: Creates the Supabase Admin Panel.
|
|
32
|
+
|
|
33
|
+
### Command Line Arguments
|
|
34
|
+
|
|
35
|
+
You can also bypass prompts:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Create a full stack project
|
|
39
|
+
npx create-nextjs-stack my-app --template full-stack
|
|
40
|
+
|
|
41
|
+
# Create just the web app
|
|
42
|
+
npx create-nextjs-stack my-web-app --template web
|
|
43
|
+
|
|
44
|
+
# Create just the admin panel
|
|
45
|
+
npx create-nextjs-stack my-admin --template admin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 🛠 Project Structure
|
|
49
|
+
|
|
50
|
+
After running the command, your project will look like this (for full-stack):
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
my-app/
|
|
54
|
+
├── web/ # Next.js Landing Page (App Router, Tailwind 4, Redux)
|
|
55
|
+
└── admin/ # Supabase Admin Panel (React, Vite, Tailwind)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 📝 License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { program } = require("commander");
|
|
6
|
+
const prompts = require("prompts");
|
|
7
|
+
const chalk = require("chalk");
|
|
8
|
+
const ora = require("ora");
|
|
9
|
+
|
|
10
|
+
// Read version from package.json
|
|
11
|
+
const packageJson = require("../package.json");
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name("create-nextjs-stack")
|
|
15
|
+
.version(packageJson.version, "-v, --version", "Output the current version")
|
|
16
|
+
.description("Scaffold a new Next.js Project with Supabase Admin support")
|
|
17
|
+
.argument("[project-directory]", "Directory to create the project in")
|
|
18
|
+
.option("-t, --template <type>", "Template type: web, admin, or full-stack")
|
|
19
|
+
.action(async (projectDirectory, options) => {
|
|
20
|
+
let targetDir = projectDirectory;
|
|
21
|
+
|
|
22
|
+
// 1. Get Project Name / Directory
|
|
23
|
+
if (!targetDir) {
|
|
24
|
+
const res = await prompts({
|
|
25
|
+
type: "text",
|
|
26
|
+
name: "value",
|
|
27
|
+
message: "What is your project named?",
|
|
28
|
+
initial: "my-awesome-project",
|
|
29
|
+
});
|
|
30
|
+
targetDir = res.value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!targetDir) {
|
|
34
|
+
console.log(chalk.red("Operation cancelled. No project name provided."));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const root = path.resolve(targetDir);
|
|
39
|
+
const appName = path.basename(root);
|
|
40
|
+
|
|
41
|
+
// 2. Select Template Type
|
|
42
|
+
let templateType = options.template;
|
|
43
|
+
|
|
44
|
+
if (!templateType) {
|
|
45
|
+
const res = await prompts({
|
|
46
|
+
type: "select",
|
|
47
|
+
name: "templateType",
|
|
48
|
+
message: "Which template would you like to generate?",
|
|
49
|
+
choices: [
|
|
50
|
+
{
|
|
51
|
+
title: "Full Stack (Web + Admin)",
|
|
52
|
+
value: "full-stack",
|
|
53
|
+
description:
|
|
54
|
+
"Creates both web and admin projects in subdirectories",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: "Web Only (Next.js Landing)",
|
|
58
|
+
value: "web",
|
|
59
|
+
description: "Just the landing page/web application",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: "Admin Only (Supabase Admin)",
|
|
63
|
+
value: "admin",
|
|
64
|
+
description: "Just the admin panel",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
initial: 0,
|
|
68
|
+
});
|
|
69
|
+
templateType = res.templateType;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!templateType) {
|
|
73
|
+
console.log(chalk.red("Operation cancelled."));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(
|
|
78
|
+
`\nCreating a new ${chalk.cyan(templateType)} project in ${chalk.green(root)}.\n`,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// 3. Ensure Directory exists
|
|
82
|
+
if (fs.existsSync(root)) {
|
|
83
|
+
const files = fs.readdirSync(root);
|
|
84
|
+
if (files.length > 0) {
|
|
85
|
+
const { shouldOverwrite } = await prompts({
|
|
86
|
+
type: "confirm",
|
|
87
|
+
name: "shouldOverwrite",
|
|
88
|
+
message: `Directory ${appName} is not empty. Overwrite?`,
|
|
89
|
+
initial: false,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!shouldOverwrite) {
|
|
93
|
+
console.log(chalk.red("Aborting installation."));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
fs.emptyDirSync(root);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
fs.ensureDirSync(root);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const spinner = ora(`Scaffolding ${templateType}...`).start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const templatesDir = path.join(__dirname, "..", "templates");
|
|
106
|
+
|
|
107
|
+
// Helper function to copy a template
|
|
108
|
+
const copyTemplate = (sourceName, destPath) => {
|
|
109
|
+
const source = path.join(templatesDir, sourceName);
|
|
110
|
+
fs.copySync(source, destPath, {
|
|
111
|
+
filter: (src) => {
|
|
112
|
+
const basename = path.basename(src);
|
|
113
|
+
return (
|
|
114
|
+
basename !== "node_modules" &&
|
|
115
|
+
basename !== ".next" &&
|
|
116
|
+
basename !== ".git" &&
|
|
117
|
+
basename !== "package-lock.json" &&
|
|
118
|
+
basename !== ".env" &&
|
|
119
|
+
basename !== ".DS_Store"
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Handle .env.example -> .env
|
|
125
|
+
const envExample = path.join(destPath, ".env.example");
|
|
126
|
+
const envTarget = path.join(destPath, ".env");
|
|
127
|
+
if (fs.existsSync(envExample)) {
|
|
128
|
+
fs.copySync(envExample, envTarget);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Update package.json name
|
|
132
|
+
const pkgPath = path.join(destPath, "package.json");
|
|
133
|
+
if (fs.existsSync(pkgPath)) {
|
|
134
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
135
|
+
pkg.name = path.basename(destPath);
|
|
136
|
+
fs.writeJsonSync(pkgPath, pkg, { spaces: 2 });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (templateType === "full-stack") {
|
|
141
|
+
// Create subdirectories
|
|
142
|
+
const webDir = path.join(root, "web");
|
|
143
|
+
const adminDir = path.join(root, "admin");
|
|
144
|
+
|
|
145
|
+
fs.ensureDirSync(webDir);
|
|
146
|
+
fs.ensureDirSync(adminDir);
|
|
147
|
+
|
|
148
|
+
copyTemplate("web", webDir);
|
|
149
|
+
copyTemplate("admin", adminDir);
|
|
150
|
+
|
|
151
|
+
// Create a root package.json for convenience (workspaces)?
|
|
152
|
+
// Optional, but let's at least leave a README
|
|
153
|
+
fs.writeFileSync(
|
|
154
|
+
path.join(root, "README.md"),
|
|
155
|
+
`# ${appName}\n\nThis project contains both Web and Admin applications.\n\n- [Web](./web)\n- [Admin](./admin)`,
|
|
156
|
+
);
|
|
157
|
+
} else if (templateType === "web") {
|
|
158
|
+
copyTemplate("web", root);
|
|
159
|
+
} else if (templateType === "admin") {
|
|
160
|
+
copyTemplate("admin", root);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
spinner.succeed("Scaffolding complete!");
|
|
164
|
+
|
|
165
|
+
console.log(`\nSuccess! Created project at ${root}\n`);
|
|
166
|
+
|
|
167
|
+
if (templateType === "full-stack") {
|
|
168
|
+
console.log("Next steps:");
|
|
169
|
+
console.log(chalk.cyan(` cd ${appName}`));
|
|
170
|
+
console.log(" Then go to either web or admin folder:");
|
|
171
|
+
console.log(chalk.cyan(` cd web`));
|
|
172
|
+
console.log(chalk.cyan(` npm install`));
|
|
173
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
174
|
+
} else {
|
|
175
|
+
console.log("Next steps:");
|
|
176
|
+
console.log(chalk.cyan(` cd ${appName}`));
|
|
177
|
+
console.log(chalk.cyan(` npm install`));
|
|
178
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
spinner.fail("Error scaffolding project.");
|
|
182
|
+
console.error(error);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-nextjs-stack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to scaffold Next.js Landing Page and Supabase Admin Panel",
|
|
5
|
+
"private": false,
|
|
6
|
+
"bin": "./bin/cli.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"bin",
|
|
9
|
+
"templates"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "vitest run"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"chalk": "^4.1.2",
|
|
16
|
+
"commander": "^14.0.3",
|
|
17
|
+
"fs-extra": "^11.3.3",
|
|
18
|
+
"ora": "^5.4.1",
|
|
19
|
+
"prompts": "^2.4.2"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"nextjs",
|
|
26
|
+
"starter",
|
|
27
|
+
"template",
|
|
28
|
+
"supabase",
|
|
29
|
+
"admin",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"author": "Mehmet Burak Altıparmak <mburakaltiparmak@gmail.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/mburakaltiparmak/create-nextjs-stack.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/mburakaltiparmak/create-nextjs-stack/blob/main/README.md",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/mburakaltiparmak/create-nextjs-stack/issues"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/fs-extra": "^11.0.4",
|
|
44
|
+
"@types/node": "^25.2.3",
|
|
45
|
+
"execa": "^9.6.1",
|
|
46
|
+
"vitest": "^4.0.18"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Supabase Configuration
|
|
2
|
+
NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
|
3
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
4
|
+
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
5
|
+
|
|
6
|
+
# Cloudinary Configuration (for image uploads)
|
|
7
|
+
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name
|
|
8
|
+
CLOUDINARY_API_KEY=your-api-key
|
|
9
|
+
CLOUDINARY_API_SECRET=your-api-secret
|
|
10
|
+
CLOUDINARY_URL=cloudinary://your-api-key:your-api-secret@your-cloud-name
|
|
11
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Supabase Admin Panel
|
|
2
|
+
|
|
3
|
+
A lightweight admin panel built with Next.js and Supabase for managing database content.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
- **Authentication**: Secure login with Supabase Auth
|
|
8
|
+
- **CRUD Operations**: Create, Read, Update, Delete database records
|
|
9
|
+
- **Responsive Design**: Works on desktop and mobile
|
|
10
|
+
- **TypeScript**: Full type safety
|
|
11
|
+
- **Modern Stack**: Next.js 16 + React 19 + Tailwind CSS 4
|
|
12
|
+
|
|
13
|
+
## 📋 Prerequisites
|
|
14
|
+
|
|
15
|
+
- Node.js 18+ and npm installed
|
|
16
|
+
- Supabase account (free tier available)
|
|
17
|
+
|
|
18
|
+
## 🛠 Setup
|
|
19
|
+
|
|
20
|
+
### 1. Configure Environment Variables
|
|
21
|
+
|
|
22
|
+
Copy `.env.example` to `.env` and fill in your Supabase credentials:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cp .env.example .env
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Required environment variables:
|
|
29
|
+
|
|
30
|
+
- `NEXT_PUBLIC_SUPABASE_URL`: Your Supabase project URL
|
|
31
|
+
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`: Your Supabase anonymous key
|
|
32
|
+
|
|
33
|
+
### 2. Install Dependencies
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Run Development Server
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Open [http://localhost:3000](http://localhost:3000) to see the admin panel.
|
|
46
|
+
|
|
47
|
+
## 📁 Project Structure
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
admin/
|
|
51
|
+
├── app/ # Next.js App Router pages
|
|
52
|
+
├── components/ # Reusable UI components
|
|
53
|
+
├── lib/ # Utilities and helpers
|
|
54
|
+
├── public/ # Static assets
|
|
55
|
+
└── config/ # Configuration files
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 🔐 Authentication
|
|
59
|
+
|
|
60
|
+
The admin panel uses Supabase Auth. Make sure to:
|
|
61
|
+
|
|
62
|
+
1. Enable Email/Password authentication in your Supabase project
|
|
63
|
+
2. Configure the correct redirect URLs in Supabase Dashboard
|
|
64
|
+
|
|
65
|
+
## 📚 Learn More
|
|
66
|
+
|
|
67
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
68
|
+
- [Supabase Documentation](https://supabase.com/docs)
|
|
69
|
+
- [Tailwind CSS](https://tailwindcss.com/docs)
|
|
70
|
+
|
|
71
|
+
## 🚀 Deployment
|
|
72
|
+
|
|
73
|
+
### Vercel (Recommended)
|
|
74
|
+
|
|
75
|
+
1. Push code to GitHub
|
|
76
|
+
2. Import project in [Vercel](https://vercel.com)
|
|
77
|
+
3. Add environment variables
|
|
78
|
+
4. Deploy!
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
**Built with ❤️ using Next.js and Supabase**
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { getBrowserClient } from "@/lib/supabase/client";
|
|
6
|
+
|
|
7
|
+
export default function LoginPage() {
|
|
8
|
+
const [email, setEmail] = useState("");
|
|
9
|
+
const [password, setPassword] = useState("");
|
|
10
|
+
const [error, setError] = useState<string | null>(null);
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
|
|
14
|
+
const handleLogin = async (e: React.FormEvent) => {
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
setLoading(true);
|
|
17
|
+
setError(null);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const supabase = getBrowserClient();
|
|
21
|
+
const { error } = await supabase.auth.signInWithPassword({
|
|
22
|
+
email,
|
|
23
|
+
password,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (error) {
|
|
27
|
+
setError(error.message);
|
|
28
|
+
} else {
|
|
29
|
+
// Refresh router to update server components
|
|
30
|
+
router.refresh();
|
|
31
|
+
router.push("/dashboard");
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(err);
|
|
35
|
+
setError("An unexpected error occurred.");
|
|
36
|
+
} finally {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex min-h-screen items-center justify-center bg-gray-100 p-4">
|
|
43
|
+
<div className="w-full max-w-md bg-white rounded-lg shadow-lg p-8">
|
|
44
|
+
<h1 className="text-2xl font-bold mb-6 text-center text-gray-900">Admin Login</h1>
|
|
45
|
+
|
|
46
|
+
{error && (
|
|
47
|
+
<div className="mb-4 p-3 text-sm text-red-500 bg-red-50 border border-red-200 rounded">
|
|
48
|
+
{error}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
<form onSubmit={handleLogin} className="space-y-4">
|
|
53
|
+
<div>
|
|
54
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
55
|
+
<input
|
|
56
|
+
type="email"
|
|
57
|
+
value={email}
|
|
58
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
59
|
+
required
|
|
60
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
|
65
|
+
<input
|
|
66
|
+
type="password"
|
|
67
|
+
value={password}
|
|
68
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
69
|
+
required
|
|
70
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
<button
|
|
74
|
+
type="submit"
|
|
75
|
+
disabled={loading}
|
|
76
|
+
className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
77
|
+
>
|
|
78
|
+
{loading ? "Signing in..." : "Sign In"}
|
|
79
|
+
</button>
|
|
80
|
+
</form>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resources } from "@/config/resources";
|
|
2
|
+
import { notFound } from "next/navigation";
|
|
3
|
+
import { getServerClient } from "@/lib/supabase/server";
|
|
4
|
+
import ResourceFormClient from "@/components/admin/ResourceFormClient";
|
|
5
|
+
|
|
6
|
+
interface PageProps {
|
|
7
|
+
params: {
|
|
8
|
+
resource: string;
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async function EditResourcePage({ params }: PageProps) {
|
|
14
|
+
const resourceName = params.resource;
|
|
15
|
+
const config = resources.find((r) => r.name === resourceName);
|
|
16
|
+
|
|
17
|
+
if (!config) {
|
|
18
|
+
return notFound();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const supabase = await getServerClient();
|
|
22
|
+
const { data: item, error } = await supabase
|
|
23
|
+
.from(config.table)
|
|
24
|
+
.select("*")
|
|
25
|
+
.eq("id", params.id)
|
|
26
|
+
.single();
|
|
27
|
+
|
|
28
|
+
if (error || !item) {
|
|
29
|
+
return notFound();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<div className="mb-6">
|
|
35
|
+
<h1 className="text-2xl font-bold">Edit {config.singular}</h1>
|
|
36
|
+
</div>
|
|
37
|
+
<ResourceFormClient
|
|
38
|
+
config={config}
|
|
39
|
+
mode="update"
|
|
40
|
+
initialData={item}
|
|
41
|
+
id={params.id}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resources } from "@/config/resources";
|
|
2
|
+
import { notFound, redirect } from "next/navigation";
|
|
3
|
+
import FormLayout from "@/components/admin/FormLayout";
|
|
4
|
+
import { createResource } from "@/app/actions/resources"; // We will create this
|
|
5
|
+
import ResourceFormClient from "@/components/admin/ResourceFormClient"; // Wrapper for logic
|
|
6
|
+
|
|
7
|
+
interface PageProps {
|
|
8
|
+
params: {
|
|
9
|
+
resource: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function CreateResourcePage({ params }: PageProps) {
|
|
14
|
+
const resourceName = params.resource;
|
|
15
|
+
const config = resources.find((r) => r.name === resourceName);
|
|
16
|
+
|
|
17
|
+
if (!config) {
|
|
18
|
+
return notFound();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div>
|
|
23
|
+
<div className="mb-6">
|
|
24
|
+
<h1 className="text-2xl font-bold">Create {config.singular}</h1>
|
|
25
|
+
</div>
|
|
26
|
+
<ResourceFormClient
|
|
27
|
+
config={config}
|
|
28
|
+
mode="create"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|