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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/bin/cli.js +187 -0
  4. package/package.json +48 -0
  5. package/templates/admin/.env.example +11 -0
  6. package/templates/admin/README.md +82 -0
  7. package/templates/admin/app/(auth)/login/page.tsx +84 -0
  8. package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
  9. package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
  10. package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
  11. package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
  12. package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
  13. package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
  14. package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
  15. package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
  16. package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
  17. package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
  18. package/templates/admin/app/(dashboard)/layout.tsx +13 -0
  19. package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
  20. package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
  21. package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
  22. package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
  23. package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
  24. package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
  25. package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
  26. package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
  27. package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
  28. package/templates/admin/app/actions/resources.ts +46 -0
  29. package/templates/admin/app/actions/upload.ts +58 -0
  30. package/templates/admin/app/favicon.ico +0 -0
  31. package/templates/admin/app/globals.css +23 -0
  32. package/templates/admin/app/layout.tsx +23 -0
  33. package/templates/admin/app/page.tsx +5 -0
  34. package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
  35. package/templates/admin/components/admin/DeleteModal.tsx +90 -0
  36. package/templates/admin/components/admin/FormLayout.tsx +113 -0
  37. package/templates/admin/components/admin/ImageUpload.tsx +137 -0
  38. package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
  39. package/templates/admin/components/admin/Sidebar.tsx +74 -0
  40. package/templates/admin/components/admin/SubmitButton.tsx +34 -0
  41. package/templates/admin/components/admin/ToastProvider.tsx +8 -0
  42. package/templates/admin/components/categories/CategoryForm.tsx +24 -0
  43. package/templates/admin/components/categories/CategoryList.tsx +113 -0
  44. package/templates/admin/components/clients/ClientForm.tsx +24 -0
  45. package/templates/admin/components/clients/ClientList.tsx +113 -0
  46. package/templates/admin/components/products/ProductForm.tsx +24 -0
  47. package/templates/admin/components/products/ProductList.tsx +117 -0
  48. package/templates/admin/components/projects/ProjectForm.tsx +24 -0
  49. package/templates/admin/components/projects/ProjectList.tsx +121 -0
  50. package/templates/admin/components/users/UserForm.tsx +39 -0
  51. package/templates/admin/components/users/UserList.tsx +101 -0
  52. package/templates/admin/config/resources.ts +123 -0
  53. package/templates/admin/eslint.config.mjs +18 -0
  54. package/templates/admin/hooks/useResource.ts +86 -0
  55. package/templates/admin/lib/services/base.service.ts +106 -0
  56. package/templates/admin/lib/services/categories.service.ts +7 -0
  57. package/templates/admin/lib/services/clients.service.ts +7 -0
  58. package/templates/admin/lib/services/index.ts +27 -0
  59. package/templates/admin/lib/services/products.service.ts +9 -0
  60. package/templates/admin/lib/services/projects.service.ts +22 -0
  61. package/templates/admin/lib/services/resource.service.ts +26 -0
  62. package/templates/admin/lib/services/users.service.ts +9 -0
  63. package/templates/admin/lib/supabase/client.ts +9 -0
  64. package/templates/admin/lib/supabase/middleware.ts +57 -0
  65. package/templates/admin/lib/supabase/server.ts +29 -0
  66. package/templates/admin/middleware.ts +15 -0
  67. package/templates/admin/next.config.ts +10 -0
  68. package/templates/admin/package-lock.json +6768 -0
  69. package/templates/admin/package.json +33 -0
  70. package/templates/admin/postcss.config.mjs +7 -0
  71. package/templates/admin/public/file.svg +1 -0
  72. package/templates/admin/public/globe.svg +1 -0
  73. package/templates/admin/public/next.svg +1 -0
  74. package/templates/admin/public/vercel.svg +1 -0
  75. package/templates/admin/public/window.svg +1 -0
  76. package/templates/admin/supabase_mock_data.sql +57 -0
  77. package/templates/admin/supabase_schema.sql +93 -0
  78. package/templates/admin/tsconfig.json +34 -0
  79. package/templates/web/.env.example +21 -0
  80. package/templates/web/README.md +129 -0
  81. package/templates/web/components.json +22 -0
  82. package/templates/web/eslint.config.mjs +25 -0
  83. package/templates/web/next.config.ts +25 -0
  84. package/templates/web/package-lock.json +6778 -0
  85. package/templates/web/package.json +45 -0
  86. package/templates/web/postcss.config.mjs +5 -0
  87. package/templates/web/src/app/api/contact/route.ts +181 -0
  88. package/templates/web/src/app/api/revalidate/route.ts +95 -0
  89. package/templates/web/src/app/error.tsx +28 -0
  90. package/templates/web/src/app/globals.css +838 -0
  91. package/templates/web/src/app/layout.tsx +126 -0
  92. package/templates/web/src/app/loading.tsx +60 -0
  93. package/templates/web/src/app/not-found.tsx +68 -0
  94. package/templates/web/src/app/page.tsx +106 -0
  95. package/templates/web/src/app/robots.ts +12 -0
  96. package/templates/web/src/app/sitemap.ts +66 -0
  97. package/templates/web/src/components/home/StatsGrid.tsx +89 -0
  98. package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
  99. package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
  100. package/templates/web/src/lib/seo/index.ts +4 -0
  101. package/templates/web/src/lib/seo/metadata.ts +103 -0
  102. package/templates/web/src/lib/seo/seo.config.ts +161 -0
  103. package/templates/web/src/lib/seo/seo.types.ts +76 -0
  104. package/templates/web/src/lib/services/categories.service.ts +38 -0
  105. package/templates/web/src/lib/services/categoryService.ts +251 -0
  106. package/templates/web/src/lib/services/clientService.ts +132 -0
  107. package/templates/web/src/lib/services/clients.service.ts +20 -0
  108. package/templates/web/src/lib/services/productService.ts +261 -0
  109. package/templates/web/src/lib/services/products.service.ts +38 -0
  110. package/templates/web/src/lib/services/projectService.ts +234 -0
  111. package/templates/web/src/lib/services/projects.service.ts +38 -0
  112. package/templates/web/src/lib/services/users.service.ts +20 -0
  113. package/templates/web/src/lib/supabase/client.ts +42 -0
  114. package/templates/web/src/lib/supabase/constants.ts +25 -0
  115. package/templates/web/src/lib/supabase/server.ts +29 -0
  116. package/templates/web/src/lib/supabase/types.ts +112 -0
  117. package/templates/web/src/lib/utils/cache.ts +98 -0
  118. package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
  119. package/templates/web/src/store/actions/index.ts +2 -0
  120. package/templates/web/src/store/index.ts +13 -0
  121. package/templates/web/src/store/reducers/index.ts +13 -0
  122. package/templates/web/src/store/types/index.ts +2 -0
  123. 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
+ [![npm version](https://img.shields.io/npm/v/create-nextjs-stack.svg)](https://www.npmjs.com/package/create-nextjs-stack)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Downloads](https://img.shields.io/npm/dm/create-nextjs-stack.svg)](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
+ }