@emberkit/cli 0.5.2 → 0.6.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/dist/commands/create.js +7 -6
- package/dist/templates/action.js +12 -0
- package/dist/templates/api/api.js +280 -0
- package/dist/templates/apiRoute.js +20 -0
- package/dist/templates/blog/blog.js +358 -0
- package/dist/templates/component.js +14 -0
- package/dist/templates/config.js +13 -0
- package/dist/templates/context.js +21 -0
- package/dist/templates/dashboard/dashboard.js +406 -0
- package/dist/templates/entry.js +15 -0
- package/dist/templates/errorBoundary.js +17 -0
- package/dist/templates/form.js +59 -0
- package/dist/templates/index.js +0 -260
- package/dist/templates/layout.js +16 -0
- package/dist/templates/layoutRoutes.js +9 -0
- package/dist/templates/loader.js +10 -0
- package/dist/templates/minimal/minimal.js +89 -0
- package/dist/templates/projects/starter.js +286 -0
- package/dist/templates/projects/with-ui.js +445 -0
- package/dist/templates/projects.js +419 -191
- package/dist/templates/route.js +12 -0
- package/dist/templates/saas/saas.js +539 -0
- package/dist/templates/signal.js +25 -0
- package/dist/utils/generator.js +30 -2
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const componentTemplate = `interface {{name}}Props {
|
|
2
|
+
className?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const {{name}} = ({ className = '' }: {{name}}Props) => {
|
|
6
|
+
return (
|
|
7
|
+
<div className={className}>
|
|
8
|
+
{{name}} component
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default {{name}};
|
|
14
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const contextTemplate = `import { createContext, useContext } from '@emberkit/core';
|
|
2
|
+
|
|
3
|
+
interface {{name}}Context {
|
|
4
|
+
// Define your context shape
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const {{name}}Context = createContext<{{name}}Context>({
|
|
9
|
+
value: 'default',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Provider usage:
|
|
13
|
+
// <{{name}}Context.Provider value={{ value: 'hello' }}>
|
|
14
|
+
// {children}
|
|
15
|
+
// </{{name}}Context.Provider>
|
|
16
|
+
|
|
17
|
+
// Consumer usage:
|
|
18
|
+
// const ctx = useContext({{name}}Context);
|
|
19
|
+
|
|
20
|
+
export { {{name}}Context };
|
|
21
|
+
`;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
export const dashboardTemplate = {
|
|
2
|
+
"package.json": `{
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "emberkit dev",
|
|
9
|
+
"build": "emberkit build",
|
|
10
|
+
"preview": "emberkit preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@emberkit/core": "^0.2.4",
|
|
14
|
+
"@emberkit/ui": "^0.2.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@emberkit/cli": "^0.2.4",
|
|
18
|
+
"typescript": "^5.7.0",
|
|
19
|
+
"vite": "^6.0.0",
|
|
20
|
+
"tailwindcss": "^4.0.0",
|
|
21
|
+
"@tailwindcss/vite": "^4.0.0"
|
|
22
|
+
}
|
|
23
|
+
}`,
|
|
24
|
+
"tsconfig.json": `{
|
|
25
|
+
"compilerOptions": {
|
|
26
|
+
"target": "ES2022",
|
|
27
|
+
"module": "ESNext",
|
|
28
|
+
"moduleResolution": "bundler",
|
|
29
|
+
"jsx": "react-jsx",
|
|
30
|
+
"jsxImportSource": "@emberkit/core",
|
|
31
|
+
"strict": true,
|
|
32
|
+
"esModuleInterop": true,
|
|
33
|
+
"skipLibCheck": true,
|
|
34
|
+
"forceConsistentCasingInFileNames": true,
|
|
35
|
+
"resolveJsonModule": true,
|
|
36
|
+
"isolatedModules": true,
|
|
37
|
+
"noEmit": true,
|
|
38
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
39
|
+
"paths": {
|
|
40
|
+
"@/*": ["./src/*"]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"include": ["src"],
|
|
44
|
+
"exclude": ["node_modules", "dist"]
|
|
45
|
+
}`,
|
|
46
|
+
"vite.config.ts": `import { defineConfig } from 'vite';
|
|
47
|
+
import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
|
|
48
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
plugins: [emberkitVitePlugin(), tailwindcss()],
|
|
52
|
+
server: {
|
|
53
|
+
port: 3000,
|
|
54
|
+
host: 'localhost',
|
|
55
|
+
},
|
|
56
|
+
esbuild: {
|
|
57
|
+
jsxImportSource: '@emberkit/core',
|
|
58
|
+
},
|
|
59
|
+
});`,
|
|
60
|
+
"index.html": `<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="UTF-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
65
|
+
<title>{{name}}</title>
|
|
66
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
67
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
68
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
69
|
+
</head>
|
|
70
|
+
<body id="app">
|
|
71
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>`,
|
|
74
|
+
"src/index.tsx": `import { render } from '@emberkit/core';
|
|
75
|
+
import { routes } from 'virtual:emberkit-routes';
|
|
76
|
+
import App from './routes/_layout';
|
|
77
|
+
import './styles.css';
|
|
78
|
+
|
|
79
|
+
const root = document.getElementById('app');
|
|
80
|
+
|
|
81
|
+
if (root) {
|
|
82
|
+
try {
|
|
83
|
+
render(App, root, { routes });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('[entry] Render error:', error);
|
|
86
|
+
}
|
|
87
|
+
}`,
|
|
88
|
+
"src/styles.css": `@import "tailwindcss";
|
|
89
|
+
|
|
90
|
+
@theme {
|
|
91
|
+
--color-brand-50: #f0fdf4;
|
|
92
|
+
--color-brand-100: #dcfce7;
|
|
93
|
+
--color-brand-200: #bbf7d0;
|
|
94
|
+
--color-brand-300: #86efac;
|
|
95
|
+
--color-brand-400: #4ade80;
|
|
96
|
+
--color-brand-500: #22c55e;
|
|
97
|
+
--color-brand-600: #16a34a;
|
|
98
|
+
--color-brand-700: #15803d;
|
|
99
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
body {
|
|
103
|
+
@apply bg-gray-50 text-gray-900 font-sans;
|
|
104
|
+
}`,
|
|
105
|
+
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
106
|
+
import { signal } from '@emberkit/core';
|
|
107
|
+
import { Sidebar, Header } from '@emberkit/ui';
|
|
108
|
+
|
|
109
|
+
const Layout: RouteComponent = ({ children }) => {
|
|
110
|
+
const sidebarOpen = signal(true);
|
|
111
|
+
|
|
112
|
+
const sidebarItems = [
|
|
113
|
+
{ label: 'Dashboard', href: '/dashboard', icon: 'grid' },
|
|
114
|
+
{ label: 'Analytics', href: '/analytics', icon: 'chart' },
|
|
115
|
+
{ label: 'Users', href: '/users', icon: 'users' },
|
|
116
|
+
{ label: 'Projects', href: '/projects', icon: 'folder' },
|
|
117
|
+
{ label: 'Settings', href: '/settings', icon: 'settings' },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="min-h-screen flex">
|
|
122
|
+
<Sidebar
|
|
123
|
+
logo={<span className="font-bold text-lg">⚡ {{name}}</span>}
|
|
124
|
+
items={sidebarItems}
|
|
125
|
+
collapsed={!sidebarOpen.value}
|
|
126
|
+
onToggle={() => { sidebarOpen.value = !sidebarOpen.value; }}
|
|
127
|
+
className="w-64"
|
|
128
|
+
/>
|
|
129
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
130
|
+
<Header
|
|
131
|
+
title="Dashboard"
|
|
132
|
+
user={{ name: 'User', avatar: '' }}
|
|
133
|
+
onMenuClick={() => { sidebarOpen.value = !sidebarOpen.value; }}
|
|
134
|
+
/>
|
|
135
|
+
<main className="flex-1 p-6 overflow-auto">
|
|
136
|
+
{children}
|
|
137
|
+
</main>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default Layout;`,
|
|
144
|
+
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
145
|
+
import { Card, Badge } from '@emberkit/ui';
|
|
146
|
+
|
|
147
|
+
const DashboardPage: RouteComponent = () => {
|
|
148
|
+
const stats = [
|
|
149
|
+
{ label: 'Total Revenue', value: '$45,231', change: '+20.1%', positive: true },
|
|
150
|
+
{ label: 'Active Users', value: '2,338', change: '+15.3%', positive: true },
|
|
151
|
+
{ label: 'New Signups', value: '1,247', change: '+8.2%', positive: true },
|
|
152
|
+
{ label: 'Churn Rate', value: '2.4%', change: '-0.5%', positive: true },
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const recentActivity = [
|
|
156
|
+
{ user: 'John Doe', action: 'created a new project', time: '2 minutes ago' },
|
|
157
|
+
{ user: 'Jane Smith', action: 'upgraded to Pro plan', time: '15 minutes ago' },
|
|
158
|
+
{ user: 'Mike Johnson', action: 'invited 3 team members', time: '1 hour ago' },
|
|
159
|
+
{ user: 'Sarah Williams', action: 'published a new post', time: '2 hours ago' },
|
|
160
|
+
{ user: 'Alex Brown', action: 'deleted an old project', time: '3 hours ago' },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="space-y-6">
|
|
165
|
+
<div>
|
|
166
|
+
<h1 className="text-2xl font-bold">Welcome back</h1>
|
|
167
|
+
<p className="text-gray-600 mt-1">Here's what's happening with your account today.</p>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Stats Grid */}
|
|
171
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
172
|
+
{stats.map((stat) => (
|
|
173
|
+
<Card key={stat.label} padding="lg">
|
|
174
|
+
<p className="text-sm text-gray-600">{stat.label}</p>
|
|
175
|
+
<p className="text-2xl font-bold mt-1">{stat.value}</p>
|
|
176
|
+
<Badge variant={stat.positive ? 'success' : 'danger'} size="sm" className="mt-2">
|
|
177
|
+
{stat.change}
|
|
178
|
+
</Badge>
|
|
179
|
+
</Card>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
184
|
+
{/* Chart Placeholder */}
|
|
185
|
+
<Card padding="lg">
|
|
186
|
+
<h3 className="font-semibold mb-4">Revenue Overview</h3>
|
|
187
|
+
<div className="h-64 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500">
|
|
188
|
+
Chart placeholder - integrate your preferred charting library
|
|
189
|
+
</div>
|
|
190
|
+
</Card>
|
|
191
|
+
|
|
192
|
+
{/* Recent Activity */}
|
|
193
|
+
<Card padding="lg">
|
|
194
|
+
<h3 className="font-semibold mb-4">Recent Activity</h3>
|
|
195
|
+
<div className="space-y-4">
|
|
196
|
+
{recentActivity.map((item, i) => (
|
|
197
|
+
<div key={i} className="flex items-start gap-3 pb-4 border-b border-gray-100 last:border-0 last:pb-0">
|
|
198
|
+
<div className="w-8 h-8 rounded-full bg-brand-100 flex items-center justify-center text-brand-700 font-medium text-sm">
|
|
199
|
+
{item.user.charAt(0)}
|
|
200
|
+
</div>
|
|
201
|
+
<div className="flex-1 min-w-0">
|
|
202
|
+
<p className="text-sm">
|
|
203
|
+
<span className="font-medium">{item.user}</span> {item.action}
|
|
204
|
+
</p>
|
|
205
|
+
<p className="text-xs text-gray-500 mt-0.5">{item.time}</p>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
</Card>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{/* Quick Actions */}
|
|
214
|
+
<Card padding="lg">
|
|
215
|
+
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
|
216
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
217
|
+
{[
|
|
218
|
+
{ label: 'New Project', icon: '+' },
|
|
219
|
+
{ label: 'Invite User', icon: '👤' },
|
|
220
|
+
{ label: 'View Reports', icon: '📊' },
|
|
221
|
+
{ label: 'Settings', icon: '⚙' },
|
|
222
|
+
].map((action) => (
|
|
223
|
+
<button
|
|
224
|
+
key={action.label}
|
|
225
|
+
className="p-4 rounded-lg border border-gray-200 hover:border-brand-300 hover:bg-brand-50 transition-colors text-center"
|
|
226
|
+
>
|
|
227
|
+
<span className="text-2xl block mb-2">{action.icon}</span>
|
|
228
|
+
<span className="text-sm font-medium">{action.label}</span>
|
|
229
|
+
</button>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
</Card>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export default DashboardPage;`,
|
|
238
|
+
"src/routes/users.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
239
|
+
import { Card, Badge, Button, Input } from '@emberkit/ui';
|
|
240
|
+
import { signal } from '@emberkit/core';
|
|
241
|
+
|
|
242
|
+
interface User {
|
|
243
|
+
id: number;
|
|
244
|
+
name: string;
|
|
245
|
+
email: string;
|
|
246
|
+
role: string;
|
|
247
|
+
status: 'active' | 'inactive' | 'pending';
|
|
248
|
+
lastActive: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const users: User[] = [
|
|
252
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'active', lastActive: '2 min ago' },
|
|
253
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'Editor', status: 'active', lastActive: '1 hour ago' },
|
|
254
|
+
{ id: 3, name: 'Mike Johnson', email: 'mike@example.com', role: 'Viewer', status: 'inactive', lastActive: '3 days ago' },
|
|
255
|
+
{ id: 4, name: 'Sarah Williams', email: 'sarah@example.com', role: 'Editor', status: 'active', lastActive: '5 min ago' },
|
|
256
|
+
{ id: 5, name: 'Alex Brown', email: 'alex@example.com', role: 'Viewer', status: 'pending', lastActive: 'Never' },
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const UsersPage: RouteComponent = () => {
|
|
260
|
+
const search = signal('');
|
|
261
|
+
|
|
262
|
+
const filteredUsers = users.filter(
|
|
263
|
+
(u) =>
|
|
264
|
+
u.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
|
265
|
+
u.email.toLowerCase().includes(search.value.toLowerCase())
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const statusVariant = (status: User['status']) => {
|
|
269
|
+
switch (status) {
|
|
270
|
+
case 'active': return 'success';
|
|
271
|
+
case 'inactive': return 'default';
|
|
272
|
+
case 'pending': return 'warning';
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div className="space-y-6">
|
|
278
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
279
|
+
<div>
|
|
280
|
+
<h1 className="text-2xl font-bold">Users</h1>
|
|
281
|
+
<p className="text-gray-600 mt-1">Manage your team members and their roles.</p>
|
|
282
|
+
</div>
|
|
283
|
+
<Button variant="primary">Add User</Button>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<Card padding="lg">
|
|
287
|
+
<div className="mb-4">
|
|
288
|
+
<Input
|
|
289
|
+
placeholder="Search users..."
|
|
290
|
+
value={search.value}
|
|
291
|
+
onInput={(e) => { search.value = e.currentTarget.value; }}
|
|
292
|
+
className="max-w-sm"
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="overflow-x-auto">
|
|
296
|
+
<table className="w-full text-sm">
|
|
297
|
+
<thead>
|
|
298
|
+
<tr className="border-b border-gray-200">
|
|
299
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Name</th>
|
|
300
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Role</th>
|
|
301
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Status</th>
|
|
302
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Last Active</th>
|
|
303
|
+
<th className="text-right py-3 px-4 font-medium text-gray-600">Actions</th>
|
|
304
|
+
</tr>
|
|
305
|
+
</thead>
|
|
306
|
+
<tbody>
|
|
307
|
+
{filteredUsers.map((user) => (
|
|
308
|
+
<tr key={user.id} className="border-b border-gray-100 hover:bg-gray-50">
|
|
309
|
+
<td className="py-3 px-4">
|
|
310
|
+
<div>
|
|
311
|
+
<p className="font-medium">{user.name}</p>
|
|
312
|
+
<p className="text-gray-500 text-xs">{user.email}</p>
|
|
313
|
+
</div>
|
|
314
|
+
</td>
|
|
315
|
+
<td className="py-3 px-4">{user.role}</td>
|
|
316
|
+
<td className="py-3 px-4">
|
|
317
|
+
<Badge variant={statusVariant(user.status)} size="sm">
|
|
318
|
+
{user.status}
|
|
319
|
+
</Badge>
|
|
320
|
+
</td>
|
|
321
|
+
<td className="py-3 px-4 text-gray-500">{user.lastActive}</td>
|
|
322
|
+
<td className="py-3 px-4 text-right">
|
|
323
|
+
<button className="text-brand-600 hover:underline text-sm">Edit</button>
|
|
324
|
+
</td>
|
|
325
|
+
</tr>
|
|
326
|
+
))}
|
|
327
|
+
</tbody>
|
|
328
|
+
</table>
|
|
329
|
+
</div>
|
|
330
|
+
</Card>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export default UsersPage;`,
|
|
336
|
+
"src/routes/settings.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
337
|
+
import { Card, Input, Button, Alert } from '@emberkit/ui';
|
|
338
|
+
import { signal } from '@emberkit/core';
|
|
339
|
+
|
|
340
|
+
const SettingsPage: RouteComponent = () => {
|
|
341
|
+
const name = signal('John Doe');
|
|
342
|
+
const email = signal('john@example.com');
|
|
343
|
+
const saved = signal(false);
|
|
344
|
+
|
|
345
|
+
const handleSave = (e: Event) => {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
saved.value = true;
|
|
348
|
+
setTimeout(() => { saved.value = false; }, 3000);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div className="space-y-6 max-w-2xl">
|
|
353
|
+
<div>
|
|
354
|
+
<h1 className="text-2xl font-bold">Settings</h1>
|
|
355
|
+
<p className="text-gray-600 mt-1">Manage your account preferences.</p>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
{saved.value && (
|
|
359
|
+
<Alert variant="success">Settings saved successfully!</Alert>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
<Card padding="lg">
|
|
363
|
+
<h3 className="font-semibold mb-6">Profile</h3>
|
|
364
|
+
<form onSubmit={handleSave} className="space-y-4">
|
|
365
|
+
<Input
|
|
366
|
+
label="Full Name"
|
|
367
|
+
value={name.value}
|
|
368
|
+
onInput={(e) => { name.value = e.currentTarget.value; }}
|
|
369
|
+
/>
|
|
370
|
+
<Input
|
|
371
|
+
label="Email"
|
|
372
|
+
type="email"
|
|
373
|
+
value={email.value}
|
|
374
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
375
|
+
/>
|
|
376
|
+
<div className="flex justify-end">
|
|
377
|
+
<Button variant="primary" type="submit">Save Changes</Button>
|
|
378
|
+
</div>
|
|
379
|
+
</form>
|
|
380
|
+
</Card>
|
|
381
|
+
|
|
382
|
+
<Card padding="lg">
|
|
383
|
+
<h3 className="font-semibold mb-6">Password</h3>
|
|
384
|
+
<form className="space-y-4">
|
|
385
|
+
<Input label="Current Password" type="password" />
|
|
386
|
+
<Input label="New Password" type="password" />
|
|
387
|
+
<Input label="Confirm Password" type="password" />
|
|
388
|
+
<div className="flex justify-end">
|
|
389
|
+
<Button variant="primary">Update Password</Button>
|
|
390
|
+
</div>
|
|
391
|
+
</form>
|
|
392
|
+
</Card>
|
|
393
|
+
|
|
394
|
+
<Card padding="lg" className="border-red-200">
|
|
395
|
+
<h3 className="font-semibold mb-2 text-red-600">Danger Zone</h3>
|
|
396
|
+
<p className="text-sm text-gray-600 mb-4">
|
|
397
|
+
Once you delete your account, there is no going back.
|
|
398
|
+
</p>
|
|
399
|
+
<Button variant="danger">Delete Account</Button>
|
|
400
|
+
</Card>
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export default SettingsPage;`,
|
|
406
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const indexTemplate = `import { render } from '@emberkit/core';
|
|
2
|
+
import { routes } from 'virtual:emberkit-routes';
|
|
3
|
+
import App from './routes/_layout';
|
|
4
|
+
import './styles.css';
|
|
5
|
+
|
|
6
|
+
const root = document.getElementById('app');
|
|
7
|
+
|
|
8
|
+
if (root) {
|
|
9
|
+
try {
|
|
10
|
+
render(App, root, { routes });
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error('[entry] Render error:', error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const errorBoundaryTemplate = `import type { RouteComponent } from '@emberkit/core';
|
|
2
|
+
|
|
3
|
+
interface {{name}}ErrorProps {
|
|
4
|
+
error: Error;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const {{name}}Error: RouteComponent<{{name}}ErrorProps> = ({ error }) => {
|
|
8
|
+
return (
|
|
9
|
+
<div className="error-boundary">
|
|
10
|
+
<h2>Something went wrong</h2>
|
|
11
|
+
<p>{error.message}</p>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default {{name}}Error;
|
|
17
|
+
`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const formTemplate = `import { signal } from '@emberkit/core';
|
|
2
|
+
|
|
3
|
+
const {{name}}Form = () => {
|
|
4
|
+
const email = signal('');
|
|
5
|
+
const password = signal('');
|
|
6
|
+
const error = signal<string | null>(null);
|
|
7
|
+
const loading = signal(false);
|
|
8
|
+
|
|
9
|
+
const handleSubmit = async (e: Event) => {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
error.value = null;
|
|
12
|
+
loading.value = true;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch('/api/auth/login', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
email: email.value,
|
|
20
|
+
password: password.value,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error('Login failed');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Handle success
|
|
29
|
+
} catch (err) {
|
|
30
|
+
error.value = err instanceof Error ? err.message : 'Unknown error';
|
|
31
|
+
} finally {
|
|
32
|
+
loading.value = false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<form onSubmit={handleSubmit}>
|
|
38
|
+
<input
|
|
39
|
+
type="email"
|
|
40
|
+
value={email.value}
|
|
41
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
42
|
+
placeholder="Email"
|
|
43
|
+
/>
|
|
44
|
+
<input
|
|
45
|
+
type="password"
|
|
46
|
+
value={password.value}
|
|
47
|
+
onInput={(e) => { password.value = e.currentTarget.value; }}
|
|
48
|
+
placeholder="Password"
|
|
49
|
+
/>
|
|
50
|
+
{error.value && <p className="text-red-500">{error.value}</p>}
|
|
51
|
+
<button type="submit" disabled={loading.value}>
|
|
52
|
+
{loading.value ? 'Loading...' : 'Submit'}
|
|
53
|
+
</button>
|
|
54
|
+
</form>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default {{name}}Form;
|
|
59
|
+
`;
|