@proveanything/smartlinks 1.9.20 → 1.9.22
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/docs/API_SUMMARY.md +3 -1
- package/dist/docs/app-manifest.md +38 -1
- package/dist/docs/auth-kit.md +162 -0
- package/dist/docs/forms.md +113 -0
- package/dist/docs/overview.md +4 -0
- package/dist/docs/records-admin-pattern.md +315 -0
- package/dist/docs/ui-utils.md +294 -0
- package/dist/openapi.yaml +6 -0
- package/dist/types/collection.d.ts +2 -0
- package/docs/API_SUMMARY.md +3 -1
- package/docs/app-manifest.md +38 -1
- package/docs/auth-kit.md +162 -0
- package/docs/forms.md +113 -0
- package/docs/overview.md +4 -0
- package/docs/records-admin-pattern.md +315 -0
- package/docs/ui-utils.md +294 -0
- package/openapi.yaml +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# SmartLinks UI Utils (`@proveanything/smartlinks-utils-ui`)
|
|
2
|
+
|
|
3
|
+
> Companion React component library for the SmartLinks SDK. Ships the heavy, opinionated admin UI pieces that almost every SmartLinks microapp ends up needing — built once, theme-able, tree-shakeable, and wired straight into the SmartLinks SDK.
|
|
4
|
+
>
|
|
5
|
+
> Package: `@proveanything/smartlinks-utils-ui`
|
|
6
|
+
> Tracks: `@proveanything/smartlinks ≥ 1.9`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What is this module for?
|
|
11
|
+
|
|
12
|
+
`@proveanything/smartlinks-utils-ui` sits on top of `@proveanything/smartlinks`. The core SDK handles data — records, configurations, interactions. This module handles **UI** — the shared React components, hooks, and admin shells that translate SDK data into consistent admin interfaces.
|
|
13
|
+
|
|
14
|
+
**When do you need it?**
|
|
15
|
+
|
|
16
|
+
- You are building an admin UI for a records-based microapp (see [records-admin-pattern.md](records-admin-pattern.md))
|
|
17
|
+
- You need a media asset picker, icon picker, or font picker in an admin panel
|
|
18
|
+
- You need a recursive rule/conditions editor for targeting or audience logic
|
|
19
|
+
- You want the standard inheritance/override editor for scoped records
|
|
20
|
+
- You need the `useResolvedRecord` hook on the public widget side
|
|
21
|
+
|
|
22
|
+
You do **not** need it for apps that only use `appConfiguration`, basic widgets without scoped data, or executor bundles.
|
|
23
|
+
|
|
24
|
+
> **Admin-only**: all components call the SDK with `admin: true`. Do not render them in public-facing views.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @proveanything/smartlinks-utils-ui
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Peer dependencies (you already have these in a SmartLinks app):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install react react-dom @proveanything/smartlinks
|
|
38
|
+
# Recommended — enables caching and pagination in Records Admin Shell:
|
|
39
|
+
npm install @tanstack/react-query
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Import the compiled styles **once** in your app entry (e.g. `main.tsx`):
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import '@proveanything/smartlinks-utils-ui/styles.css';
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Components inherit your shadcn-compatible CSS variables (`--primary`, `--background`, `--border`, …) so they pick up your theme automatically.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## What's in the box
|
|
53
|
+
|
|
54
|
+
| Module | What it is | When to reach for it |
|
|
55
|
+
|--------|------------|----------------------|
|
|
56
|
+
| [Records Admin Shell](#records-admin-shell) | Full admin UI for `app.records` with scope inheritance | Per-product / per-variant / per-batch / per-facet config tools |
|
|
57
|
+
| [Asset Picker](#asset-picker) | Browse / upload / paste / URL-import images and files | Any time the admin needs to pick or upload media |
|
|
58
|
+
| [Icon Picker](#icon-picker) | Searchable Font Awesome 7 Pro picker | Configurable buttons, badges, menus, tiles |
|
|
59
|
+
| [Font Picker](#font-picker) | Google Fonts + custom uploaded fonts | Theme editors, brand customisation panels |
|
|
60
|
+
| [Conditions Editor](#conditions-editor) | Recursive AND/OR rule builder for 12 condition types | Targeting, gating, audience rules, segmentation |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Records Admin Shell
|
|
65
|
+
|
|
66
|
+
The primary export. A complete admin UI for managing `app.records` — typed JSON blobs attached to facets, products, variants, and batches — with scope inheritance built in. You provide the form for one record; the shell handles everything else.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import {
|
|
70
|
+
RecordsAdminShell,
|
|
71
|
+
InheritanceMarker,
|
|
72
|
+
type EditorContext,
|
|
73
|
+
} from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
74
|
+
import * as SL from '@proveanything/smartlinks';
|
|
75
|
+
|
|
76
|
+
<RecordsAdminShell<NutritionData>
|
|
77
|
+
SL={SL}
|
|
78
|
+
collectionId={collectionId}
|
|
79
|
+
appId={appId}
|
|
80
|
+
recordType="nutrition"
|
|
81
|
+
label="Nutrition info"
|
|
82
|
+
scopes={['facet', 'product', 'variant', 'batch']}
|
|
83
|
+
contextScope={{ productId, variantId, batchId }} // from iframe URL — optional
|
|
84
|
+
defaultData={() => ({})}
|
|
85
|
+
csvSchema={{ columns: [/* ... */] }}
|
|
86
|
+
renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
|
|
87
|
+
renderPreview={({ resolved }) => <pre>{JSON.stringify(resolved, null, 2)}</pre>}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**What it handles:**
|
|
92
|
+
|
|
93
|
+
- **Browser pane** with scope tabs (Facet / Product / Variant / Batch), search, and status filter pills (All / Configured / Partial / Empty)
|
|
94
|
+
- **Editor pane** with sticky save / discard / delete footer
|
|
95
|
+
- **Per-field `<InheritanceMarker>`** showing whether a value is the record's own or inherited from a parent scope, with one-click revert-to-inherited
|
|
96
|
+
- **Inheritance resolver** walks `batch → variant → product → facet` and returns both the resolved and parent values
|
|
97
|
+
- **Collection-aware tabs**: calls `collection.get` and hides Variants / Batches tabs unless `collection.variants` / `collection.batches` are true — no flicker
|
|
98
|
+
- **Server-side pagination** via `useInfiniteQuery` — handles thousands of products with a "Load more" button
|
|
99
|
+
- **Context-aware**: pass `contextScope` from your iframe URL (`productId` / `variantId` / `batchId`) and the browser is constrained to that subtree with the right tab auto-selected
|
|
100
|
+
- **CSV import / export** with a schema you define; failed rows come back as an annotated CSV
|
|
101
|
+
- **Bulk actions menu** (apply-to-many, copy-from, clear) via `bulkUpsert` / `bulkDelete`
|
|
102
|
+
- **Telemetry hook** (`onTelemetry`) emits typed events for save, delete, scope change, CSV import/export, bulk apply
|
|
103
|
+
- **i18n strings** fully overridable
|
|
104
|
+
|
|
105
|
+
> Requires a `<QueryClientProvider>` from `@tanstack/react-query` somewhere up the tree.
|
|
106
|
+
|
|
107
|
+
### Lower-level pieces (advanced use)
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import {
|
|
111
|
+
// Hooks
|
|
112
|
+
useRecordList, useRecordEditor, useResolvedRecord, useScopeProbe,
|
|
113
|
+
// Data helpers
|
|
114
|
+
parseRef, buildRef, resolutionChain,
|
|
115
|
+
listRecords, getRecordByRef, upsertRecord, deleteRecord,
|
|
116
|
+
bulkUpsert, bulkDelete,
|
|
117
|
+
exportCsv, importCsv, downloadBlob,
|
|
118
|
+
// UI pieces
|
|
119
|
+
RecordBrowser, RecordEditor, ScopeBreadcrumb,
|
|
120
|
+
InheritanceMarker, ResolvedPreview, BulkActionsMenu,
|
|
121
|
+
} from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `useResolvedRecord` hook
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
128
|
+
|
|
129
|
+
const { data, source, isLoading } = useResolvedRecord({
|
|
130
|
+
SL,
|
|
131
|
+
appId,
|
|
132
|
+
recordType: 'nutrition',
|
|
133
|
+
collectionId,
|
|
134
|
+
productId,
|
|
135
|
+
variantId, // optional
|
|
136
|
+
batchId, // optional
|
|
137
|
+
proofId, // optional
|
|
138
|
+
});
|
|
139
|
+
// source: 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'default' | null
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Walks the resolution chain defined in [records-admin-pattern.md §4](records-admin-pattern.md#4-resolution-order-required). Use this on the **public widget side** to read the correct value for a given context.
|
|
143
|
+
|
|
144
|
+
### `parseRef` / `buildRef` utilities
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { parseRef, buildRef } from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
148
|
+
|
|
149
|
+
const parsed = parseRef('variant:prod_abc:var_500ml');
|
|
150
|
+
// → { kind: 'variant', productId: 'prod_abc', variantId: 'var_500ml' }
|
|
151
|
+
|
|
152
|
+
const ref = buildRef({ kind: 'facet', facetKey: 'bread_type', valueKey: 'sourdough' });
|
|
153
|
+
// → 'facet:bread_type:sourdough'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
See [records-admin-pattern.md Appendix A](records-admin-pattern.md#appendix-a--ref-parser-reference) for the full `ParsedRef` type.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Asset Picker
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
|
|
164
|
+
|
|
165
|
+
<AssetPicker
|
|
166
|
+
scope={{ type: 'collection', collectionId: 'abc123' }}
|
|
167
|
+
mode="dialog" // or "inline"
|
|
168
|
+
allowUpload
|
|
169
|
+
accept={['image/*']} // MIME filtering
|
|
170
|
+
onSelect={(asset) => setHeroUrl(asset.url)}
|
|
171
|
+
trigger={<Button>Choose image</Button>}
|
|
172
|
+
/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**What it does:**
|
|
176
|
+
- Browses assets at **collection** or **product** scope (dual-scope tabs)
|
|
177
|
+
- Four ingest paths: file upload, URL import, clipboard paste (with rename preview), and selection from existing assets
|
|
178
|
+
- Inline mode for embedding in a panel; dialog mode for modal pickers
|
|
179
|
+
- Double-click a tile to confirm instantly
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Icon Picker
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { IconPicker } from '@proveanything/smartlinks-utils-ui/icon-picker';
|
|
187
|
+
|
|
188
|
+
<IconPicker
|
|
189
|
+
mode="dialog"
|
|
190
|
+
value="fa-solid fa-heart"
|
|
191
|
+
onSelect={(icon) => setIcon(icon.name)}
|
|
192
|
+
trigger={<Button>Pick icon</Button>}
|
|
193
|
+
/>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**What it does:**
|
|
197
|
+
- Searches **Font Awesome 7 Pro** (uses the shared kit `75493b59b3`)
|
|
198
|
+
- Family hierarchy: Classic (Solid / Regular / Light), Duotone, and Brands
|
|
199
|
+
- Search-first with a background catalogue crawler — first results appear instantly, the full index fills in behind
|
|
200
|
+
- Auto-switches families intelligently (e.g. brand searches surface brand icons even when "Classic" is selected)
|
|
201
|
+
|
|
202
|
+
> Requires the FA kit script to be present on the host page.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Font Picker
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
import { FontPicker } from '@proveanything/smartlinks-utils-ui/font-picker';
|
|
210
|
+
|
|
211
|
+
<FontPicker
|
|
212
|
+
mode="dialog"
|
|
213
|
+
value="Inter"
|
|
214
|
+
showPreview
|
|
215
|
+
onSelect={(font) => {
|
|
216
|
+
console.log(font.family); // "Inter"
|
|
217
|
+
console.log(font.cssFontFamily); // "'Inter', ui-sans-serif, system-ui, sans-serif"
|
|
218
|
+
console.log(font.loadSnippet); // <link href="..." rel="stylesheet">
|
|
219
|
+
}}
|
|
220
|
+
/>
|
|
221
|
+
|
|
222
|
+
{/* With custom fonts uploaded into the collection */}
|
|
223
|
+
<FontPicker
|
|
224
|
+
mode="dialog"
|
|
225
|
+
showCustomFonts
|
|
226
|
+
scope={{ collectionId: 'abc123' }}
|
|
227
|
+
onSelect={(font) => /* ... */}
|
|
228
|
+
/>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**What it does:**
|
|
232
|
+
- Full **Google Fonts** catalogue plus any **custom fonts** uploaded for the brand (stored via `appConfiguration` under `customFonts`)
|
|
233
|
+
- Upload zone auto-detects weight/style from the filename (e.g. `MyFont-BoldItalic.woff2`)
|
|
234
|
+
- Returns a `FontSelection` with `family`, `cssFontFamily`, and a ready-to-inject `loadSnippet` (`<link>` for Google fonts, `@font-face` CSS for custom uploads)
|
|
235
|
+
- Lazy-loads previews via `IntersectionObserver`; includes a management UI for editing custom font definitions
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Conditions Editor
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { ConditionsEditor } from '@proveanything/smartlinks-utils-ui/conditions-editor';
|
|
243
|
+
|
|
244
|
+
<ConditionsEditor
|
|
245
|
+
value={rules}
|
|
246
|
+
onChange={setRules}
|
|
247
|
+
collectionId={collectionId} // auto-loads facet definitions
|
|
248
|
+
versions={[{ title: 'Default', value: '' }]}
|
|
249
|
+
tags={['featured', 'new']}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**What it does:**
|
|
254
|
+
- Recursive AND / OR group builder — nest conditions to any depth
|
|
255
|
+
- **12 condition types:** Version, Country, Value, User, Date, Device, Tag, Facet, Geofence, Product, Item Status, Condition Reference
|
|
256
|
+
- **Facet condition** auto-fetches definitions from `facets.publicList(collectionId, { includeValues: true })` when only `collectionId` is passed (SDK ≥ 1.9.20)
|
|
257
|
+
- **Country picker** is a searchable multi-select with removable ISO 3166-1 chips and a "Use regions" toggle
|
|
258
|
+
- Renders correctly inside iframe contexts — avoids `overflow-hidden` so dropdowns escape their cards
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Tree shaking
|
|
263
|
+
|
|
264
|
+
Each component has its own subpath export:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
// Bundles only the Asset Picker
|
|
268
|
+
import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
|
|
269
|
+
|
|
270
|
+
// Barrel import — bundler tree-shakes the rest
|
|
271
|
+
import { AssetPicker } from '@proveanything/smartlinks-utils-ui';
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
If you use subpath imports, import `styles.css` separately — subpaths do not pull it in automatically.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Relationship to the core SDK
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
@proveanything/smartlinks ← data layer (records, config, interactions, …)
|
|
282
|
+
↑
|
|
283
|
+
@proveanything/smartlinks-utils-ui ← UI layer (components, hooks, admin shells)
|
|
284
|
+
↑
|
|
285
|
+
your microapp ← domain logic and custom forms
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Further reading
|
|
291
|
+
|
|
292
|
+
- [records-admin-pattern.md](records-admin-pattern.md) — the data contract that `RecordsAdminShell` implements
|
|
293
|
+
- [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all components
|
|
294
|
+
- [app-manifest.md](app-manifest.md) — the `records` manifest block that drives tab generation
|
package/dist/openapi.yaml
CHANGED
|
@@ -17134,6 +17134,10 @@ components:
|
|
|
17134
17134
|
type: string
|
|
17135
17135
|
allowAutoGenerateClaims:
|
|
17136
17136
|
type: boolean
|
|
17137
|
+
variants:
|
|
17138
|
+
type: boolean
|
|
17139
|
+
batches:
|
|
17140
|
+
type: boolean
|
|
17137
17141
|
defaultAuthKitId:
|
|
17138
17142
|
type: string
|
|
17139
17143
|
required:
|
|
@@ -17159,6 +17163,8 @@ components:
|
|
|
17159
17163
|
- supported
|
|
17160
17164
|
- roles
|
|
17161
17165
|
- shortId
|
|
17166
|
+
- variants
|
|
17167
|
+
- batches
|
|
17162
17168
|
- defaultAuthKitId
|
|
17163
17169
|
AppConfig:
|
|
17164
17170
|
type: object
|
|
@@ -69,6 +69,8 @@ export interface Collection {
|
|
|
69
69
|
portalUrl?: string;
|
|
70
70
|
/** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
|
|
71
71
|
allowAutoGenerateClaims?: boolean;
|
|
72
|
+
variants: boolean;
|
|
73
|
+
batches: boolean;
|
|
72
74
|
defaultAuthKitId: string;
|
|
73
75
|
}
|
|
74
76
|
export type CollectionResponse = Collection;
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.22 | Generated: 2026-04-25T11:11:00.344Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -2994,6 +2994,8 @@ interface Collection {
|
|
|
2994
2994
|
secondaryColor?: string
|
|
2995
2995
|
portalUrl?: string // URL for the collection's portal (if applicable)
|
|
2996
2996
|
allowAutoGenerateClaims?: boolean
|
|
2997
|
+
variants: boolean // does this collection support variants?
|
|
2998
|
+
batches: boolean // does this collection support batches?
|
|
2997
2999
|
defaultAuthKitId: string // default auth kit for this collection, used for auth
|
|
2998
3000
|
}
|
|
2999
3001
|
```
|
package/docs/app-manifest.md
CHANGED
|
@@ -95,7 +95,20 @@ The manifest is loaded automatically by the platform for every collection page.
|
|
|
95
95
|
{ "title": "Home", "path": "/" },
|
|
96
96
|
{ "title": "Gallery", "path": "/gallery" },
|
|
97
97
|
{ "title": "Settings", "path": "/settings", "params": { "tab": "advanced" } }
|
|
98
|
-
]
|
|
98
|
+
],
|
|
99
|
+
|
|
100
|
+
"records": {
|
|
101
|
+
"nutrition": {
|
|
102
|
+
"scopes": ["product", "facet", "batch"],
|
|
103
|
+
"defaultScope": "facet",
|
|
104
|
+
"label": "Nutrition info"
|
|
105
|
+
},
|
|
106
|
+
"cooking_steps": {
|
|
107
|
+
"scopes": ["product", "facet"],
|
|
108
|
+
"defaultScope": "product",
|
|
109
|
+
"label": "Cooking steps"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
99
112
|
}
|
|
100
113
|
```
|
|
101
114
|
|
|
@@ -188,6 +201,30 @@ See the [Deep Link Discovery guide](deep-link-discovery.md) for the full dual-so
|
|
|
188
201
|
| `path` | string | ❌ | Hash route within the app (defaults to `"/"` if omitted) |
|
|
189
202
|
| `params` | object | ❌ | App-specific query params appended to the URL — do **not** include platform params (`collectionId`, `productId`, etc.) |
|
|
190
203
|
|
|
204
|
+
#### `records`
|
|
205
|
+
|
|
206
|
+
Declares which `app.records` record types the app stores, and which scopes each type supports. Required for any app that follows the [Records-Based Admin Pattern](records-admin-pattern.md). Omit if the app does not use scoped records.
|
|
207
|
+
|
|
208
|
+
The platform and the `<RecordsAdminShell>` from `@proveanything/ui-utils` read this block to render only the relevant tabs and affordances.
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
"records": {
|
|
212
|
+
"<recordType>": {
|
|
213
|
+
"scopes": ["product", "facet", "batch"],
|
|
214
|
+
"defaultScope": "facet",
|
|
215
|
+
"label": "Human-readable label"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
| Field | Type | Required | Description |
|
|
221
|
+
|----------------|----------|----------|-------------|
|
|
222
|
+
| `scopes` | string[] | ✅ | Allowed scope kinds in resolution order. Valid values: `"product"`, `"variant"`, `"batch"`, `"facet"`, `"proof"`, `"default"`. |
|
|
223
|
+
| `defaultScope` | string | ✅ | The scope the "Create new" button targets in the admin shell. Must be one of the declared `scopes`. |
|
|
224
|
+
| `label` | string | ✅ | Human-readable label for the record type, used in headings and tabs. |
|
|
225
|
+
|
|
226
|
+
An app may declare multiple record types under different keys (e.g. `"nutrition"` and `"cooking_steps"`).
|
|
227
|
+
|
|
191
228
|
#### `executor`
|
|
192
229
|
|
|
193
230
|
Declares the executor bundle — a standalone JS library for programmatic configuration, server-side SEO, and LLM content generation. Omit if the app has no executor.
|
package/docs/auth-kit.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# SmartLinks Auth Kit (`@proveanything/smartlinks` — `authKit` namespace)
|
|
2
|
+
|
|
3
|
+
> End-user authentication flows for SmartLinks microapps. Covers email/password, magic links, phone OTP, Google OAuth, profile management, and password/email change flows.
|
|
4
|
+
>
|
|
5
|
+
> **This is part of the core SDK** — no separate install required. Import from `@proveanything/smartlinks`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What is Auth Kit for?
|
|
10
|
+
|
|
11
|
+
Auth Kit is the **end-user identity layer** for microapps that need users to sign in. It is distinct from the admin/platform authentication (Bearer tokens) used to call admin endpoints.
|
|
12
|
+
|
|
13
|
+
Use Auth Kit when:
|
|
14
|
+
- Your app has a login/register screen for end users (not collection admins)
|
|
15
|
+
- You need to gate features behind a verified user identity
|
|
16
|
+
- You want to store user-specific data with the `userAppData` API (see [app-data-storage.md](app-data-storage.md))
|
|
17
|
+
|
|
18
|
+
Do **not** use Auth Kit for:
|
|
19
|
+
- Admin-side API calls (those use Bearer tokens set by the platform shell)
|
|
20
|
+
- Claiming proofs (see proof claiming methods)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Setup: creating an Auth Kit client
|
|
25
|
+
|
|
26
|
+
Each app requires an Auth Kit configuration, created by a collection admin:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// Admin setup (one-time, done in admin console)
|
|
30
|
+
import { authKit } from '@proveanything/smartlinks';
|
|
31
|
+
|
|
32
|
+
// Returns an authKitId to store in your app config
|
|
33
|
+
const config = await authKit.create(collectionId, {
|
|
34
|
+
name: 'My App Auth',
|
|
35
|
+
loginMethods: ['email', 'google', 'magic_link'],
|
|
36
|
+
redirectUrl: 'https://myapp.example.com/auth/callback',
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Key flows
|
|
43
|
+
|
|
44
|
+
### Email / password
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { authKit } from '@proveanything/smartlinks';
|
|
48
|
+
|
|
49
|
+
// Register
|
|
50
|
+
const session = await authKit.register(clientId, {
|
|
51
|
+
email: 'user@example.com',
|
|
52
|
+
password: 'securePassword123',
|
|
53
|
+
displayName: 'Alice',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Login
|
|
57
|
+
const session = await authKit.login(clientId, 'user@example.com', 'securePassword123');
|
|
58
|
+
|
|
59
|
+
// session.token — store this; pass to initializeApi for subsequent calls
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Magic link (passwordless email)
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
await authKit.sendMagicLink(clientId, {
|
|
66
|
+
email: 'user@example.com',
|
|
67
|
+
redirectUrl: 'https://myapp.example.com/auth/verify',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// On callback page, extract token from URL and verify
|
|
71
|
+
const session = await authKit.verifyMagicLink(clientId, tokenFromUrl);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Phone OTP
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await authKit.sendPhoneCode(clientId, '+61400000000');
|
|
78
|
+
const session = await authKit.verifyPhoneCode(clientId, '+61400000000', '123456');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Google OAuth
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// After Google sign-in, pass the id_token to Auth Kit
|
|
85
|
+
const session = await authKit.googleLogin(clientId, googleIdToken);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Profile management
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { authKit } from '@proveanything/smartlinks';
|
|
94
|
+
|
|
95
|
+
// Get current user's profile
|
|
96
|
+
const profile = await authKit.getProfile(clientId);
|
|
97
|
+
|
|
98
|
+
// Update profile
|
|
99
|
+
await authKit.updateProfile(clientId, { displayName: 'Alice B.', avatarUrl: '...' });
|
|
100
|
+
|
|
101
|
+
// Change password
|
|
102
|
+
await authKit.changePassword(clientId, 'currentPass', 'newPass');
|
|
103
|
+
|
|
104
|
+
// Change email (triggers verification)
|
|
105
|
+
await authKit.changeEmail(clientId, 'newemail@example.com', 'password', redirectUrl);
|
|
106
|
+
|
|
107
|
+
// Delete account
|
|
108
|
+
await authKit.deleteAccount(clientId, 'password', 'DELETE');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Email verification
|
|
114
|
+
|
|
115
|
+
Auth Kit can send and verify email addresses after registration:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
await authKit.sendEmailVerification(clientId, {
|
|
119
|
+
userId,
|
|
120
|
+
email: 'user@example.com',
|
|
121
|
+
redirectUrl: 'https://myapp.example.com/auth/verified',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// On callback page
|
|
125
|
+
await authKit.verifyEmail(clientId, tokenFromUrl);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Password reset
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
await authKit.requestPasswordReset(clientId, {
|
|
134
|
+
email: 'user@example.com',
|
|
135
|
+
redirectUrl: 'https://myapp.example.com/auth/reset',
|
|
136
|
+
clientName: 'My App',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// On reset page — verify token is still valid before showing the form
|
|
140
|
+
await authKit.verifyResetToken(clientId, tokenFromUrl);
|
|
141
|
+
|
|
142
|
+
// Complete reset
|
|
143
|
+
await authKit.completePasswordReset(clientId, tokenFromUrl, 'newSecurePassword');
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Relationship to other parts of the SDK
|
|
149
|
+
|
|
150
|
+
| Concern | Where it lives |
|
|
151
|
+
|---------|---------------|
|
|
152
|
+
| End-user sign-in / register | `authKit` namespace (this doc) |
|
|
153
|
+
| Admin Bearer token auth | Platform shell — not set by your app |
|
|
154
|
+
| Per-user data storage | `userAppData` namespace — see [app-data-storage.md](app-data-storage.md) |
|
|
155
|
+
| User identity in analytics | `userId` field on `analytics` and `interactions` calls |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Further reading
|
|
160
|
+
|
|
161
|
+
- [app-data-storage.md](app-data-storage.md) — storing user-specific data after login
|
|
162
|
+
- [app-manifest.md](app-manifest.md) — `app.admin.json` setup questions for configuring `clientId`
|
package/docs/forms.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# SmartLinks Forms
|
|
2
|
+
|
|
3
|
+
> Platform-managed form definitions and submissions. Covers the `form` data API (core SDK) and the `@proveanything/ui-forms` React package for schema-driven form UIs.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Two layers
|
|
8
|
+
|
|
9
|
+
| Layer | Package | What it does |
|
|
10
|
+
|-------|---------|--------------|
|
|
11
|
+
| **Forms data API** | `@proveanything/smartlinks` (`form` namespace) | CRUD for form definitions; stores submission schema |
|
|
12
|
+
| **Forms UI** | `@proveanything/ui-forms` | React components that render a form definition, validate input, and submit |
|
|
13
|
+
|
|
14
|
+
You can use the data API without the UI package (e.g. in an executor), but the UI package requires both.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Forms data API (`form` namespace)
|
|
19
|
+
|
|
20
|
+
The `form` namespace in the core SDK manages **form definitions** — the schema that describes fields, validation rules, and submission behaviour. This is an admin-side API.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { form } from '@proveanything/smartlinks';
|
|
24
|
+
|
|
25
|
+
// List all forms in a collection
|
|
26
|
+
const forms = await form.list(collectionId, /* admin= */ true);
|
|
27
|
+
|
|
28
|
+
// Get a specific form
|
|
29
|
+
const myForm = await form.get(collectionId, formId);
|
|
30
|
+
|
|
31
|
+
// Create a form (admin)
|
|
32
|
+
const created = await form.create(collectionId, {
|
|
33
|
+
name: 'Warranty Registration',
|
|
34
|
+
fields: [
|
|
35
|
+
{ id: 'name', type: 'text', label: 'Full name', required: true },
|
|
36
|
+
{ id: 'email', type: 'email', label: 'Email address', required: true },
|
|
37
|
+
{ id: 'serial', type: 'text', label: 'Serial number', required: true },
|
|
38
|
+
{ id: 'receipt', type: 'file', label: 'Proof of purchase' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Update a form (admin)
|
|
43
|
+
await form.update(collectionId, formId, { name: 'Warranty Registration v2' });
|
|
44
|
+
|
|
45
|
+
// Delete a form (admin)
|
|
46
|
+
await form.remove(collectionId, formId);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Form definitions are stored per-collection and referenced by `formId`.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Forms UI (`@proveanything/ui-forms`)
|
|
54
|
+
|
|
55
|
+
`@proveanything/ui-forms` provides React components that consume a form definition from the API and render a complete, accessible, validated form.
|
|
56
|
+
|
|
57
|
+
Install: `npm install @proveanything/ui-forms`
|
|
58
|
+
|
|
59
|
+
### Rendering a form
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { SmartForm } from '@proveanything/ui-forms';
|
|
63
|
+
|
|
64
|
+
// Fetches the form definition and renders it
|
|
65
|
+
<SmartForm
|
|
66
|
+
collectionId={collectionId}
|
|
67
|
+
formId={formId}
|
|
68
|
+
onSubmit={async (values) => {
|
|
69
|
+
// handle submission — e.g. create an app.records entry or send via comms
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Headless usage
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useFormDefinition, FormRenderer } from '@proveanything/ui-forms';
|
|
78
|
+
|
|
79
|
+
const { fields, isLoading } = useFormDefinition(collectionId, formId);
|
|
80
|
+
|
|
81
|
+
// Render fields yourself using the definition
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Common patterns
|
|
87
|
+
|
|
88
|
+
### Warranty / registration forms
|
|
89
|
+
|
|
90
|
+
Define the form in `app.admin.json` setup or via the admin API. On the public widget, render with `<SmartForm>` and on submit create an `app.records` entry:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
onSubmit={async (values) => {
|
|
94
|
+
await app.records.create(collectionId, appId, {
|
|
95
|
+
recordType: 'warranty_registration',
|
|
96
|
+
ref: `proof:${proofId}`,
|
|
97
|
+
data: values,
|
|
98
|
+
});
|
|
99
|
+
}}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Competition / feedback forms
|
|
103
|
+
|
|
104
|
+
Same pattern — use `<SmartForm>` for capture and write to `app.records` or trigger an `interactions.appendEvent` on submit.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Further reading
|
|
109
|
+
|
|
110
|
+
- [app-objects.md](app-objects.md) — `app.records` for storing submissions
|
|
111
|
+
- [app-data-storage.md](app-data-storage.md) — choosing the right storage model
|
|
112
|
+
- [interactions.md](interactions.md) — firing events on form submission
|
|
113
|
+
- [comms.md](comms.md) — sending confirmation emails after submission
|
package/docs/overview.md
CHANGED
|
@@ -64,6 +64,10 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
64
64
|
| **Liquid Templates** | `docs/liquid-templates.md` | Dynamic content rendering with LiquidJS |
|
|
65
65
|
| **Iframe Responder** | `docs/iframe-responder.md` | Iframe communication and proxy mode internals |
|
|
66
66
|
| **AI Guide Template** | `docs/ai-guide-template.md` | Template for creating `public/ai-guide.md` — customise per app |
|
|
67
|
+
| **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
|
|
68
|
+
| **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
|
|
69
|
+
| **Records Admin Pattern** | `docs/records-admin-pattern.md` | Standard pattern for per-product/facet/variant/batch admin UIs |
|
|
70
|
+
| **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
|
|
67
71
|
|
|
68
72
|
---
|
|
69
73
|
|