create-next-pro-stack 1.0.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 +123 -0
- package/index.js +140 -0
- package/package.json +22 -0
- package/templates/axios-setup.js +166 -0
- package/templates/cleanup.js +23 -0
- package/templates/dashboard-setup.js +113 -0
- package/templates/env-setup.js +8 -0
- package/templates/folder-structure.js +36 -0
- package/templates/gitignore-setup.js +43 -0
- package/templates/jsconfig-setup.js +0 -0
- package/templates/page-files.js +186 -0
- package/templates/providers-setup.js +44 -0
- package/templates/radix-setup.js +9 -0
- package/templates/react-query-setup.js +34 -0
- package/templates/redux-setup.js +79 -0
- package/templates/tailwind-setup.js +0 -0
- package/templates/tsconfig-setup.js +0 -0
- package/templates/utils-setup.js +90 -0
- package/templates/vercel-setup.js +11 -0
- package/text.txt +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jahirul
|
|
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,123 @@
|
|
|
1
|
+
# 🚀 BlitzStack CLI Tool
|
|
2
|
+
|
|
3
|
+
**BlitzStack** is a powerful, professional-grade CLI tool designed to scaffold high-performance Next.js applications in seconds. It comes pre-configured with industry-standard patterns, a premium UI/UX structure, and a robust state-management architecture.
|
|
4
|
+
|
|
5
|
+
Built by **[Jahirul](https://github.com/jahirul077)** for developers who value speed, consistency, and best practices.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🌟 Why BlitzStack?
|
|
10
|
+
|
|
11
|
+
Staring at a blank `create-next-app` is time-consuming. **BlitzStack** bridges the gap between a raw installation and a production-ready application.
|
|
12
|
+
|
|
13
|
+
- **Zero-Config Professional Routing:** Automatically sets up Route Groups (`(main)`, `auth`, `dashboard`).
|
|
14
|
+
- **Premium UI Starter:** Includes a stunning Landing Page, Sidebar, and TopNavbar out of the box.
|
|
15
|
+
- **Dynamic Secure Storage:** Automatically generates unique LocalStorage keys for each project to avoid token conflicts.
|
|
16
|
+
- **Pre-baked Interceptors:** Axios is pre-configured with request/response interceptors for seamless API handling.
|
|
17
|
+
- **Production Ready:** Pre-configured `.env`, `vercel.json` (rewrites), and `.gitignore`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 Core Tech Stack & Packages
|
|
22
|
+
|
|
23
|
+
BlitzStack automatically installs and configures the following professional packages:
|
|
24
|
+
|
|
25
|
+
| Category | Packages |
|
|
26
|
+
| :--------------------- | :-------------------------------------------- |
|
|
27
|
+
| **Framework** | Next.js (Latest), React 19 |
|
|
28
|
+
| **Styling** | Tailwind CSS v4, Lucide React, Shadcn UI |
|
|
29
|
+
| **State Management** | Redux Toolkit, React Redux |
|
|
30
|
+
| **Data Fetching** | @tanstack/react-query, Axios |
|
|
31
|
+
| **Forms & Validation** | React Hook Form |
|
|
32
|
+
| **UI Components** | Shadcn UI (Button), Sonner (Toasts), Recharts |
|
|
33
|
+
| **Utilities** | clsx, tailwind-merge, localforage |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Getting Started
|
|
38
|
+
|
|
39
|
+
### 1. Local Installation
|
|
40
|
+
|
|
41
|
+
First, clone this repository and link the CLI tool to your local machine:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Clone the repository
|
|
45
|
+
git clone <your-repo-link>
|
|
46
|
+
|
|
47
|
+
# Navigate to the folder
|
|
48
|
+
cd create-next-stack-CLI-Tools
|
|
49
|
+
|
|
50
|
+
# Link the tool globally
|
|
51
|
+
npm link
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Creating a New Project
|
|
55
|
+
|
|
56
|
+
Now you can create a new **BlitzStack** project from anywhere in your terminal:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
create-next-pro-stack my-awesome-project
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Development
|
|
63
|
+
|
|
64
|
+
The tool will automatically:
|
|
65
|
+
|
|
66
|
+
- Initialize the Next.js app.
|
|
67
|
+
- Install all dependencies.
|
|
68
|
+
- Apply the professional folder structure.
|
|
69
|
+
- **Run the development server (`npm run dev`) automatically.**
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 📁 Folder Structure Overview
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
src/
|
|
77
|
+
├── app/
|
|
78
|
+
│ ├── (main)/ # Landing & Public pages
|
|
79
|
+
│ ├── auth/ # Login/Register flows
|
|
80
|
+
│ └── dashboard/ # Protected dashboard routes
|
|
81
|
+
├── components/ # Reusable UI components
|
|
82
|
+
├── hooks/
|
|
83
|
+
│ └── Axios/ # Pre-configured useAxiosPublic & useAxiosPrivate
|
|
84
|
+
├── providers/ # Redux, React Query, and UI Providers
|
|
85
|
+
├── redux/ # Store configuration and Slices
|
|
86
|
+
├── shared/ # Shared Layout components (Sidebar, Navbar)
|
|
87
|
+
└── utils/ # Helper functions (cn, localStorage, etc.)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🛠️ Key Features Under the Hood
|
|
93
|
+
|
|
94
|
+
### Professional Axios Interceptors
|
|
95
|
+
|
|
96
|
+
Your project comes with two main API hooks:
|
|
97
|
+
|
|
98
|
+
- `useAxiosPublic`: For open APIs with built-in network error handling.
|
|
99
|
+
- `useAxiosPrivate`: For secure APIs. It automatically handles:
|
|
100
|
+
- Dynamic Authorization tokens.
|
|
101
|
+
- Automatic logout on 401 (Session Expired).
|
|
102
|
+
- Friendly `sonner` toasts for every error status (403, 404, 500+).
|
|
103
|
+
|
|
104
|
+
### Vercel Ready
|
|
105
|
+
|
|
106
|
+
Includes a `vercel.json` with a catch-all rewrite rule:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"rewrites": [{ "source": "/(.*)", "destination": "/" }]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 🤝 Contributing
|
|
117
|
+
|
|
118
|
+
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/jahirul077).
|
|
119
|
+
|
|
120
|
+
## 📝 License
|
|
121
|
+
|
|
122
|
+
Copyright © 2026 [Jahirul Islam](https://github.com/jahirul077).
|
|
123
|
+
This project is [MIT](https://opensource.org/licenses/MIT) licensed.
|
package/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const run = (cmd) => execSync(cmd, { stdio: "inherit", shell: true });
|
|
13
|
+
|
|
14
|
+
function askQuestion(query) {
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
});
|
|
19
|
+
return new Promise((resolve) =>
|
|
20
|
+
rl.question(query, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer);
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const projectName = process.argv[2];
|
|
28
|
+
|
|
29
|
+
if (!projectName) {
|
|
30
|
+
console.log("❌ Please provide a project name. Example:");
|
|
31
|
+
console.log(" create-next-pro-stack my-next-app");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(projectName)) {
|
|
38
|
+
console.log(`\n⚠️ Folder "${projectName}" already exists!`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("\n🚀 Starting Next.js Stack Setup...\n");
|
|
43
|
+
|
|
44
|
+
const useTypeScript = await askQuestion(
|
|
45
|
+
"Do you want to use TypeScript? (y/n): ",
|
|
46
|
+
);
|
|
47
|
+
const isTypeScript =
|
|
48
|
+
useTypeScript.toLowerCase() === "y" ||
|
|
49
|
+
useTypeScript.toLowerCase() === "yes";
|
|
50
|
+
|
|
51
|
+
// 1. Create Next.js using official create-next-app (Latest)
|
|
52
|
+
console.log("\n📦 Initializing Next.js project...");
|
|
53
|
+
const tsFlag = isTypeScript ? "--ts" : "--js";
|
|
54
|
+
const craCommand = `npx create-next-app@latest ${projectName} ${tsFlag} --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --no-git`;
|
|
55
|
+
|
|
56
|
+
run(craCommand);
|
|
57
|
+
|
|
58
|
+
// Explicitly remove .git folder if create-next-app initialized it
|
|
59
|
+
const gitDir = path.join(projectName, ".git");
|
|
60
|
+
if (fs.existsSync(gitDir)) {
|
|
61
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("\n✅ Base Next.js created (and .git removed)!");
|
|
65
|
+
|
|
66
|
+
// 2. Install Core Stack Packages (Latest versions)
|
|
67
|
+
console.log("\nInstalling core stack packages...");
|
|
68
|
+
|
|
69
|
+
const dependencies = [
|
|
70
|
+
"axios",
|
|
71
|
+
"@reduxjs/toolkit",
|
|
72
|
+
"react-redux",
|
|
73
|
+
"@tanstack/react-query",
|
|
74
|
+
"@tanstack/react-query-devtools",
|
|
75
|
+
"@tanstack/react-table",
|
|
76
|
+
"react-hook-form",
|
|
77
|
+
"react-icons",
|
|
78
|
+
"react-hot-toast",
|
|
79
|
+
"sonner",
|
|
80
|
+
"recharts",
|
|
81
|
+
"lucide-react",
|
|
82
|
+
"react-router-dom",
|
|
83
|
+
"localforage",
|
|
84
|
+
"match-sorter",
|
|
85
|
+
"sort-by",
|
|
86
|
+
"clsx",
|
|
87
|
+
"tailwind-merge",
|
|
88
|
+
"tailwindcss",
|
|
89
|
+
"@tailwindcss/postcss",
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
console.log("\nInstalling dependencies...");
|
|
93
|
+
run(`cd ${projectName} && npm install ${dependencies.join(" ")}`);
|
|
94
|
+
|
|
95
|
+
// 2.5 Initialize Shadcn UI (Default/Non-interactive)
|
|
96
|
+
console.log("\n🎨 Initializing Shadcn UI...");
|
|
97
|
+
run(`cd ${projectName} && npx shadcn@latest init -d`);
|
|
98
|
+
|
|
99
|
+
// Install common shadcn components
|
|
100
|
+
console.log("\n🧩 Installing Shadcn components (Button)...");
|
|
101
|
+
run(`cd ${projectName} && npx shadcn@latest add button -y`);
|
|
102
|
+
|
|
103
|
+
// 3. Run Custom Templates (Folder structure, setups)
|
|
104
|
+
console.log("\n📁 Applying custom folder structure and templates...");
|
|
105
|
+
|
|
106
|
+
const templatesDir = path.join(__dirname, "templates");
|
|
107
|
+
const setupModules = [
|
|
108
|
+
"folder-structure.js",
|
|
109
|
+
"utils-setup.js",
|
|
110
|
+
"dashboard-setup.js",
|
|
111
|
+
"axios-setup.js",
|
|
112
|
+
"redux-setup.js",
|
|
113
|
+
"react-query-setup.js",
|
|
114
|
+
"providers-setup.js",
|
|
115
|
+
"page-files.js",
|
|
116
|
+
"gitignore-setup.js",
|
|
117
|
+
"vercel-setup.js",
|
|
118
|
+
"env-setup.js",
|
|
119
|
+
"cleanup.js",
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const moduleName of setupModules) {
|
|
123
|
+
const modulePath = path.join(templatesDir, moduleName);
|
|
124
|
+
if (fs.existsSync(modulePath)) {
|
|
125
|
+
const module = await import(`file://${modulePath}`);
|
|
126
|
+
if (module.default) {
|
|
127
|
+
await module.default(projectName, isTypeScript);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log("\n✨ Setup complete! Starting development server... 🚀\n");
|
|
133
|
+
run(`cd ${projectName} && npm run dev`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error("\n❌ Something went wrong:", err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-next-pro-stack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A professional CLI tool to quickly scaffold Next.js projects with Redux, React Query, TailwindCSS, and premium UI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-next-pro-stack": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"nextjs",
|
|
12
|
+
"cli",
|
|
13
|
+
"scaffold",
|
|
14
|
+
"redux",
|
|
15
|
+
"react-query",
|
|
16
|
+
"tailwindcss",
|
|
17
|
+
"shadcn"
|
|
18
|
+
],
|
|
19
|
+
"author": "Jahirul Islam",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "tsx" : "jsx";
|
|
6
|
+
const axiosDir = path.join(projectName, "src", "hooks", "Axios");
|
|
7
|
+
if (!fs.existsSync(axiosDir)) fs.mkdirSync(axiosDir, { recursive: true });
|
|
8
|
+
|
|
9
|
+
const tokenKey = `${projectName.toUpperCase().replace(/[-\s]/g, "_")}_USER_ACCESS_TOKEN`;
|
|
10
|
+
|
|
11
|
+
// 1. Axios Public Logic (JSX/TSX Mode)
|
|
12
|
+
const publicHookContent = `import axios from "axios";
|
|
13
|
+
|
|
14
|
+
const BASE_URL = \`\${process.env.NEXT_PUBLIC_BASE_URL}/api\`;
|
|
15
|
+
|
|
16
|
+
if (!process.env.NEXT_PUBLIC_BASE_URL) {
|
|
17
|
+
console.warn("NEXT_PUBLIC_BASE_URL is not defined in .env file");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const axiosInstance = axios.create({
|
|
21
|
+
baseURL: BASE_URL,
|
|
22
|
+
timeout: 10000,
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
},
|
|
27
|
+
withCredentials: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const useAxiosPublic = () => axiosInstance;
|
|
31
|
+
|
|
32
|
+
axiosInstance.interceptors.response.use(
|
|
33
|
+
(response) => response,
|
|
34
|
+
(error) => {
|
|
35
|
+
if (!error.response) {
|
|
36
|
+
return Promise.reject({
|
|
37
|
+
message: "Network error. Please check your connection.",
|
|
38
|
+
status: 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { status, data } = error.response;
|
|
43
|
+
|
|
44
|
+
return Promise.reject({
|
|
45
|
+
status,
|
|
46
|
+
message: data?.message || "Something went wrong. Please try again.",
|
|
47
|
+
...data,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export default useAxiosPublic;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
// 2. Axios Private Logic (JSX/TSX Mode)
|
|
56
|
+
const privateHookContent = `import { getLocalStorage, removeLocalStorage } from "@/utils/localStorage";
|
|
57
|
+
import {
|
|
58
|
+
getSessionStorage,
|
|
59
|
+
removeSessionStorage,
|
|
60
|
+
} from "@/utils/sessionStorage";
|
|
61
|
+
import axios from "axios";
|
|
62
|
+
import { toast } from "sonner";
|
|
63
|
+
|
|
64
|
+
const axiosInstance = axios.create({
|
|
65
|
+
baseURL: \`\${process.env.NEXT_PUBLIC_BASE_URL}/api\`,
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Accept: "application/json",
|
|
69
|
+
},
|
|
70
|
+
timeout: 30000,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const useAxiosPrivate = () => axiosInstance;
|
|
74
|
+
|
|
75
|
+
axiosInstance.interceptors.request.use(
|
|
76
|
+
(config) => {
|
|
77
|
+
const token =
|
|
78
|
+
getLocalStorage("${tokenKey}") ||
|
|
79
|
+
getSessionStorage("${tokenKey}");
|
|
80
|
+
|
|
81
|
+
if (token) {
|
|
82
|
+
config.headers.Authorization = \`Bearer \${token}\`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return config;
|
|
86
|
+
},
|
|
87
|
+
(error) => Promise.reject(error)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// RESPONSE INTERCEPTOR
|
|
91
|
+
axiosInstance.interceptors.response.use(
|
|
92
|
+
(response) => response,
|
|
93
|
+
|
|
94
|
+
async (error) => {
|
|
95
|
+
const { response } = error;
|
|
96
|
+
|
|
97
|
+
// Network Error
|
|
98
|
+
if (!response) {
|
|
99
|
+
toast.error("Network error — please check your connection.");
|
|
100
|
+
return Promise.reject(error);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const status = response.status;
|
|
104
|
+
|
|
105
|
+
// 401 Unauthorized
|
|
106
|
+
if (status === 401) {
|
|
107
|
+
toast.error("Session expired. Please log in again.");
|
|
108
|
+
removeLocalStorage("${tokenKey}");
|
|
109
|
+
removeSessionStorage("${tokenKey}");
|
|
110
|
+
if (typeof window !== "undefined" && window.location.pathname !== "/auth/login") {
|
|
111
|
+
window.location.href = "/auth/login";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Promise.reject(error);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 403 Forbidden
|
|
118
|
+
if (status === 403) {
|
|
119
|
+
toast.error("You don’t have permission to access this resource.");
|
|
120
|
+
return Promise.reject(error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 400 Bad Request
|
|
124
|
+
if (status === 400) {
|
|
125
|
+
toast.error(response.data?.message || "Invalid request.");
|
|
126
|
+
return Promise.reject(error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 404 Not Found
|
|
130
|
+
if (status === 404) {
|
|
131
|
+
toast.error("Requested resource not found.");
|
|
132
|
+
return Promise.reject(error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 429 Too Many Requests
|
|
136
|
+
if (status === 429) {
|
|
137
|
+
toast.error("Too many requests — please slow down.");
|
|
138
|
+
return Promise.reject(error);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 500+ Server Error
|
|
142
|
+
if (status >= 500) {
|
|
143
|
+
toast.error("Server error — please try again later.");
|
|
144
|
+
return Promise.reject(error);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Default
|
|
148
|
+
toast.error(response.data?.message || "Something went wrong.");
|
|
149
|
+
return Promise.reject(error);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
export default useAxiosPrivate;
|
|
154
|
+
`;
|
|
155
|
+
|
|
156
|
+
fs.writeFileSync(
|
|
157
|
+
path.join(axiosDir, `useAxiosPublic.${ext}`),
|
|
158
|
+
publicHookContent,
|
|
159
|
+
);
|
|
160
|
+
fs.writeFileSync(
|
|
161
|
+
path.join(axiosDir, `useAxiosPrivate.${ext}`),
|
|
162
|
+
privateHookContent,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
console.log(` ✅ Axios hooks created with dynamic token key: ${tokenKey}`);
|
|
166
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default function cleanupFiles(projectName) {
|
|
5
|
+
console.log("Cleaning up default Next.js files...");
|
|
6
|
+
|
|
7
|
+
const filesToRemove = [
|
|
8
|
+
"public/next.svg",
|
|
9
|
+
"public/vercel.svg",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
filesToRemove.forEach((file) => {
|
|
13
|
+
try {
|
|
14
|
+
const filePath = path.join(projectName, file);
|
|
15
|
+
if (fs.existsSync(filePath)) {
|
|
16
|
+
fs.unlinkSync(filePath);
|
|
17
|
+
console.log(` ✓ Removed ${file}`);
|
|
18
|
+
}
|
|
19
|
+
} catch (err) {
|
|
20
|
+
// skipping
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "tsx" : "jsx";
|
|
6
|
+
const sharedDir = path.join(projectName, "src", "shared", "dashboardShared");
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(sharedDir)) {
|
|
9
|
+
fs.mkdirSync(sharedDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 1. Sidebar Component (Your Professional Design)
|
|
13
|
+
const sidebarContent = `'use client';
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { LayoutDashboard, Settings, User, BookOpen, Bell, Search } from 'lucide-react';
|
|
17
|
+
import Link from 'next/link';
|
|
18
|
+
import { usePathname } from 'next/navigation';
|
|
19
|
+
|
|
20
|
+
const Sidebar = () => {
|
|
21
|
+
const pathname = usePathname();
|
|
22
|
+
|
|
23
|
+
const menuItems = [
|
|
24
|
+
{ icon: <LayoutDashboard size={20} />, label: 'Dashboard', href: '/dashboard/overview' },
|
|
25
|
+
{ icon: <BookOpen size={20} />, label: 'My Bookings', href: '/dashboard/my-bookings' },
|
|
26
|
+
{ icon: <User size={20} />, label: 'Payments History', href: '/dashboard/payments-history' },
|
|
27
|
+
{ icon: <Bell size={20} />, label: 'My Quote Requests', href: '/dashboard/my-quote-requests' },
|
|
28
|
+
{ icon: <Settings size={20} />, label: 'Settings', href: '/dashboard/settings' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="h-full flex flex-col bg-white">
|
|
33
|
+
<Link href="/" className="p-6 flex items-center gap-2">
|
|
34
|
+
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
|
|
35
|
+
<span className="text-primary-foreground font-bold text-xl leading-none">B</span>
|
|
36
|
+
</div>
|
|
37
|
+
<span className="text-xl font-bold tracking-tight text-primary">BlitzStack</span>
|
|
38
|
+
</Link>
|
|
39
|
+
|
|
40
|
+
<nav className="flex-1 px-4 space-y-2 overflow-y-auto">
|
|
41
|
+
{menuItems.map((item, index) => {
|
|
42
|
+
const isActive = pathname === item.href;
|
|
43
|
+
return (
|
|
44
|
+
<Link
|
|
45
|
+
key={index}
|
|
46
|
+
href={item.href}
|
|
47
|
+
className={\`flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer transition-all duration-300 border w-full
|
|
48
|
+
\${isActive
|
|
49
|
+
? 'bg-linear-to-br from-white/40 to-white/10 backdrop-blur-md border-white/30 text-primary shadow-[0_8px_32px_0_rgba(99,102,241,0.1)]'
|
|
50
|
+
: 'text-gray-500 hover:bg-white/40 border-transparent hover:border-white/20'
|
|
51
|
+
}\`}
|
|
52
|
+
>
|
|
53
|
+
{item.icon}
|
|
54
|
+
<span className="font-medium">{item.label}</span>
|
|
55
|
+
</Link>
|
|
56
|
+
)
|
|
57
|
+
})}
|
|
58
|
+
</nav>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default Sidebar;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// 2. TopNavbar Component (Your Professional Design)
|
|
67
|
+
const navbarContent = `'use client';
|
|
68
|
+
|
|
69
|
+
import React from 'react';
|
|
70
|
+
import { Search, Bell, User } from 'lucide-react';
|
|
71
|
+
import { Button } from "@/components/ui/button";
|
|
72
|
+
|
|
73
|
+
const TopNavbar = () => {
|
|
74
|
+
return (
|
|
75
|
+
<div className="flex w-full items-center justify-between">
|
|
76
|
+
<div className="flex flex-col">
|
|
77
|
+
<h1 className="text-xl font-bold tracking-tight text-foreground">Overview</h1>
|
|
78
|
+
<p className="text-sm text-muted-foreground">Welcome back, Jahirul!</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="flex items-center gap-4">
|
|
82
|
+
{/* Simple Search (Placeholder) */}
|
|
83
|
+
<div className="relative hidden md:block">
|
|
84
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
85
|
+
<input
|
|
86
|
+
type="text"
|
|
87
|
+
placeholder="Search anything..."
|
|
88
|
+
className="h-10 w-64 rounded-xl border bg-muted/30 pl-10 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 text-foreground shadow-sm"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Action Buttons */}
|
|
93
|
+
<Button variant="ghost" size="icon" className="rounded-xl">
|
|
94
|
+
<Bell className="h-5 w-5" />
|
|
95
|
+
</Button>
|
|
96
|
+
<div className="h-10 w-10 rounded-xl bg-primary/10 p-2 flex items-center justify-center border border-primary/20">
|
|
97
|
+
<User className="h-5 w-5 text-primary" />
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default TopNavbar;
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
fs.writeFileSync(path.join(sharedDir, `Sidebar.${ext}`), sidebarContent);
|
|
108
|
+
fs.writeFileSync(path.join(sharedDir, `TopNavbar.${ext}`), navbarContent);
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
" ✅ Dashboard shared components (Professional Sidebar, TopNavbar) created.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName) {
|
|
5
|
+
const baseDir = path.join(projectName, "src");
|
|
6
|
+
|
|
7
|
+
const folders = [
|
|
8
|
+
"app/(main)",
|
|
9
|
+
"app/auth/login",
|
|
10
|
+
"app/dashboard/overview",
|
|
11
|
+
"assets/images",
|
|
12
|
+
"assets/videos",
|
|
13
|
+
"components/common",
|
|
14
|
+
"components/landingPageComponents",
|
|
15
|
+
"components/authPageComponents",
|
|
16
|
+
"components/dashboardPageComponents",
|
|
17
|
+
"hooks/Axios",
|
|
18
|
+
"providers",
|
|
19
|
+
"redux/slices",
|
|
20
|
+
"shared/landingShared",
|
|
21
|
+
"shared/authShared",
|
|
22
|
+
"shared/dashboardShared",
|
|
23
|
+
"utils",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
folders.forEach((folder) => {
|
|
27
|
+
const dir = path.join(baseDir, folder);
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
" ✅ Folder structure updated: (main), auth/login, and dashboard/overview.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName) {
|
|
5
|
+
const gitignoreContent = `# dependencies
|
|
6
|
+
/node_modules
|
|
7
|
+
/.pnp
|
|
8
|
+
.pnp.js
|
|
9
|
+
|
|
10
|
+
# testing
|
|
11
|
+
/coverage
|
|
12
|
+
|
|
13
|
+
# next.js
|
|
14
|
+
/.next/
|
|
15
|
+
/out/
|
|
16
|
+
|
|
17
|
+
# production
|
|
18
|
+
/build
|
|
19
|
+
|
|
20
|
+
# misc
|
|
21
|
+
.DS_Store
|
|
22
|
+
*.pem
|
|
23
|
+
|
|
24
|
+
# debug
|
|
25
|
+
npm-debug.log*
|
|
26
|
+
yarn-debug.log*
|
|
27
|
+
yarn-error.log*
|
|
28
|
+
|
|
29
|
+
# local env files
|
|
30
|
+
.env*.local
|
|
31
|
+
.env
|
|
32
|
+
|
|
33
|
+
# vercel
|
|
34
|
+
.vercel
|
|
35
|
+
|
|
36
|
+
# typescript
|
|
37
|
+
*.tsbuildinfo
|
|
38
|
+
next-env.d.ts
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(path.join(projectName, ".gitignore"), gitignoreContent);
|
|
42
|
+
console.log(" ✅ .gitignore created/updated.");
|
|
43
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "tsx" : "jsx";
|
|
6
|
+
const appDir = path.join(projectName, "src", "app");
|
|
7
|
+
|
|
8
|
+
// 1. Remove default page.js or page.tsx
|
|
9
|
+
const defaultPageTypes = ["page.js", "page.tsx", "page.jsx"];
|
|
10
|
+
defaultPageTypes.forEach((file) => {
|
|
11
|
+
const filePath = path.join(appDir, file);
|
|
12
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Helper to create layout
|
|
16
|
+
const createLayoutFile = (dirPath, content) => {
|
|
17
|
+
const targetDir = path.join(appDir, dirPath);
|
|
18
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
19
|
+
fs.writeFileSync(path.join(targetDir, `layout.${ext}`), content);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// --- Main Layout ---
|
|
23
|
+
const mainLayoutContent = `export default function MainLayout({ children }${isTypeScript ? ": { children: React.ReactNode }" : ""}) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="main-layout">
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
createLayoutFile("(main)", mainLayoutContent);
|
|
32
|
+
|
|
33
|
+
// --- Dashboard Layout (Your Professional Design) ---
|
|
34
|
+
const dashboardLayoutContent = `import Sidebar from "@/shared/dashboardShared/Sidebar";
|
|
35
|
+
import TopNavbar from "@/shared/dashboardShared/TopNavbar";
|
|
36
|
+
import React from "react";
|
|
37
|
+
|
|
38
|
+
const DashboardLayout = ({ children }${isTypeScript ? ": { children: React.ReactNode }" : ""}) => {
|
|
39
|
+
return (
|
|
40
|
+
<main className="w-full h-screen flex relative bg-background">
|
|
41
|
+
<div className="xl:w-[280px] h-screen xl:block hidden border-r shrink-0">
|
|
42
|
+
<Sidebar />
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Main Content */}
|
|
46
|
+
<div className="flex flex-col w-full xl:w-[calc(100%-280px)] h-screen overflow-hidden">
|
|
47
|
+
<div className="w-full h-auto xl:h-[80px] flex flex-col justify-center px-4 md:px-8 py-4 bg-white/50 backdrop-blur-md xl:bg-transparent border-b">
|
|
48
|
+
<TopNavbar />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div className="w-full h-[calc(100%-80px)] overflow-auto custom-scrollbar p-10 relative">
|
|
52
|
+
<div className="relative z-10">
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</main>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default DashboardLayout;
|
|
62
|
+
`;
|
|
63
|
+
createLayoutFile("dashboard", dashboardLayoutContent);
|
|
64
|
+
|
|
65
|
+
// --- Auth Layout ---
|
|
66
|
+
const authLayoutContent = `export default function AuthLayout({ children }${isTypeScript ? ": { children: React.ReactNode }" : ""}) {
|
|
67
|
+
return (
|
|
68
|
+
<div className="auth-layout flex min-h-screen items-center justify-center bg-muted/50">
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
createLayoutFile("auth", authLayoutContent);
|
|
75
|
+
|
|
76
|
+
// --- Create Pages ---
|
|
77
|
+
|
|
78
|
+
// 1. Landing Page (Keeping your professional design)
|
|
79
|
+
const landingPageContent = `import Link from "next/link";
|
|
80
|
+
import { Button } from "@/components/ui/button";
|
|
81
|
+
import { ExternalLink, LayoutDashboard, LogIn, Sparkles } from "lucide-react";
|
|
82
|
+
|
|
83
|
+
export default function LandingPage() {
|
|
84
|
+
return (
|
|
85
|
+
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-background px-6">
|
|
86
|
+
{/* Subtle Background Radial Gradient */}
|
|
87
|
+
<div className="absolute inset-x-0 top-0 -z-10 h-full w-full bg-[radial-gradient(circle_at_center,var(--color-primary)_0%,transparent_70%)] opacity-[0.03]" />
|
|
88
|
+
|
|
89
|
+
<div className="flex flex-col items-center text-center">
|
|
90
|
+
{/* Modern Icon/Badge */}
|
|
91
|
+
<div className="mb-8 flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
|
92
|
+
<Sparkles className="h-8 w-8" />
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Main Heading */}
|
|
96
|
+
<h1 className="mb-4 text-5xl font-extrabold tracking-tight text-foreground sm:text-7xl">
|
|
97
|
+
<span className="text-primary tracking-tighter">Your Next.js</span> Project <br />
|
|
98
|
+
is <span className="bg-linear-to-r from-primary to-chart-1 bg-clip-text text-transparent">Ready</span>
|
|
99
|
+
</h1>
|
|
100
|
+
|
|
101
|
+
{/* Subtext */}
|
|
102
|
+
<p className="mb-10 max-w-lg text-lg text-muted-foreground sm:text-xl">
|
|
103
|
+
Everything is set up and configured. Now you can start building your amazing application.
|
|
104
|
+
</p>
|
|
105
|
+
|
|
106
|
+
{/* Navigation Buttons */}
|
|
107
|
+
<div className="flex flex-col gap-4 sm:flex-row">
|
|
108
|
+
<Link href="/auth/login">
|
|
109
|
+
<Button size="lg" className="h-12 gap-2 rounded-xl px-8 font-semibold shadow-lg shadow-primary/20 transition-all hover:scale-105 active:scale-95">
|
|
110
|
+
<LogIn className="h-4 w-4" />
|
|
111
|
+
Go to Login
|
|
112
|
+
</Button>
|
|
113
|
+
</Link>
|
|
114
|
+
<Link href="/dashboard/overview">
|
|
115
|
+
<Button variant="outline" size="lg" className="h-12 gap-2 rounded-xl px-8 font-semibold transition-all hover:scale-105 active:scale-95">
|
|
116
|
+
<LayoutDashboard className="h-4 w-4" />
|
|
117
|
+
Dashboard
|
|
118
|
+
</Button>
|
|
119
|
+
</Link>
|
|
120
|
+
<Link href="https://blitzstack.vercel.app/" target="_blank">
|
|
121
|
+
<Button variant="ghost" size="lg" className="h-12 gap-2 rounded-xl px-8 font-semibold transition-all hover:scale-105 active:scale-95 text-primary border border-primary/10 hover:bg-primary/5">
|
|
122
|
+
<ExternalLink className="h-4 w-4" />
|
|
123
|
+
Visit Website
|
|
124
|
+
</Button>
|
|
125
|
+
</Link>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Footer Credit */}
|
|
129
|
+
<div className="mt-20 flex flex-col items-center gap-2">
|
|
130
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
131
|
+
Built by <Link href="https://github.com/jahirul077" target="_blank" className="text-primary hover:underline">Jahirul</Link>
|
|
132
|
+
</p>
|
|
133
|
+
<div className="flex h-1 w-12 rounded-full bg-primary/20" />
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
const mainDir = path.join(appDir, "(main)");
|
|
141
|
+
if (!fs.existsSync(mainDir)) fs.mkdirSync(mainDir, { recursive: true });
|
|
142
|
+
fs.writeFileSync(path.join(mainDir, `page.${ext}`), landingPageContent);
|
|
143
|
+
|
|
144
|
+
// 2. Overview Page (Simplified as requested)
|
|
145
|
+
const dashboardOverviewDir = path.join(appDir, "dashboard/overview");
|
|
146
|
+
if (!fs.existsSync(dashboardOverviewDir))
|
|
147
|
+
fs.mkdirSync(dashboardOverviewDir, { recursive: true });
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
path.join(dashboardOverviewDir, `page.${ext}`),
|
|
150
|
+
`import Link from "next/link";
|
|
151
|
+
|
|
152
|
+
export default function OverviewPage() {
|
|
153
|
+
return (
|
|
154
|
+
<div className="flex flex-col items-center justify-center min-h-[60vh] gap-4">
|
|
155
|
+
<h1 className="text-4xl font-bold tracking-tight">Overview Page </h1>
|
|
156
|
+
<Link href="/" className="text-primary hover:underline font-medium italic text-xl">
|
|
157
|
+
Go to Home
|
|
158
|
+
</Link>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// 3. Login Page (Simplified as requested)
|
|
165
|
+
const loginDir = path.join(appDir, "auth/login");
|
|
166
|
+
if (!fs.existsSync(loginDir)) fs.mkdirSync(loginDir, { recursive: true });
|
|
167
|
+
fs.writeFileSync(
|
|
168
|
+
path.join(loginDir, `page.${ext}`),
|
|
169
|
+
`import Link from "next/link";
|
|
170
|
+
|
|
171
|
+
export default function LoginPage() {
|
|
172
|
+
return (
|
|
173
|
+
<div className="flex flex-col items-center justify-center p-24 gap-4">
|
|
174
|
+
<h1 className="text-4xl font-bold tracking-tight">Login Page </h1>
|
|
175
|
+
<Link href="/" className="text-primary hover:underline font-medium italic text-xl">
|
|
176
|
+
Go to Home
|
|
177
|
+
</Link>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}`,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
console.log(
|
|
184
|
+
" ✅ Routes updated: Login and Overview simplified as requested.",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "tsx" : "jsx";
|
|
6
|
+
const appDir = path.join(projectName, "src", "app");
|
|
7
|
+
const layoutFilename = isTypeScript ? "layout.tsx" : "layout.js";
|
|
8
|
+
const layoutPath = path.join(appDir, layoutFilename);
|
|
9
|
+
|
|
10
|
+
const rootLayoutContent = `import "./globals.css";
|
|
11
|
+
import ReduxProvider from "@/providers/ReduxProvider";
|
|
12
|
+
import ReactQueryProvider from "@/providers/ReactQueryProvider";
|
|
13
|
+
import { Toaster } from "sonner";
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
title: "Professional Next.js Stack",
|
|
17
|
+
description: "Generated by Next.js Stack CLI",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function RootLayout({
|
|
21
|
+
children,
|
|
22
|
+
}${isTypeScript ? ": { children: React.ReactNode }" : ""}) {
|
|
23
|
+
return (
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<body className="antialiased">
|
|
26
|
+
<ReduxProvider>
|
|
27
|
+
<ReactQueryProvider>
|
|
28
|
+
{children}
|
|
29
|
+
<Toaster richColors position="top-right" />
|
|
30
|
+
</ReactQueryProvider>
|
|
31
|
+
</ReduxProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Overwrite the root layout to remove custom fonts and use browser default
|
|
39
|
+
fs.writeFileSync(layoutPath, rootLayoutContent);
|
|
40
|
+
|
|
41
|
+
console.log(
|
|
42
|
+
` ✅ Root Layout (${layoutFilename}) updated: Removed Google Fonts to use browser defaults.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
export default async function setup(projectName) {
|
|
4
|
+
// Optional: Install Radix UI primitives as needed
|
|
5
|
+
// run(`cd ${projectName} && npm install @radix-ui/react-navigation-menu @radix-ui/react-dropdown-menu`);
|
|
6
|
+
console.log(
|
|
7
|
+
" ✅ Radix UI setup placeholder (Add your preferred primitives here).",
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "tsx" : "jsx";
|
|
6
|
+
const providersDir = path.join(projectName, "src", "providers");
|
|
7
|
+
if (!fs.existsSync(providersDir))
|
|
8
|
+
fs.mkdirSync(providersDir, { recursive: true });
|
|
9
|
+
|
|
10
|
+
const queryProvider = `"use client";
|
|
11
|
+
|
|
12
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default function ReactQueryProvider({ children }${isTypeScript ? ": { children: React.ReactNode }" : ""}) {
|
|
18
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<QueryClientProvider client={queryClient}>
|
|
22
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
23
|
+
{children}
|
|
24
|
+
</QueryClientProvider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(providersDir, `ReactQueryProvider.${ext}`),
|
|
31
|
+
queryProvider,
|
|
32
|
+
);
|
|
33
|
+
console.log(" ✅ ReactQueryProvider updated with Devtools.");
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "ts" : "js";
|
|
6
|
+
const jsxExt = isTypeScript ? "tsx" : "jsx";
|
|
7
|
+
const reduxDir = path.join(projectName, "src", "redux");
|
|
8
|
+
const providersDir = path.join(projectName, "src", "providers");
|
|
9
|
+
const slicesDir = path.join(reduxDir, "slices");
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(slicesDir)) fs.mkdirSync(slicesDir, { recursive: true });
|
|
12
|
+
if (!fs.existsSync(providersDir))
|
|
13
|
+
fs.mkdirSync(providersDir, { recursive: true });
|
|
14
|
+
|
|
15
|
+
// 1. Create Counter Slice only
|
|
16
|
+
const counterSliceContent = `import { createSlice } from "@reduxjs/toolkit";
|
|
17
|
+
|
|
18
|
+
const counterSlice = createSlice({
|
|
19
|
+
name: "counter",
|
|
20
|
+
initialState: { value: 0 },
|
|
21
|
+
reducers: {
|
|
22
|
+
increment: (state) => {
|
|
23
|
+
state.value += 1;
|
|
24
|
+
},
|
|
25
|
+
decrement: (state) => {
|
|
26
|
+
state.value -= 1;
|
|
27
|
+
},
|
|
28
|
+
incrementByAmount: (state, action) => {
|
|
29
|
+
state.value += action.payload;
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
|
|
35
|
+
export default counterSlice.reducer;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
fs.writeFileSync(
|
|
39
|
+
path.join(slicesDir, `counterSlice.${ext}`),
|
|
40
|
+
counterSliceContent,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// 2. Store configuration
|
|
44
|
+
const storeConfig = `import { configureStore } from "@reduxjs/toolkit";
|
|
45
|
+
import counterReducer from "./slices/counterSlice";
|
|
46
|
+
|
|
47
|
+
export const store = configureStore({
|
|
48
|
+
reducer: {
|
|
49
|
+
counter: counterReducer,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
${isTypeScript ? "export type RootState = ReturnType<typeof store.getState>;\nexport type AppDispatch = typeof store.dispatch;" : ""}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(path.join(reduxDir, `store.${ext}`), storeConfig);
|
|
57
|
+
|
|
58
|
+
// 3. Redux Provider
|
|
59
|
+
const reduxProviderContent = `"use client";
|
|
60
|
+
|
|
61
|
+
import { Provider } from "react-redux";
|
|
62
|
+
import { store } from "@/redux/store";
|
|
63
|
+
|
|
64
|
+
export default function ReduxProvider({ children }${isTypeScript ? ": { children: React.ReactNode }" : ""}) {
|
|
65
|
+
return (
|
|
66
|
+
<Provider store={store}>
|
|
67
|
+
{children}
|
|
68
|
+
</Provider>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(providersDir, `ReduxProvider.${jsxExt}`),
|
|
75
|
+
reduxProviderContent,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
console.log(" ✅ Redux setup updated with counterSlice only.");
|
|
79
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName, isTypeScript) {
|
|
5
|
+
const ext = isTypeScript ? "ts" : "js";
|
|
6
|
+
const utilsDir = path.join(projectName, "src", "utils");
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(utilsDir)) {
|
|
9
|
+
fs.mkdirSync(utilsDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 1. cn helper
|
|
13
|
+
const cnContent = `import { clsx ${isTypeScript ? ", type ClassValue" : ""} } from "clsx";
|
|
14
|
+
import { twMerge } from "tailwind-merge";
|
|
15
|
+
|
|
16
|
+
export function cn(...inputs${isTypeScript ? ": ClassValue[]" : ""}) {
|
|
17
|
+
return twMerge(clsx(inputs));
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
// 2. localStorage helper
|
|
22
|
+
const localStoreContent = `export const setLocalStorage = (key${isTypeScript ? ": string" : ""}, value${isTypeScript ? ": any" : ""}) => {
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const getLocalStorage = (key${isTypeScript ? ": string" : ""}) => {
|
|
29
|
+
if (typeof window !== "undefined") {
|
|
30
|
+
const data = localStorage.getItem(key);
|
|
31
|
+
if (data) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(data);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const removeLocalStorage = (key${isTypeScript ? ": string" : ""}) => {
|
|
43
|
+
if (typeof window !== "undefined") {
|
|
44
|
+
localStorage.removeItem(key);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
// 3. sessionStorage helper
|
|
50
|
+
const sessionStoreContent = `export const setSessionStorage = (key${isTypeScript ? ": string" : ""}, value${isTypeScript ? ": any" : ""}) => {
|
|
51
|
+
if (typeof window !== "undefined") {
|
|
52
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const getSessionStorage = (key${isTypeScript ? ": string" : ""}) => {
|
|
57
|
+
if (typeof window !== "undefined") {
|
|
58
|
+
const data = sessionStorage.getItem(key);
|
|
59
|
+
if (data) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(data);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const removeSessionStorage = (key${isTypeScript ? ": string" : ""}) => {
|
|
71
|
+
if (typeof window !== "undefined") {
|
|
72
|
+
sessionStorage.removeItem(key);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
fs.writeFileSync(path.join(utilsDir, `cn.${ext}`), cnContent);
|
|
78
|
+
fs.writeFileSync(
|
|
79
|
+
path.join(utilsDir, `localStorage.${ext}`),
|
|
80
|
+
localStoreContent,
|
|
81
|
+
);
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
path.join(utilsDir, `sessionStorage.${ext}`),
|
|
84
|
+
sessionStoreContent,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
console.log(
|
|
88
|
+
" ✅ Utility helpers (cn, localStorage, sessionStorage) created in src/utils.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default async function setup(projectName) {
|
|
5
|
+
const vercelConfig = `{
|
|
6
|
+
"rewrites": [{ "source": "/(.*)", "destination": "/" }]
|
|
7
|
+
}`;
|
|
8
|
+
|
|
9
|
+
fs.writeFileSync(path.join(projectName, "vercel.json"), vercelConfig);
|
|
10
|
+
console.log(" ✅ vercel.json created.");
|
|
11
|
+
}
|
package/text.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
create-next-stack my-awesome-app
|