@seip/blue-bird 0.3.5 → 0.3.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 +5 -2
- package/core/cli/scaffolding-auth.js +58 -4
- package/frontend/resources/js/blue-bird/components/Label.jsx +12 -12
- package/frontend/resources/js/blue-bird/components/Typography.jsx +25 -25
- package/frontend/resources/js/blue-bird/contexts/LanguageContext.jsx +2 -2
- package/frontend/resources/js/blue-bird/contexts/ThemeContext.jsx +49 -49
- package/package.json +1 -1
package/.env_example
CHANGED
|
@@ -14,10 +14,13 @@ JWT_SECRET="JWT_SECRET"
|
|
|
14
14
|
|
|
15
15
|
# Database Configuration
|
|
16
16
|
# SQLite (Default)
|
|
17
|
+
#Remove this line if you are not going to use sqlite
|
|
17
18
|
DATABASE_URL="file:./dev.db"
|
|
18
19
|
|
|
19
20
|
# MySQL
|
|
20
|
-
#
|
|
21
|
+
#Remove this line if you are not going to use Mysql
|
|
22
|
+
#DATABASE_URL="mysql://root:@localhost:3306/blue_bird"
|
|
21
23
|
|
|
22
24
|
# PostgreSQL
|
|
23
|
-
#
|
|
25
|
+
#Remove this line if you are not going to use PostgreSQL
|
|
26
|
+
# DATABASE_URL="postgresql://root:@localhost:5432/blue_bird?schema=public"
|
|
@@ -29,7 +29,7 @@ class ScaffoldingAuth {
|
|
|
29
29
|
console.log(chalk.white("Update your App.jsx to use the newly created React components."));
|
|
30
30
|
console.log(chalk.yellow("Running setup script to execute database migrations..."));
|
|
31
31
|
try {
|
|
32
|
-
execSync('node backend/databases/setup_tables.js', { stdio: "inherit", cwd: this.appDir });
|
|
32
|
+
execSync('node --env-file=.env backend/databases/setup_tables.js', { stdio: "inherit", cwd: this.appDir });
|
|
33
33
|
console.log(chalk.green("✓ Database migrations success. Users and LoginHistory tables created."));
|
|
34
34
|
} catch (e) {
|
|
35
35
|
console.log(chalk.red("Failed to execute setup_tables.js. You may need to run it manually."));
|
|
@@ -225,7 +225,61 @@ class DatabaseConnection {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
generateSetupTables(dbDir, dialect) {
|
|
228
|
-
let content = `import db from './connection.js';\n\
|
|
228
|
+
let content = `import db from './connection.js';\n\n`;
|
|
229
|
+
|
|
230
|
+
if (dialect === "mysql" || dialect === "postgres") {
|
|
231
|
+
content += `const dbUrl = process.env.DATABASE_URL;\n\nasync function createDatabaseIfNotExists() {\n`;
|
|
232
|
+
if (dialect === "mysql") {
|
|
233
|
+
content += ` try {
|
|
234
|
+
const mysql = await import('mysql2/promise');
|
|
235
|
+
const createConnection = mysql.createConnection || (mysql.default && mysql.default.createConnection);
|
|
236
|
+
const url = new URL(dbUrl);
|
|
237
|
+
const dbName = url.pathname.replace('/', '');
|
|
238
|
+
const connectionParams = {
|
|
239
|
+
host: url.hostname,
|
|
240
|
+
user: decodeURIComponent(url.username),
|
|
241
|
+
password: decodeURIComponent(url.password),
|
|
242
|
+
port: url.port ? Number(url.port) : 3306
|
|
243
|
+
};
|
|
244
|
+
const connection = await createConnection(connectionParams);
|
|
245
|
+
await connection.query(\`CREATE DATABASE IF NOT EXISTS \\\`\${dbName}\\\`;\`);
|
|
246
|
+
await connection.end();
|
|
247
|
+
console.log(\`Database '\${dbName}' checked/created successfully.\`);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error("Failed to create database automatically:", err.message);
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
} else if (dialect === "postgres") {
|
|
253
|
+
content += ` try {
|
|
254
|
+
const pkg = await import('pg');
|
|
255
|
+
const Client = pkg.Client || (pkg.default && pkg.default.Client);
|
|
256
|
+
const url = new URL(dbUrl);
|
|
257
|
+
const dbName = url.pathname.replace('/', '');
|
|
258
|
+
const connectionString = \`postgres://\${url.username}:\${url.password}@\${url.hostname}:\${url.port ? url.port : 5432}/postgres\`;
|
|
259
|
+
const client = new Client({ connectionString });
|
|
260
|
+
await client.connect();
|
|
261
|
+
|
|
262
|
+
const res = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [dbName]);
|
|
263
|
+
if (res.rowCount === 0) {
|
|
264
|
+
await client.query(\`CREATE DATABASE "\${dbName}"\`);
|
|
265
|
+
console.log(\`Database '\${dbName}' created successfully.\`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log(\`Database '\${dbName}' already exists.\`);
|
|
268
|
+
}
|
|
269
|
+
await client.end();
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error("Failed to create database automatically:", err.message);
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
content += `}\n\n`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
content += `async function setup() {\n`;
|
|
279
|
+
|
|
280
|
+
if (dialect === "mysql" || dialect === "postgres") {
|
|
281
|
+
content += ` await createDatabaseIfNotExists();\n`;
|
|
282
|
+
}
|
|
229
283
|
|
|
230
284
|
let usersTable = "";
|
|
231
285
|
let historyTable = "";
|
|
@@ -601,7 +655,7 @@ export default function Login() {
|
|
|
601
655
|
|
|
602
656
|
const handleSubmit = async (e) => {
|
|
603
657
|
e.preventDefault();
|
|
604
|
-
const lang = localStorage.getItem("
|
|
658
|
+
const lang = localStorage.getItem("blue_bird_lang") ?? "en";
|
|
605
659
|
try {
|
|
606
660
|
const res = await fetch('/auth/login', {
|
|
607
661
|
method: 'POST',
|
|
@@ -664,7 +718,7 @@ export default function Register() {
|
|
|
664
718
|
|
|
665
719
|
const handleSubmit = async (e) => {
|
|
666
720
|
e.preventDefault();
|
|
667
|
-
const lang = localStorage.getItem("
|
|
721
|
+
const lang = localStorage.getItem("blue_bird_lang") ?? "en";
|
|
668
722
|
try {
|
|
669
723
|
const res = await fetch('/auth/register', {
|
|
670
724
|
method: 'POST',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Label({ children, className = '', ...props }) {
|
|
4
|
-
return (
|
|
5
|
-
<label
|
|
6
|
-
className={`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${className}`}
|
|
7
|
-
{...props}
|
|
8
|
-
>
|
|
9
|
-
{children}
|
|
10
|
-
</label>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Label({ children, className = '', ...props }) {
|
|
4
|
+
return (
|
|
5
|
+
<label
|
|
6
|
+
className={`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${className}`}
|
|
7
|
+
{...props}
|
|
8
|
+
>
|
|
9
|
+
{children}
|
|
10
|
+
</label>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Typography({ variant = 'p', children, className = '', ...props }) {
|
|
4
|
-
const variants = {
|
|
5
|
-
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
|
|
6
|
-
h2: "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
7
|
-
h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
|
|
8
|
-
h4: "scroll-m-20 text-xl font-semibold tracking-tight",
|
|
9
|
-
p: "leading-7 [&:not(:first-child)]:mt-6",
|
|
10
|
-
blockquote: "mt-6 border-l-2 pl-6 italic",
|
|
11
|
-
lead: "text-xl text-slate-700 dark:text-slate-300",
|
|
12
|
-
large: "text-lg font-semibold",
|
|
13
|
-
small: "text-sm font-medium leading-none",
|
|
14
|
-
muted: "text-sm text-slate-500 dark:text-slate-400",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const Component = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p'].includes(variant) ? variant : 'p';
|
|
18
|
-
const style = `${variants[variant]} ${className}`;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<Component className={style} {...props}>
|
|
22
|
-
{children}
|
|
23
|
-
</Component>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Typography({ variant = 'p', children, className = '', ...props }) {
|
|
4
|
+
const variants = {
|
|
5
|
+
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
|
|
6
|
+
h2: "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
7
|
+
h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
|
|
8
|
+
h4: "scroll-m-20 text-xl font-semibold tracking-tight",
|
|
9
|
+
p: "leading-7 [&:not(:first-child)]:mt-6",
|
|
10
|
+
blockquote: "mt-6 border-l-2 pl-6 italic",
|
|
11
|
+
lead: "text-xl text-slate-700 dark:text-slate-300",
|
|
12
|
+
large: "text-lg font-semibold",
|
|
13
|
+
small: "text-sm font-medium leading-none",
|
|
14
|
+
muted: "text-sm text-slate-500 dark:text-slate-400",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const Component = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p'].includes(variant) ? variant : 'p';
|
|
18
|
+
const style = `${variants[variant]} ${className}`;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Component className={style} {...props}>
|
|
22
|
+
{children}
|
|
23
|
+
</Component>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -6,10 +6,10 @@ const translations = { en, es };
|
|
|
6
6
|
export const LanguageContext = createContext();
|
|
7
7
|
|
|
8
8
|
export const LanguageProvider = ({ children }) => {
|
|
9
|
-
const [lang, setLang] = useState(() => localStorage.getItem('
|
|
9
|
+
const [lang, setLang] = useState(() => localStorage.getItem('blue_bird_lang') || 'en');
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
localStorage.setItem('
|
|
12
|
+
localStorage.setItem('blue_bird_lang', lang);
|
|
13
13
|
}, [lang]);
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const ThemeContext = createContext();
|
|
4
|
-
|
|
5
|
-
export function ThemeProvider({ children }) {
|
|
6
|
-
const [theme, setTheme] = useState(() => {
|
|
7
|
-
return localStorage.getItem('theme') || 'system';
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const root = window.document.documentElement;
|
|
12
|
-
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
13
|
-
|
|
14
|
-
const applyTheme = (currentTheme) => {
|
|
15
|
-
root.classList.remove('light', 'dark');
|
|
16
|
-
|
|
17
|
-
if (currentTheme === 'system') {
|
|
18
|
-
const systemTheme = mediaQuery.matches ? 'dark' : 'light';
|
|
19
|
-
root.classList.add(systemTheme);
|
|
20
|
-
} else {
|
|
21
|
-
root.classList.add(currentTheme);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
applyTheme(theme);
|
|
26
|
-
|
|
27
|
-
const handleChange = () => {
|
|
28
|
-
if (theme === 'system') {
|
|
29
|
-
applyTheme('system');
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
mediaQuery.addEventListener('change', handleChange);
|
|
34
|
-
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
35
|
-
}, [theme]);
|
|
36
|
-
|
|
37
|
-
const changeTheme = (newTheme) => {
|
|
38
|
-
setTheme(newTheme);
|
|
39
|
-
localStorage.setItem('theme', newTheme);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<ThemeContext.Provider value={{ theme, changeTheme }}>
|
|
44
|
-
{children}
|
|
45
|
-
</ThemeContext.Provider>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const useTheme = () => useContext(ThemeContext);
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const ThemeContext = createContext();
|
|
4
|
+
|
|
5
|
+
export function ThemeProvider({ children }) {
|
|
6
|
+
const [theme, setTheme] = useState(() => {
|
|
7
|
+
return localStorage.getItem('theme') || 'system';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const root = window.document.documentElement;
|
|
12
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
13
|
+
|
|
14
|
+
const applyTheme = (currentTheme) => {
|
|
15
|
+
root.classList.remove('light', 'dark');
|
|
16
|
+
|
|
17
|
+
if (currentTheme === 'system') {
|
|
18
|
+
const systemTheme = mediaQuery.matches ? 'dark' : 'light';
|
|
19
|
+
root.classList.add(systemTheme);
|
|
20
|
+
} else {
|
|
21
|
+
root.classList.add(currentTheme);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
applyTheme(theme);
|
|
26
|
+
|
|
27
|
+
const handleChange = () => {
|
|
28
|
+
if (theme === 'system') {
|
|
29
|
+
applyTheme('system');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
34
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
35
|
+
}, [theme]);
|
|
36
|
+
|
|
37
|
+
const changeTheme = (newTheme) => {
|
|
38
|
+
setTheme(newTheme);
|
|
39
|
+
localStorage.setItem('theme', newTheme);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ThemeContext.Provider value={{ theme, changeTheme }}>
|
|
44
|
+
{children}
|
|
45
|
+
</ThemeContext.Provider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const useTheme = () => useContext(ThemeContext);
|