@seip/blue-bird 0.4.5 → 0.4.7
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/.env_example +26 -25
- package/AGENTS.md +199 -199
- package/README.md +79 -79
- package/backend/index.js +13 -13
- package/backend/routes/frontend.js +41 -41
- package/backend/routes/seo.js +39 -39
- package/core/app.js +330 -325
- package/core/auth.js +142 -114
- package/core/cache.js +44 -44
- package/core/cli/component.js +42 -42
- package/core/cli/init.js +119 -118
- package/core/cli/react.js +435 -435
- package/core/cli/route.js +42 -42
- package/core/config.js +51 -47
- package/core/debug.js +248 -248
- package/core/logger.js +100 -100
- package/core/middleware.js +27 -27
- package/core/router.js +333 -333
- package/core/seo.js +95 -100
- package/core/template.js +478 -462
- package/core/upload.js +77 -76
- package/core/validate.js +380 -380
- package/frontend/index.html +31 -26
- package/frontend/landing.html +70 -69
- package/frontend/resources/css/tailwind.css +17 -17
- package/frontend/resources/js/App.jsx +70 -70
- package/frontend/resources/js/Main.jsx +18 -18
- package/frontend/resources/js/blue-bird/components/Button.jsx +67 -67
- package/frontend/resources/js/blue-bird/components/Card.jsx +18 -18
- package/frontend/resources/js/blue-bird/components/DataTable.jsx +126 -126
- package/frontend/resources/js/blue-bird/components/Input.jsx +21 -21
- package/frontend/resources/js/blue-bird/components/Label.jsx +12 -12
- package/frontend/resources/js/blue-bird/components/LanguageButton.jsx +23 -23
- package/frontend/resources/js/blue-bird/components/Link.jsx +15 -15
- package/frontend/resources/js/blue-bird/components/Modal.jsx +27 -27
- package/frontend/resources/js/blue-bird/components/Skeleton.jsx +44 -44
- package/frontend/resources/js/blue-bird/components/Translate.jsx +12 -12
- package/frontend/resources/js/blue-bird/components/Typography.jsx +69 -69
- package/frontend/resources/js/blue-bird/contexts/LanguageContext.jsx +41 -41
- package/frontend/resources/js/blue-bird/contexts/SPAContext.jsx +239 -237
- package/frontend/resources/js/blue-bird/contexts/SnackbarContext.jsx +38 -38
- package/frontend/resources/js/blue-bird/contexts/ThemeContext.jsx +49 -49
- package/frontend/resources/js/blue-bird/locales/en.json +47 -47
- package/frontend/resources/js/blue-bird/locales/es.json +47 -47
- package/frontend/resources/js/components/Header.jsx +55 -55
- package/frontend/resources/js/pages/About.jsx +31 -31
- package/frontend/resources/js/pages/Home.jsx +82 -82
- package/package.json +1 -1
- package/vite.config.js +22 -22
- package/frontend/public/robots.txt +0 -0
- package/frontend/public/sitemap.xml +0 -0
package/core/cli/react.js
CHANGED
|
@@ -1,435 +1,435 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import Config from "../config.js";
|
|
6
|
-
|
|
7
|
-
const __dirname = Config.dirname();
|
|
8
|
-
const props = Config.props();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Scaffolding class for setting up React and Vite in the project.
|
|
12
|
-
*/
|
|
13
|
-
class ReactScaffold {
|
|
14
|
-
/**
|
|
15
|
-
* Initializes the Scaffolder with the base application directory.
|
|
16
|
-
*/
|
|
17
|
-
constructor() {
|
|
18
|
-
this.appDir = __dirname;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Executes the installation process.
|
|
23
|
-
*/
|
|
24
|
-
async install() {
|
|
25
|
-
console.log(chalk.cyan("Initializing React + Vite setup..."));
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
this.createStructure();
|
|
29
|
-
this.updatePackageJson();
|
|
30
|
-
this.createViteConfig();
|
|
31
|
-
this.createAppjs();
|
|
32
|
-
this.createMainJs();
|
|
33
|
-
this.createHeaderJs();
|
|
34
|
-
this.createPagesJs();
|
|
35
|
-
this.updateGitIgnore();
|
|
36
|
-
this.npmInstall();
|
|
37
|
-
|
|
38
|
-
console.log(chalk.green("React scaffolding created successfully!"));
|
|
39
|
-
console.log(chalk.cyan("\nNext steps:"));
|
|
40
|
-
console.log(chalk.white(" 1. Run (Blue Bird Server): ") + chalk.bold("npm run dev"));
|
|
41
|
-
console.log(chalk.white(" 2. Run (React Vite Dev): ") + chalk.bold("npm run vite:dev"));
|
|
42
|
-
console.log(chalk.blue("\nBlue Bird React setup completed!"));
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error(chalk.red("Fatal error during scaffolding:"), error.message);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Creates the necessary directory structure for React resources.
|
|
50
|
-
*/
|
|
51
|
-
createStructure() {
|
|
52
|
-
const dirs = [
|
|
53
|
-
'frontend/resources/js/pages',
|
|
54
|
-
'frontend/resources/js/components'
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
dirs.forEach(dir => {
|
|
58
|
-
const fullPath = path.join(this.appDir, dir);
|
|
59
|
-
if (!fs.existsSync(fullPath)) {
|
|
60
|
-
fs.mkdirSync(fullPath, { recursive: true });
|
|
61
|
-
console.log(chalk.gray(`Created directory: ${dir}`));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Updates the project's package.json with React and Vite dependencies and scripts.
|
|
68
|
-
*/
|
|
69
|
-
updatePackageJson() {
|
|
70
|
-
const packagePath = path.join(this.appDir, 'package.json');
|
|
71
|
-
if (!fs.existsSync(packagePath)) {
|
|
72
|
-
console.warn(chalk.yellow("package.json not found. Initializing with npm init..."));
|
|
73
|
-
execSync('npm init -y', { cwd: this.appDir });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
77
|
-
|
|
78
|
-
pkg.scripts = pkg.scripts || {};
|
|
79
|
-
pkg.scripts["vite:dev"] = "vite";
|
|
80
|
-
pkg.scripts["vite:build"] = "vite build";
|
|
81
|
-
|
|
82
|
-
pkg.devDependencies = pkg.devDependencies || {};
|
|
83
|
-
|
|
84
|
-
pkg.devDependencies["vite"] = "^7.3.1";
|
|
85
|
-
pkg.devDependencies["@vitejs/plugin-react"] = "^4.3.4";
|
|
86
|
-
|
|
87
|
-
pkg.dependencies = pkg.dependencies || {};
|
|
88
|
-
pkg.dependencies["react"] = "^19.2.4";
|
|
89
|
-
pkg.dependencies["react-dom"] = "^19.2.4";
|
|
90
|
-
pkg.dependencies["react-router-dom"] = "^7.2.0";
|
|
91
|
-
pkg.dependencies["@tailwindcss/vite"] = "^4.2.2";
|
|
92
|
-
pkg.dependencies["tailwindcss"] = "^4.2.2";
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
|
|
96
|
-
console.log(chalk.gray("Updated package.json dependencies and scripts."));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Creates the vite.config.js file with appropriate root and outDir settings.
|
|
101
|
-
*/
|
|
102
|
-
createViteConfig() {
|
|
103
|
-
const file = path.join(this.appDir, 'vite.config.js');
|
|
104
|
-
if (fs.existsSync(file)) {
|
|
105
|
-
console.warn(chalk.yellow("vite.config.js already exists. Skipping."));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// We use props.static.path to determine where the build goes
|
|
110
|
-
const outDir = path.join(props.static.path, 'build');
|
|
111
|
-
|
|
112
|
-
const content = `import { defineConfig } from 'vite';
|
|
113
|
-
import react from '@vitejs/plugin-react';
|
|
114
|
-
import path from 'path';
|
|
115
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
116
|
-
|
|
117
|
-
export default defineConfig({
|
|
118
|
-
plugins: [react(), tailwindcss()],
|
|
119
|
-
root: path.resolve(__dirname, 'frontend/resources/js'),
|
|
120
|
-
base: '/build/',
|
|
121
|
-
build: {
|
|
122
|
-
outDir: path.resolve(__dirname, '${outDir.replace(/\\/g, '/')}'),
|
|
123
|
-
emptyOutDir: true,
|
|
124
|
-
manifest: true,
|
|
125
|
-
rollupOptions: {
|
|
126
|
-
input: path.resolve(__dirname, 'frontend/resources/js/Main.jsx'),
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
server: {
|
|
130
|
-
origin: 'http://localhost:5173',
|
|
131
|
-
strictPort: true,
|
|
132
|
-
cors: true,
|
|
133
|
-
},
|
|
134
|
-
});`;
|
|
135
|
-
fs.writeFileSync(file, content);
|
|
136
|
-
console.log(chalk.gray("Created vite.config.js"));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
createPagesJs() {
|
|
140
|
-
const file = path.join(this.appDir, 'frontend/resources/js/pages/Home.jsx');
|
|
141
|
-
if (fs.existsSync(file)) {
|
|
142
|
-
console.warn(chalk.yellow("Home.jsx already exists. Skipping."));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const content = ` import React, { useEffect } from 'react';
|
|
147
|
-
import Card from '../blue-bird/components/Card';
|
|
148
|
-
import Header from '../components/Header';
|
|
149
|
-
import { useLanguage } from '../blue-bird/contexts/LanguageContext';
|
|
150
|
-
import Typography from '../blue-bird/components/Typography';
|
|
151
|
-
|
|
152
|
-
export default function Home() {
|
|
153
|
-
const { t } = useLanguage();
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
// Example API call to the backend
|
|
156
|
-
fetch("http://localhost:3000/login", {
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: {
|
|
159
|
-
"Content-Type": "application/json",
|
|
160
|
-
},
|
|
161
|
-
body: JSON.stringify({
|
|
162
|
-
email: "example@example.com",
|
|
163
|
-
password: "myPassword123"
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
.then((response) => response.json())
|
|
167
|
-
.then((data) => console.log('Backend response:', data))
|
|
168
|
-
.catch((error) => console.error('Error fetching from backend:', error));
|
|
169
|
-
}, []);
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<div
|
|
173
|
-
className="bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen"
|
|
174
|
-
>
|
|
175
|
-
<Header />
|
|
176
|
-
<main className='max-w-7xl mx-auto'>
|
|
177
|
-
<div className='text-center p-4'>
|
|
178
|
-
<header className='mb-8 mt-8'>
|
|
179
|
-
<Typography variant='h1' className='text-4xl font-extrabold tracking-tight lg:text-5xl text-slate-900 dark:text-slate-100 mb-4'>
|
|
180
|
-
{t("home_page.title")}
|
|
181
|
-
</Typography>
|
|
182
|
-
<Typography className='text-xl text-slate-500 dark:text-slate-400 max-w-[600px] mx-auto'>
|
|
183
|
-
{t("home_page.description")}
|
|
184
|
-
</Typography>
|
|
185
|
-
</header>
|
|
186
|
-
|
|
187
|
-
<div className='flex gap-4 justify-center mb-12'>
|
|
188
|
-
<a
|
|
189
|
-
href="https://seip25.github.io/Blue-bird/en.html"
|
|
190
|
-
target="_blank"
|
|
191
|
-
rel="noopener noreferrer"
|
|
192
|
-
className='inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-slate-900 dark:bg-slate-100 text-slate-50 dark:text-slate-900 hover:bg-slate-900/90 dark:hover:bg-slate-100/90 h-10 px-4 py-2'
|
|
193
|
-
>
|
|
194
|
-
Documentation (Eng)
|
|
195
|
-
</a>
|
|
196
|
-
<a
|
|
197
|
-
href="https://seip25.github.io/Blue-bird/"
|
|
198
|
-
target="_blank"
|
|
199
|
-
rel="noopener noreferrer"
|
|
200
|
-
className='inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800 h-10 px-4 py-2'
|
|
201
|
-
>
|
|
202
|
-
Documentación (Esp)
|
|
203
|
-
</a>
|
|
204
|
-
</div>
|
|
205
|
-
|
|
206
|
-
<div className='mt-8 grid grid-cols-1 md:grid-cols-3 gap-6 max-w-[1000px] mx-auto text-left'>
|
|
207
|
-
<Card title={t("home_page.lightweight")}>
|
|
208
|
-
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
209
|
-
{t("home_page.lightweightDescription")}
|
|
210
|
-
</Typography>
|
|
211
|
-
</Card>
|
|
212
|
-
<Card title={t("home_page.reactPowered")}>
|
|
213
|
-
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
214
|
-
{t("home_page.reactPoweredDescription")}
|
|
215
|
-
</Typography>
|
|
216
|
-
</Card>
|
|
217
|
-
<Card title={t("home_page.expressBackend")}>
|
|
218
|
-
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
219
|
-
{t("home_page.expressBackendDescription")}
|
|
220
|
-
</Typography>
|
|
221
|
-
</Card>
|
|
222
|
-
</div>
|
|
223
|
-
</div>
|
|
224
|
-
</main>
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
`;
|
|
231
|
-
fs.writeFileSync(file, content);
|
|
232
|
-
console.log(chalk.gray("Created frontend/resources/js/pages/Home.jsx"));
|
|
233
|
-
|
|
234
|
-
const file2 = path.join(this.appDir, 'frontend/resources/js/pages/About.jsx');
|
|
235
|
-
if (fs.existsSync(file2)) {
|
|
236
|
-
console.warn(chalk.yellow("About.jsx already exists. Skipping."));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const content2 = `import React from 'react';
|
|
241
|
-
import Header from '../components/Header';
|
|
242
|
-
import { useLanguage } from '../blue-bird/contexts/LanguageContext';
|
|
243
|
-
|
|
244
|
-
import Card from '../blue-bird/components/Card';
|
|
245
|
-
import Typography from '../blue-bird/components/Typography'
|
|
246
|
-
|
|
247
|
-
export default function About() {
|
|
248
|
-
const { t } = useLanguage();
|
|
249
|
-
return (
|
|
250
|
-
<div
|
|
251
|
-
className="bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen"
|
|
252
|
-
>
|
|
253
|
-
<Header />
|
|
254
|
-
<main className='max-w-3xl mx-auto mt-8 p-4'>
|
|
255
|
-
<Card>
|
|
256
|
-
<Typography variant='h1' className='text-3xl font-bold tracking-tight text-slate-900 dark:text-slate-100 mb-4'>
|
|
257
|
-
{t("about_page.title")}
|
|
258
|
-
</Typography>
|
|
259
|
-
<Typography className='text-slate-500 dark:text-slate-400 leading-7'>
|
|
260
|
-
{t("about_page.description")}
|
|
261
|
-
</Typography>
|
|
262
|
-
<div className='mt-8 pt-4 border-t border-slate-200 dark:border-slate-800'>
|
|
263
|
-
<Typography className='text-sm text-red-500 font-medium'>
|
|
264
|
-
{t("about_page.check_your_console")}
|
|
265
|
-
</Typography>
|
|
266
|
-
</div>
|
|
267
|
-
</Card>
|
|
268
|
-
</main>
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
}`;
|
|
272
|
-
fs.writeFileSync(file2, content2);
|
|
273
|
-
console.log(chalk.gray("Created frontend/resources/js/pages/About.jsx"));
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Creates the main entry point for React (main.jsx) which handles island hydration.
|
|
278
|
-
*/
|
|
279
|
-
createAppjs() {
|
|
280
|
-
const file = path.join(this.appDir, 'frontend/resources/js/App.jsx');
|
|
281
|
-
if (fs.existsSync(file)) {
|
|
282
|
-
console.warn(chalk.yellow("App.jsx already exists. Skipping."));
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const content = `import React, { lazy, Suspense } from 'react';
|
|
287
|
-
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
|
288
|
-
import { ThemeProvider } from './blue-bird/contexts/ThemeContext.jsx';
|
|
289
|
-
import Skeleton from './blue-bird/components/Skeleton.jsx';
|
|
290
|
-
import { LanguageProvider } from './blue-bird/contexts/LanguageContext.jsx';
|
|
291
|
-
|
|
292
|
-
const Home = lazy(() => import('./pages/Home'));
|
|
293
|
-
const About = lazy(() => import('./pages/About'));
|
|
294
|
-
|
|
295
|
-
export default function App(_props) {
|
|
296
|
-
const {
|
|
297
|
-
component,
|
|
298
|
-
props
|
|
299
|
-
} = _props;
|
|
300
|
-
|
|
301
|
-
console.log('Check props and component ')
|
|
302
|
-
console.log('Component:'+component)
|
|
303
|
-
console.log(props)
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
<ThemeProvider>
|
|
307
|
-
<LanguageProvider>
|
|
308
|
-
<Router>
|
|
309
|
-
<Suspense fallback={<Skeleton />}>
|
|
310
|
-
<Routes>
|
|
311
|
-
<Route path="/" element={<Home />} />
|
|
312
|
-
<Route path="/about" element={<About />} />
|
|
313
|
-
</Routes>
|
|
314
|
-
</Suspense>
|
|
315
|
-
</Router>
|
|
316
|
-
</LanguageProvider>
|
|
317
|
-
</ThemeProvider>
|
|
318
|
-
);
|
|
319
|
-
}`;
|
|
320
|
-
fs.writeFileSync(file, content);
|
|
321
|
-
console.log(chalk.gray("Created frontend/resources/js/App.jsx"));
|
|
322
|
-
}
|
|
323
|
-
createMainJs() {
|
|
324
|
-
const file = path.join(this.appDir, 'frontend/resources/js/Main.jsx');
|
|
325
|
-
if (fs.existsSync(file)) {
|
|
326
|
-
console.warn(chalk.yellow("Main.jsx already exists. Skipping."));
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const content = `import React from 'react';
|
|
331
|
-
import { createRoot } from 'react-dom/client';
|
|
332
|
-
import App from './App';
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
336
|
-
document.querySelectorAll('[data-react-component]').forEach(el => {
|
|
337
|
-
const component = {
|
|
338
|
-
component:el.dataset.reactComponent
|
|
339
|
-
};
|
|
340
|
-
const props = JSON.parse(el.dataset.props || '{}');
|
|
341
|
-
const allProps={
|
|
342
|
-
...props,
|
|
343
|
-
...component
|
|
344
|
-
}
|
|
345
|
-
createRoot(el).render(<App {...allProps} />);
|
|
346
|
-
});
|
|
347
|
-
});`;
|
|
348
|
-
fs.writeFileSync(file, content);
|
|
349
|
-
console.log(chalk.gray("Created frontend/resources/js/Main.jsx"));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
createHeaderJs() {
|
|
353
|
-
const file = path.join(this.appDir, 'frontend/resources/js/components/Header.jsx');
|
|
354
|
-
if (fs.existsSync(file)) {
|
|
355
|
-
console.warn(chalk.yellow("Header.jsx already exists. Skipping."));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const content = `import { Link } from "react-router-dom";
|
|
360
|
-
import { useState } from "react";
|
|
361
|
-
import Button from "../blue-bird/components/Button";
|
|
362
|
-
import { useLanguage } from "../blue-bird/contexts/LanguageContext";
|
|
363
|
-
import { useTheme } from "../blue-bird/contexts/ThemeContext";
|
|
364
|
-
|
|
365
|
-
export default function Header() {
|
|
366
|
-
const { t, setLang } = useLanguage();
|
|
367
|
-
const { changeTheme } = useTheme();
|
|
368
|
-
const [emojiTheme, setEmojiTheme] = useState("🌞");
|
|
369
|
-
|
|
370
|
-
const changeThemeEmoji = () => {
|
|
371
|
-
if (emojiTheme === "🌞") {
|
|
372
|
-
setEmojiTheme("🌙");
|
|
373
|
-
changeTheme("dark");
|
|
374
|
-
} else {
|
|
375
|
-
setEmojiTheme("🌞");
|
|
376
|
-
changeTheme("light");
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<header>
|
|
382
|
-
<nav className='bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-slate-800 px-4 py-4 flex justify-between items-center gap-4 sticky top-0 z-10'>
|
|
383
|
-
<div className='font-bold text-xl text-slate-900 dark:text-slate-100'>Blue Bird</div>
|
|
384
|
-
<div className='flex justify-between items-center gap-4'>
|
|
385
|
-
<div className="flex justify-between items-center gap-4">
|
|
386
|
-
<Button variant="outline" size="sm" onClick={() => setLang("es")}>ES</Button>
|
|
387
|
-
<Button variant="outline" size="sm" onClick={() => setLang("en")}>EN</Button>
|
|
388
|
-
<Button variant="ghost" size="icon" onClick={changeThemeEmoji}>{emojiTheme}</Button>
|
|
389
|
-
</div>
|
|
390
|
-
<div className="flex justify-between items-center gap-4">
|
|
391
|
-
<Link to="/" className='text-sm font-medium text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 transition-colors'>{t("home")}</Link>
|
|
392
|
-
<Link to="/about" className='text-sm font-medium text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 transition-colors'>{t("about")}</Link>
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
</nav>
|
|
396
|
-
</header>
|
|
397
|
-
);
|
|
398
|
-
}`;
|
|
399
|
-
fs.writeFileSync(file, content);
|
|
400
|
-
console.log(chalk.gray("Created frontend/resources/js/components/Header.jsx"));
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Ensures node_modules and other build artifacts are ignored by Git.
|
|
407
|
-
*/
|
|
408
|
-
updateGitIgnore() {
|
|
409
|
-
const file = path.join(this.appDir, '.gitignore');
|
|
410
|
-
const entry = "\nnode_modules\ndist\nfrontend/public/build\n";
|
|
411
|
-
|
|
412
|
-
if (fs.existsSync(file)) {
|
|
413
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
414
|
-
if (!content.includes('node_modules')) {
|
|
415
|
-
fs.appendFileSync(file, entry);
|
|
416
|
-
console.log(chalk.gray("Updated .gitignore"));
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
fs.writeFileSync(file, entry);
|
|
420
|
-
console.log(chalk.gray("Created .gitignore"));
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
npmInstall() {
|
|
424
|
-
try {
|
|
425
|
-
execSync('npm install', { cwd: this.appDir });
|
|
426
|
-
console.log(chalk.gray("Installed dependencies"));
|
|
427
|
-
} catch (error) {
|
|
428
|
-
console.error(chalk.red("Error installing dependencies:"), error.message);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const scaffold = new ReactScaffold();
|
|
435
|
-
scaffold.install();
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import Config from "../config.js";
|
|
6
|
+
|
|
7
|
+
const __dirname = Config.dirname();
|
|
8
|
+
const props = Config.props();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scaffolding class for setting up React and Vite in the project.
|
|
12
|
+
*/
|
|
13
|
+
class ReactScaffold {
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the Scaffolder with the base application directory.
|
|
16
|
+
*/
|
|
17
|
+
constructor() {
|
|
18
|
+
this.appDir = __dirname;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Executes the installation process.
|
|
23
|
+
*/
|
|
24
|
+
async install() {
|
|
25
|
+
console.log(chalk.cyan("Initializing React + Vite setup..."));
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
this.createStructure();
|
|
29
|
+
this.updatePackageJson();
|
|
30
|
+
this.createViteConfig();
|
|
31
|
+
this.createAppjs();
|
|
32
|
+
this.createMainJs();
|
|
33
|
+
this.createHeaderJs();
|
|
34
|
+
this.createPagesJs();
|
|
35
|
+
this.updateGitIgnore();
|
|
36
|
+
this.npmInstall();
|
|
37
|
+
|
|
38
|
+
console.log(chalk.green("React scaffolding created successfully!"));
|
|
39
|
+
console.log(chalk.cyan("\nNext steps:"));
|
|
40
|
+
console.log(chalk.white(" 1. Run (Blue Bird Server): ") + chalk.bold("npm run dev"));
|
|
41
|
+
console.log(chalk.white(" 2. Run (React Vite Dev): ") + chalk.bold("npm run vite:dev"));
|
|
42
|
+
console.log(chalk.blue("\nBlue Bird React setup completed!"));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(chalk.red("Fatal error during scaffolding:"), error.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates the necessary directory structure for React resources.
|
|
50
|
+
*/
|
|
51
|
+
createStructure() {
|
|
52
|
+
const dirs = [
|
|
53
|
+
'frontend/resources/js/pages',
|
|
54
|
+
'frontend/resources/js/components'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
dirs.forEach(dir => {
|
|
58
|
+
const fullPath = path.join(this.appDir, dir);
|
|
59
|
+
if (!fs.existsSync(fullPath)) {
|
|
60
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
61
|
+
console.log(chalk.gray(`Created directory: ${dir}`));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Updates the project's package.json with React and Vite dependencies and scripts.
|
|
68
|
+
*/
|
|
69
|
+
updatePackageJson() {
|
|
70
|
+
const packagePath = path.join(this.appDir, 'package.json');
|
|
71
|
+
if (!fs.existsSync(packagePath)) {
|
|
72
|
+
console.warn(chalk.yellow("package.json not found. Initializing with npm init..."));
|
|
73
|
+
execSync('npm init -y', { cwd: this.appDir });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
77
|
+
|
|
78
|
+
pkg.scripts = pkg.scripts || {};
|
|
79
|
+
pkg.scripts["vite:dev"] = "vite";
|
|
80
|
+
pkg.scripts["vite:build"] = "vite build";
|
|
81
|
+
|
|
82
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
83
|
+
|
|
84
|
+
pkg.devDependencies["vite"] = "^7.3.1";
|
|
85
|
+
pkg.devDependencies["@vitejs/plugin-react"] = "^4.3.4";
|
|
86
|
+
|
|
87
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
88
|
+
pkg.dependencies["react"] = "^19.2.4";
|
|
89
|
+
pkg.dependencies["react-dom"] = "^19.2.4";
|
|
90
|
+
pkg.dependencies["react-router-dom"] = "^7.2.0";
|
|
91
|
+
pkg.dependencies["@tailwindcss/vite"] = "^4.2.2";
|
|
92
|
+
pkg.dependencies["tailwindcss"] = "^4.2.2";
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
|
|
96
|
+
console.log(chalk.gray("Updated package.json dependencies and scripts."));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Creates the vite.config.js file with appropriate root and outDir settings.
|
|
101
|
+
*/
|
|
102
|
+
createViteConfig() {
|
|
103
|
+
const file = path.join(this.appDir, 'vite.config.js');
|
|
104
|
+
if (fs.existsSync(file)) {
|
|
105
|
+
console.warn(chalk.yellow("vite.config.js already exists. Skipping."));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// We use props.static.path to determine where the build goes
|
|
110
|
+
const outDir = path.join(props.static.path, 'build');
|
|
111
|
+
|
|
112
|
+
const content = `import { defineConfig } from 'vite';
|
|
113
|
+
import react from '@vitejs/plugin-react';
|
|
114
|
+
import path from 'path';
|
|
115
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
116
|
+
|
|
117
|
+
export default defineConfig({
|
|
118
|
+
plugins: [react(), tailwindcss()],
|
|
119
|
+
root: path.resolve(__dirname, 'frontend/resources/js'),
|
|
120
|
+
base: '/build/',
|
|
121
|
+
build: {
|
|
122
|
+
outDir: path.resolve(__dirname, '${outDir.replace(/\\/g, '/')}'),
|
|
123
|
+
emptyOutDir: true,
|
|
124
|
+
manifest: true,
|
|
125
|
+
rollupOptions: {
|
|
126
|
+
input: path.resolve(__dirname, 'frontend/resources/js/Main.jsx'),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
server: {
|
|
130
|
+
origin: 'http://localhost:5173',
|
|
131
|
+
strictPort: true,
|
|
132
|
+
cors: true,
|
|
133
|
+
},
|
|
134
|
+
});`;
|
|
135
|
+
fs.writeFileSync(file, content);
|
|
136
|
+
console.log(chalk.gray("Created vite.config.js"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
createPagesJs() {
|
|
140
|
+
const file = path.join(this.appDir, 'frontend/resources/js/pages/Home.jsx');
|
|
141
|
+
if (fs.existsSync(file)) {
|
|
142
|
+
console.warn(chalk.yellow("Home.jsx already exists. Skipping."));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const content = ` import React, { useEffect } from 'react';
|
|
147
|
+
import Card from '../blue-bird/components/Card';
|
|
148
|
+
import Header from '../components/Header';
|
|
149
|
+
import { useLanguage } from '../blue-bird/contexts/LanguageContext';
|
|
150
|
+
import Typography from '../blue-bird/components/Typography';
|
|
151
|
+
|
|
152
|
+
export default function Home() {
|
|
153
|
+
const { t } = useLanguage();
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
// Example API call to the backend
|
|
156
|
+
fetch("http://localhost:3000/login", {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
email: "example@example.com",
|
|
163
|
+
password: "myPassword123"
|
|
164
|
+
}),
|
|
165
|
+
})
|
|
166
|
+
.then((response) => response.json())
|
|
167
|
+
.then((data) => console.log('Backend response:', data))
|
|
168
|
+
.catch((error) => console.error('Error fetching from backend:', error));
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div
|
|
173
|
+
className="bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen"
|
|
174
|
+
>
|
|
175
|
+
<Header />
|
|
176
|
+
<main className='max-w-7xl mx-auto'>
|
|
177
|
+
<div className='text-center p-4'>
|
|
178
|
+
<header className='mb-8 mt-8'>
|
|
179
|
+
<Typography variant='h1' className='text-4xl font-extrabold tracking-tight lg:text-5xl text-slate-900 dark:text-slate-100 mb-4'>
|
|
180
|
+
{t("home_page.title")}
|
|
181
|
+
</Typography>
|
|
182
|
+
<Typography className='text-xl text-slate-500 dark:text-slate-400 max-w-[600px] mx-auto'>
|
|
183
|
+
{t("home_page.description")}
|
|
184
|
+
</Typography>
|
|
185
|
+
</header>
|
|
186
|
+
|
|
187
|
+
<div className='flex gap-4 justify-center mb-12'>
|
|
188
|
+
<a
|
|
189
|
+
href="https://seip25.github.io/Blue-bird/en.html"
|
|
190
|
+
target="_blank"
|
|
191
|
+
rel="noopener noreferrer"
|
|
192
|
+
className='inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-slate-900 dark:bg-slate-100 text-slate-50 dark:text-slate-900 hover:bg-slate-900/90 dark:hover:bg-slate-100/90 h-10 px-4 py-2'
|
|
193
|
+
>
|
|
194
|
+
Documentation (Eng)
|
|
195
|
+
</a>
|
|
196
|
+
<a
|
|
197
|
+
href="https://seip25.github.io/Blue-bird/"
|
|
198
|
+
target="_blank"
|
|
199
|
+
rel="noopener noreferrer"
|
|
200
|
+
className='inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800 h-10 px-4 py-2'
|
|
201
|
+
>
|
|
202
|
+
Documentación (Esp)
|
|
203
|
+
</a>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div className='mt-8 grid grid-cols-1 md:grid-cols-3 gap-6 max-w-[1000px] mx-auto text-left'>
|
|
207
|
+
<Card title={t("home_page.lightweight")}>
|
|
208
|
+
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
209
|
+
{t("home_page.lightweightDescription")}
|
|
210
|
+
</Typography>
|
|
211
|
+
</Card>
|
|
212
|
+
<Card title={t("home_page.reactPowered")}>
|
|
213
|
+
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
214
|
+
{t("home_page.reactPoweredDescription")}
|
|
215
|
+
</Typography>
|
|
216
|
+
</Card>
|
|
217
|
+
<Card title={t("home_page.expressBackend")}>
|
|
218
|
+
<Typography className="text-sm text-slate-500 dark:text-slate-400">
|
|
219
|
+
{t("home_page.expressBackendDescription")}
|
|
220
|
+
</Typography>
|
|
221
|
+
</Card>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</main>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
`;
|
|
231
|
+
fs.writeFileSync(file, content);
|
|
232
|
+
console.log(chalk.gray("Created frontend/resources/js/pages/Home.jsx"));
|
|
233
|
+
|
|
234
|
+
const file2 = path.join(this.appDir, 'frontend/resources/js/pages/About.jsx');
|
|
235
|
+
if (fs.existsSync(file2)) {
|
|
236
|
+
console.warn(chalk.yellow("About.jsx already exists. Skipping."));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const content2 = `import React from 'react';
|
|
241
|
+
import Header from '../components/Header';
|
|
242
|
+
import { useLanguage } from '../blue-bird/contexts/LanguageContext';
|
|
243
|
+
|
|
244
|
+
import Card from '../blue-bird/components/Card';
|
|
245
|
+
import Typography from '../blue-bird/components/Typography'
|
|
246
|
+
|
|
247
|
+
export default function About() {
|
|
248
|
+
const { t } = useLanguage();
|
|
249
|
+
return (
|
|
250
|
+
<div
|
|
251
|
+
className="bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen"
|
|
252
|
+
>
|
|
253
|
+
<Header />
|
|
254
|
+
<main className='max-w-3xl mx-auto mt-8 p-4'>
|
|
255
|
+
<Card>
|
|
256
|
+
<Typography variant='h1' className='text-3xl font-bold tracking-tight text-slate-900 dark:text-slate-100 mb-4'>
|
|
257
|
+
{t("about_page.title")}
|
|
258
|
+
</Typography>
|
|
259
|
+
<Typography className='text-slate-500 dark:text-slate-400 leading-7'>
|
|
260
|
+
{t("about_page.description")}
|
|
261
|
+
</Typography>
|
|
262
|
+
<div className='mt-8 pt-4 border-t border-slate-200 dark:border-slate-800'>
|
|
263
|
+
<Typography className='text-sm text-red-500 font-medium'>
|
|
264
|
+
{t("about_page.check_your_console")}
|
|
265
|
+
</Typography>
|
|
266
|
+
</div>
|
|
267
|
+
</Card>
|
|
268
|
+
</main>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}`;
|
|
272
|
+
fs.writeFileSync(file2, content2);
|
|
273
|
+
console.log(chalk.gray("Created frontend/resources/js/pages/About.jsx"));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Creates the main entry point for React (main.jsx) which handles island hydration.
|
|
278
|
+
*/
|
|
279
|
+
createAppjs() {
|
|
280
|
+
const file = path.join(this.appDir, 'frontend/resources/js/App.jsx');
|
|
281
|
+
if (fs.existsSync(file)) {
|
|
282
|
+
console.warn(chalk.yellow("App.jsx already exists. Skipping."));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const content = `import React, { lazy, Suspense } from 'react';
|
|
287
|
+
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
|
288
|
+
import { ThemeProvider } from './blue-bird/contexts/ThemeContext.jsx';
|
|
289
|
+
import Skeleton from './blue-bird/components/Skeleton.jsx';
|
|
290
|
+
import { LanguageProvider } from './blue-bird/contexts/LanguageContext.jsx';
|
|
291
|
+
|
|
292
|
+
const Home = lazy(() => import('./pages/Home'));
|
|
293
|
+
const About = lazy(() => import('./pages/About'));
|
|
294
|
+
|
|
295
|
+
export default function App(_props) {
|
|
296
|
+
const {
|
|
297
|
+
component,
|
|
298
|
+
props
|
|
299
|
+
} = _props;
|
|
300
|
+
|
|
301
|
+
console.log('Check props and component ')
|
|
302
|
+
console.log('Component:'+component)
|
|
303
|
+
console.log(props)
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<ThemeProvider>
|
|
307
|
+
<LanguageProvider>
|
|
308
|
+
<Router>
|
|
309
|
+
<Suspense fallback={<Skeleton />}>
|
|
310
|
+
<Routes>
|
|
311
|
+
<Route path="/" element={<Home />} />
|
|
312
|
+
<Route path="/about" element={<About />} />
|
|
313
|
+
</Routes>
|
|
314
|
+
</Suspense>
|
|
315
|
+
</Router>
|
|
316
|
+
</LanguageProvider>
|
|
317
|
+
</ThemeProvider>
|
|
318
|
+
);
|
|
319
|
+
}`;
|
|
320
|
+
fs.writeFileSync(file, content);
|
|
321
|
+
console.log(chalk.gray("Created frontend/resources/js/App.jsx"));
|
|
322
|
+
}
|
|
323
|
+
createMainJs() {
|
|
324
|
+
const file = path.join(this.appDir, 'frontend/resources/js/Main.jsx');
|
|
325
|
+
if (fs.existsSync(file)) {
|
|
326
|
+
console.warn(chalk.yellow("Main.jsx already exists. Skipping."));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const content = `import React from 'react';
|
|
331
|
+
import { createRoot } from 'react-dom/client';
|
|
332
|
+
import App from './App';
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
336
|
+
document.querySelectorAll('[data-react-component]').forEach(el => {
|
|
337
|
+
const component = {
|
|
338
|
+
component:el.dataset.reactComponent
|
|
339
|
+
};
|
|
340
|
+
const props = JSON.parse(el.dataset.props || '{}');
|
|
341
|
+
const allProps={
|
|
342
|
+
...props,
|
|
343
|
+
...component
|
|
344
|
+
}
|
|
345
|
+
createRoot(el).render(<App {...allProps} />);
|
|
346
|
+
});
|
|
347
|
+
});`;
|
|
348
|
+
fs.writeFileSync(file, content);
|
|
349
|
+
console.log(chalk.gray("Created frontend/resources/js/Main.jsx"));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
createHeaderJs() {
|
|
353
|
+
const file = path.join(this.appDir, 'frontend/resources/js/components/Header.jsx');
|
|
354
|
+
if (fs.existsSync(file)) {
|
|
355
|
+
console.warn(chalk.yellow("Header.jsx already exists. Skipping."));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const content = `import { Link } from "react-router-dom";
|
|
360
|
+
import { useState } from "react";
|
|
361
|
+
import Button from "../blue-bird/components/Button";
|
|
362
|
+
import { useLanguage } from "../blue-bird/contexts/LanguageContext";
|
|
363
|
+
import { useTheme } from "../blue-bird/contexts/ThemeContext";
|
|
364
|
+
|
|
365
|
+
export default function Header() {
|
|
366
|
+
const { t, setLang } = useLanguage();
|
|
367
|
+
const { changeTheme } = useTheme();
|
|
368
|
+
const [emojiTheme, setEmojiTheme] = useState("🌞");
|
|
369
|
+
|
|
370
|
+
const changeThemeEmoji = () => {
|
|
371
|
+
if (emojiTheme === "🌞") {
|
|
372
|
+
setEmojiTheme("🌙");
|
|
373
|
+
changeTheme("dark");
|
|
374
|
+
} else {
|
|
375
|
+
setEmojiTheme("🌞");
|
|
376
|
+
changeTheme("light");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<header>
|
|
382
|
+
<nav className='bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-slate-800 px-4 py-4 flex justify-between items-center gap-4 sticky top-0 z-10'>
|
|
383
|
+
<div className='font-bold text-xl text-slate-900 dark:text-slate-100'>Blue Bird</div>
|
|
384
|
+
<div className='flex justify-between items-center gap-4'>
|
|
385
|
+
<div className="flex justify-between items-center gap-4">
|
|
386
|
+
<Button variant="outline" size="sm" onClick={() => setLang("es")}>ES</Button>
|
|
387
|
+
<Button variant="outline" size="sm" onClick={() => setLang("en")}>EN</Button>
|
|
388
|
+
<Button variant="ghost" size="icon" onClick={changeThemeEmoji}>{emojiTheme}</Button>
|
|
389
|
+
</div>
|
|
390
|
+
<div className="flex justify-between items-center gap-4">
|
|
391
|
+
<Link to="/" className='text-sm font-medium text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 transition-colors'>{t("home")}</Link>
|
|
392
|
+
<Link to="/about" className='text-sm font-medium text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 transition-colors'>{t("about")}</Link>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</nav>
|
|
396
|
+
</header>
|
|
397
|
+
);
|
|
398
|
+
}`;
|
|
399
|
+
fs.writeFileSync(file, content);
|
|
400
|
+
console.log(chalk.gray("Created frontend/resources/js/components/Header.jsx"));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Ensures node_modules and other build artifacts are ignored by Git.
|
|
407
|
+
*/
|
|
408
|
+
updateGitIgnore() {
|
|
409
|
+
const file = path.join(this.appDir, '.gitignore');
|
|
410
|
+
const entry = "\nnode_modules\ndist\nfrontend/public/build\n";
|
|
411
|
+
|
|
412
|
+
if (fs.existsSync(file)) {
|
|
413
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
414
|
+
if (!content.includes('node_modules')) {
|
|
415
|
+
fs.appendFileSync(file, entry);
|
|
416
|
+
console.log(chalk.gray("Updated .gitignore"));
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
fs.writeFileSync(file, entry);
|
|
420
|
+
console.log(chalk.gray("Created .gitignore"));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
npmInstall() {
|
|
424
|
+
try {
|
|
425
|
+
execSync('npm install', { cwd: this.appDir });
|
|
426
|
+
console.log(chalk.gray("Installed dependencies"));
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error(chalk.red("Error installing dependencies:"), error.message);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
const scaffold = new ReactScaffold();
|
|
435
|
+
scaffold.install();
|