@picked/client 0.0.71-next.1 → 0.0.71-next.2

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.
Files changed (148) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{api-CgqRvuKL.js → api-B0ydpQBE.js} +1 -1
  3. package/dist/{api-CH2FNa6S.cjs → api-CObostrU.cjs} +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  6. package/dist/cjs/server/index.js +1 -1
  7. package/dist/docs/explanation/conditions-and-the-logic-engine.md +94 -0
  8. package/dist/docs/explanation/feature-flag-allocation.md +176 -0
  9. package/dist/docs/explanation/how-picked-works.md +102 -0
  10. package/dist/docs/explanation/how-pickedql-works.md +76 -0
  11. package/dist/docs/explanation/pages-and-flows.md +64 -0
  12. package/dist/docs/explanation/the-content-model.md +77 -0
  13. package/dist/docs/explanation/the-two-apis.md +68 -0
  14. package/dist/docs/explanation/why-server-side-rendering.md +76 -0
  15. package/dist/docs/explanation/workspaces-and-versions.md +71 -0
  16. package/dist/docs/how-to/add-a-step-field-or-action.md +43 -0
  17. package/dist/docs/{pickedql/examples.md → how-to/analytics/index.md} +19 -6
  18. package/dist/docs/how-to/archive-or-delete-a-workspace.md +46 -0
  19. package/dist/docs/how-to/authenticate-with-a-magic-link.md +40 -0
  20. package/dist/docs/how-to/authenticate-with-a-password.md +51 -0
  21. package/dist/docs/how-to/authenticate-with-an-api-key.md +51 -0
  22. package/dist/docs/how-to/authenticate-with-oauth.md +40 -0
  23. package/dist/docs/{flow-builder/building-flows.md → how-to/build-a-flow.md} +13 -9
  24. package/dist/docs/how-to/connect-the-mcp-server.md +38 -0
  25. package/dist/docs/how-to/create-a-project.md +29 -0
  26. package/dist/docs/how-to/create-an-experience.md +42 -0
  27. package/dist/docs/how-to/create-and-revoke-an-api-key.md +37 -0
  28. package/dist/docs/how-to/debug-with-the-chrome-extension.md +57 -0
  29. package/dist/docs/how-to/define-a-page-layout.md +39 -0
  30. package/dist/docs/how-to/fill-and-publish-a-page.md +50 -0
  31. package/dist/docs/{gtm-template.md → how-to/install-the-gtm-template.md} +9 -3
  32. package/dist/docs/how-to/install-the-sdk.md +68 -0
  33. package/dist/docs/how-to/install-the-vite-plugin.md +56 -0
  34. package/dist/docs/how-to/invite-and-remove-members.md +34 -0
  35. package/dist/docs/how-to/preview-a-workspace.md +28 -0
  36. package/dist/docs/how-to/publish-a-workspace.md +30 -0
  37. package/dist/docs/how-to/read-a-feature-flag.md +101 -0
  38. package/dist/docs/how-to/register-a-component.md +55 -0
  39. package/dist/docs/how-to/register-an-action-handler.md +67 -0
  40. package/dist/docs/how-to/render-a-step-form.md +132 -0
  41. package/dist/docs/how-to/render-an-unpublished-preview.md +41 -0
  42. package/dist/docs/how-to/reuse-a-section.md +37 -0
  43. package/dist/docs/how-to/reuse-a-step-field-or-action.md +28 -0
  44. package/dist/docs/how-to/roll-back-a-published-version.md +26 -0
  45. package/dist/docs/how-to/run-an-ab-test.md +49 -0
  46. package/dist/docs/how-to/run-code-on-a-form-submission.md +125 -0
  47. package/dist/docs/how-to/server-render-a-page.md +81 -0
  48. package/dist/docs/how-to/set-a-content-security-policy.md +32 -0
  49. package/dist/docs/how-to/set-up-mfa.md +69 -0
  50. package/dist/docs/how-to/upload-and-use-an-asset.mdx +73 -0
  51. package/dist/docs/how-to/validate-a-field.md +52 -0
  52. package/dist/docs/how-to/write-a-condition.md +45 -0
  53. package/dist/docs/how-to/write-a-predicate.md +60 -0
  54. package/dist/docs/index.md +41 -70
  55. package/dist/docs/reference/dashboard/actions.mdx +53 -0
  56. package/dist/docs/{api-keys.mdx → reference/dashboard/api-keys.mdx} +7 -15
  57. package/dist/docs/reference/dashboard/conditions.mdx +42 -0
  58. package/dist/docs/reference/dashboard/content/asset-library.mdx +63 -0
  59. package/dist/docs/{content → reference/dashboard/content}/content-entries.mdx +11 -3
  60. package/dist/docs/{content → reference/dashboard/content}/layouts.mdx +11 -35
  61. package/dist/docs/reference/dashboard/content/pages.mdx +35 -0
  62. package/dist/docs/reference/dashboard/content/sections.mdx +28 -0
  63. package/dist/docs/{experiences.mdx → reference/dashboard/experiences.mdx} +9 -16
  64. package/dist/docs/{features.mdx → reference/dashboard/features.mdx} +26 -41
  65. package/dist/docs/{fields.mdx → reference/dashboard/fields.mdx} +26 -20
  66. package/dist/docs/{flow-builder/overview.mdx → reference/dashboard/flow-builder/canvas.mdx} +8 -13
  67. package/dist/docs/{flow-builder → reference/dashboard/flow-builder}/condition-builder.md +5 -13
  68. package/dist/docs/{flow-builder → reference/dashboard/flow-builder}/keyboard-shortcuts.md +7 -1
  69. package/dist/docs/{flow-builder → reference/dashboard/flow-builder}/nodes.md +10 -3
  70. package/dist/docs/reference/dashboard/projects.mdx +44 -0
  71. package/dist/docs/reference/dashboard/reports.mdx +43 -0
  72. package/dist/docs/reference/dashboard/steps.mdx +37 -0
  73. package/dist/docs/{team-management.md → reference/dashboard/team-management.md} +5 -22
  74. package/dist/docs/{workspace-versioning.mdx → reference/dashboard/workspace-versioning.mdx} +6 -10
  75. package/dist/docs/reference/dashboard/workspaces.mdx +76 -0
  76. package/dist/docs/{delivery-api → reference/delivery-api}/experience-content.md +11 -4
  77. package/dist/docs/reference/delivery-api/index.md +45 -0
  78. package/dist/docs/{delivery-api → reference/delivery-api}/preview-caching-crawling.md +12 -5
  79. package/dist/docs/{delivery-api → reference/delivery-api}/project-config.md +11 -4
  80. package/dist/docs/reference/glossary.md +173 -0
  81. package/dist/docs/reference/management-api/authentication.md +74 -0
  82. package/dist/docs/{api → reference/management-api}/authorization-and-scopes.md +10 -3
  83. package/dist/docs/{api → reference/management-api}/error-handling.md +9 -3
  84. package/dist/docs/{api → reference/management-api}/pagination.md +6 -0
  85. package/dist/docs/{pickedql → reference/pickedql}/data-sources.md +16 -9
  86. package/dist/docs/{pickedql → reference/pickedql}/dimensions.md +7 -0
  87. package/dist/docs/{pickedql → reference/pickedql}/filtering.md +7 -0
  88. package/dist/docs/reference/pickedql/index.md +18 -0
  89. package/dist/docs/{pickedql → reference/pickedql}/schema.md +13 -6
  90. package/dist/docs/{pickedql → reference/pickedql}/virtual-functions.md +7 -0
  91. package/dist/docs/reference/sdk/actions.md +30 -0
  92. package/dist/docs/{sdk → reference/sdk}/conditions.md +8 -1
  93. package/dist/docs/reference/sdk/content-security-policy.md +21 -0
  94. package/dist/docs/reference/sdk/experiences.md +51 -0
  95. package/dist/docs/{sdk → reference/sdk}/features.md +30 -117
  96. package/dist/docs/{sdk → reference/sdk}/fields.md +10 -88
  97. package/dist/docs/reference/sdk/index.md +68 -0
  98. package/dist/docs/{sdk → reference/sdk}/plugins/create-experience.md +10 -22
  99. package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +26 -0
  100. package/dist/docs/reference/sdk/plugins/index.md +29 -0
  101. package/dist/docs/{sdk → reference/sdk}/plugins/window-object.md +6 -0
  102. package/dist/docs/reference/sdk/predicates.md +43 -0
  103. package/dist/docs/reference/sdk/ssr/index.md +33 -0
  104. package/dist/docs/{sdk → reference/sdk}/ssr/rendering-and-hydration.md +4 -63
  105. package/dist/docs/{sdk → reference/sdk}/ssr/serialize.md +6 -33
  106. package/dist/docs/{sdk → reference/sdk}/ssr/slots-and-placements.md +7 -1
  107. package/dist/docs/reference/sdk/steps.md +87 -0
  108. package/dist/docs/reference/tools/chrome-extension.md +16 -0
  109. package/dist/docs/{image-optimisation-service.md → reference/tools/image-optimisation-service.md} +8 -0
  110. package/dist/docs/{mcp.md → reference/tools/mcp.md} +5 -45
  111. package/dist/docs/{vite-plugin.md → reference/tools/vite-plugin.md} +8 -38
  112. package/dist/docs/{api/index.md → tutorials/first-api-call.md} +27 -15
  113. package/dist/docs/tutorials/first-experience-in-the-dashboard.mdx +143 -0
  114. package/dist/docs/tutorials/first-experience.mdx +255 -0
  115. package/dist/docs/tutorials/first-server-rendered-page.mdx +186 -0
  116. package/dist/esm/index.js +260 -257
  117. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  118. package/dist/esm/server/index.js +1 -1
  119. package/dist/{plugin-DCW-tnjO.cjs → plugin-CNna0UjB.cjs} +1 -1
  120. package/dist/{plugin-D2UqFikQ.js → plugin-DUOJpHaq.js} +1 -1
  121. package/dist/types/entities/feature/feature.d.ts +16 -0
  122. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  123. package/dist/types/picked.d.ts.map +1 -1
  124. package/package.json +1 -1
  125. package/dist/docs/actions.mdx +0 -98
  126. package/dist/docs/api/authentication.md +0 -403
  127. package/dist/docs/chrome-extension.md +0 -68
  128. package/dist/docs/conditions.mdx +0 -72
  129. package/dist/docs/content/asset-library.mdx +0 -124
  130. package/dist/docs/content/pages.mdx +0 -82
  131. package/dist/docs/content/sections.mdx +0 -44
  132. package/dist/docs/delivery-api/index.md +0 -53
  133. package/dist/docs/guides/build-a-personalized-page.mdx +0 -161
  134. package/dist/docs/pickedql/index.md +0 -54
  135. package/dist/docs/projects.mdx +0 -44
  136. package/dist/docs/reports.mdx +0 -20
  137. package/dist/docs/sdk/actions.md +0 -82
  138. package/dist/docs/sdk/content-security-policy.md +0 -33
  139. package/dist/docs/sdk/experiences.md +0 -64
  140. package/dist/docs/sdk/feature-allocation.md +0 -161
  141. package/dist/docs/sdk/index.md +0 -98
  142. package/dist/docs/sdk/plugins/dynamic-experiences.md +0 -84
  143. package/dist/docs/sdk/plugins/index.md +0 -9
  144. package/dist/docs/sdk/predicates.md +0 -128
  145. package/dist/docs/sdk/ssr/index.md +0 -79
  146. package/dist/docs/sdk/steps.md +0 -479
  147. package/dist/docs/steps.mdx +0 -53
  148. package/dist/docs/workspaces.mdx +0 -133
