@strav/spring 0.3.3 → 0.3.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @strav/spring
2
2
 
3
- Flagship framework scaffolding tool for the Strav ecosystem - the Laravel of the Bun ecosystem.
3
+ Flagship framework scaffolding tool for the Strav ecosystem - the rite of the Bun ecosystem.
4
4
 
5
5
  ## Usage
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strav/spring",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "description": "Flagship framework scaffolding tool for the Strav ecosystem.",
6
6
  "license": "MIT",
@@ -10,7 +10,6 @@
10
10
  "framework",
11
11
  "scaffold",
12
12
  "create",
13
- "laravel",
14
13
  "typescript",
15
14
  "vue"
16
15
  ],
@@ -23,7 +22,7 @@
23
22
  "README.md"
24
23
  ],
25
24
  "dependencies": {
26
- "@strav/kernel": "0.3.3"
25
+ "@strav/kernel": "0.3.5"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@types/bun": "latest"
package/src/index.ts CHANGED
@@ -58,7 +58,7 @@ function parseArgs(): ParsedArgs {
58
58
  function printUsage(): void {
59
59
  console.log(`
60
60
  ${bold('@strav/spring')} ${dim(`v${VERSION}`)}
61
- ${dim('The Laravel of the Bun ecosystem')}
61
+ ${dim('The Rite of the Bun ecosystem')}
62
62
 
63
63
  ${bold('Usage:')}
64
64
  bunx @strav/spring ${cyan('<project-name>')} [options]
@@ -98,7 +98,7 @@ async function main(): Promise<void> {
98
98
 
99
99
  console.log()
100
100
  console.log(` ${bold('@strav/spring')} ${dim(`v${VERSION}`)}`)
101
- console.log(` ${dim('The Laravel of the Bun ecosystem')}`)
101
+ console.log(` ${dim('The rite of the Bun ecosystem')}`)
102
102
  console.log()
103
103
 
104
104
  // Project name
@@ -1,24 +1,10 @@
1
1
  import type { Context } from '@strav/http'
2
- import { Controller } from './controller.ts'
3
- import User from '../models/user.ts'
4
2
 
5
- export default class HomeController extends Controller {
3
+ export default class HomeController {
6
4
  async index(ctx: Context) {
7
- const userCount = await User.count()
8
-
9
5
  return ctx.view('pages/home', {
10
- title: 'Welcome to __PROJECT_NAME__',
11
- userCount,
6
+ title: 'Welcome',
12
7
  message: 'Welcome to your new Strav application!',
13
8
  })
14
9
  }
15
-
16
- async users(ctx: Context) {
17
- const users = await User.all()
18
-
19
- return ctx.view('pages/users', {
20
- title: 'Users',
21
- users,
22
- })
23
- }
24
10
  }
@@ -1,5 +1,11 @@
1
- /* Basic Tailwind CSS-inspired styles */
2
- /* In a real project, you'd use Tailwind CSS or your preferred CSS framework */
1
+ :root {
2
+ --bg: #f4f4f0;
3
+ --text: #1e293b;
4
+ --accent: #f97316;
5
+ --bg-feature: #e8e8e3;
6
+ --border-feature: #d8d8d0;
7
+ --border-link-hover: #abab9c;
8
+ }
3
9
 
4
10
  * {
5
11
  margin: 0;
@@ -8,169 +14,64 @@
8
14
  }
9
15
 
10
16
  body {
11
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
12
- 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', sans-serif;
13
- line-height: 1.6;
14
- color: #374151;
15
- }
16
-
17
- /* Basic utility classes */
18
- .bg-gray-50 { background-color: #f9fafb; }
19
- .bg-white { background-color: #ffffff; }
20
- .bg-blue-600 { background-color: #2563eb; }
21
- .bg-blue-700 { background-color: #1d4ed8; }
22
- .bg-red-500 { background-color: #ef4444; }
23
- .bg-red-600 { background-color: #dc2626; }
24
- .bg-green-500 { background-color: #10b981; }
25
- .bg-green-600 { background-color: #059669; }
26
- .bg-gray-100 { background-color: #f3f4f6; }
27
- .bg-yellow-50 { background-color: #fffbeb; }
28
-
29
- .text-white { color: #ffffff; }
30
- .text-gray-900 { color: #111827; }
31
- .text-gray-600 { color: #4b5563; }
32
- .text-gray-500 { color: #6b7280; }
33
- .text-blue-600 { color: #2563eb; }
34
- .text-green-600 { color: #059669; }
35
-
36
- .text-xs { font-size: 0.75rem; }
37
- .text-sm { font-size: 0.875rem; }
38
- .text-base { font-size: 1rem; }
39
- .text-lg { font-size: 1.125rem; }
40
- .text-xl { font-size: 1.25rem; }
41
- .text-2xl { font-size: 1.5rem; }
42
- .text-4xl { font-size: 2.25rem; }
43
-
44
- .font-bold { font-weight: 700; }
45
- .font-semibold { font-weight: 600; }
46
- .font-medium { font-weight: 500; }
47
-
48
- .max-w-7xl { max-width: 80rem; }
49
- .max-w-4xl { max-width: 56rem; }
50
- .max-w-md { max-width: 28rem; }
51
- .mx-auto { margin-left: auto; margin-right: auto; }
52
-
53
- .px-4 { padding-left: 1rem; padding-right: 1rem; }
54
- .py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
55
- .p-6 { padding: 1.5rem; }
56
- .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
57
- .py-4 { padding-top: 1rem; padding-bottom: 1rem; }
58
- .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
59
- .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
60
- .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
61
- .py-12 { padding-top: 3rem; padding-bottom: 3rem; }
62
-
63
- .mb-4 { margin-bottom: 1rem; }
64
- .mb-8 { margin-bottom: 2rem; }
65
- .mt-8 { margin-top: 2rem; }
66
- .mt-2 { margin-top: 0.5rem; }
67
-
68
- .flex { display: flex; }
69
- .grid { display: grid; }
70
- .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
71
- .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
72
- .gap-6 { gap: 1.5rem; }
73
- .space-x-4 > * + * { margin-left: 1rem; }
74
- .space-y-6 > * + * { margin-top: 1.5rem; }
75
- .items-center { align-items: center; }
76
- .justify-between { justify-content: space-between; }
77
-
78
- .rounded { border-radius: 0.25rem; }
79
- .rounded-lg { border-radius: 0.5rem; }
80
- .shadow { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
81
-
82
- .border { border-width: 1px; }
83
- .border-4 { border-width: 4px; }
84
- .border-dashed { border-style: dashed; }
85
- .border-gray-200 { border-color: #e5e7eb; }
86
-
87
- .text-center { text-align: center; }
88
-
89
- .hover\:bg-blue-700:hover { background-color: #1d4ed8; }
90
- .hover\:bg-red-600:hover { background-color: #dc2626; }
91
- .hover\:bg-green-600:hover { background-color: #059669; }
92
- .hover\:text-gray-900:hover { color: #111827; }
93
-
94
- .transition-colors {
95
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
96
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
97
- transition-duration: 150ms;
98
- }
99
-
100
- /* Navigation styles */
101
- nav {
102
- border-bottom: 1px solid #e5e7eb;
103
- }
104
-
105
- nav a {
106
- text-decoration: none;
107
- transition: color 0.15s ease-in-out;
17
+ font-family: "Barlow Semi Condensed", sans-serif;
18
+ font-weight: 400;
19
+ font-style: normal;
20
+ font-size: 15px;
21
+ color: var(--text);
22
+ background-color: var(--bg);
23
+ line-height: 1.8;
108
24
  }
109
25
 
110
- /* Table styles */
111
- table {
112
- border-collapse: collapse;
113
- width: 100%;
114
- }
115
-
116
- th, td {
117
- text-align: left;
118
- border-bottom: 1px solid #e5e7eb;
119
- }
120
-
121
- th {
122
- background-color: #f9fafb;
123
- font-weight: 500;
124
- text-transform: uppercase;
125
- letter-spacing: 0.025em;
126
- }
127
-
128
- /* Form styles */
129
- input[type="text"],
130
- input[type="email"],
131
- textarea {
132
- border: 1px solid #d1d5db;
133
- border-radius: 0.375rem;
134
- padding: 0.5rem 0.75rem;
135
- width: 100%;
136
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
137
- }
138
-
139
- input:focus,
140
- textarea:focus {
141
- outline: none;
142
- border-color: #3b82f6;
143
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
144
- }
145
-
146
- button {
147
- cursor: pointer;
148
- border: none;
149
- border-radius: 0.375rem;
150
- font-weight: 500;
26
+ main {
27
+ width: 800px;
28
+ margin: 50px auto 0;
151
29
  text-align: center;
152
- transition: background-color 0.15s ease-in-out;
153
- }
154
-
155
- button:disabled {
156
- opacity: 0.5;
157
- cursor: not-allowed;
158
- }
159
-
160
- code {
161
- background-color: #f3f4f6;
162
- padding: 0.125rem 0.5rem;
163
- border-radius: 0.25rem;
164
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
165
- }
166
-
167
- /* Media queries for responsiveness */
168
- @media (min-width: 768px) {
169
- .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
170
- .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
171
- }
172
-
173
- @media (min-width: 640px) {
174
- .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
175
- .sm\:px-0 { padding-left: 0; padding-right: 0; }
30
+ h3 {
31
+ font-size: 50px;
32
+ font-weight: 400;
33
+ color: #374151;
34
+ margin-bottom: 30px;
35
+ em {
36
+ font-style: normal;
37
+ color: var(--accent);
38
+ }
39
+ }
40
+ .features {
41
+ display: grid;
42
+ grid-template-columns: repeat(2, 1fr);
43
+ grid-template-rows: repeat(2, 1fr);
44
+ grid-column-gap: 20px;
45
+ grid-row-gap: 20px;
46
+ margin-top: 50px;
47
+ .feature {
48
+ padding: 20px;
49
+ background-color: var(--bg-feature);
50
+ border: 1px solid var(--border-feature);
51
+ border-radius: 10px;
52
+ text-align: left;
53
+ h4 {
54
+ text-align: left;
55
+ color: var(--accent);
56
+ }
57
+ }
58
+ }
59
+ .links {
60
+ display: flex;
61
+ justify-content: center;
62
+ margin-top: 50px;
63
+ a {
64
+ display: inline-block;
65
+ padding: 7px 15px;
66
+ margin: 0 20px;
67
+ border-radius: 10px;
68
+ text-decoration: none;
69
+ color: var(--text);
70
+ background-color: var(--bg-feature);
71
+ border: 1px solid var(--border-feature);
72
+ &:hover {
73
+ border-color: var(--border-link-hover);
74
+ }
75
+ }
76
+ }
176
77
  }
@@ -1,42 +0,0 @@
1
- <template>
2
- <div class="flex items-center space-x-4">
3
- <button
4
- @click="decrement"
5
- class="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
6
- >
7
- -
8
- </button>
9
-
10
- <span class="text-xl font-bold text-gray-900 min-w-[3rem] text-center">
11
- {{ count }}
12
- </span>
13
-
14
- <button
15
- @click="increment"
16
- class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600 transition-colors"
17
- >
18
- +
19
- </button>
20
-
21
- <span class="text-sm text-gray-600 ml-4">{{ label }}</span>
22
- </div>
23
- </template>
24
-
25
- <script setup>
26
- import { ref } from 'vue'
27
-
28
- const props = defineProps({
29
- initial: { type: Number, default: 0 },
30
- label: { type: String, default: 'Counter' }
31
- })
32
-
33
- const count = ref(props.initial)
34
-
35
- function increment() {
36
- count.value++
37
- }
38
-
39
- function decrement() {
40
- count.value--
41
- }
42
- </script>
@@ -3,30 +3,16 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>{{ title }} - __PROJECT_NAME__</title>
6
+ <title>{{ title }} - Strav Spring</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Barlow+Semi+Condensed:ital,wght@0,400;0,500;1,400;1,500&display=swap" rel="stylesheet">
7
10
  <link rel="stylesheet" href="{{ asset('/css/app.css') }}">
8
- @stack('styles')
9
11
  </head>
10
12
  <body class="bg-gray-50">
11
- <nav class="bg-white shadow">
12
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
13
- <div class="flex justify-between h-16">
14
- <div class="flex items-center">
15
- <a href="/" class="text-xl font-bold text-gray-900">__PROJECT_NAME__</a>
16
- </div>
17
- <div class="flex items-center space-x-4">
18
- <a href="/" class="text-gray-600 hover:text-gray-900">Home</a>
19
- <a href="/users" class="text-gray-600 hover:text-gray-900">Users</a>
20
- </div>
21
- </div>
22
- </div>
23
- </nav>
24
-
25
- <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
13
+ <main>
26
14
  @show('content')
27
15
  </main>
28
-
29
16
  @islands
30
- @stack('scripts')
31
17
  </body>
32
18
  </html>
@@ -1,52 +1,31 @@
1
1
  @layout('layouts/app')
2
2
 
3
3
  @section('content')
4
- <div class="px-4 py-6 sm:px-0">
5
- <div class="border-4 border-dashed border-gray-200 rounded-lg">
6
- <div class="text-center py-12">
7
- <h1 class="text-4xl font-bold text-gray-900 mb-4">{{ message }}</h1>
8
- <p class="text-lg text-gray-600 mb-8">
9
- You have successfully created a new Strav application. Start building something amazing!
10
- </p>
11
-
12
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto mb-8">
13
- <div class="bg-white p-6 rounded-lg shadow">
14
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Database Ready</h3>
15
- <p class="text-gray-600">{{ userCount }} users in the database</p>
16
- </div>
17
-
18
- <div class="bg-white p-6 rounded-lg shadow">
19
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Vue Islands</h3>
20
- <p class="text-gray-600">Interactive components below</p>
21
- </div>
22
-
23
- <div class="bg-white p-6 rounded-lg shadow">
24
- <h3 class="text-lg font-semibold text-gray-900 mb-2">TypeScript</h3>
25
- <p class="text-gray-600">Full type safety everywhere</p>
26
- </div>
27
- </div>
28
-
29
- {{-- Demo Vue Islands --}}
30
- <div class="space-y-6">
31
- <h2 class="text-2xl font-bold text-gray-900">Vue Islands Demo</h2>
32
-
33
- <div class="bg-white p-6 rounded-lg shadow max-w-md mx-auto">
34
- <h3 class="text-lg font-semibold mb-4">Counter Component</h3>
35
- <vue:counter :initial="5" label="Click me!" />
36
- </div>
37
-
38
- <div class="bg-white p-6 rounded-lg shadow max-w-md mx-auto">
39
- <h3 class="text-lg font-semibold mb-4">User Search</h3>
40
- <vue:user-search placeholder="Search users..." :userCount="{{ userCount }}" />
41
- </div>
42
- </div>
43
-
44
- <div class="mt-8">
45
- <a href="/users" class="inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">
46
- View Users
47
- </a>
48
- </div>
49
- </div>
4
+ <h3>Welcome <em>Strav Spring</em></h3>
5
+ <p>
6
+ You're all set. Your project is live and ready to build. <br/>
7
+ Blazing fast, powered by Bun and Vue.js
8
+ </p>
9
+ <div class="features">
10
+ <div class="feature">
11
+ <h4>Schema-First Architecture</h4>
12
+ <p>Define your database schema once, automatically migrations and models. No more manual SQL and model boilerplate.</p>
13
+ </div>
14
+ <div class="feature">
15
+ <h4>Vue Islands</h4>
16
+ <p>Embed Vue.js components as interactive islands within server-rendered HTML. Get reactivity where you need it without the complexity of a full SPA.</p>
17
+ </div>
18
+ <div class="feature">
19
+ <h4>AI as a First-Class Citizen</h4>
20
+ <p>Built-in AI agents, workflows, and memory management with multi-provider support (Claude, GPT, Gemini). Create AI-powered features with a single import.</p>
50
21
  </div>
22
+ <div class="feature">
23
+ <h4>Zero-Config Real-Time Stack</h4>
24
+ <p>WebSocket broadcasting, multi-channel notifications, and transactional email built-in. From chat to live updates, ship real-time features instantly.</p>
25
+ </div>
26
+ </div>
27
+ <div class="links">
28
+ <a href="https://spring.strav.dev/docs" target="_blank">Documentation</a>
29
+ <a href="https://github.com/stravigor/strav" target="_blank">GitHub</a>
51
30
  </div>
52
31
  @end
@@ -1,9 +1,8 @@
1
1
  import { router } from '@strav/http'
2
- import HomeController from '../app/controllers/home_controller'
2
+ import HomeController from '../app/controllers/home_controller.ts'
3
3
 
4
4
  // Web routes
5
5
  router.get('/', [HomeController, 'index'])
6
- router.get('/users', [HomeController, 'users'])
7
6
 
8
7
  // Health check endpoint
9
8
  router.get('/health', async (ctx) => {
@@ -1,30 +0,0 @@
1
- import { Model, column } from '@strav/database'
2
-
3
- export default class User extends Model {
4
- @column({ isPrimary: true })
5
- declare id: string
6
-
7
- @column()
8
- declare email: string
9
-
10
- @column()
11
- declare name: string
12
-
13
- @column()
14
- declare password_hash: string
15
-
16
- @column()
17
- declare email_verified_at: Date | null
18
-
19
- @column()
20
- declare remember_token: string | null
21
-
22
- @column()
23
- declare created_at: Date
24
-
25
- @column()
26
- declare updated_at: Date
27
-
28
- @column()
29
- declare deleted_at: Date | null
30
- }
@@ -1,127 +0,0 @@
1
- <template>
2
- <div class="bg-white p-6 rounded-lg shadow">
3
- <h3 class="text-lg font-semibold text-gray-900 mb-4">Interactive User Management</h3>
4
-
5
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
6
- <!-- Add User Form -->
7
- <div>
8
- <h4 class="font-medium text-gray-900 mb-3">Add New User</h4>
9
- <form @submit.prevent="addUser" class="space-y-3">
10
- <input
11
- v-model="newUser.name"
12
- type="text"
13
- placeholder="Full Name"
14
- required
15
- class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
16
- />
17
- <input
18
- v-model="newUser.email"
19
- type="email"
20
- placeholder="Email Address"
21
- required
22
- class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
23
- />
24
- <button
25
- type="submit"
26
- :disabled="isLoading"
27
- class="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
28
- >
29
- {{ isLoading ? 'Adding...' : 'Add User' }}
30
- </button>
31
- </form>
32
- </div>
33
-
34
- <!-- User Stats -->
35
- <div>
36
- <h4 class="font-medium text-gray-900 mb-3">Statistics</h4>
37
- <div class="space-y-2">
38
- <div class="flex justify-between">
39
- <span class="text-gray-600">Total Users:</span>
40
- <span class="font-semibold">{{ users.length }}</span>
41
- </div>
42
- <div class="flex justify-between">
43
- <span class="text-gray-600">Users Added:</span>
44
- <span class="font-semibold text-green-600">+{{ addedCount }}</span>
45
- </div>
46
- <div class="flex justify-between">
47
- <span class="text-gray-600">Last Added:</span>
48
- <span class="text-sm text-gray-500">
49
- {{ lastAdded || 'None yet' }}
50
- </span>
51
- </div>
52
- </div>
53
- </div>
54
- </div>
55
-
56
- <!-- Recent Users List -->
57
- <div class="mt-6">
58
- <h4 class="font-medium text-gray-900 mb-3">Recent Users</h4>
59
- <div v-if="users.length === 0" class="text-center text-gray-500 py-8">
60
- No users yet. Add one above!
61
- </div>
62
- <div v-else class="space-y-2 max-h-48 overflow-y-auto">
63
- <div
64
- v-for="(user, index) in users.slice(-5).reverse()"
65
- :key="user.id || index"
66
- class="flex items-center justify-between p-3 bg-gray-50 rounded-md"
67
- >
68
- <div>
69
- <div class="font-medium text-gray-900">{{ user.name }}</div>
70
- <div class="text-sm text-gray-500">{{ user.email }}</div>
71
- </div>
72
- <span v-if="index === 0" class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
73
- Latest
74
- </span>
75
- </div>
76
- </div>
77
- </div>
78
- </div>
79
- </template>
80
-
81
- <script setup>
82
- import { ref, reactive, computed } from 'vue'
83
-
84
- const props = defineProps({
85
- initialUsers: { type: String, default: '[]' }
86
- })
87
-
88
- // Parse initial users from JSON string
89
- const users = ref(JSON.parse(props.initialUsers))
90
- const addedCount = ref(0)
91
- const isLoading = ref(false)
92
-
93
- const newUser = reactive({
94
- name: '',
95
- email: ''
96
- })
97
-
98
- const lastAdded = computed(() => {
99
- if (addedCount.value === 0) return null
100
- const latest = users.value[users.value.length - 1]
101
- return latest ? `${latest.name} (${latest.email})` : null
102
- })
103
-
104
- async function addUser() {
105
- if (!newUser.name || !newUser.email) return
106
-
107
- isLoading.value = true
108
-
109
- // Simulate API call
110
- await new Promise(resolve => setTimeout(resolve, 500))
111
-
112
- // Add the new user
113
- users.value.push({
114
- id: crypto.randomUUID(),
115
- name: newUser.name,
116
- email: newUser.email,
117
- created_at: new Date()
118
- })
119
-
120
- addedCount.value++
121
-
122
- // Reset form
123
- newUser.name = ''
124
- newUser.email = ''
125
- isLoading.value = false
126
- }
127
- </script>
@@ -1,71 +0,0 @@
1
- <template>
2
- <div class="space-y-4">
3
- <div class="relative">
4
- <input
5
- v-model="searchTerm"
6
- type="text"
7
- :placeholder="placeholder"
8
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
9
- />
10
- <div class="absolute inset-y-0 right-0 pr-3 flex items-center">
11
- <svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12
- <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" />
13
- </svg>
14
- </div>
15
- </div>
16
-
17
- <div class="text-sm text-gray-600">
18
- <span v-if="searchTerm">
19
- Searching for "{{ searchTerm }}"...
20
- </span>
21
- <span v-else>
22
- Search through {{ userCount }} users
23
- </span>
24
- </div>
25
-
26
- <div v-if="searchTerm && searchResults.length > 0" class="border rounded-lg p-4 bg-gray-50">
27
- <p class="text-sm font-medium text-gray-900 mb-2">Search Results:</p>
28
- <ul class="space-y-1">
29
- <li v-for="result in searchResults" :key="result" class="text-sm text-gray-700">
30
- • {{ result }}
31
- </li>
32
- </ul>
33
- </div>
34
-
35
- <div v-else-if="searchTerm" class="border rounded-lg p-4 bg-gray-50 text-center text-gray-500">
36
- No results found for "{{ searchTerm }}"
37
- </div>
38
- </div>
39
- </template>
40
-
41
- <script setup>
42
- import { ref, computed, watch } from 'vue'
43
-
44
- const props = defineProps({
45
- placeholder: { type: String, default: 'Search...' },
46
- userCount: { type: Number, default: 0 }
47
- })
48
-
49
- const searchTerm = ref('')
50
- const searchResults = computed(() => {
51
- if (!searchTerm.value) return []
52
-
53
- // Mock search results - in a real app, this would call an API
54
- const mockResults = [
55
- 'John Doe (john@example.com)',
56
- 'Jane Smith (jane@example.com)',
57
- 'Bob Johnson (bob@example.com)'
58
- ]
59
-
60
- return mockResults.filter(result =>
61
- result.toLowerCase().includes(searchTerm.value.toLowerCase())
62
- )
63
- })
64
-
65
- // Demo of reactive watchers
66
- watch(searchTerm, (newTerm) => {
67
- if (newTerm) {
68
- console.log(`Searching for: ${newTerm}`)
69
- }
70
- })
71
- </script>
@@ -1,63 +0,0 @@
1
- @layout('layouts/app')
2
-
3
- @section('content')
4
- <div class="px-4 py-6 sm:px-0">
5
- <div class="sm:flex sm:items-center">
6
- <div class="sm:flex-auto">
7
- <h1 class="text-2xl font-semibold text-gray-900">Users</h1>
8
- <p class="mt-2 text-sm text-gray-700">
9
- A list of all users in your application.
10
- </p>
11
- </div>
12
- </div>
13
-
14
- <div class="mt-8 flow-root">
15
- <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
16
- <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
17
- <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
18
- <table class="min-w-full divide-y divide-gray-300">
19
- <thead class="bg-gray-50">
20
- <tr>
21
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Name</th>
22
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Email</th>
23
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Created</th>
24
- </tr>
25
- </thead>
26
- <tbody class="bg-white divide-y divide-gray-200">
27
- @each(user in users)
28
- <tr @class([
29
- 'hover:bg-gray-50',
30
- 'bg-yellow-50' => $first,
31
- ])>
32
- <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
33
- {{ user.name }}
34
- </td>
35
- <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
36
- {{ user.email }}
37
- </td>
38
- <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
39
- {{ user.created_at?.toLocaleDateString() }}
40
- </td>
41
- </tr>
42
- @end
43
-
44
- @if(users.length === 0)
45
- <tr>
46
- <td colspan="3" class="px-6 py-12 text-center text-sm text-gray-500">
47
- No users found. Run <code class="bg-gray-100 px-2 py-1 rounded">bun strav seed</code> to add sample data.
48
- </td>
49
- </tr>
50
- @end
51
- </tbody>
52
- </table>
53
- </div>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- {{-- Interactive user management with Vue island --}}
59
- <div class="mt-8">
60
- <vue:user-manager :initialUsers="{{ JSON.stringify(users) }}" />
61
- </div>
62
- </div>
63
- @end