@seip/blue-bird 0.3.9 → 0.4.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/core/cli/react.js CHANGED
@@ -30,6 +30,7 @@ class ReactScaffold {
30
30
  this.createViteConfig();
31
31
  this.createAppjs();
32
32
  this.createMainJs();
33
+ this.createHeaderJs();
33
34
  this.createPagesJs();
34
35
  this.updateGitIgnore();
35
36
  this.npmInstall();
@@ -49,7 +50,8 @@ class ReactScaffold {
49
50
  */
50
51
  createStructure() {
51
52
  const dirs = [
52
- 'frontend/resources/js/pages'
53
+ 'frontend/resources/js/pages',
54
+ 'frontend/resources/js/components'
53
55
  ];
54
56
 
55
57
  dirs.forEach(dir => {
@@ -86,6 +88,8 @@ class ReactScaffold {
86
88
  pkg.dependencies["react"] = "^19.2.4";
87
89
  pkg.dependencies["react-dom"] = "^19.2.4";
88
90
  pkg.dependencies["react-router-dom"] = "^7.2.0";
91
+ pkg.dependencies["@tailwindcss/vite"] = "^4.2.2";
92
+ pkg.dependencies["tailwindcss"] = "^4.2.2";
89
93
 
90
94
 
91
95
  fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
@@ -108,9 +112,10 @@ class ReactScaffold {
108
112
  const content = `import { defineConfig } from 'vite';
109
113
  import react from '@vitejs/plugin-react';
110
114
  import path from 'path';
115
+ import tailwindcss from '@tailwindcss/vite';
111
116
 
112
117
  export default defineConfig({
113
- plugins: [react()],
118
+ plugins: [react(), tailwindcss()],
114
119
  root: path.resolve(__dirname, 'frontend/resources/js'),
115
120
  base: '/build/',
116
121
  build: {
@@ -138,99 +143,89 @@ export default defineConfig({
138
143
  return;
139
144
  }
140
145
 
141
- const content = `import React, { useEffect } from 'react';
142
- import { Link } from 'react-router-dom';
143
- import Card from '../blue-bird/components/Card';
144
-
145
- export default function Home() {
146
- useEffect(() => {
147
- // Example API call to the backend
148
- fetch("http://localhost:3000/login", {
149
- method: "POST",
150
- headers: {
151
- "Content-Type": "application/json",
152
- },
153
- body: JSON.stringify({
154
- email: "example@example.com",
155
- password: "myPassword123"
156
- }),
157
- })
158
- .then((response) => response.json())
159
- .then((data) => console.log('Backend response:', data))
160
- .catch((error) => console.error('Error fetching from backend:', error));
161
- }, []);
162
-
163
- return (
164
- <div
165
- className="bg-white text-gray-900"
166
- >
167
- <nav
168
- className='bg-white text-gray-900 border border-gray-200 px-4 py-4 flex justify-between items-center gap-4 sticky top-0 z-10'
169
- >
170
- <div className='font-bold text-xl text-blue-600'>
171
- Blue Bird
172
- </div>
173
- <div className='flex justify-between items-center gap-4'>
174
- <Link to="/" className='text-gray-500 hover:text-gray-900'>Home</Link>
175
- <Link to="/about" className='text-gray-500 hover:text-gray-900'>About</Link>
176
- </div>
177
- </nav>
178
- <main className='max-w-7xl mx-auto'>
179
- <div className='text-center p-4'>
180
- <header className='mb-4'>
181
- <h1 className='text-3xl font-bold bg-gradient-to-r from-blue-500 to-blue-600 bg-clip-text text-transparent mb-4'>
182
- Welcome to Blue Bird
183
- </h1>
184
- <p className='text-gray-500 max-w-600px mx-auto'>
185
- The elegant, fast, and weightless framework for modern web development.
186
- </p>
187
- </header>
188
-
189
- <Card title={" Documentation (Eng)"} className='mt-8 border-none shadow-none'>
190
- <div className='flex gap-4 justify-center mb-8'>
191
- <a
192
- href="https://seip25.github.io/Blue-bird/en.html"
193
- target="_blank"
194
- rel="noopener noreferrer"
195
- className='bg-blue-600 text-white px-4 py-2 rounded-lg font-semibold transition-colors hover:bg-blue-400'
196
- >
197
- Documentation(Eng)
198
- </a>
199
- <a
200
- href="https://seip25.github.io/Blue-bird/"
201
- target="_blank"
202
- rel="noopener noreferrer"
203
- className='bg-blue-50 text-blue-500 px-4 py-2 rounded-lg font-semibold transition-colors hover:bg-blue-100 '
204
- >
205
- Documentación (Esp)
206
-
207
- </a>
208
- </div>
209
- </Card>
210
-
211
- <Card className='mt-8 border-none shadow-none'>
212
- <div className='mt-8 grid grid-cols-1 md:grid-cols-3 gap-4 max-w-1000px mx-auto'>
213
- <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
214
- <h3 className='text-blue-500 font-semibold text-xl mb-4'>Lightweight</h3>
215
- <p>Built with performance and simplicity in mind.</p>
216
- </div>
217
- <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
218
- <h3 className='text-blue-500 font-semibold text-xl mb-4'>React Powered</h3>
219
- <p>Full React + Vite integration .</p>
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>
220
204
  </div>
221
- <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
222
- <h3 className='text-blue-500 font-semibold text-xl mb-4'>Express Backend</h3>
223
- <p>Robust and scalable backend architecture.</p>
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>
224
222
  </div>
225
223
  </div>
226
- </Card>
227
-
224
+ </main>
228
225
  </div>
229
- </main>
230
- </div>
231
- );
232
- }
233
-
226
+ );
227
+ }
228
+
234
229
 
235
230
  `;
236
231
  fs.writeFileSync(file, content);
@@ -243,39 +238,37 @@ export default function Home() {
243
238
  }
244
239
 
245
240
  const content2 = `import React from 'react';
246
- import { Link } from 'react-router-dom';
247
-
248
- export default function About() {
249
- return (
250
- <div
251
- className="bg-white text-gray-900"
252
- >
253
- <nav
254
- className='bg-white text-gray-900 border border-gray-200 px-4 py-4 flex justify-between items-center gap-4 sticky top-0 z-10'
255
- >
256
- <div className='font-bold text-xl text-blue-600'>
257
- Blue Bird
258
- </div>
259
- <div className='flex justify-between items-center gap-4'>
260
- <Link to="/" className='text-gray-500 hover:text-gray-900'>Home</Link>
261
- <Link to="/about" className='text-gray-500 hover:text-gray-900'>About</Link>
262
- </div>
263
- </nav>
264
- <main className='max-w-7xl mx-auto'>
265
- <div className='p-4'>
266
- <h1 className='text-xl font-bold text-gray-900 mb-4'>About Blue Bird</h1>
267
- <p className='text-gray-500 leading-1.6'>
268
- Blue Bird is a modern framework designed to bridge the gap between backend routing and frontend interactivity.
269
- It provides a seamless developer experience for building fast, reactive web applications.
270
- </p>
271
- <p className='text-red-500 text-xl mt-8 '>
272
- Check your console JS
273
- </p>
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>
274
269
  </div>
275
- </main>
276
- </div>
277
- );
278
- }`;
270
+ );
271
+ }`;
279
272
  fs.writeFileSync(file2, content2);
280
273
  console.log(chalk.gray("Created frontend/resources/js/pages/About.jsx"));
281
274
  }
@@ -290,10 +283,14 @@ export default function About() {
290
283
  return;
291
284
  }
292
285
 
293
- const content = `import React from 'react';
286
+ const content = `import React, { lazy, Suspense } from 'react';
294
287
  import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
295
- import Home from './pages/Home';
296
- import About from './pages/About';
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'));
297
294
 
298
295
  export default function App(_props) {
299
296
  const {
@@ -306,16 +303,20 @@ export default function App(_props) {
306
303
  console.log(props)
307
304
 
308
305
  return (
309
- <Router>
310
- <Routes>
311
- <Route path="/" element={<Home />} />
312
- <Route path="/about" element={<About />} />
313
- </Routes>
314
- </Router>
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>
315
318
  );
316
- }
317
-
318
- `;
319
+ }`;
319
320
  fs.writeFileSync(file, content);
320
321
  console.log(chalk.gray("Created frontend/resources/js/App.jsx"));
321
322
  }
@@ -348,6 +349,57 @@ document.addEventListener('DOMContentLoaded', () => {
348
349
  console.log(chalk.gray("Created frontend/resources/js/Main.jsx"));
349
350
  }
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
+
351
403
 
352
404
 
353
405
  /**
@@ -523,7 +523,7 @@ routerAuth.post("/register", new Validator({ password_confirmation: { required:
523
523
  const deletedAccount = await AuthService.findUserByEmail(email, 0);
524
524
  if (deletedAccount) return res.status(400).json({ message: "Error, your account is deleted", deleted_account_error: true });
525
525
  const user = await AuthService.createUser(name, email, password);
526
- const secure = process.env.NODE_ENV === "production" ? true : false;
526
+ const secure = props.debug == false ? true : false;
527
527
  const token = Auth.generateToken({ id: user.id_public, email: user.email });
528
528
  res.cookie("token", token, { httpOnly: true, secure: secure, sameSite: "strict", maxAge: 24 * 60 * 60 * 1000 });
529
529
  return res.json({ message: "Registered", user: { id: user.id_public, email: user.email } });
@@ -715,9 +715,12 @@ export default function Register() {
715
715
  const [password, setPassword] = useState('');
716
716
  const [password_confirmation, setPasswordConfirmation] = useState('');
717
717
  const [error, setError] = useState(null);
718
+ const [message, setMessage] = useState(null);
718
719
 
719
720
  const handleSubmit = async (e) => {
720
721
  e.preventDefault();
722
+ setError(null);
723
+ setMessage(null);
721
724
  const lang = localStorage.getItem("blue_bird_lang") ?? "en";
722
725
  try {
723
726
  const res = await fetch('/auth/register', {
@@ -732,7 +735,16 @@ export default function Register() {
732
735
  if (data.error_email_register) throw new Error(t('error_email_register'));
733
736
  throw new Error(data.message || t('error_general'));
734
737
  }
735
- window.location.href = '/login';
738
+ else{
739
+ setMessage(t('register_success') || 'Register success');
740
+ setEmail('');
741
+ setName('');
742
+ setPassword('');
743
+ setPasswordConfirmation('');
744
+ setTimeout(() => {
745
+ window.location.href = '/login';
746
+ }, 4000);
747
+ }
736
748
  } catch (err) {
737
749
  setError(err.message);
738
750
  }
@@ -744,6 +756,7 @@ export default function Register() {
744
756
  <div className="mb-6 text-center">
745
757
  <Typography variant="h3">{t('register')}</Typography>
746
758
  </div>
759
+ {message && <div className="bg-green-100 text-green-700 p-3 mb-4 rounded-md text-sm">{message}</div>}
747
760
  {error && <div className="bg-red-100 text-red-700 p-3 mb-4 rounded-md text-sm">{error}</div>}
748
761
  <form onSubmit={handleSubmit} className="space-y-4">
749
762
  <Input label={t('name')} type="text" value={name} onChange={(e) => setName(e.target.value)} required />
@@ -856,8 +869,6 @@ export default function ResetPassword() {
856
869
  body: JSON.stringify({ token })
857
870
  });
858
871
  if (!res.ok) return window.location.href = '/login';
859
- const data = await res.json();
860
- setUser(data.user);
861
872
  };
862
873
  fetchUser();
863
874
  }
@@ -974,12 +985,17 @@ export default function ResetPassword() {
974
985
  let backendIndex = fs.readFileSync(backendIndexFile, "utf-8");
975
986
  if (!backendIndex.includes("routerAuth")) {
976
987
  backendIndex = backendIndex.replace(
977
- 'import routerFrontendExample from "./routes/frontend.js";',
988
+ /import\s+routerFrontendExample\s+from\s+["']\.\/routes\/frontend\.js["'];?/,
978
989
  'import routerFrontendExample from "./routes/frontend.js";\nimport routerAuth from "./routes/auth.js";\nimport routerAuthenticated from "./routes/authenticated.js";'
979
990
  );
980
991
  backendIndex = backendIndex.replace(
981
- 'routes: [routerApiExample, routerFrontendExample]',
982
- 'routes: [routerApiExample, routerAuth, routerAuthenticated, routerFrontendExample]'
992
+ /routes:\s*\[([^\]]+)\]/,
993
+ (match, p1) => {
994
+ if (p1.includes('routerFrontendExample') && !p1.includes('routerAuth')) {
995
+ return `routes: [${p1.replace('routerFrontendExample', 'routerAuth, routerAuthenticated, routerFrontendExample')}]`;
996
+ }
997
+ return match;
998
+ }
983
999
  );
984
1000
  fs.writeFileSync(backendIndexFile, backendIndex, "utf-8");
985
1001
  console.log(chalk.green("✓ backend/index.js updated to include new routes."));
@@ -991,19 +1007,19 @@ export default function ResetPassword() {
991
1007
  let appJsx = fs.readFileSync(appJsxFile, "utf-8");
992
1008
  if (!appJsx.includes("LanguageProvider")) {
993
1009
  appJsx = appJsx.replace(
994
- "import Home from './pages/Home';",
1010
+ /import\s+Home\s+from\s+["']\.\/pages\/Home["'];?/,
995
1011
  "import { LanguageProvider } from './blue-bird/contexts/LanguageContext.jsx';\nimport Login from './pages/auth/Login.jsx';\nimport Register from './pages/auth/Register.jsx';\nimport ForgotPassword from './pages/auth/ForgotPassword.jsx';\nimport ResetPassword from './pages/auth/ResetPassword.jsx';\nimport Dashboard from './pages/authenticated/Dashboard.jsx';\nimport Home from './pages/Home';"
996
1012
  );
997
1013
  appJsx = appJsx.replace(
998
- "<Router>",
1014
+ /<Router>/,
999
1015
  "<LanguageProvider>\n <Router>"
1000
1016
  );
1001
1017
  appJsx = appJsx.replace(
1002
- "</Router>",
1018
+ /<\/Router>/,
1003
1019
  "</Router>\n </LanguageProvider>"
1004
1020
  );
1005
1021
  appJsx = appJsx.replace(
1006
- '<Route path="/about" element={<About />} />',
1022
+ /<Route\s+path=["']\/about["']\s+element=\{<About\s*\/?>\}\s*\/?>/,
1007
1023
  '<Route path="/about" element={<About />} />\n <Route path="/login" element={<Login />} />\n <Route path="/register" element={<Register />} />\n <Route path="/forgot-password" element={<ForgotPassword />} />\n <Route path="/reset-password" element={<ResetPassword />} />\n <Route path="/dashboard" element={<Dashboard />} />'
1008
1024
  );
1009
1025
  fs.writeFileSync(appJsxFile, appJsx, "utf-8");