@manyrows/appkit-react 0.1.5 → 0.1.7

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,59 +1,394 @@
1
- Customer usage
2
- import { AppKit, AppKitAuthed, useAppKit } from "@manyrows/appkit-react";
1
+ # @manyrows/appkit-react
3
2
 
4
- function CustomerApp() {
5
- const { snapshot, logout } = useAppKit();
3
+ React SDK for Manyrows AppKit. Provides authentication, real-time data, and image management for customer-facing apps.
6
4
 
7
- return (
8
- <div>
9
- <button onClick={() => logout()}>Logout</button>
10
- <pre>{JSON.stringify(snapshot?.appData, null, 2)}</pre>
11
- </div>
12
- );
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @manyrows/appkit-react
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { AppKit, AppKitAuthed, useUser, UserButton } from "@manyrows/appkit-react";
15
+
16
+ function MyApp() {
17
+ const user = useUser();
18
+
19
+ return (
20
+ <div>
21
+ <header style={{ display: "flex", justifyContent: "space-between", padding: 16 }}>
22
+ <h1>My App</h1>
23
+ <UserButton />
24
+ </header>
25
+ {user && <p>Welcome, {user.name || user.email}</p>}
26
+ </div>
27
+ );
13
28
  }
14
29
 
15
30
  export default function Page() {
16
- return (
17
- <div>
18
- <AppKit
19
- src={`${baseURL}/appkit/assets/appkit.js`}
20
- workspace="acme"
21
- project="drums"
22
- />
23
-
31
+ return (
32
+ <AppKit workspace="acme" appId="your-app-id">
24
33
  <AppKitAuthed fallback={null}>
25
- <CustomerApp />
34
+ <MyApp />
26
35
  </AppKitAuthed>
36
+ </AppKit>
37
+ );
38
+ }
39
+ ```
40
+
41
+ Only `workspace` and `appId` are required. The runtime script loads automatically.
42
+
43
+ ### Convenience Hooks
44
+
45
+ Access common data without drilling into `snapshot`:
46
+
47
+ ```tsx
48
+ import {
49
+ useUser, // user's account (email, name)
50
+ useRoles, // string[] of roles
51
+ usePermissions, // string[] of permissions
52
+ usePermission, // check a single permission: usePermission("edit")
53
+ useRole, // check a single role: useRole("admin")
54
+ useFeatureFlags, // all feature flags
55
+ useFeatureFlag, // check one flag: useFeatureFlag("dark-mode")
56
+ useConfig, // all config values
57
+ useConfigValue, // single config: useConfigValue("api-url", "default")
58
+ useToken, // JWT token for API calls
59
+ } from "@manyrows/appkit-react";
60
+ ```
61
+
62
+ ### Auth Patterns
63
+
64
+ **Option A (recommended):** Use `<AppKitAuthed>` to gate authenticated content:
65
+
66
+ ```tsx
67
+ <AppKit workspace="acme" appId="your-app-id">
68
+ <AppKitAuthed fallback={null}>
69
+ <MyApp />
70
+ </AppKitAuthed>
71
+ </AppKit>
72
+ ```
73
+
74
+ **Option B:** Gate manually with `useAppKit()`:
75
+
76
+ ```tsx
77
+ <AppKit workspace="acme" appId="your-app-id">
78
+ {useAppKit().isAuthenticated ? <MyApp /> : null}
79
+ </AppKit>
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Images
85
+
86
+ The SDK includes a full image management system — upload, browse, display, edit, and delete — backed by the `manyrows-images` service.
87
+
88
+ ### Setup
89
+
90
+ Images work automatically when the workspace has an `imagesBaseURL` configured. All image hooks and components read auth and config from the AppKit context, so they must be rendered inside `<AppKitAuthed>`.
91
+
92
+ ### useImages
93
+
94
+ Hook for listing, searching, editing, and deleting images.
95
+
96
+ ```tsx
97
+ import { useImages } from "@manyrows/appkit-react";
98
+
99
+ function Gallery() {
100
+ const {
101
+ images, // ImageResource[]
102
+ loading,
103
+ error,
104
+ page,
105
+ pageCount,
106
+ total,
107
+ setPage,
108
+ setQuery, // search by title
109
+ refetch,
110
+ removeImage, // (imageId) => Promise<void>
111
+ updateImage, // (imageId, { title, description }) => Promise<void>
112
+ available, // false if images service not configured
113
+ } = useImages({ page: 0, pageSize: 20 });
114
+
115
+ if (!available) return null;
116
+ // ...
117
+ }
118
+ ```
119
+
120
+ **Options:**
121
+
122
+ | Prop | Type | Default | Description |
123
+ |------|------|---------|-------------|
124
+ | `page` | number | 0 | Starting page (zero-indexed) |
125
+ | `pageSize` | number | 50 | Items per page |
126
+ | `q` | string | — | Search query |
127
+ | `enabled` | boolean | true | Enable/disable fetching |
128
+
129
+ ### useImageUpload
130
+
131
+ Hook for uploading images with progress tracking.
132
+
133
+ ```tsx
134
+ import { useImageUpload } from "@manyrows/appkit-react";
135
+
136
+ function Uploader() {
137
+ const { upload, cancel, progress, reset, available } = useImageUpload();
138
+
139
+ const handleUpload = async (file: File) => {
140
+ await upload({
141
+ file,
142
+ title: "My image",
143
+ description: "Optional description",
144
+ variants: "sq200,w400,w800",
145
+ });
146
+ };
147
+
148
+ return (
149
+ <div>
150
+ <p>Status: {progress.status}</p>
151
+ <p>Progress: {progress.progress}%</p>
152
+ <button onClick={cancel}>Cancel</button>
27
153
  </div>
28
- );
154
+ );
29
155
  }
156
+ ```
157
+
158
+ **Progress state:**
159
+
160
+ | Field | Type | Description |
161
+ |-------|------|-------------|
162
+ | `status` | `"idle" \| "uploading" \| "success" \| "error"` | Current state |
163
+ | `progress` | number | 0–100 percentage |
164
+ | `bytesUploaded` | number | Bytes transferred |
165
+ | `bytesTotal` | number | Total file size |
166
+ | `error` | string \| null | Error message |
30
167
 
31
- Option A (recommended): AppKitAuthed gate component
168
+ ### MrImage
32
169
 
33
- Pros
170
+ Responsive image component with automatic variant selection.
34
171
 
35
- Dead simple for customers.
172
+ ```tsx
173
+ import { MrImage } from "@manyrows/appkit-react";
36
174
 
37
- Doesn’t require them to understand snapshot shape.
175
+ // Auto-selects best variant for dimensions + device DPR
176
+ <MrImage image={image} width={400} height={300} />
38
177
 
39
- Lets you change snapshot internals later without breaking them.
178
+ // Explicit variant
179
+ <MrImage image={image} variant="sq200" />
40
180
 
41
- Customer code
181
+ // Full-size original
182
+ <MrImage image={image} />
183
+ ```
42
184
 
43
- <AppKit ... />
44
- <AppKitAuthed fallback={null}>
45
- <CustomerApp />
46
- </AppKitAuthed>
185
+ **Props:**
47
186
 
48
- Option B: only useAppKit() and customers gate themselves
187
+ | Prop | Type | Default | Description |
188
+ |------|------|---------|-------------|
189
+ | `image` | ImageResource | required | Image to display |
190
+ | `width` | number | — | Display width (used for variant selection) |
191
+ | `height` | number | — | Display height |
192
+ | `variant` | string | — | Explicit variant name (disables auto-selection) |
193
+ | `alt` | string | image.title | Alt text |
194
+ | `className` | string | — | CSS class |
195
+ | `style` | CSSProperties | — | Inline styles |
196
+ | `loading` | `"lazy" \| "eager"` | `"lazy"` | Browser loading strategy |
197
+ | `objectFit` | CSSProperties["objectFit"] | `"cover"` | CSS object-fit |
198
+ | `sizes` | string | — | Responsive sizes attribute |
199
+ | `onLoad` | () => void | — | Load callback |
200
+ | `onError` | () => void | — | Error callback |
49
201
 
50
- Pros
202
+ Generates a `srcSet` from available width-based variants for responsive loading.
51
203
 
52
- Fewer exports.
204
+ ### ImageUploader
53
205
 
54
- Very explicit.
206
+ Drop-in upload form with drag-and-drop, preview, variant selection, and progress.
207
+
208
+ ```tsx
209
+ import { ImageUploader } from "@manyrows/appkit-react";
210
+
211
+ <ImageUploader
212
+ onUpload={() => console.log("Done!")}
213
+ onError={(err) => console.error(err)}
214
+ defaultTitle="Untitled"
215
+ />
216
+ ```
217
+
218
+ **Props:**
219
+
220
+ | Prop | Type | Default | Description |
221
+ |------|------|---------|-------------|
222
+ | `onUpload` | () => void | — | Called on successful upload |
223
+ | `onError` | (error: string) => void | — | Called on upload error |
224
+ | `defaultTitle` | string | — | Pre-fill title |
225
+ | `defaultDescription` | string | `""` | Pre-fill description |
226
+ | `variants` | string | `"sq200,w400,w800"` | Variant names to generate |
227
+ | `showFields` | boolean | true | Show title/description inputs |
228
+ | `showVariantPicker` | boolean | false | Show advanced variant selector |
229
+ | `accept` | string | `"image/png,image/jpeg,..."` | Accepted MIME types |
230
+ | `maxSize` | number | 4194304 | Max file size in bytes |
231
+ | `label` | string | `"Drop an image here or click to select"` | Drop zone text |
232
+ | `disabled` | boolean | false | Disable all interactions |
233
+ | `className` | string | — | CSS class |
234
+ | `style` | CSSProperties | — | Inline styles |
235
+
236
+ Features:
237
+ - Drag-and-drop or click to select
238
+ - Image preview before upload
239
+ - File size validation
240
+ - Progress bar during upload
241
+ - Success toast (auto-dismisses after 4s)
242
+ - Auto-populates title from filename
243
+ - Collapsible "Advanced" section for variant selection
244
+
245
+ ### ImagePicker
246
+
247
+ Image grid browser with search, pagination, and optional inline actions.
248
+
249
+ ```tsx
250
+ import { ImagePicker } from "@manyrows/appkit-react";
251
+
252
+ // Inline grid
253
+ <ImagePicker
254
+ onSelect={(image) => setSelected(image)}
255
+ pageSize={20}
256
+ showSearch
257
+ showActions
258
+ />
259
+
260
+ // Modal overlay
261
+ <ImagePicker
262
+ mode="modal"
263
+ onSelect={handleSelect}
264
+ onClose={() => setOpen(false)}
265
+ />
266
+
267
+ // Refresh after upload
268
+ const [uploadCount, setUploadCount] = useState(0);
269
+ <ImageUploader onUpload={() => setUploadCount(c => c + 1)} />
270
+ <ImagePicker refreshSignal={uploadCount} />
271
+ ```
272
+
273
+ **Props:**
274
+
275
+ | Prop | Type | Default | Description |
276
+ |------|------|---------|-------------|
277
+ | `onSelect` | (image: ImageResource) => void | — | Called when an image is clicked |
278
+ | `onClose` | () => void | — | Called when modal is closed |
279
+ | `mode` | `"modal" \| "inline"` | `"inline"` | Display mode |
280
+ | `pageSize` | number | 20 | Images per page |
281
+ | `showSearch` | boolean | true | Show search bar |
282
+ | `searchPlaceholder` | string | `"Search images..."` | Placeholder text |
283
+ | `selectedImageId` | string | — | Highlight selected image |
284
+ | `columns` | number | 4 | Grid columns |
285
+ | `showActions` | boolean | false | Show Info/Edit/Delete on hover |
286
+ | `refreshSignal` | number | — | Increment to trigger refetch |
287
+ | `className` | string | — | CSS class |
288
+ | `style` | CSSProperties | — | Inline styles |
289
+
290
+ ### ImageDetails
291
+
292
+ Full-screen detail modal for a single image. Shows variant selector, metadata, copy-to-clipboard, and action buttons.
293
+
294
+ ```tsx
295
+ import { ImageDetails } from "@manyrows/appkit-react";
296
+
297
+ <ImageDetails
298
+ image={selectedImage}
299
+ onClose={() => setSelected(null)}
300
+ onEdit={(img) => openEditor(img)}
301
+ onDelete={(img) => confirmDelete(img)}
302
+ />
303
+ ```
304
+
305
+ **Props:**
306
+
307
+ | Prop | Type | Default | Description |
308
+ |------|------|---------|-------------|
309
+ | `image` | ImageResource | required | Image to display |
310
+ | `onClose` | () => void | required | Close callback |
311
+ | `onEdit` | (image: ImageResource) => void | — | Edit button callback |
312
+ | `onDelete` | (image: ImageResource) => void | — | Delete button callback |
313
+ | `className` | string | — | CSS class |
314
+ | `style` | CSSProperties | — | Inline styles |
315
+
316
+ Displays:
317
+ - Large image preview with variant selector tabs
318
+ - File size, dimensions, format
319
+ - Image ID and URL (both copyable)
320
+ - Upload date and description
321
+
322
+ ---
323
+
324
+ ## Types
325
+
326
+ ```typescript
327
+ interface ImageResource {
328
+ imageId: string;
329
+ title: string;
330
+ description: string;
331
+ originalName: string;
332
+ uploadedAt: string;
333
+ uploadedBy?: string;
334
+ variants: ImageVariant[];
335
+ }
336
+
337
+ interface ImageVariant {
338
+ id: string;
339
+ variant: string; // "sq200", "w800", "orig", etc.
340
+ s3Key: string;
341
+ content_type: string;
342
+ format: string;
343
+ size_bytes: number;
344
+ sha256_hex: string;
345
+ ext: string;
346
+ width: number;
347
+ height: number;
348
+ etag: string;
349
+ versionId: string;
350
+ objectURL: string; // presigned CloudFront URL
351
+ expiresAt: string;
352
+ }
353
+
354
+ interface ImageListResponse {
355
+ images: ImageResource[];
356
+ page: number;
357
+ pageSize: number;
358
+ total: number;
359
+ pageCount: number;
360
+ }
361
+
362
+ interface ImageUploadOptions {
363
+ file: File;
364
+ title: string;
365
+ description?: string;
366
+ variants?: string; // comma-separated, e.g. "sq200,w400,w800"
367
+ }
368
+
369
+ interface ImageUpdateOptions {
370
+ title: string;
371
+ description?: string;
372
+ }
373
+
374
+ interface UploadProgress {
375
+ status: "idle" | "uploading" | "success" | "error";
376
+ progress: number;
377
+ bytesUploaded: number;
378
+ bytesTotal: number;
379
+ error: string | null;
380
+ }
381
+ ```
55
382
 
56
- Customer code
383
+ ## Available Variants
57
384
 
58
- <AppKit ... />
59
- {useAppKit().isAuthenticated ? <CustomerApp /> : null}
385
+ | Name | Description |
386
+ |------|-------------|
387
+ | `sq200` | Thumbnail (200x200 square crop) |
388
+ | `sq400` | Square (400x400) |
389
+ | `w400` | Small (400px wide) |
390
+ | `w800` | Medium (800px wide) |
391
+ | `w1200` | Large (1200px wide) |
392
+ | `w1600` | Retina (1600px wide) |
393
+ | `w1920` | Full HD (1920px wide) |
394
+ | `orig` | Original upload (always created) |