@omnikit-js/ui 0.5.4 → 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/README.md +100 -255
- package/dist/components/client/index.d.mts +7 -0
- package/dist/components/client/index.mjs +6 -0
- package/dist/components/client/index.mjs.map +1 -0
- package/dist/components/server/index.d.mts +2 -0
- package/dist/components/server/index.mjs +201 -0
- package/dist/components/server/index.mjs.map +1 -0
- package/dist/index-D-5etDTV.d.mts +80 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +204 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -77
- package/LICENSE +0 -21
- package/dist/styles.css +0 -2486
- package/dist/styles.isolated.css +0 -2643
- package/dist/styles.prefixed.css +0 -248
- package/src/components/BillingClient.tsx +0 -686
- package/src/components/BillingServer.tsx +0 -129
- package/src/components/PaymentMethodForm.tsx +0 -125
- package/src/components/SubscriptionConfirmModal.tsx +0 -213
- package/src/components/theme-provider.tsx +0 -11
- package/src/components/ui/alert.tsx +0 -59
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/card-brand-icon.tsx +0 -88
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/chart.tsx +0 -353
- package/src/components/ui/dialog.tsx +0 -122
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/navigation-menu.tsx +0 -168
- package/src/components/ui/progress.tsx +0 -31
- package/src/components/ui/radio-group.tsx +0 -44
- package/src/components/ui/select.tsx +0 -185
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/tabs.tsx +0 -66
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/tooltip.tsx +0 -30
- package/src/index.ts +0 -13
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -1
- package/src/styles/isolated.css +0 -316
- package/src/styles/main.css +0 -95
- package/src/styles/prefixed-main.css +0 -95
- package/src/styles/prefixed.css +0 -1
package/README.md
CHANGED
|
@@ -1,300 +1,145 @@
|
|
|
1
1
|
# @omnikit-js/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 🚀 **Server Components Support** - Optimized for Next.js App Router
|
|
8
|
-
- 💳 **Stripe Integration** - Complete payment processing and subscription management
|
|
9
|
-
- 🎨 **Beautiful UI** - Built with Tailwind CSS v4 and Radix UI
|
|
10
|
-
- 📊 **Usage Tracking** - Monitor and display resource usage
|
|
11
|
-
- 🔄 **Subscription Management** - Handle upgrades, downgrades, and cancellations
|
|
12
|
-
- 💰 **Payment Methods** - Add, remove, and manage multiple payment methods
|
|
13
|
-
- 📜 **Billing History** - View and download past invoices
|
|
14
|
-
- 🔐 **Secure** - Server-side API key handling
|
|
15
|
-
- 🎯 **No Style Conflicts** - Prefixed CSS option to avoid conflicts with your app
|
|
3
|
+
React component library for OmniKit serverless platform. Provides both server and client components for building data-driven applications.
|
|
16
4
|
|
|
17
5
|
## Installation
|
|
18
6
|
|
|
19
7
|
```bash
|
|
20
8
|
npm install @omnikit-js/ui
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @omnikit-js/ui
|
|
11
|
+
# or
|
|
12
|
+
yarn add @omnikit-js/ui
|
|
21
13
|
```
|
|
22
14
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### Option 1: Standard CSS (For Apps Without Tailwind)
|
|
26
|
-
|
|
27
|
-
If your app doesn't use Tailwind CSS or you want to use the component's default styles:
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
// Import the CSS in your app's entry point (e.g., _app.tsx or layout.tsx)
|
|
31
|
-
import '@omnikit-js/ui/dist/styles.css'
|
|
15
|
+
## Components
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
import { Billing } from '@omnikit-js/ui'
|
|
35
|
-
|
|
36
|
-
export default function BillingPage() {
|
|
37
|
-
return (
|
|
38
|
-
<Billing
|
|
39
|
-
userId="user_123"
|
|
40
|
-
apiUrl="https://your-api.com"
|
|
41
|
-
apiKey="your-api-key"
|
|
42
|
-
/>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
```
|
|
17
|
+
### DataList
|
|
46
18
|
|
|
47
|
-
|
|
19
|
+
A server-side component that fetches and displays data from OmniKit API with zero client JavaScript.
|
|
48
20
|
|
|
49
|
-
|
|
21
|
+
**Features:**
|
|
22
|
+
- Server-side rendering (SSR)
|
|
23
|
+
- Automatic data fetching from OmniKit `/data/{table}` endpoints
|
|
24
|
+
- Support for filtering, sorting, pagination
|
|
25
|
+
- Tailwind CSS styling (customizable)
|
|
26
|
+
- TypeScript support
|
|
50
27
|
|
|
51
|
-
|
|
52
|
-
// Import the isolated CSS version
|
|
53
|
-
import '@omnikit-js/ui/dist/styles.isolated.css'
|
|
28
|
+
**Usage:**
|
|
54
29
|
|
|
55
|
-
|
|
56
|
-
import {
|
|
30
|
+
```tsx
|
|
31
|
+
import { DataList } from '@omnikit-js/ui'
|
|
57
32
|
|
|
58
|
-
export default function
|
|
33
|
+
export default async function UsersPage() {
|
|
59
34
|
return (
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
apiKey=
|
|
35
|
+
<DataList
|
|
36
|
+
table="users"
|
|
37
|
+
baseUrl={process.env.OMNIKIT_BASE_URL!}
|
|
38
|
+
apiKey={process.env.OMNIKIT_API_KEY!}
|
|
39
|
+
columns={['name', 'email', 'created_at']}
|
|
40
|
+
filter={{ active: true }}
|
|
41
|
+
limit={20}
|
|
64
42
|
/>
|
|
65
43
|
)
|
|
66
44
|
}
|
|
67
45
|
```
|
|
68
46
|
|
|
69
|
-
**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<h1 className="text-2xl font-bold mb-6">Billing & Subscription</h1>
|
|
110
|
-
<Billing
|
|
111
|
-
userId="user_123"
|
|
112
|
-
apiUrl={process.env.NEXT_PUBLIC_API_URL}
|
|
113
|
-
apiKey={process.env.API_KEY}
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Using Client Component Directly
|
|
121
|
-
|
|
122
|
-
If you need to use the client component directly (e.g., in a client-side only context):
|
|
123
|
-
|
|
124
|
-
```jsx
|
|
125
|
-
'use client';
|
|
126
|
-
|
|
127
|
-
import { BillingClient } from '@omnikit-js/ui';
|
|
128
|
-
|
|
129
|
-
export default function ClientBillingPage({ customerData, pricingData, paymentMethods }) {
|
|
130
|
-
return (
|
|
131
|
-
<BillingClient
|
|
132
|
-
customerData={customerData}
|
|
133
|
-
pricingData={pricingData}
|
|
134
|
-
paymentMethods={paymentMethods}
|
|
135
|
-
apiUrl="https://api.yourdomain.com"
|
|
136
|
-
apiKey="your-api-key"
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
47
|
+
**Props:**
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Required | Description |
|
|
50
|
+
|------|------|----------|-------------|
|
|
51
|
+
| `table` | `string` | Yes | Table name to query |
|
|
52
|
+
| `baseUrl` | `string` | Yes | OmniKit API base URL (e.g., `http://localhost:3000/omnikit`) |
|
|
53
|
+
| `apiKey` | `string` | No | API Key for authentication |
|
|
54
|
+
| `jwt` | `string` | No | JWT token (alternative to apiKey) |
|
|
55
|
+
| `columns` | `Column[]` | Yes | Columns to display |
|
|
56
|
+
| `keyField` | `string` | No | Row key field (default: `'id'`) |
|
|
57
|
+
| `filter` | `object` | No | Query filter |
|
|
58
|
+
| `sort` | `string` | No | Sort order (e.g., `'name:asc'`) |
|
|
59
|
+
| `limit` | `number` | No | Max records to fetch |
|
|
60
|
+
| `offset` | `number` | No | Pagination offset |
|
|
61
|
+
| `className` | `string` | No | Custom container class |
|
|
62
|
+
| `emptyMessage` | `string` | No | Message when no data |
|
|
63
|
+
| `errorMessage` | `string` | No | Error message |
|
|
64
|
+
|
|
65
|
+
**Custom Columns:**
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<DataList
|
|
69
|
+
table="users"
|
|
70
|
+
baseUrl={baseUrl}
|
|
71
|
+
apiKey={apiKey}
|
|
72
|
+
columns={[
|
|
73
|
+
'name',
|
|
74
|
+
'email',
|
|
75
|
+
{
|
|
76
|
+
key: 'created_at',
|
|
77
|
+
label: 'Joined',
|
|
78
|
+
render: (value) => new Date(value).toLocaleDateString(),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: 'active',
|
|
82
|
+
label: 'Status',
|
|
83
|
+
render: (value) => value ? '✓ Active' : '✗ Inactive',
|
|
84
|
+
},
|
|
85
|
+
]}
|
|
86
|
+
/>
|
|
140
87
|
```
|
|
141
88
|
|
|
142
|
-
##
|
|
143
|
-
|
|
144
|
-
If you experience style conflicts, especially with progress bars or grid layouts:
|
|
89
|
+
## API Client
|
|
145
90
|
|
|
146
|
-
|
|
91
|
+
Use the OmniKit client for custom data fetching:
|
|
147
92
|
|
|
148
|
-
|
|
93
|
+
```tsx
|
|
94
|
+
import { createOmniKitClient } from '@omnikit-js/ui'
|
|
149
95
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
96
|
+
const client = createOmniKitClient({
|
|
97
|
+
baseUrl: 'http://localhost:3000/omnikit',
|
|
98
|
+
apiKey: 'your-api-key',
|
|
99
|
+
})
|
|
155
100
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
```
|
|
101
|
+
// Query data
|
|
102
|
+
const users = await client.query('users', {
|
|
103
|
+
filter: { active: true },
|
|
104
|
+
sort: 'name:asc',
|
|
105
|
+
limit: 10,
|
|
106
|
+
})
|
|
163
107
|
|
|
164
|
-
|
|
108
|
+
// Find by ID
|
|
109
|
+
const user = await client.findById('users', '123')
|
|
165
110
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```css
|
|
169
|
-
/* Create an isolation container */
|
|
170
|
-
.billing-wrapper {
|
|
171
|
-
/* Reset conflicting styles */
|
|
172
|
-
all: initial;
|
|
173
|
-
font-family: inherit;
|
|
174
|
-
color: inherit;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.billing-wrapper * {
|
|
178
|
-
box-sizing: border-box;
|
|
179
|
-
}
|
|
111
|
+
// Insert
|
|
112
|
+
await client.insert('users', { name: 'John', email: 'john@example.com' })
|
|
180
113
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
display: grid !important;
|
|
184
|
-
gap: 1rem !important;
|
|
185
|
-
}
|
|
186
|
-
```
|
|
114
|
+
// Update
|
|
115
|
+
await client.update('users', '123', { name: 'Jane' })
|
|
187
116
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<Billing userId="user_123" />
|
|
191
|
-
</div>
|
|
117
|
+
// Delete
|
|
118
|
+
await client.delete('users', '123')
|
|
192
119
|
```
|
|
193
120
|
|
|
194
|
-
##
|
|
195
|
-
|
|
196
|
-
### Billing (Server Component)
|
|
197
|
-
|
|
198
|
-
| Prop | Type | Default | Description |
|
|
199
|
-
|------|------|---------|-------------|
|
|
200
|
-
| userId | string | required | The user ID to fetch billing data for |
|
|
201
|
-
| apiUrl | string | `process.env.NEXT_PUBLIC_API_URL` | The API base URL |
|
|
202
|
-
| apiKey | string | `process.env.API_KEY` | The API key for authentication |
|
|
203
|
-
| projectId | string | `process.env.NEXT_PUBLIC_API_PROJECT` | The project ID for multi-tenant support |
|
|
204
|
-
|
|
205
|
-
### BillingClient (Client Component)
|
|
121
|
+
## Development
|
|
206
122
|
|
|
207
|
-
|
|
208
|
-
|------|------|-------------|
|
|
209
|
-
| customerData | object | Customer data including subscriptions and usage |
|
|
210
|
-
| pricingData | object | Available pricing plans and products |
|
|
211
|
-
| paymentMethods | object | User's payment methods |
|
|
212
|
-
| apiUrl | string | The API base URL |
|
|
213
|
-
| apiKey | string | The API key for authentication |
|
|
123
|
+
### Quick Start
|
|
214
124
|
|
|
215
|
-
|
|
125
|
+
Run library watch build + demo app:
|
|
216
126
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# API Configuration
|
|
221
|
-
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
222
|
-
API_KEY=your-secret-api-key
|
|
223
|
-
|
|
224
|
-
# Stripe Configuration
|
|
225
|
-
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key
|
|
226
|
-
|
|
227
|
-
# Project Configuration (optional)
|
|
228
|
-
NEXT_PUBLIC_API_PROJECT=1
|
|
127
|
+
```bash
|
|
128
|
+
cd /Users/marcoschwartz/Documents/code/frontends/omnikit/omnikit-ui
|
|
129
|
+
./dev.sh
|
|
229
130
|
```
|
|
230
131
|
|
|
231
|
-
|
|
132
|
+
Visit: **http://localhost:3060**
|
|
232
133
|
|
|
233
|
-
|
|
134
|
+
See [QUICKSTART.md](QUICKSTART.md) for detailed instructions.
|
|
234
135
|
|
|
235
|
-
|
|
236
|
-
- `GET /billing/prices?project_id=:projectId` - Fetch pricing plans
|
|
237
|
-
- `GET /billing/payment-methods?user_id=:userId` - Fetch payment methods
|
|
238
|
-
- `POST /billing/setup-intent` - Create Stripe setup intent
|
|
239
|
-
- `POST /billing/subscriptions` - Create subscription
|
|
240
|
-
- `DELETE /billing/subscriptions/:id` - Cancel subscription
|
|
241
|
-
- `POST /billing/payment-methods/:id/default` - Set default payment method
|
|
242
|
-
- `DELETE /billing/payment-methods/:id` - Delete payment method
|
|
136
|
+
### Tech Stack
|
|
243
137
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
1. **Progress bar showing wrong color**
|
|
249
|
-
- Use the prefixed CSS version
|
|
250
|
-
- Override `.omnikit-progress-fill` with `!important`
|
|
251
|
-
|
|
252
|
-
2. **Grid layout breaking on pricing page**
|
|
253
|
-
- Check for global grid styles in your app
|
|
254
|
-
- Use the isolation container approach shown above
|
|
255
|
-
- Ensure no conflicting CSS Grid properties
|
|
256
|
-
|
|
257
|
-
3. **Modal z-index conflicts**
|
|
258
|
-
- Component modals use `z-50` (z-index: 50)
|
|
259
|
-
- Adjust your app's z-index scale if needed
|
|
260
|
-
|
|
261
|
-
4. **Font inconsistencies**
|
|
262
|
-
- The component inherits font-family
|
|
263
|
-
- Set font on a parent element
|
|
264
|
-
|
|
265
|
-
5. **Styles not loading**
|
|
266
|
-
- Ensure you're importing the CSS file
|
|
267
|
-
- Check the correct path: `@omnikit-js/ui/dist/styles.css` or `@omnikit-js/ui/dist/styles.isolated.css`
|
|
268
|
-
- For apps with style conflicts, use the isolated version
|
|
269
|
-
|
|
270
|
-
## Version History
|
|
271
|
-
|
|
272
|
-
- **0.5.3** - Added isolated CSS option for better style conflict resolution
|
|
273
|
-
- **0.5.2** - Fixed CSS exports in package.json
|
|
274
|
-
- **0.5.1** - Tailwind CSS v4 support with prefix option
|
|
275
|
-
- **0.3.5** - Previous version with Tailwind CSS v3
|
|
276
|
-
|
|
277
|
-
## Browser Support
|
|
278
|
-
|
|
279
|
-
Requires modern browsers:
|
|
280
|
-
- Safari 16.4+
|
|
281
|
-
- Chrome 111+
|
|
282
|
-
- Firefox 128+
|
|
283
|
-
|
|
284
|
-
For older browser support, use version 0.3.5.
|
|
285
|
-
|
|
286
|
-
## TypeScript
|
|
287
|
-
|
|
288
|
-
The package includes TypeScript definitions. No additional setup required.
|
|
138
|
+
- **React 18/19** - Peer dependency
|
|
139
|
+
- **TypeScript** - Full type safety
|
|
140
|
+
- **Tailwind CSS v4** - Styling (requires Tailwind in your project)
|
|
141
|
+
- **tsup** - Build tooling
|
|
289
142
|
|
|
290
143
|
## License
|
|
291
144
|
|
|
292
145
|
MIT
|
|
293
|
-
|
|
294
|
-
## Contributing
|
|
295
|
-
|
|
296
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
297
|
-
|
|
298
|
-
## Support
|
|
299
|
-
|
|
300
|
-
For issues and feature requests, please use the [GitHub issue tracker](https://github.com/omnikit-ui/billing/issues).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/client/index.ts"],"names":[],"mappings":";AAMO,IAAM,sBAAA,GAAyB","file":"index.mjs","sourcesContent":["/**\n * Client Components\n * These components run on the client side\n */\n\n// Placeholder for future client components\nexport const OMNIKIT_CLIENT_VERSION = '0.1.0'\n"]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/lib/omnikit-client.ts
|
|
4
|
+
var OmniKitClient = class {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7
|
+
this.apiKey = config.apiKey;
|
|
8
|
+
this.jwt = config.jwt;
|
|
9
|
+
}
|
|
10
|
+
getHeaders() {
|
|
11
|
+
const headers = {
|
|
12
|
+
"Content-Type": "application/json"
|
|
13
|
+
};
|
|
14
|
+
if (this.jwt) {
|
|
15
|
+
headers["Authorization"] = `Bearer ${this.jwt}`;
|
|
16
|
+
} else if (this.apiKey) {
|
|
17
|
+
headers["X-API-Key"] = this.apiKey;
|
|
18
|
+
}
|
|
19
|
+
return headers;
|
|
20
|
+
}
|
|
21
|
+
async request(endpoint, options) {
|
|
22
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
...options,
|
|
25
|
+
headers: {
|
|
26
|
+
...this.getHeaders(),
|
|
27
|
+
...options?.headers
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
32
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
return response.json();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Query records from a table
|
|
38
|
+
*/
|
|
39
|
+
async query(table, options) {
|
|
40
|
+
const params = new URLSearchParams();
|
|
41
|
+
if (options?.filter) {
|
|
42
|
+
params.append("filter", JSON.stringify(options.filter));
|
|
43
|
+
}
|
|
44
|
+
if (options?.sort) {
|
|
45
|
+
params.append("sort", options.sort);
|
|
46
|
+
}
|
|
47
|
+
if (options?.limit) {
|
|
48
|
+
params.append("limit", options.limit.toString());
|
|
49
|
+
}
|
|
50
|
+
if (options?.offset) {
|
|
51
|
+
params.append("offset", options.offset.toString());
|
|
52
|
+
}
|
|
53
|
+
const queryString = params.toString();
|
|
54
|
+
const endpoint = `/data/${table}${queryString ? `?${queryString}` : ""}`;
|
|
55
|
+
return this.request(endpoint);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Find a record by ID
|
|
59
|
+
*/
|
|
60
|
+
async findById(table, id) {
|
|
61
|
+
return this.request(`/data/${table}/${id}`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Insert a new record
|
|
65
|
+
*/
|
|
66
|
+
async insert(table, data) {
|
|
67
|
+
return this.request(`/data/${table}`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: JSON.stringify(data)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Update an existing record
|
|
74
|
+
*/
|
|
75
|
+
async update(table, id, data) {
|
|
76
|
+
return this.request(`/data/${table}/${id}`, {
|
|
77
|
+
method: "PUT",
|
|
78
|
+
body: JSON.stringify(data)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Delete a record
|
|
83
|
+
*/
|
|
84
|
+
async delete(table, id) {
|
|
85
|
+
return this.request(`/data/${table}/${id}`, {
|
|
86
|
+
method: "DELETE"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Upsert a record (insert or update)
|
|
91
|
+
*/
|
|
92
|
+
async upsert(table, data) {
|
|
93
|
+
return this.request(`/data/${table}`, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
body: JSON.stringify(data)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function createOmniKitClient(config) {
|
|
100
|
+
return new OmniKitClient(config);
|
|
101
|
+
}
|
|
102
|
+
function normalizeColumn(column) {
|
|
103
|
+
if (typeof column === "string") {
|
|
104
|
+
return {
|
|
105
|
+
key: column,
|
|
106
|
+
label: column.charAt(0).toUpperCase() + column.slice(1).replace(/_/g, " "),
|
|
107
|
+
sortable: true
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...column,
|
|
112
|
+
label: column.label || column.key.charAt(0).toUpperCase() + column.key.slice(1).replace(/_/g, " ")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getCellValue(row, key) {
|
|
116
|
+
return key.split(".").reduce((obj, k) => obj?.[k], row);
|
|
117
|
+
}
|
|
118
|
+
async function DataList({
|
|
119
|
+
table,
|
|
120
|
+
baseUrl,
|
|
121
|
+
apiKey,
|
|
122
|
+
jwt,
|
|
123
|
+
columns,
|
|
124
|
+
keyField = "id",
|
|
125
|
+
filter,
|
|
126
|
+
sort,
|
|
127
|
+
limit,
|
|
128
|
+
offset,
|
|
129
|
+
className = "",
|
|
130
|
+
headerClassName = "",
|
|
131
|
+
rowClassName = "",
|
|
132
|
+
cellClassName = "",
|
|
133
|
+
emptyMessage = "No data found",
|
|
134
|
+
errorMessage = "Failed to load data"
|
|
135
|
+
}) {
|
|
136
|
+
const client = createOmniKitClient({ baseUrl, apiKey, jwt });
|
|
137
|
+
let data = [];
|
|
138
|
+
let error = null;
|
|
139
|
+
try {
|
|
140
|
+
const response = await client.query(table, {
|
|
141
|
+
filter,
|
|
142
|
+
sort,
|
|
143
|
+
limit,
|
|
144
|
+
offset
|
|
145
|
+
});
|
|
146
|
+
if (response.success) {
|
|
147
|
+
data = response.data;
|
|
148
|
+
} else {
|
|
149
|
+
error = response.error || errorMessage;
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
error = err instanceof Error ? err.message : errorMessage;
|
|
153
|
+
}
|
|
154
|
+
const normalizedColumns = columns.map(normalizeColumn);
|
|
155
|
+
if (error) {
|
|
156
|
+
return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-4 text-red-800 dark:text-red-200 ${className}`, children: [
|
|
157
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "Error" }),
|
|
158
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: error })
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
if (data.length === 0) {
|
|
162
|
+
return /* @__PURE__ */ jsx("div", { className: `rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-8 text-center text-gray-600 dark:text-gray-400 ${className}`, children: /* @__PURE__ */ jsx("p", { children: emptyMessage }) });
|
|
163
|
+
}
|
|
164
|
+
return /* @__PURE__ */ jsx("div", { className: `overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 ${className}`, children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-gray-200 dark:divide-gray-700", children: [
|
|
165
|
+
/* @__PURE__ */ jsx("thead", { className: `bg-gray-50 dark:bg-gray-800 ${headerClassName}`, children: /* @__PURE__ */ jsx("tr", { children: normalizedColumns.map((column) => /* @__PURE__ */ jsx(
|
|
166
|
+
"th",
|
|
167
|
+
{
|
|
168
|
+
className: `px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-700 dark:text-gray-300 ${column.className || ""}`,
|
|
169
|
+
children: column.label
|
|
170
|
+
},
|
|
171
|
+
column.key
|
|
172
|
+
)) }) }),
|
|
173
|
+
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-900", children: data.map((row) => {
|
|
174
|
+
const rowKey = String(row[keyField]);
|
|
175
|
+
const computedRowClassName = typeof rowClassName === "function" ? rowClassName(row) : rowClassName;
|
|
176
|
+
return /* @__PURE__ */ jsx(
|
|
177
|
+
"tr",
|
|
178
|
+
{
|
|
179
|
+
className: `hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${computedRowClassName}`,
|
|
180
|
+
children: normalizedColumns.map((column) => {
|
|
181
|
+
const cellValue = getCellValue(row, column.key);
|
|
182
|
+
const computedCellClassName = typeof cellClassName === "function" ? cellClassName(column, row) : cellClassName;
|
|
183
|
+
return /* @__PURE__ */ jsx(
|
|
184
|
+
"td",
|
|
185
|
+
{
|
|
186
|
+
className: `px-6 py-4 text-sm text-gray-900 dark:text-gray-100 ${computedCellClassName}`,
|
|
187
|
+
children: column.render ? column.render(cellValue, row) : cellValue
|
|
188
|
+
},
|
|
189
|
+
column.key
|
|
190
|
+
);
|
|
191
|
+
})
|
|
192
|
+
},
|
|
193
|
+
rowKey
|
|
194
|
+
);
|
|
195
|
+
}) })
|
|
196
|
+
] }) });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { DataList };
|
|
200
|
+
//# sourceMappingURL=index.mjs.map
|
|
201
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/omnikit-client.ts","../../../src/components/server/DataList/index.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * OmniKit API Client\n * Wrapper for making requests to OmniKit serverless platform\n */\n\nimport type {\n QueryOptions,\n QueryResponse,\n SingleRecordResponse,\n MutationResponse,\n OmniKitConfig,\n} from './types'\n\nexport class OmniKitClient {\n private baseUrl: string\n private apiKey?: string\n private jwt?: string\n\n constructor(config: OmniKitConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '') // Remove trailing slash\n this.apiKey = config.apiKey\n this.jwt = config.jwt\n }\n\n private getHeaders(): HeadersInit {\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n }\n\n if (this.jwt) {\n headers['Authorization'] = `Bearer ${this.jwt}`\n } else if (this.apiKey) {\n headers['X-API-Key'] = this.apiKey\n }\n\n return headers\n }\n\n private async request<T>(\n endpoint: string,\n options?: RequestInit\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.getHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Request failed' }))\n throw new Error(error.error || `HTTP ${response.status}`)\n }\n\n return response.json()\n }\n\n /**\n * Query records from a table\n */\n async query<T = any>(\n table: string,\n options?: QueryOptions\n ): Promise<QueryResponse<T>> {\n const params = new URLSearchParams()\n\n if (options?.filter) {\n params.append('filter', JSON.stringify(options.filter))\n }\n if (options?.sort) {\n params.append('sort', options.sort)\n }\n if (options?.limit) {\n params.append('limit', options.limit.toString())\n }\n if (options?.offset) {\n params.append('offset', options.offset.toString())\n }\n\n const queryString = params.toString()\n const endpoint = `/data/${table}${queryString ? `?${queryString}` : ''}`\n\n return this.request<QueryResponse<T>>(endpoint)\n }\n\n /**\n * Find a record by ID\n */\n async findById<T = any>(\n table: string,\n id: string | number\n ): Promise<SingleRecordResponse<T>> {\n return this.request<SingleRecordResponse<T>>(`/data/${table}/${id}`)\n }\n\n /**\n * Insert a new record\n */\n async insert(\n table: string,\n data: Record<string, any>\n ): Promise<MutationResponse> {\n return this.request<MutationResponse>(`/data/${table}`, {\n method: 'POST',\n body: JSON.stringify(data),\n })\n }\n\n /**\n * Update an existing record\n */\n async update(\n table: string,\n id: string | number,\n data: Record<string, any>\n ): Promise<MutationResponse> {\n return this.request<MutationResponse>(`/data/${table}/${id}`, {\n method: 'PUT',\n body: JSON.stringify(data),\n })\n }\n\n /**\n * Delete a record\n */\n async delete(table: string, id: string | number): Promise<MutationResponse> {\n return this.request<MutationResponse>(`/data/${table}/${id}`, {\n method: 'DELETE',\n })\n }\n\n /**\n * Upsert a record (insert or update)\n */\n async upsert(\n table: string,\n data: Record<string, any>\n ): Promise<MutationResponse> {\n return this.request<MutationResponse>(`/data/${table}`, {\n method: 'PUT',\n body: JSON.stringify(data),\n })\n }\n}\n\n/**\n * Create a new OmniKit client instance\n */\nexport function createOmniKitClient(config: OmniKitConfig): OmniKitClient {\n return new OmniKitClient(config)\n}\n","/**\n * DataList Component\n *\n * A server-side React component that fetches and displays data from OmniKit API.\n * Renders on the server with zero client JavaScript for data fetching.\n *\n * @example\n * ```tsx\n * import { DataList } from '@omnikit-js/ui'\n *\n * export default function UsersPage() {\n * return (\n * <DataList\n * table=\"users\"\n * baseUrl={process.env.OMNIKIT_BASE_URL}\n * apiKey={process.env.OMNIKIT_API_KEY}\n * columns={['name', 'email', 'created_at']}\n * filter={{ active: true }}\n * limit={20}\n * />\n * )\n * }\n * ```\n */\n\nimport { createOmniKitClient } from '../../../lib/omnikit-client'\nimport type { DataListProps, Column, ColumnDefinition } from './types'\n\nfunction normalizeColumn<T>(column: Column<T>): ColumnDefinition<T> {\n if (typeof column === 'string') {\n return {\n key: column,\n label: column.charAt(0).toUpperCase() + column.slice(1).replace(/_/g, ' '),\n sortable: true,\n }\n }\n return {\n ...column,\n label: column.label || column.key.charAt(0).toUpperCase() + column.key.slice(1).replace(/_/g, ' '),\n }\n}\n\nfunction getCellValue(row: any, key: string): any {\n return key.split('.').reduce((obj, k) => obj?.[k], row)\n}\n\nexport async function DataList<T extends Record<string, any>>({\n table,\n baseUrl,\n apiKey,\n jwt,\n columns,\n keyField = 'id' as keyof T,\n filter,\n sort,\n limit,\n offset,\n className = '',\n headerClassName = '',\n rowClassName = '',\n cellClassName = '',\n emptyMessage = 'No data found',\n errorMessage = 'Failed to load data',\n}: DataListProps<T>) {\n // Create API client\n const client = createOmniKitClient({ baseUrl, apiKey, jwt })\n\n // Fetch data\n let data: T[] = []\n let error: string | null = null\n\n try {\n const response = await client.query<T>(table, {\n filter,\n sort,\n limit,\n offset,\n })\n\n if (response.success) {\n data = response.data\n } else {\n error = response.error || errorMessage\n }\n } catch (err) {\n error = err instanceof Error ? err.message : errorMessage\n }\n\n // Normalize columns\n const normalizedColumns = columns.map(normalizeColumn)\n\n // Error state\n if (error) {\n return (\n <div className={`rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-4 text-red-800 dark:text-red-200 ${className}`}>\n <p className=\"font-medium\">Error</p>\n <p className=\"mt-1 text-sm\">{error}</p>\n </div>\n )\n }\n\n // Empty state\n if (data.length === 0) {\n return (\n <div className={`rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-8 text-center text-gray-600 dark:text-gray-400 ${className}`}>\n <p>{emptyMessage}</p>\n </div>\n )\n }\n\n // Render table\n return (\n <div className={`overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 ${className}`}>\n <table className=\"min-w-full divide-y divide-gray-200 dark:divide-gray-700\">\n <thead className={`bg-gray-50 dark:bg-gray-800 ${headerClassName}`}>\n <tr>\n {normalizedColumns.map((column) => (\n <th\n key={column.key}\n className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-700 dark:text-gray-300 ${column.className || ''}`}\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-900\">\n {data.map((row) => {\n const rowKey = String(row[keyField])\n const computedRowClassName = typeof rowClassName === 'function'\n ? rowClassName(row)\n : rowClassName\n\n return (\n <tr\n key={rowKey}\n className={`hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${computedRowClassName}`}\n >\n {normalizedColumns.map((column) => {\n const cellValue = getCellValue(row, column.key)\n const computedCellClassName = typeof cellClassName === 'function'\n ? cellClassName(column, row)\n : cellClassName\n\n return (\n <td\n key={column.key}\n className={`px-6 py-4 text-sm text-gray-900 dark:text-gray-100 ${computedCellClassName}`}\n >\n {column.render ? column.render(cellValue, row) : cellValue}\n </td>\n )\n })}\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )\n}\n"]}
|