@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.
Files changed (61) hide show
  1. package/index.js +205 -0
  2. package/package.json +30 -0
  3. package/template/.env.example +24 -0
  4. package/template/README.md +59 -0
  5. package/template/basicben.config.js +33 -0
  6. package/template/index.html +54 -0
  7. package/template/migrations/001_create_users.js +15 -0
  8. package/template/migrations/002_create_posts.js +18 -0
  9. package/template/public/.gitkeep +0 -0
  10. package/template/seeds/01_users.js +29 -0
  11. package/template/seeds/02_posts.js +43 -0
  12. package/template/src/client/components/Alert.jsx +11 -0
  13. package/template/src/client/components/Avatar.jsx +11 -0
  14. package/template/src/client/components/BackLink.jsx +10 -0
  15. package/template/src/client/components/Button.jsx +19 -0
  16. package/template/src/client/components/Card.jsx +10 -0
  17. package/template/src/client/components/Empty.jsx +6 -0
  18. package/template/src/client/components/Input.jsx +12 -0
  19. package/template/src/client/components/Loading.jsx +6 -0
  20. package/template/src/client/components/Logo.jsx +40 -0
  21. package/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
  22. package/template/src/client/components/Nav/DesktopNav.jsx +32 -0
  23. package/template/src/client/components/Nav/MobileNav.jsx +107 -0
  24. package/template/src/client/components/NavLink.jsx +10 -0
  25. package/template/src/client/components/PageHeader.jsx +8 -0
  26. package/template/src/client/components/PostCard.jsx +19 -0
  27. package/template/src/client/components/Textarea.jsx +12 -0
  28. package/template/src/client/components/ThemeContext.jsx +5 -0
  29. package/template/src/client/contexts/ToastContext.jsx +94 -0
  30. package/template/src/client/layouts/AppLayout.jsx +60 -0
  31. package/template/src/client/layouts/AuthLayout.jsx +33 -0
  32. package/template/src/client/layouts/DocsLayout.jsx +60 -0
  33. package/template/src/client/layouts/RootLayout.jsx +25 -0
  34. package/template/src/client/pages/Auth.jsx +55 -0
  35. package/template/src/client/pages/Authentication.jsx +236 -0
  36. package/template/src/client/pages/Database.jsx +426 -0
  37. package/template/src/client/pages/Feed.jsx +34 -0
  38. package/template/src/client/pages/FeedPost.jsx +37 -0
  39. package/template/src/client/pages/GettingStarted.jsx +136 -0
  40. package/template/src/client/pages/Home.jsx +206 -0
  41. package/template/src/client/pages/PostForm.jsx +69 -0
  42. package/template/src/client/pages/Posts.jsx +59 -0
  43. package/template/src/client/pages/Profile.jsx +68 -0
  44. package/template/src/client/pages/Routing.jsx +207 -0
  45. package/template/src/client/pages/Testing.jsx +251 -0
  46. package/template/src/client/pages/Validation.jsx +210 -0
  47. package/template/src/controllers/AuthController.js +81 -0
  48. package/template/src/controllers/HomeController.js +17 -0
  49. package/template/src/controllers/PostController.js +86 -0
  50. package/template/src/controllers/ProfileController.js +66 -0
  51. package/template/src/helpers/api.js +24 -0
  52. package/template/src/main.jsx +9 -0
  53. package/template/src/middleware/auth.js +16 -0
  54. package/template/src/models/Post.js +63 -0
  55. package/template/src/models/User.js +42 -0
  56. package/template/src/routes/App.jsx +38 -0
  57. package/template/src/routes/api/auth.js +7 -0
  58. package/template/src/routes/api/posts.js +15 -0
  59. package/template/src/routes/api/profile.js +8 -0
  60. package/template/src/server/index.js +16 -0
  61. 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,10 @@
1
+ import { useTheme } from './ThemeContext'
2
+
3
+ export function BackLink({ onClick, children }) {
4
+ const { t } = useTheme()
5
+ return (
6
+ <button onClick={onClick} className={`text-sm ${t.muted} mb-4 hover:underline`}>
7
+ &larr; {children}
8
+ </button>
9
+ )
10
+ }
@@ -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,10 @@
1
+ import { useTheme } from './ThemeContext'
2
+
3
+ export function Card({ children, className = '' }) {
4
+ const { t } = useTheme()
5
+ return (
6
+ <div className={`p-4 rounded-xl ${t.card} border ${t.border} ${className}`}>
7
+ {children}
8
+ </div>
9
+ )
10
+ }
@@ -0,0 +1,6 @@
1
+ import { useTheme } from './ThemeContext'
2
+
3
+ export function Empty({ children }) {
4
+ const { t } = useTheme()
5
+ return <p className={`text-center ${t.muted} py-12`}>{children}</p>
6
+ }
@@ -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,6 @@
1
+ import { useTheme } from './ThemeContext'
2
+
3
+ export function Loading() {
4
+ const { t } = useTheme()
5
+ return <div className={`text-center ${t.muted} py-12`}>Loading...</div>
6
+ }
@@ -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
+ }