canx-starter-admin 1.0.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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Canx Admin Starter
2
+
3
+ 🛠️ Admin dashboard template for CanxJS with CRUD, data tables, and charts.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Dashboard** - Stats cards and activity feed
8
+ - ✅ **User Management** - Full CRUD with data tables
9
+ - ✅ **Authentication** - Admin login system
10
+ - ✅ **Tailwind CSS** - Modern, responsive design
11
+ - ✅ **Queue Dashboard** - Built-in at `/canx-queue`
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Clone the starter
17
+ bunx degit chandafa/canx-starters/admin my-admin
18
+
19
+ # Install dependencies
20
+ cd my-admin
21
+ bun install
22
+
23
+ # Start development server
24
+ bun run dev
25
+ ```
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ src/
31
+ ├── controllers/
32
+ │ ├── AuthController.ts # Login/logout
33
+ │ ├── DashboardController.ts # Main dashboard
34
+ │ └── UserController.ts # User CRUD
35
+ ├── views/
36
+ │ └── layouts/
37
+ │ └── admin.ts # Admin layout with sidebar
38
+ ├── css/
39
+ │ └── input.css # Tailwind input
40
+ ├── routes.ts # Route definitions
41
+ └── app.ts # Application entry
42
+ ```
43
+
44
+ ## Available Routes
45
+
46
+ | Route | Description |
47
+ | --------------- | --------------------- |
48
+ | `/` | Dashboard with stats |
49
+ | `/users` | User management table |
50
+ | `/users/create` | Create new user form |
51
+ | `/auth/login` | Admin login |
52
+ | `/auth/logout` | Logout |
53
+ | `/canx-queue` | Queue Dashboard |
54
+
55
+ ## Available Scripts
56
+
57
+ | Command | Description |
58
+ | ------------------- | ------------------------ |
59
+ | `bun run dev` | Start development server |
60
+ | `bun run build` | Build for production |
61
+ | `bun run css:dev` | Watch Tailwind CSS |
62
+ | `bun run css:build` | Build minified CSS |
63
+
64
+ ## Extending
65
+
66
+ To add a new resource (e.g., Products):
67
+
68
+ 1. Create `ProductController.ts` in `controllers/`
69
+ 2. Add route in `routes.ts`:
70
+ ```ts
71
+ router.controller("/products", ProductController);
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "canx-starter-admin",
3
+ "version": "1.0.0",
4
+ "description": "Admin Dashboard Starter Kit for CanxJS - CRUD, Data Tables, Charts",
5
+ "author": "CanxJS Team",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "canxjs",
9
+ "starter",
10
+ "admin",
11
+ "dashboard",
12
+ "crud",
13
+ "bun"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/chandafa/canx-starters.git",
18
+ "directory": "admin"
19
+ },
20
+ "scripts": {
21
+ "dev": "bun --watch src/app.ts",
22
+ "build": "bun build src/app.ts --outdir dist",
23
+ "start": "bun run dist/app.js",
24
+ "css:dev": "tailwindcss -i ./src/css/input.css -o ./public/css/admin.css --watch",
25
+ "css:build": "tailwindcss -i ./src/css/input.css -o ./public/css/admin.css --minify"
26
+ },
27
+ "dependencies": {
28
+ "canxjs": "^1.2.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "bun-types": "latest",
33
+ "tailwindcss": "^3.4.0",
34
+ "autoprefixer": "^10.4.0",
35
+ "postcss": "^8.4.0",
36
+ "typescript": "^5.3.0"
37
+ },
38
+ "engines": {
39
+ "bun": ">=1.0.0"
40
+ }
41
+ }
package/src/app.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { createApp, QueueController } from 'canxjs';
2
+ import { webRoutes } from './routes';
3
+
4
+ const app = createApp({
5
+ port: 3000,
6
+ staticDir: './public',
7
+ });
8
+
9
+ // Register routes
10
+ app.routes(webRoutes);
11
+
12
+ // Mount Queue Dashboard
13
+ app.routes((router) => {
14
+ router.controller('/canx-queue', QueueController);
15
+ });
16
+
17
+ console.log('🚀 Admin Dashboard running at http://localhost:3000');
18
+ app.listen();
@@ -0,0 +1,62 @@
1
+ import { BaseController, Controller, Get, Post } from 'canxjs';
2
+ import { renderPage, jsx } from 'canxjs';
3
+
4
+ @Controller('/auth')
5
+ export class AuthController extends BaseController {
6
+ @Get('/login')
7
+ login() {
8
+ const html = renderPage(
9
+ jsx('div', { className: 'min-h-screen bg-gray-100 flex items-center justify-center p-4' },
10
+ jsx('div', { className: 'bg-white rounded-2xl shadow-xl p-8 w-full max-w-md' },
11
+ jsx('div', { className: 'text-center mb-8' },
12
+ jsx('h1', { className: 'text-2xl font-bold text-gray-900' }, 'Admin Login'),
13
+ jsx('p', { className: 'text-gray-500 mt-2' }, 'Sign in to access the dashboard')
14
+ ),
15
+ jsx('form', { action: '/auth/login', method: 'POST', className: 'space-y-4' },
16
+ jsx('div', {},
17
+ jsx('label', { className: 'block text-sm font-medium text-gray-700 mb-2' }, 'Email'),
18
+ jsx('input', {
19
+ type: 'email',
20
+ name: 'email',
21
+ className: 'w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500',
22
+ placeholder: 'admin@example.com',
23
+ required: true
24
+ })
25
+ ),
26
+ jsx('div', {},
27
+ jsx('label', { className: 'block text-sm font-medium text-gray-700 mb-2' }, 'Password'),
28
+ jsx('input', {
29
+ type: 'password',
30
+ name: 'password',
31
+ className: 'w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500',
32
+ placeholder: '••••••••',
33
+ required: true
34
+ })
35
+ ),
36
+ jsx('button', {
37
+ type: 'submit',
38
+ className: 'w-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium'
39
+ }, 'Sign In')
40
+ )
41
+ )
42
+ ),
43
+ { title: 'Login - Admin' }
44
+ );
45
+
46
+ return this.response.html(html);
47
+ }
48
+
49
+ @Post('/login')
50
+ async authenticate() {
51
+ const body = await this.request.body();
52
+ // In a real app, validate credentials
53
+ console.log('Login attempt:', body);
54
+ return this.response.redirect('/');
55
+ }
56
+
57
+ @Get('/logout')
58
+ logout() {
59
+ // In a real app, clear session
60
+ return this.response.redirect('/auth/login');
61
+ }
62
+ }
@@ -0,0 +1,110 @@
1
+ import { BaseController, Controller, Get } from 'canxjs';
2
+ import { adminLayout } from '../views/layouts/admin';
3
+
4
+ @Controller('/')
5
+ export class DashboardController extends BaseController {
6
+ @Get('/')
7
+ index() {
8
+ const stats = {
9
+ users: 1234,
10
+ orders: 567,
11
+ revenue: '$45,678',
12
+ growth: '+12.5%'
13
+ };
14
+
15
+ const recentActivity = [
16
+ { id: 1, action: 'New user registered', user: 'john@example.com', time: '5 min ago' },
17
+ { id: 2, action: 'Order completed', user: 'jane@example.com', time: '12 min ago' },
18
+ { id: 3, action: 'Payment received', user: 'bob@example.com', time: '1 hour ago' },
19
+ ];
20
+
21
+ const html = adminLayout({
22
+ title: 'Dashboard',
23
+ content: `
24
+ <div class="mb-8">
25
+ <h1 class="text-3xl font-bold text-gray-900">Dashboard</h1>
26
+ <p class="text-gray-500">Welcome back, Admin!</p>
27
+ </div>
28
+
29
+ <!-- Stats Grid -->
30
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
31
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
32
+ <div class="flex items-center justify-between">
33
+ <div>
34
+ <p class="text-sm font-medium text-gray-500">Total Users</p>
35
+ <p class="text-2xl font-bold text-gray-900">${stats.users}</p>
36
+ </div>
37
+ <div class="p-3 bg-blue-100 rounded-lg">
38
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
39
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
40
+ </svg>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
46
+ <div class="flex items-center justify-between">
47
+ <div>
48
+ <p class="text-sm font-medium text-gray-500">Total Orders</p>
49
+ <p class="text-2xl font-bold text-gray-900">${stats.orders}</p>
50
+ </div>
51
+ <div class="p-3 bg-green-100 rounded-lg">
52
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path>
54
+ </svg>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
60
+ <div class="flex items-center justify-between">
61
+ <div>
62
+ <p class="text-sm font-medium text-gray-500">Revenue</p>
63
+ <p class="text-2xl font-bold text-gray-900">${stats.revenue}</p>
64
+ </div>
65
+ <div class="p-3 bg-purple-100 rounded-lg">
66
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
67
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
68
+ </svg>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
74
+ <div class="flex items-center justify-between">
75
+ <div>
76
+ <p class="text-sm font-medium text-gray-500">Growth</p>
77
+ <p class="text-2xl font-bold text-green-600">${stats.growth}</p>
78
+ </div>
79
+ <div class="p-3 bg-emerald-100 rounded-lg">
80
+ <svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
82
+ </svg>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Recent Activity -->
89
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100">
90
+ <div class="p-6 border-b border-gray-100">
91
+ <h2 class="text-lg font-semibold text-gray-900">Recent Activity</h2>
92
+ </div>
93
+ <div class="divide-y divide-gray-100">
94
+ ${recentActivity.map(item => `
95
+ <div class="p-4 flex items-center justify-between hover:bg-gray-50">
96
+ <div>
97
+ <p class="font-medium text-gray-900">${item.action}</p>
98
+ <p class="text-sm text-gray-500">${item.user}</p>
99
+ </div>
100
+ <span class="text-sm text-gray-400">${item.time}</span>
101
+ </div>
102
+ `).join('')}
103
+ </div>
104
+ </div>
105
+ `
106
+ });
107
+
108
+ return this.response.html(html);
109
+ }
110
+ }
@@ -0,0 +1,185 @@
1
+ import { BaseController, Controller, Get, Post } from 'canxjs';
2
+ import { adminLayout } from '../views/layouts/admin';
3
+
4
+ // Mock users data
5
+ const users = [
6
+ { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'Active', createdAt: '2024-01-15' },
7
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'Editor', status: 'Active', createdAt: '2024-01-10' },
8
+ { id: 3, name: 'Bob Wilson', email: 'bob@example.com', role: 'User', status: 'Inactive', createdAt: '2024-01-05' },
9
+ { id: 4, name: 'Alice Brown', email: 'alice@example.com', role: 'User', status: 'Active', createdAt: '2024-01-02' },
10
+ { id: 5, name: 'Charlie Davis', email: 'charlie@example.com', role: 'Editor', status: 'Active', createdAt: '2024-01-01' },
11
+ ];
12
+
13
+ @Controller('/users')
14
+ export class UserController extends BaseController {
15
+ @Get('/')
16
+ index() {
17
+ const html = adminLayout({
18
+ title: 'Users',
19
+ content: `
20
+ <div class="flex items-center justify-between mb-8">
21
+ <div>
22
+ <h1 class="text-3xl font-bold text-gray-900">Users</h1>
23
+ <p class="text-gray-500">Manage your users and their roles</p>
24
+ </div>
25
+ <a href="/users/create" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2">
26
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
27
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
28
+ </svg>
29
+ Add User
30
+ </a>
31
+ </div>
32
+
33
+ <!-- Data Table -->
34
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
35
+ <div class="p-4 border-b border-gray-100 flex items-center justify-between">
36
+ <div class="relative">
37
+ <input type="text" placeholder="Search users..." class="pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-64">
38
+ <svg class="w-5 h-5 text-gray-400 absolute left-3 top-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
39
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
40
+ </svg>
41
+ </div>
42
+ <div class="flex items-center gap-2">
43
+ <select class="px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
44
+ <option>All Roles</option>
45
+ <option>Admin</option>
46
+ <option>Editor</option>
47
+ <option>User</option>
48
+ </select>
49
+ </div>
50
+ </div>
51
+
52
+ <table class="w-full">
53
+ <thead class="bg-gray-50">
54
+ <tr>
55
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User</th>
56
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
57
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
58
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>
59
+ <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
60
+ </tr>
61
+ </thead>
62
+ <tbody class="divide-y divide-gray-100">
63
+ ${users.map(user => `
64
+ <tr class="hover:bg-gray-50">
65
+ <td class="px-6 py-4 whitespace-nowrap">
66
+ <div class="flex items-center">
67
+ <div class="w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-medium">
68
+ ${user.name.charAt(0)}
69
+ </div>
70
+ <div class="ml-3">
71
+ <p class="font-medium text-gray-900">${user.name}</p>
72
+ <p class="text-sm text-gray-500">${user.email}</p>
73
+ </div>
74
+ </div>
75
+ </td>
76
+ <td class="px-6 py-4 whitespace-nowrap">
77
+ <span class="px-2 py-1 text-xs font-medium rounded-full ${
78
+ user.role === 'Admin' ? 'bg-purple-100 text-purple-700' :
79
+ user.role === 'Editor' ? 'bg-blue-100 text-blue-700' :
80
+ 'bg-gray-100 text-gray-700'
81
+ }">${user.role}</span>
82
+ </td>
83
+ <td class="px-6 py-4 whitespace-nowrap">
84
+ <span class="flex items-center gap-1.5">
85
+ <span class="w-2 h-2 rounded-full ${user.status === 'Active' ? 'bg-green-500' : 'bg-gray-400'}"></span>
86
+ <span class="text-sm text-gray-600">${user.status}</span>
87
+ </span>
88
+ </td>
89
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${user.createdAt}</td>
90
+ <td class="px-6 py-4 whitespace-nowrap text-right">
91
+ <div class="flex items-center justify-end gap-2">
92
+ <a href="/users/${user.id}/edit" class="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors">
93
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
94
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
95
+ </svg>
96
+ </a>
97
+ <button class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors">
98
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
100
+ </svg>
101
+ </button>
102
+ </div>
103
+ </td>
104
+ </tr>
105
+ `).join('')}
106
+ </tbody>
107
+ </table>
108
+
109
+ <!-- Pagination -->
110
+ <div class="px-6 py-4 border-t border-gray-100 flex items-center justify-between">
111
+ <p class="text-sm text-gray-500">Showing 1 to 5 of 5 results</p>
112
+ <div class="flex items-center gap-1">
113
+ <button class="px-3 py-1 text-sm text-gray-500 hover:bg-gray-100 rounded-lg">Previous</button>
114
+ <button class="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg">1</button>
115
+ <button class="px-3 py-1 text-sm text-gray-500 hover:bg-gray-100 rounded-lg">Next</button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ `
120
+ });
121
+
122
+ return this.response.html(html);
123
+ }
124
+
125
+ @Get('/create')
126
+ create() {
127
+ const html = adminLayout({
128
+ title: 'Add User',
129
+ content: `
130
+ <div class="mb-8">
131
+ <a href="/users" class="text-blue-600 hover:text-blue-700 flex items-center gap-1 text-sm mb-2">
132
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
133
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
134
+ </svg>
135
+ Back to Users
136
+ </a>
137
+ <h1 class="text-3xl font-bold text-gray-900">Add New User</h1>
138
+ </div>
139
+
140
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 max-w-2xl">
141
+ <form action="/users" method="POST" class="space-y-6">
142
+ <div>
143
+ <label class="block text-sm font-medium text-gray-700 mb-2">Full Name</label>
144
+ <input type="text" name="name" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
145
+ </div>
146
+
147
+ <div>
148
+ <label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
149
+ <input type="email" name="email" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
150
+ </div>
151
+
152
+ <div>
153
+ <label class="block text-sm font-medium text-gray-700 mb-2">Role</label>
154
+ <select name="role" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
155
+ <option value="User">User</option>
156
+ <option value="Editor">Editor</option>
157
+ <option value="Admin">Admin</option>
158
+ </select>
159
+ </div>
160
+
161
+ <div>
162
+ <label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
163
+ <input type="password" name="password" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
164
+ </div>
165
+
166
+ <div class="flex items-center justify-end gap-4 pt-4 border-t border-gray-100">
167
+ <a href="/users" class="px-4 py-2 text-gray-600 hover:text-gray-800">Cancel</a>
168
+ <button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Create User</button>
169
+ </div>
170
+ </form>
171
+ </div>
172
+ `
173
+ });
174
+
175
+ return this.response.html(html);
176
+ }
177
+
178
+ @Post('/')
179
+ async store() {
180
+ const body = await this.request.body();
181
+ // In a real app, save to database
182
+ console.log('Creating user:', body);
183
+ return this.response.redirect('/users');
184
+ }
185
+ }
@@ -0,0 +1,8 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Custom admin styles */
6
+ body {
7
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
8
+ }
package/src/routes.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { RouterInstance } from 'canxjs';
2
+ import { DashboardController } from './controllers/DashboardController';
3
+ import { UserController } from './controllers/UserController';
4
+ import { AuthController } from './controllers/AuthController';
5
+
6
+ export function webRoutes(router: RouterInstance) {
7
+ // Auth routes
8
+ router.controller('/auth', AuthController);
9
+
10
+ // Admin routes (protected)
11
+ router.controller('/', DashboardController);
12
+ router.controller('/users', UserController);
13
+ }
@@ -0,0 +1,88 @@
1
+ import { html } from 'canxjs';
2
+
3
+ interface AdminLayoutProps {
4
+ title: string;
5
+ content: string;
6
+ }
7
+
8
+ const menuItems = [
9
+ { icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6', label: 'Dashboard', href: '/' },
10
+ { icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z', label: 'Users', href: '/users' },
11
+ { icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10', label: 'Products', href: '/products' },
12
+ { icon: 'M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z', label: 'Orders', href: '/orders' },
13
+ { icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z', label: 'Analytics', href: '/analytics' },
14
+ { icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z', label: 'Settings', href: '/settings' },
15
+ ];
16
+
17
+ export function adminLayout({ title, content }: AdminLayoutProps): string {
18
+ return html(
19
+ `
20
+ <div class="flex h-screen bg-gray-100">
21
+ <!-- Sidebar -->
22
+ <aside class="w-64 bg-gray-900 text-white flex flex-col">
23
+ <div class="p-6 border-b border-gray-800">
24
+ <h1 class="text-xl font-bold flex items-center gap-2">
25
+ <span class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-sm">C</span>
26
+ Canx Admin
27
+ </h1>
28
+ </div>
29
+
30
+ <nav class="flex-1 p-4 space-y-1">
31
+ ${menuItems.map(item => `
32
+ <a href="${item.href}" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
33
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
35
+ </svg>
36
+ ${item.label}
37
+ </a>
38
+ `).join('')}
39
+ </nav>
40
+
41
+ <div class="p-4 border-t border-gray-800">
42
+ <a href="/canx-queue" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
43
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
45
+ </svg>
46
+ Queue Dashboard
47
+ </a>
48
+ <a href="/auth/logout" class="flex items-center gap-3 px-4 py-3 rounded-lg text-red-400 hover:bg-red-900/30 transition-colors mt-2">
49
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
51
+ </svg>
52
+ Logout
53
+ </a>
54
+ </div>
55
+ </aside>
56
+
57
+ <!-- Main Content -->
58
+ <main class="flex-1 overflow-auto">
59
+ <!-- Header -->
60
+ <header class="bg-white border-b border-gray-200 px-8 py-4">
61
+ <div class="flex items-center justify-between">
62
+ <h2 class="text-lg font-semibold text-gray-800">${title}</h2>
63
+ <div class="flex items-center gap-4">
64
+ <button class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg">
65
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
66
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
67
+ </svg>
68
+ </button>
69
+ <div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white text-sm font-medium">
70
+ A
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </header>
75
+
76
+ <!-- Page Content -->
77
+ <div class="p-8">
78
+ ${content}
79
+ </div>
80
+ </main>
81
+ </div>
82
+ `,
83
+ {
84
+ title: `${title} - Canx Admin`,
85
+ meta: { description: 'Canx Admin Dashboard' }
86
+ }
87
+ );
88
+ }
@@ -0,0 +1,12 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ['./src/**/*.{ts,tsx}'],
4
+ theme: {
5
+ extend: {
6
+ fontFamily: {
7
+ sans: ['Inter', 'system-ui', 'sans-serif'],
8
+ },
9
+ },
10
+ },
11
+ plugins: [],
12
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "types": ["bun-types"],
7
+ "esModuleInterop": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "experimentalDecorators": true,
11
+ "emitDecoratorMetadata": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }