@basicbenframework/create 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +205 -0
- package/package.json +30 -0
- package/template/.env.example +24 -0
- package/template/README.md +59 -0
- package/template/basicben.config.js +33 -0
- package/template/index.html +54 -0
- package/template/migrations/001_create_users.js +15 -0
- package/template/migrations/002_create_posts.js +18 -0
- package/template/public/.gitkeep +0 -0
- package/template/seeds/01_users.js +29 -0
- package/template/seeds/02_posts.js +43 -0
- package/template/src/client/components/Alert.jsx +11 -0
- package/template/src/client/components/Avatar.jsx +11 -0
- package/template/src/client/components/BackLink.jsx +10 -0
- package/template/src/client/components/Button.jsx +19 -0
- package/template/src/client/components/Card.jsx +10 -0
- package/template/src/client/components/Empty.jsx +6 -0
- package/template/src/client/components/Input.jsx +12 -0
- package/template/src/client/components/Loading.jsx +6 -0
- package/template/src/client/components/Logo.jsx +40 -0
- package/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/template/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/template/src/client/components/Nav/MobileNav.jsx +107 -0
- package/template/src/client/components/NavLink.jsx +10 -0
- package/template/src/client/components/PageHeader.jsx +8 -0
- package/template/src/client/components/PostCard.jsx +19 -0
- package/template/src/client/components/Textarea.jsx +12 -0
- package/template/src/client/components/ThemeContext.jsx +5 -0
- package/template/src/client/contexts/ToastContext.jsx +94 -0
- package/template/src/client/layouts/AppLayout.jsx +60 -0
- package/template/src/client/layouts/AuthLayout.jsx +33 -0
- package/template/src/client/layouts/DocsLayout.jsx +60 -0
- package/template/src/client/layouts/RootLayout.jsx +25 -0
- package/template/src/client/pages/Auth.jsx +55 -0
- package/template/src/client/pages/Authentication.jsx +236 -0
- package/template/src/client/pages/Database.jsx +426 -0
- package/template/src/client/pages/Feed.jsx +34 -0
- package/template/src/client/pages/FeedPost.jsx +37 -0
- package/template/src/client/pages/GettingStarted.jsx +136 -0
- package/template/src/client/pages/Home.jsx +206 -0
- package/template/src/client/pages/PostForm.jsx +69 -0
- package/template/src/client/pages/Posts.jsx +59 -0
- package/template/src/client/pages/Profile.jsx +68 -0
- package/template/src/client/pages/Routing.jsx +207 -0
- package/template/src/client/pages/Testing.jsx +251 -0
- package/template/src/client/pages/Validation.jsx +210 -0
- package/template/src/controllers/AuthController.js +81 -0
- package/template/src/controllers/HomeController.js +17 -0
- package/template/src/controllers/PostController.js +86 -0
- package/template/src/controllers/ProfileController.js +66 -0
- package/template/src/helpers/api.js +24 -0
- package/template/src/main.jsx +9 -0
- package/template/src/middleware/auth.js +16 -0
- package/template/src/models/Post.js +63 -0
- package/template/src/models/User.js +42 -0
- package/template/src/routes/App.jsx +38 -0
- package/template/src/routes/api/auth.js +7 -0
- package/template/src/routes/api/posts.js +15 -0
- package/template/src/routes/api/profile.js +8 -0
- package/template/src/server/index.js +16 -0
- package/template/vite.config.js +18 -0
package/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @basicbenframework/create
|
|
5
|
+
*
|
|
6
|
+
* Scaffolds a new BasicBen project with the recommended structure.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @basicbenframework/create my-app
|
|
10
|
+
* npx @basicbenframework/create my-app --template minimal
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { mkdirSync, writeFileSync, copyFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
|
14
|
+
import { join, resolve, dirname, relative } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { execSync } from 'node:child_process'
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
19
|
+
const frameworkDir = resolve(__dirname, '..')
|
|
20
|
+
|
|
21
|
+
// ANSI colors
|
|
22
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`
|
|
23
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`
|
|
24
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`
|
|
25
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`
|
|
26
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`
|
|
27
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main entry point
|
|
31
|
+
*/
|
|
32
|
+
async function main() {
|
|
33
|
+
const args = process.argv.slice(2)
|
|
34
|
+
|
|
35
|
+
// Show help
|
|
36
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
37
|
+
showHelp()
|
|
38
|
+
process.exit(0)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get project name
|
|
42
|
+
const projectName = args[0]
|
|
43
|
+
|
|
44
|
+
if (!projectName || projectName.startsWith('-')) {
|
|
45
|
+
console.error(`\n${red('Error:')} Please provide a project name.\n`)
|
|
46
|
+
console.log(` ${cyan('npx @basicbenframework/create')} ${dim('<project-name>')}\n`)
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate project name
|
|
51
|
+
if (!/^[a-z0-9-_]+$/i.test(projectName)) {
|
|
52
|
+
console.error(`\n${red('Error:')} Project name can only contain letters, numbers, dashes, and underscores.\n`)
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check for --local flag
|
|
57
|
+
const useLocal = args.includes('--local')
|
|
58
|
+
|
|
59
|
+
const projectDir = resolve(process.cwd(), projectName)
|
|
60
|
+
|
|
61
|
+
// Check if directory exists
|
|
62
|
+
if (existsSync(projectDir)) {
|
|
63
|
+
console.error(`\n${red('Error:')} Directory "${projectName}" already exists.\n`)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log()
|
|
68
|
+
console.log(`${bold('Creating a new BasicBen app')} in ${cyan(projectDir)}`)
|
|
69
|
+
console.log()
|
|
70
|
+
|
|
71
|
+
// Create project directory
|
|
72
|
+
mkdirSync(projectDir, { recursive: true })
|
|
73
|
+
|
|
74
|
+
// Copy template files
|
|
75
|
+
const templateDir = join(__dirname, 'template')
|
|
76
|
+
copyDir(templateDir, projectDir)
|
|
77
|
+
|
|
78
|
+
// Determine basicben dependency
|
|
79
|
+
let basicbenDep = 'latest'
|
|
80
|
+
if (useLocal) {
|
|
81
|
+
const relativePath = relative(projectDir, frameworkDir)
|
|
82
|
+
basicbenDep = `file:${relativePath}`
|
|
83
|
+
console.log(`${yellow('Using local framework:')} ${relativePath}\n`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create package.json with project name
|
|
87
|
+
const pkg = {
|
|
88
|
+
name: projectName,
|
|
89
|
+
version: '0.1.0',
|
|
90
|
+
private: true,
|
|
91
|
+
type: 'module',
|
|
92
|
+
scripts: {
|
|
93
|
+
dev: 'basicben dev',
|
|
94
|
+
build: 'basicben build',
|
|
95
|
+
start: 'basicben start',
|
|
96
|
+
test: 'basicben test',
|
|
97
|
+
migrate: 'basicben migrate',
|
|
98
|
+
'migrate:rollback': 'basicben migrate:rollback',
|
|
99
|
+
'migrate:fresh': 'basicben migrate:fresh',
|
|
100
|
+
'migrate:status': 'basicben migrate:status',
|
|
101
|
+
'make:migration': 'basicben make:migration',
|
|
102
|
+
'make:controller': 'basicben make:controller',
|
|
103
|
+
'make:model': 'basicben make:model'
|
|
104
|
+
},
|
|
105
|
+
dependencies: {
|
|
106
|
+
'@basicbenframework/core': basicbenDep,
|
|
107
|
+
react: '^19.2.0',
|
|
108
|
+
'react-dom': '^19.2.0'
|
|
109
|
+
},
|
|
110
|
+
devDependencies: {
|
|
111
|
+
'@vitejs/plugin-react': '^5.1.4',
|
|
112
|
+
vite: '^7.3.1',
|
|
113
|
+
vitest: '^4.0.0'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
writeFileSync(
|
|
118
|
+
join(projectDir, 'package.json'),
|
|
119
|
+
JSON.stringify(pkg, null, 2) + '\n'
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Generate APP_KEY for .env
|
|
123
|
+
const appKey = generateAppKey()
|
|
124
|
+
const envContent = `# Application
|
|
125
|
+
APP_KEY=${appKey}
|
|
126
|
+
|
|
127
|
+
# Server Ports
|
|
128
|
+
PORT=3001 # API server
|
|
129
|
+
VITE_PORT=3000 # Frontend dev server
|
|
130
|
+
|
|
131
|
+
# Database (uncomment one)
|
|
132
|
+
# DATABASE_URL=./data.db
|
|
133
|
+
# DATABASE_URL=postgres://user:pass@localhost:5432/mydb
|
|
134
|
+
`
|
|
135
|
+
writeFileSync(join(projectDir, '.env'), envContent)
|
|
136
|
+
|
|
137
|
+
console.log(`${green('✓')} Project created successfully!\n`)
|
|
138
|
+
|
|
139
|
+
// Install dependencies prompt
|
|
140
|
+
console.log(`${bold('Next steps:')}\n`)
|
|
141
|
+
console.log(` ${cyan('cd')} ${projectName}`)
|
|
142
|
+
console.log(` ${cyan('npm install')}`)
|
|
143
|
+
console.log(` ${cyan('npm run dev')}\n`)
|
|
144
|
+
|
|
145
|
+
console.log(`${dim('This will start the development server at http://localhost:3000')}\n`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Show help message
|
|
150
|
+
*/
|
|
151
|
+
function showHelp() {
|
|
152
|
+
console.log(`
|
|
153
|
+
${bold('@basicbenframework/create')} - Create a new BasicBen project
|
|
154
|
+
|
|
155
|
+
${bold('Usage:')}
|
|
156
|
+
npx @basicbenframework/create ${dim('<project-name>')} [options]
|
|
157
|
+
|
|
158
|
+
${bold('Options:')}
|
|
159
|
+
--local Use local framework (for development)
|
|
160
|
+
-h, --help Show this help message
|
|
161
|
+
|
|
162
|
+
${bold('Examples:')}
|
|
163
|
+
npx @basicbenframework/create my-app
|
|
164
|
+
npx @basicbenframework/create my-app --local ${dim('# Use local framework')}
|
|
165
|
+
`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Copy directory recursively
|
|
170
|
+
*/
|
|
171
|
+
function copyDir(src, dest) {
|
|
172
|
+
if (!existsSync(src)) return
|
|
173
|
+
|
|
174
|
+
mkdirSync(dest, { recursive: true })
|
|
175
|
+
const entries = readdirSync(src, { withFileTypes: true })
|
|
176
|
+
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
const srcPath = join(src, entry.name)
|
|
179
|
+
const destPath = join(dest, entry.name)
|
|
180
|
+
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
copyDir(srcPath, destPath)
|
|
183
|
+
} else {
|
|
184
|
+
copyFileSync(srcPath, destPath)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generate a random APP_KEY
|
|
191
|
+
*/
|
|
192
|
+
function generateAppKey() {
|
|
193
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
194
|
+
let key = ''
|
|
195
|
+
for (let i = 0; i < 32; i++) {
|
|
196
|
+
key += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
197
|
+
}
|
|
198
|
+
return key
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Run
|
|
202
|
+
main().catch((err) => {
|
|
203
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
204
|
+
process.exit(1)
|
|
205
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@basicbenframework/create",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new BasicBen application",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-basicben": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"basicben",
|
|
15
|
+
"react",
|
|
16
|
+
"framework",
|
|
17
|
+
"fullstack",
|
|
18
|
+
"create",
|
|
19
|
+
"scaffold"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/BasicBenFramework/basicben-framework"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24.14.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
APP_KEY=your-secret-key-here
|
|
3
|
+
|
|
4
|
+
# Server Ports
|
|
5
|
+
PORT=3001 # API server
|
|
6
|
+
VITE_PORT=3000 # Frontend dev server
|
|
7
|
+
|
|
8
|
+
# Database - uncomment ONE of the following:
|
|
9
|
+
|
|
10
|
+
# SQLite (default)
|
|
11
|
+
DATABASE_URL=./database.sqlite
|
|
12
|
+
|
|
13
|
+
# PostgreSQL
|
|
14
|
+
# DATABASE_URL=postgres://user:pass@localhost:5432/mydb
|
|
15
|
+
|
|
16
|
+
# Turso (edge SQLite)
|
|
17
|
+
# TURSO_URL=libsql://your-db-name.turso.io
|
|
18
|
+
# TURSO_AUTH_TOKEN=your-auth-token
|
|
19
|
+
|
|
20
|
+
# PlanetScale (serverless MySQL)
|
|
21
|
+
# DATABASE_URL=mysql://user:pass@host/database?ssl={"rejectUnauthorized":true}
|
|
22
|
+
|
|
23
|
+
# Neon (serverless Postgres)
|
|
24
|
+
# DATABASE_URL=postgres://user:pass@ep-cool-name.us-east-2.aws.neon.tech/mydb?sslmode=require
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# My BasicBen App
|
|
2
|
+
|
|
3
|
+
Built with [BasicBen](https://github.com/BasicBenFramework/basicben-framework) — a full-stack React framework with zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Open [http://localhost:3000](http://localhost:3000) to view your app.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run dev # Start development server
|
|
18
|
+
npm run build # Build for production
|
|
19
|
+
npm run start # Run production server
|
|
20
|
+
npm run test # Run tests
|
|
21
|
+
|
|
22
|
+
npm run migrate # Run database migrations
|
|
23
|
+
npm run migrate:fresh # Reset and re-run all migrations
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Project Structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/
|
|
30
|
+
├── main.jsx # React entry point
|
|
31
|
+
├── routes/
|
|
32
|
+
│ ├── App.jsx # Client routes
|
|
33
|
+
│ └── api/ # API routes (auto-loaded)
|
|
34
|
+
├── controllers/ # Business logic
|
|
35
|
+
├── models/ # Database queries
|
|
36
|
+
├── middleware/ # Route middleware
|
|
37
|
+
├── helpers/ # Utility functions
|
|
38
|
+
└── client/
|
|
39
|
+
├── layouts/ # Layout components
|
|
40
|
+
├── pages/ # Page components
|
|
41
|
+
└── components/ # Reusable UI
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Scaffolding
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx basicben make:controller User
|
|
48
|
+
npx basicben make:model User
|
|
49
|
+
npx basicben make:route users
|
|
50
|
+
npx basicben make:migration create_users
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Edit `basicben.config.js` to customize server settings, CORS, database, and more.
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
Full documentation: [github.com/BasicBenFramework/basicben-framework](https://github.com/BasicBenFramework/basicben-framework)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasicBen Configuration
|
|
3
|
+
*
|
|
4
|
+
* See documentation for all available options.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
// Server options
|
|
9
|
+
port: 3001,
|
|
10
|
+
|
|
11
|
+
// CORS configuration
|
|
12
|
+
cors: {
|
|
13
|
+
origin: '*',
|
|
14
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
15
|
+
credentials: true
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// Body parser options
|
|
19
|
+
bodyParser: {
|
|
20
|
+
limit: '1mb'
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Static file serving
|
|
24
|
+
static: {
|
|
25
|
+
dir: 'public'
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Database configuration
|
|
29
|
+
// db: {
|
|
30
|
+
// driver: 'sqlite',
|
|
31
|
+
// url: process.env.DATABASE_URL || './data.db'
|
|
32
|
+
// }
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>BasicBen App</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script>
|
|
9
|
+
tailwind.config = {
|
|
10
|
+
darkMode: 'class',
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {
|
|
13
|
+
colors: {
|
|
14
|
+
dark: {
|
|
15
|
+
900: '#0f0f0f',
|
|
16
|
+
800: '#1a1a1a',
|
|
17
|
+
700: '#222222',
|
|
18
|
+
600: '#333333'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
animation: {
|
|
22
|
+
'slide-in': 'slideIn 0.3s ease-out',
|
|
23
|
+
'slide-out': 'slideOut 0.2s ease-in',
|
|
24
|
+
'fade-in': 'fadeIn 0.2s ease-out',
|
|
25
|
+
'fade-out': 'fadeOut 0.15s ease-in',
|
|
26
|
+
},
|
|
27
|
+
keyframes: {
|
|
28
|
+
slideIn: {
|
|
29
|
+
'0%': { transform: 'translateX(100%)', opacity: '0' },
|
|
30
|
+
'100%': { transform: 'translateX(0)', opacity: '1' }
|
|
31
|
+
},
|
|
32
|
+
slideOut: {
|
|
33
|
+
'0%': { transform: 'translateX(0)', opacity: '1' },
|
|
34
|
+
'100%': { transform: 'translateX(100%)', opacity: '0' }
|
|
35
|
+
},
|
|
36
|
+
fadeIn: {
|
|
37
|
+
'0%': { opacity: '0' },
|
|
38
|
+
'100%': { opacity: '1' }
|
|
39
|
+
},
|
|
40
|
+
fadeOut: {
|
|
41
|
+
'0%': { opacity: '1' },
|
|
42
|
+
'100%': { opacity: '0' }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
</head>
|
|
50
|
+
<body class="bg-dark-900 text-white">
|
|
51
|
+
<div id="root"></div>
|
|
52
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const up = async (db) => {
|
|
2
|
+
await db.exec(`
|
|
3
|
+
CREATE TABLE users (
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
name TEXT NOT NULL,
|
|
6
|
+
email TEXT UNIQUE NOT NULL,
|
|
7
|
+
password TEXT NOT NULL,
|
|
8
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
9
|
+
)
|
|
10
|
+
`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const down = async (db) => {
|
|
14
|
+
await db.exec('DROP TABLE IF EXISTS users')
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const up = async (db) => {
|
|
2
|
+
await db.exec(`
|
|
3
|
+
CREATE TABLE posts (
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
user_id INTEGER NOT NULL,
|
|
6
|
+
title TEXT NOT NULL,
|
|
7
|
+
content TEXT NOT NULL,
|
|
8
|
+
published BOOLEAN DEFAULT 0,
|
|
9
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
11
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
12
|
+
)
|
|
13
|
+
`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const down = async (db) => {
|
|
17
|
+
await db.exec('DROP TABLE IF EXISTS posts')
|
|
18
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users seeder
|
|
3
|
+
* Creates sample users for development/testing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db } from 'basicben'
|
|
7
|
+
import { hashPassword } from 'basicben/auth'
|
|
8
|
+
|
|
9
|
+
export async function seed() {
|
|
10
|
+
const password = await hashPassword('password123')
|
|
11
|
+
|
|
12
|
+
// Create admin user
|
|
13
|
+
await (await db.table('users'))
|
|
14
|
+
.insert({
|
|
15
|
+
name: 'Admin User',
|
|
16
|
+
email: 'admin@example.com',
|
|
17
|
+
password
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Create test user
|
|
21
|
+
await (await db.table('users'))
|
|
22
|
+
.insert({
|
|
23
|
+
name: 'Test User',
|
|
24
|
+
email: 'test@example.com',
|
|
25
|
+
password
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
console.log('Seeded 2 users (password: password123)')
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts seeder
|
|
3
|
+
* Creates sample blog posts for development/testing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db } from 'basicben'
|
|
7
|
+
|
|
8
|
+
export async function seed() {
|
|
9
|
+
// Get the first user (admin)
|
|
10
|
+
const user = await (await db.table('users')).first()
|
|
11
|
+
|
|
12
|
+
if (!user) {
|
|
13
|
+
console.log('No users found. Run users seed first.')
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const posts = [
|
|
18
|
+
{
|
|
19
|
+
user_id: user.id,
|
|
20
|
+
title: 'Welcome to BasicBen',
|
|
21
|
+
content: 'This is your first blog post. BasicBen makes it easy to build full-stack React applications with minimal dependencies.',
|
|
22
|
+
published: 1
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
user_id: user.id,
|
|
26
|
+
title: 'Getting Started with Migrations',
|
|
27
|
+
content: 'Migrations help you version control your database schema. Run `basicben make:migration` to create a new migration.',
|
|
28
|
+
published: 1
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
user_id: user.id,
|
|
32
|
+
title: 'Draft Post Example',
|
|
33
|
+
content: 'This is a draft post that is not yet published.',
|
|
34
|
+
published: 0
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
for (const post of posts) {
|
|
39
|
+
await (await db.table('posts')).insert(post)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Seeded ${posts.length} posts`)
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function Alert({ type = 'error', children }) {
|
|
2
|
+
const styles = {
|
|
3
|
+
error: 'text-red-500 bg-red-500/10',
|
|
4
|
+
success: 'text-emerald-500 bg-emerald-500/10'
|
|
5
|
+
}
|
|
6
|
+
return (
|
|
7
|
+
<p className={`text-xs p-2 rounded-lg ${styles[type]}`}>
|
|
8
|
+
{children}
|
|
9
|
+
</p>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useTheme } from './ThemeContext'
|
|
2
|
+
|
|
3
|
+
export function Avatar({ name, size = 'md' }) {
|
|
4
|
+
const { dark } = useTheme()
|
|
5
|
+
const sizes = { sm: 'w-6 h-6 text-xs', md: 'w-8 h-8 text-sm' }
|
|
6
|
+
return (
|
|
7
|
+
<div className={`${sizes[size]} rounded-full flex items-center justify-center font-medium ${dark ? 'bg-white text-black' : 'bg-black text-white'}`}>
|
|
8
|
+
{name[0].toUpperCase()}
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useTheme } from './ThemeContext'
|
|
2
|
+
|
|
3
|
+
export function Button({ variant = 'primary', children, className = '', ...props }) {
|
|
4
|
+
const { t } = useTheme()
|
|
5
|
+
const styles = {
|
|
6
|
+
primary: `${t.btn} ${t.btnHover}`,
|
|
7
|
+
secondary: t.btnSecondary,
|
|
8
|
+
danger: 'bg-red-500/10 text-red-500 hover:bg-red-500/20',
|
|
9
|
+
ghost: `${t.muted} hover:opacity-70`
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
className={`text-sm font-medium rounded-full px-4 py-2 transition disabled:opacity-50 ${styles[variant]} ${className}`}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</button>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useTheme } from './ThemeContext'
|
|
2
|
+
|
|
3
|
+
export function Input({ type = 'text', className = '', ...props }) {
|
|
4
|
+
const { t } = useTheme()
|
|
5
|
+
return (
|
|
6
|
+
<input
|
|
7
|
+
type={type}
|
|
8
|
+
className={`w-full px-3 py-2 text-sm rounded-lg ${t.card} border ${t.border} focus:outline-none ${className}`}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function Logo({ className = "w-6 h-6" }) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
viewBox="0 0 32 32"
|
|
5
|
+
fill="none"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
className={className}
|
|
8
|
+
>
|
|
9
|
+
{/* Outer rounded square */}
|
|
10
|
+
<rect
|
|
11
|
+
x="2"
|
|
12
|
+
y="2"
|
|
13
|
+
width="28"
|
|
14
|
+
height="28"
|
|
15
|
+
rx="6"
|
|
16
|
+
fill="currentColor"
|
|
17
|
+
fillOpacity="0.1"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="2"
|
|
20
|
+
/>
|
|
21
|
+
{/* Stylized "B" made of two brackets and a vertical line */}
|
|
22
|
+
<path
|
|
23
|
+
d="M10 8C10 8 8 8 8 10V14C8 16 10 16 10 16C10 16 8 16 8 18V22C8 24 10 24 10 24"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
strokeWidth="2"
|
|
26
|
+
strokeLinecap="round"
|
|
27
|
+
strokeLinejoin="round"
|
|
28
|
+
/>
|
|
29
|
+
<path
|
|
30
|
+
d="M22 8C22 8 24 8 24 10V14C24 16 22 16 22 16C22 16 24 16 24 18V22C24 24 22 24 22 24"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
strokeWidth="2"
|
|
33
|
+
strokeLinecap="round"
|
|
34
|
+
strokeLinejoin="round"
|
|
35
|
+
/>
|
|
36
|
+
{/* Center dot */}
|
|
37
|
+
<circle cx="16" cy="16" r="3" fill="currentColor" />
|
|
38
|
+
</svg>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useTheme } from '../ThemeContext'
|
|
2
|
+
|
|
3
|
+
export function DarkModeToggle({ dark, setDark }) {
|
|
4
|
+
const { t } = useTheme()
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<button
|
|
8
|
+
onClick={() => setDark(!dark)}
|
|
9
|
+
className={`p-2 rounded-lg ${t.card} transition`}
|
|
10
|
+
aria-label={dark ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
11
|
+
>
|
|
12
|
+
{dark ? (
|
|
13
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
14
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
15
|
+
</svg>
|
|
16
|
+
) : (
|
|
17
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
18
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
|
19
|
+
</svg>
|
|
20
|
+
)}
|
|
21
|
+
</button>
|
|
22
|
+
)
|
|
23
|
+
}
|