@@ -1,79 +0,0 @@
1
- ---
2
- layout: docs
3
- ---
4
-
5
- # Server-Side Rendering
6
-
7
- Render a Picked Page on your server so that search engines can read it. Then hydrate
8
- the page on the client. This page shows the whole shape. The pages after it cover
9
- each part.
10
-
11
- ## Why server-side rendering
12
-
13
- The search rank of a Page matters, so the HTML that your server returns must hold the
14
- content of that Page. A crawler and a social preview both read that HTML directly.
15
- Neither waits for the JavaScript in the browser. A Page that the server renders
16
- therefore has its content in the first response, and the client takes over after
17
- that.
18
-
19
- ## The flow
20
-
21
- ```
22
- projectConfig (Delivery API)
23
- -> serialize(client, params) on the server -> PickedSnapshot
24
- -> embed snapshot in HTML
25
- -> client seeds from snapshot -> renders the same markup -> hydrates
26
- ```
27
-
28
- 1. Fetch your published `projectConfig` from the [Delivery API](/delivery-api).
29
- 2. Call `serialize()` on the server. It finds which experiences match the URL, reads
30
- their content, and returns a `PickedSnapshot` as plain JSON. See
31
- [Server Snapshot](/sdk/ssr/serialize).
32
- 3. Embed the snapshot in your HTML payload.
33
- 4. On the client, seed a Picked client from the snapshot. The first paint then
34
- matches the markup from the server. Hydrate after that. See
35
- [Rendering and Hydration](/sdk/ssr/rendering-and-hydration).
36
-
37
- The snapshot carries the exact options that the server chose with, and the condition
38
- results that it kept. The first render on the client therefore reuses the decisions
39
- of the server. It does not evaluate them again.
40
-
41
- ## React and Svelte side by side
42
-
43
- :::code-group
44
-
45
- ```tsx [React]
46
- import { PickedProvider, ExperienceSlot } from '@picked/react'
47
-
48
- <PickedProvider options={options} snapshot={snapshot}>
49
- <ExperienceSlot name="page" />
50
- </PickedProvider>
51
- ```
52
-
53
- ```svelte [Svelte]
54
- <script lang="ts">
55
- import { ExperienceSlot, setPickedContext } from '@picked/svelte'
56
-
57
- setPickedContext({ options, snapshot })
58
- </script>
59
-
60
- <ExperienceSlot name="page" />
61
- ```
62
-
63
- :::
64
-
65
- ## The server entry point
66
-
67
- The server helpers live in a dedicated entry point, so a browser bundle never holds
68
- them:
69
-
70
- ```ts
71
- import { serialize, registerProjectConfig } from '@picked/client/server'
72
- import type { PickedSnapshot } from '@picked/client/server'
73
- ```
74
-
75
- Go to [Server Snapshot](/sdk/ssr/serialize) for the `serialize()` reference. Go to
76
- [Rendering and Hydration](/sdk/ssr/rendering-and-hydration) to connect React and
77
- Svelte. Go to [Slots and Placements](/sdk/ssr/slots-and-placements) for how a slot
78
- chooses an experience. If the SDK is new to you, read
79
- [Installation](/sdk) first.
@@ -1,479 +0,0 @@
1
- ---
2
- layout: docs
3
- ---
4
-
5
- # Steps
6
-
7
- Render your steps with `<StepRouter>` and `<Step>` from `@picked/react` or
8
- `@picked/svelte`. Reach the active step through `useStep()` or `getStep()`.
9
-
10
- ## How steps work
11
-
12
- When an experience starts, its steps form a directed graph of nodes. The SDK walks
13
- this graph to find which step to show:
14
-
15
- 1. **Initialization**: when the experience opens, the SDK starts at the origin node.
16
- It walks the graph to find the first step.
17
- 2. **Step entry**: the SDK sets the active step. It sends a `step_enter` event on the
18
- event bus.
19
- 3. **Field evaluation**: the SDK evaluates the fields of the step. It shows or hides
20
- a field with a condition, and the current state decides which.
21
- 4. **User input**: the user fills in the fields of the step. Validation runs in the
22
- browser on blur and on submit.
23
- 5. **Step submission**: when the user submits, a `step_submit` event fires. The SDK
24
- commits the field responses to the client state.
25
- 6. **Navigation**: the SDK follows the edge from the current step node to the next
26
- one. It walks through each condition and action on the way.
27
- 7. **Completion**: when no step remains, the experience flow is complete.
28
-
29
- ## Step routing
30
-
31
- `StepRouter` watches the current step state of the experience. It activates the
32
- `Step` whose `id` matches. Each `Step` registers itself on mount and unregisters on
33
- unmount, so a tab or a drawer that renders lazily works with no extra code.
34
-
35
- :::code-group
36
-
37
- ```svelte [Svelte]
38
- <script lang="ts">
39
- import { StepRouter, Step } from '@picked/svelte/router'
40
- </script>
41
-
42
- <StepRouter>
43
- <Step id="welcome">
44
- <WelcomeScreen />
45
- </Step>
46
-
47
- <Step id="contact">
48
- <ContactForm />
49
- </Step>
50
-
51
- <Step id="confirmation">
52
- <ConfirmationScreen />
53
- </Step>
54
- </StepRouter>
55
- ```
56
-
57
- ```tsx [React]
58
- import { StepRouter, Step } from '@picked/react/router'
59
-
60
- function MyExperience() {
61
- return (
62
- <StepRouter>
63
- <Step id="welcome">
64
- <WelcomeScreen />
65
- </Step>
66
-
67
- <Step id="contact">
68
- <ContactForm />
69
- </Step>
70
-
71
- <Step id="confirmation">
72
- <ConfirmationScreen />
73
- </Step>
74
- </StepRouter>
75
- )
76
- }
77
- ```
78
-
79
- :::
80
-
81
- :::note
82
- A router must hold every Step, even when there is only one Step.
83
- :::
84
-
85
- ### Lazy-loaded steps
86
-
87
- For a large experience, load a step lazily with a dynamic import:
88
-
89
- :::code-group
90
-
91
- ```svelte [Svelte]
92
- <StepRouter>
93
- <Step id="welcome" component={import('./WelcomeScreen.svelte')} />
94
- </StepRouter>
95
- ```
96
-
97
- ```tsx [React]
98
- import { lazy } from 'react'
99
-
100
- const WelcomeScreen = lazy(() => import('./WelcomeScreen'))
101
-
102
- <StepRouter>
103
- <Step id="welcome">
104
- <WelcomeScreen />
105
- </Step>
106
- </StepRouter>
107
- ```
108
-
109
- :::
110
-
111
- The `component` prop takes a `Promise<Component>`. The SDK does not load the content
112
- of the step until that step becomes active.
113
-
114
- ## Reach the step context
115
-
116
- Inside a `Step` component, or inside any component that a `Step` renders, call
117
- `getStep()` or `useStep()` to reach the reactive context of the step:
118
-
119
- :::code-group
120
-
121
- ```svelte [Svelte]
122
- <script lang="ts">
123
- import { getStep } from '@picked/svelte/hooks'
124
-
125
- const { step, fields } = getStep()
126
- </script>
127
-
128
- <h1>{$step.label}</h1>
129
-
130
- {#each $fields as field}
131
- <label>{field.label}</label>
132
- <input
133
- name={field.id}
134
- value={field.value ?? ''}
135
- onchange={field.onChange}
136
- onblur={field.onBlur}
137
- />
138
- {#if field.error && field.touched}
139
- <p class="error">{field.error.userMessage}</p>
140
- {/if}
141
- {/each}
142
- ```
143
-
144
- ```tsx [React]
145
- import { useStep } from '@picked/react/hooks'
146
- import { useUnit } from 'effector-react'
147
-
148
- function ContactForm() {
149
- const { step, fields } = useStep()
150
- const currentStep = useUnit(step)
151
- const currentFields = useUnit(fields)
152
-
153
- return (
154
- <>
155
- <h1>{currentStep.label}</h1>
156
-
157
- {currentFields.map((field) => (
158
- <div key={field.id}>
159
- <label>{field.label}</label>
160
- <input
161
- name={field.id}
162
- value={field.value ?? ''}
163
- onChange={field.onChange}
164
- onBlur={field.onBlur}
165
- />
166
- {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
167
- </div>
168
- ))}
169
- </>
170
- )
171
- }
172
- ```
173
-
174
- :::
175
-
176
- `getStep()` in Svelte, and `useStep()` in React, return a `StepContext` with these
177
- properties:
178
-
179
- | Property | Type | Description |
180
- | ---------- | --------------------------------------------- | ------------------------------------------------------------------------------ |
181
- | `step` | `Store<ExperienceStep>` | A reactive store that holds the `id` and the `label` of the step |
182
- | `fields` | `Store<FormField[]>` | A reactive store of the fields, with their current values, errors, and handlers |
183
- | `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether the router holds this step as the active one |
184
- | `form` | Object | The handlers for a form submission. See [Forms](#forms). |
185
-
186
- ## Forms
187
-
188
- When a step has fields, `getStep()` and `useStep()` give you a `form` object. Use it
189
- for the submission, the validation, and the move to the next step.
190
-
191
- Both take an optional object of callbacks, as in `getStep({ onSuccess })` or
192
- `useStep({ onSuccess })`. This is the recommended way to act on a submission. The
193
- slots are `onBeforeSubmit`, `onSuccess`, and `onError`. This page covers each one
194
- below.
195
-
196
- ### Handle a submission
197
-
198
- Bind `form.props` to the `onsubmit` handler of your `<form>` element:
199
-
200
- :::code-group
201
-
202
- ```svelte [Svelte]
203
- <script lang="ts">
204
- import { getStep } from '@picked/svelte/hooks'
205
-
206
- const { fields, form } = getStep()
207
- const isSubmitting = form.submitting
208
- </script>
209
-
210
- <form {...form.props}>
211
- {#each $fields as field}
212
- <input
213
- name={field.id}
214
- value={field.value ?? ''}
215
- onchange={field.onChange}
216
- onblur={field.onBlur}
217
- />
218
- {#if field.error && field.touched}
219
- <p class="error">{field.error.userMessage}</p>
220
- {/if}
221
- {/each}
222
-
223
- <button type="submit" disabled={$isSubmitting}>
224
- {$isSubmitting ? 'Submitting…' : 'Continue'}
225
- </button>
226
- </form>
227
- ```
228
-
229
- ```tsx [React]
230
- import { useStep } from '@picked/react/hooks'
231
- import { useUnit } from 'effector-react'
232
-
233
- function StepForm() {
234
- const { fields, form } = useStep()
235
- const currentFields = useUnit(fields)
236
- const isSubmitting = useUnit(form.submitting)
237
-
238
- return (
239
- <form {...form.props}>
240
- {currentFields.map((field) => (
241
- <div key={field.id}>
242
- <input
243
- name={field.id}
244
- value={field.value ?? ''}
245
- onChange={field.onChange}
246
- onBlur={field.onBlur}
247
- />
248
- {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
249
- </div>
250
- ))}
251
-
252
- <button type="submit" disabled={isSubmitting}>
253
- {isSubmitting ? 'Submitting…' : 'Continue'}
254
- </button>
255
- </form>
256
- )
257
- }
258
- ```
259
-
260
- :::
261
-
262
- When you submit the form, the SDK does this:
263
-
264
- 1. It collects the field values from the HTML form.
265
- 2. It runs each `onBeforeSubmit` callback, one after another. A throw here aborts the
266
- submit and commits nothing.
267
- 3. It checks each required field and each validation rule.
268
- 4. It commits the responses to the experience state.
269
- 5. It fires `form_submit_success` or `form_submit_error` on the event bus.
270
- 6. It runs your `onSuccess` or `onError` callback.
271
- 7. It moves to the next step. A call to `stopNavigation` stops this. A callback that
272
- failed in step 6 does not stop it.
273
-
274
- ### Transform data before a submission
275
-
276
- Use `onBeforeSubmit` to intercept and transform the field data before the SDK stores it. The callbacks run one after another. Each one takes the output of the one before
277
- it:
278
-
279
- :::code-group
280
-
281
- ```svelte [Svelte]
282
- <script lang="ts">
283
- import { getStep } from '@picked/svelte/hooks'
284
-
285
- const { form } = getStep({
286
- onBeforeSubmit: async (data) => {
287
- // Normalize the email address to lowercase first.
288
- return {
289
- ...data,
290
- 'email-address': String(data['email-address']).toLowerCase(),
291
- }
292
- },
293
- })
294
- </script>
295
-
296
- <form {...form.props}>{/* ... */}</form>
297
- ```
298
-
299
- ```tsx [React]
300
- import { useStep } from '@picked/react/hooks'
301
-
302
- function ContactStep() {
303
- const { form } = useStep({
304
- onBeforeSubmit: async (data) => {
305
- // Normalize the email address to lowercase first.
306
- return {
307
- ...data,
308
- 'email-address': String(data['email-address']).toLowerCase(),
309
- }
310
- },
311
- })
312
-
313
- return <form {...form.props}>{/* ... */}</form>
314
- }
315
- ```
316
-
317
- :::
318
-
319
- :::warning
320
- When an `onBeforeSubmit` callback throws, the SDK aborts the submit before it commits
321
- anything, and the step does not advance. This is deliberate. A payload that is only
322
- part transformed would store data that looks correct but is wrong. Use
323
- `onBeforeSubmit` for a transform and for a validation check. Put a side effect, such as an
324
- analytics call or a backend call, in `onSuccess`.
325
- :::
326
-
327
- ### Success and error callbacks
328
-
329
- Pass `onSuccess` and `onError` to act on the outcome of a submission:
330
-
331
- :::code-group
332
-
333
- ```svelte [Svelte]
334
- <script lang="ts">
335
- import { getStep } from '@picked/svelte/hooks'
336
-
337
- const { form } = getStep({
338
- onSuccess: ({ data }) => {
339
- analytics.track('lead_captured', data)
340
- },
341
- onError: (errors) => {
342
- console.error('Validation errors:', errors)
343
- },
344
- })
345
- </script>
346
-
347
- <form {...form.props}>{/* ... */}</form>
348
- ```
349
-
350
- ```tsx [React]
351
- import { useStep } from '@picked/react/hooks'
352
-
353
- function ContactStep() {
354
- const { form } = useStep({
355
- onSuccess: ({ data }) => {
356
- analytics.track('lead_captured', data)
357
- },
358
- onError: (errors) => {
359
- console.error('Validation errors:', errors)
360
- },
361
- })
362
-
363
- return <form {...form.props}>{/* ... */}</form>
364
- }
365
- ```
366
-
367
- :::
368
-
369
- A callback belongs to the component that passes it. The SDK releases the callback
370
- when that component unmounts, so you write no teardown code. A new inline function on
371
- a re-render replaces the earlier one and does not add a second one. One submit
372
- therefore runs each callback exactly one time.
373
-
374
- An `onSuccess` or `onError` callback that throws or rejects cannot stop the flow. The
375
- step still advances, and the SDK reports the failure on the client event bus as
376
- `form_submit_callback_error`, so the failure stays observable.
377
-
378
- #### Register a callback in code
379
-
380
- `form.onSuccess`, `form.onError`, and `form.onBeforeSubmit` are an escape hatch, for
381
- a callback that you must attach only when a condition holds. They return nothing. The
382
- SDK releases them for you, as above.
383
-
384
- ```tsx [React]
385
- function ContactStep({ trackConversions }: { trackConversions: boolean }) {
386
- const { form } = useStep()
387
-
388
- if (trackConversions) {
389
- form.onSuccess(({ data }) => analytics.track('lead_captured', data))
390
- }
391
-
392
- return <form {...form.props}>{/* ... */}</form>
393
- }
394
- ```
395
-
396
- :::warning
397
- Register from the body of the component, and not from an event handler. A
398
- registration inside a click handler is added each time the handler runs, and the SDK
399
- releases it only when the component unmounts. The registrations therefore accumulate.
400
- Use the options object above where you can.
401
- :::
402
-
403
- ### Stop the move to the next step
404
-
405
- By default the experience advances to the next step after a successful submission.
406
- Call `stopNavigation()` inside `onSuccess` to stop this. Do it when you must choose
407
- the next step yourself:
408
-
409
- :::code-group
410
-
411
- ```svelte [Svelte]
412
- <script lang="ts">
413
- import { getStep } from '@picked/svelte/hooks'
414
- import { getExperience } from '@picked/svelte/hooks'
415
-
416
- const experience = getExperience()
417
-
418
- const { form } = getStep({
419
- onSuccess: ({ data, stopNavigation }) => {
420
- stopNavigation()
421
-
422
- // Choose the next step from the data that the user sent.
423
- if (data['plan-type'] === 'enterprise') {
424
- experience.goToStepById('enterprise-contact')
425
- } else {
426
- experience.goToNextStep()
427
- }
428
- },
429
- })
430
- </script>
431
-
432
- <form {...form.props}>{/* ... */}</form>
433
- ```
434
-
435
- ```tsx [React]
436
- import { useStep } from '@picked/react/hooks'
437
- import { useExperience } from '@picked/react/hooks'
438
-
439
- function PlanStep() {
440
- const experience = useExperience()
441
-
442
- const { form } = useStep({
443
- onSuccess: ({ data, stopNavigation }) => {
444
- stopNavigation()
445
-
446
- // Choose the next step from the data that the user sent.
447
- if (data['plan-type'] === 'enterprise') {
448
- experience.goToStepById('enterprise-contact')
449
- } else {
450
- experience.goToNextStep()
451
- }
452
- },
453
- })
454
-
455
- return <form {...form.props}>{/* ... */}</form>
456
- }
457
- ```
458
-
459
- :::
460
-
461
- Move by step `id` with `goToStepById('<step-id>')`, or by the `key` of the step with
462
- `goToStepByKey('<step-key>')`. Both jump directly to the step node that matches. When
463
- no step matches, the call does nothing.
464
-
465
- ## Best practices
466
-
467
- - **Give each step a meaningful label**: analytics events and the dashboard both show
468
- the `label`. Use a name that says what the step does, such as `"Contact Details"`
469
- and not `"Step 2"`.
470
- - **Keep each step small**: each step must collect one small set of related
471
- information. More steps with fewer fields each reduce the number of users who leave.
472
- - **Use a condition to take a branch**: do not make a second copy of a step. Write the
473
- branch with `nextConditional()`, so that you can reuse a shared step.
474
- - **Use `stopNavigation` only when you must**: an automatic move after a submit is
475
- what a user expects. Stop it only when you must choose the next step yourself.
476
- - **Check a field on blur, not on change**: the SDK does this for you. A field shows
477
- an error after the user leaves it, and not while the user types.
478
- - **Load a large step component lazily**: use the `component` prop on `<Step>` with a
479
- dynamic import, for content that only some users see.
@@ -1,53 +0,0 @@
1
- ---
2
- layout: docs
3
- ---
4
-
5
- import { Screenshot } from '../../components/Screenshot'
6
-
7
- # Steps
8
-
9
- A step is one screen or card that a visitor sees during an experience. Each step
10
- holds one or more fields. A field collects data or shows content.
11
-
12
- <Screenshot
13
- route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/steps"
14
- alt="The steps list for a workspace on the Picked dashboard"
15
- />
16
-
17
- ## Create a step
18
-
19
- 1. Open your project.
20
- 2. Click **Steps** in the sidebar.
21
- 3. Click **New Step**.
22
- 4. Enter a name and a key.
23
- 5. Click **Create**.
24
-
25
- The key is a unique identifier for your application. You set it once, and you cannot
26
- change it later.
27
-
28
- ## Set up a step
29
-
30
- **Name**: The name that the dashboard shows. Your visitors do not see it.
31
-
32
- **Key**: The identifier that your application uses for this step. Your
33
- visitors do not see it.
34
-
35
- **Layout Name**: The name of the component layout that renders this step. Examples
36
- are `card`, `modal`, and `fullscreen`.
37
-
38
- A step can also carry a **Step Layout**. A step layout is a reusable CMS schema, and
39
- it gives the step a Content Entry. See [Page and Step Layouts](/content/layouts).
40
-
41
- ## Assign fields
42
-
43
- In the flow builder, drag a field from the entity browser onto a step node. The field
44
- appears in the **Assigned Fields** list of the step, in the inspector.
45
-
46
- You can also choose the step node and expand the **Assigned Fields** section to see
47
- and control the assigned fields. Click a field to open its inspector.
48
-
49
- ## Reuse steps
50
-
51
- A step is a project-level entity. You can drag the same step into more than one
52
- experience flow. A change to the options of a step applies at every place that uses
53
- it.