@manyrows/appkit-react
v1.1.0
Published
React SDK for Manyrows AppKit. Provides authentication, real-time data, and image management for customer-facing apps.
Readme
@manyrows/appkit-react
React SDK for Manyrows AppKit. Provides authentication, real-time data, and image management for customer-facing apps.
Installation
npm install @manyrows/appkit-reactQuick Start
import { AppKit, AppKitAuthed, useUser, UserButton } from "@manyrows/appkit-react";
function MyApp() {
const user = useUser();
return (
<div>
<header style={{ display: "flex", justifyContent: "space-between", padding: 16 }}>
<h1>My App</h1>
<UserButton />
</header>
{user && <p>Welcome, {user.name || user.email}</p>}
</div>
);
}
export default function Page() {
return (
<AppKit workspace="acme" appId="your-app-id">
<AppKitAuthed fallback={null}>
<MyApp />
</AppKitAuthed>
</AppKit>
);
}Only workspace and appId are required. The runtime script loads automatically.
Convenience Hooks
Access common data without drilling into snapshot:
import {
useUser, // user's account (email, name)
useRoles, // string[] of roles
usePermissions, // string[] of permissions
usePermission, // check a single permission: usePermission("edit")
useRole, // check a single role: useRole("admin")
useFeatureFlags, // all feature flags
useFeatureFlag, // check one flag: useFeatureFlag("dark-mode")
useConfig, // all config values
useConfigValue, // single config: useConfigValue("api-url", "default")
useToken, // JWT token for API calls
useAuthFetch, // fetch wrapper with automatic Bearer token
} from "@manyrows/appkit-react";Auth Patterns
Option A (recommended): Use <AppKitAuthed> to gate authenticated content:
<AppKit workspace="acme" appId="your-app-id">
<AppKitAuthed fallback={null}>
<MyApp />
</AppKitAuthed>
</AppKit>Option B: Gate manually with useAppKit():
<AppKit workspace="acme" appId="your-app-id">
{useAppKit().isAuthenticated ? <MyApp /> : null}
</AppKit>Images
The SDK includes a full image management system — upload, browse, display, edit, and delete — backed by the manyrows-images service.
Setup
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>.
useImages
Hook for listing, searching, editing, and deleting images.
import { useImages } from "@manyrows/appkit-react";
function Gallery() {
const {
images, // ImageResource[]
loading,
error,
page,
pageCount,
total,
setPage,
setQuery, // search by title
refetch,
removeImage, // (imageId) => Promise<void>
updateImage, // (imageId, { title, description }) => Promise<void>
available, // false if images service not configured
} = useImages({ page: 0, pageSize: 20 });
if (!available) return null;
// ...
}Options:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| page | number | 0 | Starting page (zero-indexed) |
| pageSize | number | 50 | Items per page |
| q | string | — | Search query |
| enabled | boolean | true | Enable/disable fetching |
useImageUpload
Hook for uploading images with progress tracking.
import { useImageUpload } from "@manyrows/appkit-react";
function Uploader() {
const { upload, cancel, progress, reset, available } = useImageUpload();
const handleUpload = async (file: File) => {
await upload({
file,
title: "My image",
description: "Optional description",
variants: "sq200,w400,w800",
});
};
return (
<div>
<p>Status: {progress.status}</p>
<p>Progress: {progress.progress}%</p>
<button onClick={cancel}>Cancel</button>
</div>
);
}Progress state:
| Field | Type | Description |
|-------|------|-------------|
| status | "idle" \| "uploading" \| "success" \| "error" | Current state |
| progress | number | 0–100 percentage |
| bytesUploaded | number | Bytes transferred |
| bytesTotal | number | Total file size |
| error | string | null | Error message |
MrImage
Responsive image component with automatic variant selection.
import { MrImage } from "@manyrows/appkit-react";
// Auto-selects best variant for dimensions + device DPR
<MrImage image={image} width={400} height={300} />
// Explicit variant
<MrImage image={image} variant="sq200" />
// Full-size original
<MrImage image={image} />Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| image | ImageResource | required | Image to display |
| width | number | — | Display width (used for variant selection) |
| height | number | — | Display height |
| variant | string | — | Explicit variant name (disables auto-selection) |
| alt | string | image.title | Alt text |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline styles |
| loading | "lazy" \| "eager" | "lazy" | Browser loading strategy |
| objectFit | CSSProperties["objectFit"] | "cover" | CSS object-fit |
| sizes | string | — | Responsive sizes attribute |
| onLoad | () => void | — | Load callback |
| onError | () => void | — | Error callback |
Generates a srcSet from available width-based variants for responsive loading.
ImageUploader
Drop-in upload form with drag-and-drop, preview, variant selection, and progress.
import { ImageUploader } from "@manyrows/appkit-react";
<ImageUploader
onUpload={() => console.log("Done!")}
onError={(err) => console.error(err)}
defaultTitle="Untitled"
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onUpload | () => void | — | Called on successful upload |
| onError | (error: string) => void | — | Called on upload error |
| defaultTitle | string | — | Pre-fill title |
| defaultDescription | string | "" | Pre-fill description |
| variants | string | "sq200,w400,w800" | Variant names to generate |
| showFields | boolean | true | Show title/description inputs |
| showVariantPicker | boolean | false | Show advanced variant selector |
| accept | string | "image/png,image/jpeg,..." | Accepted MIME types |
| maxSize | number | 4194304 | Max file size in bytes |
| label | string | "Drop an image here or click to select" | Drop zone text |
| disabled | boolean | false | Disable all interactions |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline styles |
Features:
- Drag-and-drop or click to select
- Image preview before upload
- File size validation
- Progress bar during upload
- Success toast (auto-dismisses after 4s)
- Auto-populates title from filename
- Collapsible "Advanced" section for variant selection
ImagePicker
Image grid browser with search, pagination, and optional inline actions.
import { ImagePicker } from "@manyrows/appkit-react";
// Inline grid
<ImagePicker
onSelect={(image) => setSelected(image)}
pageSize={20}
showSearch
showActions
/>
// Modal overlay
<ImagePicker
mode="modal"
onSelect={handleSelect}
onClose={() => setOpen(false)}
/>
// Refresh after upload
const [uploadCount, setUploadCount] = useState(0);
<ImageUploader onUpload={() => setUploadCount(c => c + 1)} />
<ImagePicker refreshSignal={uploadCount} />Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onSelect | (image: ImageResource) => void | — | Called when an image is clicked |
| onClose | () => void | — | Called when modal is closed |
| mode | "modal" \| "inline" | "inline" | Display mode |
| pageSize | number | 20 | Images per page |
| showSearch | boolean | true | Show search bar |
| searchPlaceholder | string | "Search images..." | Placeholder text |
| selectedImageId | string | — | Highlight selected image |
| columns | number | 4 | Grid columns |
| showActions | boolean | false | Show Info/Edit/Delete on hover |
| refreshSignal | number | — | Increment to trigger refetch |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline styles |
ImageDetails
Full-screen detail modal for a single image. Shows variant selector, metadata, copy-to-clipboard, and action buttons.
import { ImageDetails } from "@manyrows/appkit-react";
<ImageDetails
image={selectedImage}
onClose={() => setSelected(null)}
onEdit={(img) => openEditor(img)}
onDelete={(img) => confirmDelete(img)}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| image | ImageResource | required | Image to display |
| onClose | () => void | required | Close callback |
| onEdit | (image: ImageResource) => void | — | Edit button callback |
| onDelete | (image: ImageResource) => void | — | Delete button callback |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline styles |
Displays:
- Large image preview with variant selector tabs
- File size, dimensions, format
- Image ID and URL (both copyable)
- Upload date and description
Types
interface ImageResource {
imageId: string;
title: string;
description: string;
originalName: string;
uploadedAt: string;
uploadedBy?: string;
visibility: string; // "private" | "shared"
variants: ImageVariant[];
}
interface ImageVariant {
id: string;
variant: string; // "sq200", "w800", "orig", etc.
s3Key: string;
content_type: string;
format: string;
size_bytes: number;
sha256_hex: string;
ext: string;
width: number;
height: number;
etag: string;
versionId: string;
objectURL: string; // presigned CloudFront URL
expiresAt: string;
}
interface ImageListResponse {
images: ImageResource[];
page: number;
pageSize: number;
total: number;
pageCount: number;
}
interface ImageUploadOptions {
file: File;
title: string;
description?: string;
variants?: string; // comma-separated, e.g. "sq200,w400,w800"
visibility?: "private" | "shared"; // default: "private"
}
interface ImageUpdateOptions {
title: string;
description?: string;
visibility?: "private" | "shared";
}
interface UploadProgress {
status: "idle" | "uploading" | "success" | "error";
progress: number;
bytesUploaded: number;
bytesTotal: number;
error: string | null;
}Available Variants
| Name | Description |
|------|-------------|
| sq200 | Thumbnail (200x200 square crop) |
| sq400 | Square (400x400) |
| w400 | Small (400px wide) |
| w800 | Medium (800px wide) |
| w1200 | Large (1200px wide) |
| w1600 | Retina (1600px wide) |
| w1920 | Full HD (1920px wide) |
| orig | Original upload (always created) |
