picked 0.0.101-next.2 → 0.0.101
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 +27 -12
- package/docs/explanation/conditions-and-the-logic-engine.md +94 -0
- package/docs/explanation/content-references.md +99 -0
- package/docs/explanation/feature-flag-allocation.md +218 -0
- package/docs/explanation/how-picked-works.md +124 -0
- package/docs/explanation/how-pickedql-works.md +76 -0
- package/docs/explanation/pages-and-flows.md +64 -0
- package/docs/explanation/the-content-model.md +87 -0
- package/docs/explanation/the-two-apis.md +68 -0
- package/docs/explanation/why-server-side-rendering.md +76 -0
- package/docs/explanation/workspaces-and-versions.md +71 -0
- package/docs/how-to/add-a-step-field-or-action.md +56 -0
- package/docs/how-to/analytics/index.md +324 -0
- package/docs/how-to/archive-or-delete-a-workspace.md +59 -0
- package/docs/how-to/authenticate-the-cli.md +101 -0
- package/docs/how-to/authenticate-with-a-magic-link.md +56 -0
- package/docs/how-to/authenticate-with-a-password.md +68 -0
- package/docs/how-to/authenticate-with-an-api-key.md +60 -0
- package/docs/how-to/authenticate-with-oauth.md +49 -0
- package/docs/how-to/build-a-flow.md +106 -0
- package/docs/how-to/call-an-external-api-from-a-workflow.md +116 -0
- package/docs/how-to/connect-the-mcp-server.md +46 -0
- package/docs/how-to/create-a-content-entry.md +58 -0
- package/docs/how-to/create-a-project.md +40 -0
- package/docs/how-to/create-an-experience.md +51 -0
- package/docs/how-to/create-and-revoke-an-api-key.md +54 -0
- package/docs/how-to/debug-with-the-chrome-extension.md +57 -0
- package/docs/how-to/define-a-page-layout.md +54 -0
- package/docs/how-to/fill-and-publish-a-page.md +82 -0
- package/docs/how-to/install-the-gtm-template.md +84 -0
- package/docs/how-to/install-the-sdk.md +93 -0
- package/docs/how-to/install-the-vite-plugin.md +69 -0
- package/docs/how-to/invite-and-remove-members.md +45 -0
- package/docs/how-to/point-one-entry-at-another.md +75 -0
- package/docs/how-to/preview-a-workspace.md +39 -0
- package/docs/how-to/publish-a-workspace.md +42 -0
- package/docs/how-to/read-a-feature-flag.md +113 -0
- package/docs/how-to/read-a-referenced-entry.md +109 -0
- package/docs/how-to/register-a-component.md +55 -0
- package/docs/how-to/register-an-action-handler.md +85 -0
- package/docs/how-to/register-an-api-that-uses-oauth2.md +107 -0
- package/docs/how-to/render-a-step-form.md +131 -0
- package/docs/how-to/render-an-unpublished-preview.md +41 -0
- package/docs/how-to/retry-a-failed-call-with-a-loop.md +81 -0
- package/docs/how-to/reuse-a-section.md +64 -0
- package/docs/how-to/reuse-a-step-field-or-action.md +39 -0
- package/docs/how-to/roll-back-a-published-version.md +39 -0
- package/docs/how-to/run-an-ab-test.md +59 -0
- package/docs/how-to/run-code-on-a-form-submission.md +125 -0
- package/docs/how-to/server-render-a-page.md +81 -0
- package/docs/how-to/set-a-content-security-policy.md +32 -0
- package/docs/how-to/set-up-mfa.md +126 -0
- package/docs/how-to/switch-workspaces.md +122 -0
- package/docs/how-to/upload-and-use-an-asset.mdx +87 -0
- package/docs/how-to/validate-a-field.md +52 -0
- package/docs/how-to/wait-for-an-action-response.md +182 -0
- package/docs/how-to/write-a-condition.md +57 -0
- package/docs/how-to/write-a-predicate.md +60 -0
- package/docs/index.md +59 -0
- package/docs/reference/dashboard/actions.mdx +65 -0
- package/docs/reference/dashboard/api-keys.mdx +47 -0
- package/docs/reference/dashboard/conditions.mdx +51 -0
- package/docs/reference/dashboard/content/asset-library.mdx +74 -0
- package/docs/reference/dashboard/content/blocks.mdx +66 -0
- package/docs/reference/dashboard/content/content-entries.mdx +109 -0
- package/docs/reference/dashboard/content/models.mdx +119 -0
- package/docs/reference/dashboard/content/pages.mdx +72 -0
- package/docs/reference/dashboard/experiences.mdx +61 -0
- package/docs/reference/dashboard/external-apis.md +182 -0
- package/docs/reference/dashboard/features.mdx +165 -0
- package/docs/reference/dashboard/fields.mdx +120 -0
- package/docs/reference/dashboard/flow-builder/canvas.mdx +58 -0
- package/docs/reference/dashboard/flow-builder/condition-builder.md +69 -0
- package/docs/reference/dashboard/flow-builder/keyboard-shortcuts.md +72 -0
- package/docs/reference/dashboard/flow-builder/nodes.md +74 -0
- package/docs/reference/dashboard/labels.md +321 -0
- package/docs/reference/dashboard/lead-search.md +235 -0
- package/docs/reference/dashboard/projects.mdx +53 -0
- package/docs/reference/dashboard/reports.mdx +53 -0
- package/docs/reference/dashboard/steps.mdx +47 -0
- package/docs/reference/dashboard/team-management.md +38 -0
- package/docs/reference/dashboard/workflow-runs.md +157 -0
- package/docs/reference/dashboard/workflow-templates.md +182 -0
- package/docs/reference/dashboard/workflow-test-runs.md +85 -0
- package/docs/reference/dashboard/workspace-versioning.mdx +42 -0
- package/docs/reference/dashboard/workspaces.mdx +91 -0
- package/docs/reference/delivery-api/experience-content.md +152 -0
- package/docs/reference/delivery-api/index.md +45 -0
- package/docs/reference/delivery-api/preview-caching-crawling.md +79 -0
- package/docs/reference/delivery-api/project-config.md +65 -0
- package/docs/reference/delivery-api/workspace-schema.md +107 -0
- package/docs/reference/glossary.md +201 -0
- package/docs/reference/management-api/authentication.md +87 -0
- package/docs/reference/management-api/authorization-and-scopes.md +291 -0
- package/docs/reference/management-api/error-handling.md +232 -0
- package/docs/reference/management-api/pagination.md +192 -0
- package/docs/reference/management-api/schema/action.md +261 -0
- package/docs/reference/management-api/schema/analytics-session.md +363 -0
- package/docs/reference/management-api/schema/analytics.md +73 -0
- package/docs/reference/management-api/schema/api-key.md +170 -0
- package/docs/reference/management-api/schema/auth.md +1177 -0
- package/docs/reference/management-api/schema/cms-fields.md +954 -0
- package/docs/reference/management-api/schema/cms-models.md +1155 -0
- package/docs/reference/management-api/schema/common.md +362 -0
- package/docs/reference/management-api/schema/condition.md +254 -0
- package/docs/reference/management-api/schema/design-system.md +199 -0
- package/docs/reference/management-api/schema/experience.md +1098 -0
- package/docs/reference/management-api/schema/external-api.md +637 -0
- package/docs/reference/management-api/schema/feature.md +350 -0
- package/docs/reference/management-api/schema/feedback.md +103 -0
- package/docs/reference/management-api/schema/field.md +400 -0
- package/docs/reference/management-api/schema/index.md +243 -0
- package/docs/reference/management-api/schema/integration.md +272 -0
- package/docs/reference/management-api/schema/label.md +349 -0
- package/docs/reference/management-api/schema/lead.md +340 -0
- package/docs/reference/management-api/schema/mcp-server.md +299 -0
- package/docs/reference/management-api/schema/metric.md +223 -0
- package/docs/reference/management-api/schema/operation.md +96 -0
- package/docs/reference/management-api/schema/organization.md +749 -0
- package/docs/reference/management-api/schema/project.md +607 -0
- package/docs/reference/management-api/schema/report.md +243 -0
- package/docs/reference/management-api/schema/saved-view.md +288 -0
- package/docs/reference/management-api/schema/shopify.md +795 -0
- package/docs/reference/management-api/schema/step.md +273 -0
- package/docs/reference/management-api/schema/tenant-branding.md +185 -0
- package/docs/reference/management-api/schema/workbench.md +246 -0
- package/docs/reference/management-api/schema/workflow-run.md +263 -0
- package/docs/reference/management-api/schema/workflow.md +1110 -0
- package/docs/reference/management-api/schema/workspace-version.md +823 -0
- package/docs/reference/management-api/schema/workspace.md +917 -0
- package/docs/reference/pickedql/data-sources.md +174 -0
- package/docs/reference/pickedql/dimensions.md +103 -0
- package/docs/reference/pickedql/filtering.md +64 -0
- package/docs/reference/pickedql/index.md +18 -0
- package/docs/reference/pickedql/schema.md +172 -0
- package/docs/reference/pickedql/virtual-functions.md +248 -0
- package/docs/reference/sdk/actions.md +154 -0
- package/docs/reference/sdk/analytics-events.md +453 -0
- package/docs/reference/sdk/conditions.md +147 -0
- package/docs/reference/sdk/content-security-policy.md +21 -0
- package/docs/reference/sdk/content.md +253 -0
- package/docs/reference/sdk/experiences.md +51 -0
- package/docs/reference/sdk/features.md +256 -0
- package/docs/reference/sdk/fields.md +255 -0
- package/docs/reference/sdk/index.md +68 -0
- package/docs/reference/sdk/plugins/create-experience.md +123 -0
- package/docs/reference/sdk/plugins/dynamic-experiences.md +74 -0
- package/docs/reference/sdk/plugins/fingerprintjs.md +39 -0
- package/docs/reference/sdk/plugins/hotjar.md +60 -0
- package/docs/reference/sdk/plugins/index.md +33 -0
- package/docs/reference/sdk/plugins/sentry.md +51 -0
- package/docs/reference/sdk/plugins/shopify.md +109 -0
- package/docs/reference/sdk/plugins/window-object.md +25 -0
- package/docs/reference/sdk/predicates.md +43 -0
- package/docs/reference/sdk/react.md +61 -0
- package/docs/reference/sdk/ssr/index.md +33 -0
- package/docs/reference/sdk/ssr/rendering-and-hydration.md +103 -0
- package/docs/reference/sdk/ssr/serialize.md +80 -0
- package/docs/reference/sdk/ssr/slots-and-placements.md +113 -0
- package/docs/reference/sdk/steps.md +86 -0
- package/docs/reference/sdk/svelte.md +74 -0
- package/docs/reference/sdk/type-generation.md +265 -0
- package/docs/reference/tools/admin-api-client.md +89 -0
- package/docs/reference/tools/chrome-extension.md +16 -0
- package/docs/reference/tools/cli.md +319 -0
- package/docs/reference/tools/delivery-api-client.md +60 -0
- package/docs/reference/tools/image-optimisation-service.md +104 -0
- package/docs/reference/tools/mcp-run.md +121 -0
- package/docs/reference/tools/mcp.md +391 -0
- package/docs/reference/tools/vite-plugin.md +163 -0
- package/docs/tutorials/first-api-call.md +253 -0
- package/docs/tutorials/first-experience-in-the-dashboard.mdx +160 -0
- package/docs/tutorials/first-experience.mdx +254 -0
- package/docs/tutorials/first-server-rendered-page.mdx +186 -0
- package/package.json +8 -7
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to read a referenced entry
|
|
7
|
+
|
|
8
|
+
This guide shows you how to read the content that a
|
|
9
|
+
[Reference field](/reference/dashboard/content/models#reference-fields) points at. You
|
|
10
|
+
need a component that renders a step first. See
|
|
11
|
+
[How to render a step form](/how-to/render-a-step-form).
|
|
12
|
+
|
|
13
|
+
A Reference field holds a pointer, and not the content. The entry travels beside the
|
|
14
|
+
content of the step, so you resolve the pointer against the content of the client.
|
|
15
|
+
|
|
16
|
+
## 1. Resolve a single reference
|
|
17
|
+
|
|
18
|
+
Pass the raw field value to `useReference()` in React, or to `getReference()` in Svelte.
|
|
19
|
+
|
|
20
|
+
:::code-group
|
|
21
|
+
|
|
22
|
+
```tsx [React]
|
|
23
|
+
import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
24
|
+
|
|
25
|
+
export function ProductCategory() {
|
|
26
|
+
const content = useStepContent()
|
|
27
|
+
const category = useStoreGlobal(useReference(content?.category))
|
|
28
|
+
|
|
29
|
+
if (!category) return null
|
|
30
|
+
|
|
31
|
+
return <span>{String(category.data.name ?? '')}</span>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```svelte [Svelte]
|
|
36
|
+
<script lang="ts">
|
|
37
|
+
import { getStepContent, getReference } from '@picked/svelte/hooks'
|
|
38
|
+
|
|
39
|
+
const content = getStepContent()
|
|
40
|
+
const category = getReference(content.map((c) => c?.category ?? null, { skipVoid: false }))
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if $category}
|
|
44
|
+
<span>{$category.data.name ?? ''}</span>
|
|
45
|
+
{/if}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
:::
|
|
49
|
+
|
|
50
|
+
In Svelte, pass a store and not a plain value. Content arrives after the component
|
|
51
|
+
starts. A plain value therefore holds the result at "not set".
|
|
52
|
+
|
|
53
|
+
## 2. Resolve a repeatable reference
|
|
54
|
+
|
|
55
|
+
Use `useReferences()` or `getReferences()`. They give a list in field order.
|
|
56
|
+
|
|
57
|
+
:::code-group
|
|
58
|
+
|
|
59
|
+
```tsx [React]
|
|
60
|
+
import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
61
|
+
|
|
62
|
+
export function ProductCategories() {
|
|
63
|
+
const content = useStepContent()
|
|
64
|
+
const categories = useStoreGlobal(useReferences(content?.categories))
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ul>
|
|
68
|
+
{categories.map((category) => (
|
|
69
|
+
<li key={category.id}>{String(category.data.name ?? '')}</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```svelte [Svelte]
|
|
77
|
+
<script lang="ts">
|
|
78
|
+
import { getStepContent, getReferences } from '@picked/svelte/hooks'
|
|
79
|
+
|
|
80
|
+
const content = getStepContent()
|
|
81
|
+
const categories = getReferences(content.map((c) => c?.categories ?? null, { skipVoid: false }))
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<ul>
|
|
85
|
+
{#each $categories as category (category.id)}
|
|
86
|
+
<li>{category.data.name ?? ''}</li>
|
|
87
|
+
{/each}
|
|
88
|
+
</ul>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
:::
|
|
92
|
+
|
|
93
|
+
The list holds no empty places, so you need no check for each item.
|
|
94
|
+
|
|
95
|
+
## Handle a target that does not resolve
|
|
96
|
+
|
|
97
|
+
A single reference gives an empty result when Picked cannot resolve the target. A
|
|
98
|
+
repeatable reference leaves the item out of the list.
|
|
99
|
+
|
|
100
|
+
This happens when somebody deleted the target, when the target belongs to another
|
|
101
|
+
workspace, or when the chain of references is deeper than the limit.
|
|
102
|
+
|
|
103
|
+
Render something else for the empty case, as the React example above does.
|
|
104
|
+
|
|
105
|
+
## Related
|
|
106
|
+
|
|
107
|
+
- [Content](/reference/sdk/content) — every hook and its return type.
|
|
108
|
+
- [About content references](/explanation/content-references) — why the entry travels separately.
|
|
109
|
+
- [How to point one entry at another](/how-to/point-one-entry-at-another) — the dashboard side.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to register a component
|
|
7
|
+
|
|
8
|
+
This guide shows you how to connect a component in your application to an experience in
|
|
9
|
+
the dashboard.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Wrap your component with the adapter for your framework.
|
|
14
|
+
2. Pass it in the `components` object of `createClient()`.
|
|
15
|
+
3. Use the same key as the component name of the experience.
|
|
16
|
+
|
|
17
|
+
:::code-group
|
|
18
|
+
|
|
19
|
+
```tsx [React]
|
|
20
|
+
import { createClient } from 'picked'
|
|
21
|
+
import { adaptReactComponent } from '@picked/react'
|
|
22
|
+
import { MyWidget } from './MyWidget'
|
|
23
|
+
|
|
24
|
+
const picked = createClient({
|
|
25
|
+
projectId: 'your-project-id',
|
|
26
|
+
components: {
|
|
27
|
+
MyWidget: adaptReactComponent(MyWidget),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ts [Svelte]
|
|
33
|
+
import { createClient } from 'picked'
|
|
34
|
+
import { adaptSvelteComponent } from '@picked/svelte'
|
|
35
|
+
import MyWidget from './MyWidget.svelte'
|
|
36
|
+
|
|
37
|
+
const picked = createClient({
|
|
38
|
+
projectId: 'your-project-id',
|
|
39
|
+
components: {
|
|
40
|
+
MyWidget: adaptSvelteComponent(MyWidget),
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
:::
|
|
46
|
+
|
|
47
|
+
:::warning
|
|
48
|
+
`components` takes an adapted component. A plain React or Svelte component does not work
|
|
49
|
+
without `adaptReactComponent` or `adaptSvelteComponent`.
|
|
50
|
+
:::
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- [SDK experiences](/reference/sdk/experiences) — the experience shape.
|
|
55
|
+
- [How to install the SDK](/how-to/install-the-sdk).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to register an action handler
|
|
7
|
+
|
|
8
|
+
This guide shows you how to connect an action node in a flow to a function in your
|
|
9
|
+
application. You need an action in your project first, with a key. See
|
|
10
|
+
[Actions](/reference/dashboard/actions).
|
|
11
|
+
|
|
12
|
+
A published action that starts a workflow runs without a handler. A handler under its key
|
|
13
|
+
replaces the workflow.
|
|
14
|
+
|
|
15
|
+
## 1. Register the handler
|
|
16
|
+
|
|
17
|
+
Pass an `actions` object to `createClient()`. Each key must match an action key from the
|
|
18
|
+
dashboard:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createClient } from 'picked'
|
|
22
|
+
|
|
23
|
+
const picked = createClient({
|
|
24
|
+
projectId: 'your-project-id',
|
|
25
|
+
actions: {
|
|
26
|
+
'track-signup': () => {
|
|
27
|
+
analytics.track('signup_completed')
|
|
28
|
+
},
|
|
29
|
+
notify: (params) => {
|
|
30
|
+
sendNotification(params)
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The SDK calls the function that matches when the flow reaches the action node.
|
|
37
|
+
|
|
38
|
+
## 2. Throw only for a true error
|
|
39
|
+
|
|
40
|
+
The flow takes the failure branch when your handler throws or its promise rejects. Throw
|
|
41
|
+
when the request itself did not complete:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
'check-eligibility': async () => {
|
|
45
|
+
const response = await fetch('/api/eligibility')
|
|
46
|
+
// Throw only when the request itself fails. That takes the failure branch.
|
|
47
|
+
if (!response.ok) throw new Error('Eligibility request failed')
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 3. Return a response
|
|
52
|
+
|
|
53
|
+
The value that your handler returns is the response of the action. Code that runs the
|
|
54
|
+
action reads this value:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
'quote-premium': async ({ responses }) => {
|
|
58
|
+
const age = responses.find((r) => r.fieldKey === 'age')?.value
|
|
59
|
+
const { premium } = await fetch(`/api/premium?age=${age}`).then((r) => r.json())
|
|
60
|
+
return { premium }
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. Record a negative answer as a response
|
|
65
|
+
|
|
66
|
+
A valid negative result is not a failure. An endpoint can answer `200` with a "no". Store
|
|
67
|
+
that value as a response and branch on it with a condition instead:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
'check-serviced-area': async ({ client, responses }) => {
|
|
71
|
+
const postcode = responses.find((r) => r.fieldKey === 'postcode')?.value
|
|
72
|
+
const { serviced } = await fetch(`/api/serviced?postcode=${postcode}`).then((r) => r.json())
|
|
73
|
+
// Store the outcome as a response. A later condition then takes the branch.
|
|
74
|
+
client.responses.register('is-serviced', serviced)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Related
|
|
79
|
+
|
|
80
|
+
- [About how Picked works](/explanation/how-picked-works#how-an-action-decides-which-way-to-go)
|
|
81
|
+
— why a returned `false` is a success.
|
|
82
|
+
- [SDK actions](/reference/sdk/actions) — what the handler receives and returns.
|
|
83
|
+
- [How to wait for the response of an action](/how-to/wait-for-an-action-response) — show the
|
|
84
|
+
response in a component.
|
|
85
|
+
- [Actions](/reference/dashboard/actions) — the dashboard options.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to register an API that uses OAuth2
|
|
7
|
+
|
|
8
|
+
This guide shows you how to let a workflow call an API whose access tokens expire, such as
|
|
9
|
+
Zoho CRM. You need a client ID and a client secret from the provider. For the refresh token
|
|
10
|
+
grant, you also need a refresh token.
|
|
11
|
+
|
|
12
|
+
Picked keeps the credential, gets an access token from the provider, and sends the token
|
|
13
|
+
with each request. Picked gets a new token before the old token expires.
|
|
14
|
+
|
|
15
|
+
## 1. Make a refresh token in Zoho
|
|
16
|
+
|
|
17
|
+
Do this step for Zoho CRM only. For another provider, get the credential from the
|
|
18
|
+
provider's documentation.
|
|
19
|
+
|
|
20
|
+
1. Open the Zoho API console for the region of your Zoho account.
|
|
21
|
+
2. Add a client of the type `Self Client`.
|
|
22
|
+
3. Make a grant code with the scopes that the workflow needs, such as
|
|
23
|
+
`ZohoCRM.modules.leads.CREATE`.
|
|
24
|
+
4. Send a `POST` request to the token URL of your region, such as
|
|
25
|
+
`https://accounts.zoho.com.au/oauth/v2/token`. Put the parameters in the form body, not
|
|
26
|
+
in the URL. The parameters are `grant_type=authorization_code`, `client_id`,
|
|
27
|
+
`client_secret`, and `code` with the grant code. The grant code expires soon, so do this
|
|
28
|
+
step immediately.
|
|
29
|
+
5. Keep the `refresh_token` from the answer.
|
|
30
|
+
|
|
31
|
+
:::warning
|
|
32
|
+
Make the refresh token as a Zoho user that only Picked uses. Zoho keeps 20 refresh tokens
|
|
33
|
+
for each user and client. When you make one more, Zoho stops the oldest refresh token. That
|
|
34
|
+
token can be the token that Picked uses.
|
|
35
|
+
:::
|
|
36
|
+
|
|
37
|
+
## 2. Register the external API
|
|
38
|
+
|
|
39
|
+
1. Open **Integrations**.
|
|
40
|
+
2. Choose **Configure external APIs**.
|
|
41
|
+
3. Choose **New external API**.
|
|
42
|
+
4. For Zoho CRM, set **Preset** to the region of your Zoho account. The preset sets the
|
|
43
|
+
**Display name**, the **Origin**, the **Credential** and the settings of the OAuth2
|
|
44
|
+
credential. Then go to step 10. If **Preset** does not show your region, do steps 5 to 9.
|
|
45
|
+
5. Enter a **Display name**.
|
|
46
|
+
6. Enter the **Origin**, such as `https://www.zohoapis.com.au`.
|
|
47
|
+
7. Set **Credential** to **OAuth2 credential**.
|
|
48
|
+
8. Enter the **Token URL** from the provider's documentation. Do not put a secret in the
|
|
49
|
+
token URL.
|
|
50
|
+
9. Set the **Grant**, the **Client authentication** and the **Header scheme** from the
|
|
51
|
+
provider's documentation.
|
|
52
|
+
10. Enter the **Client ID**.
|
|
53
|
+
11. Enter the **Client secret**.
|
|
54
|
+
12. For the refresh token grant, enter the **Refresh token**.
|
|
55
|
+
13. Choose **Register external API**.
|
|
56
|
+
|
|
57
|
+
The client secret and the refresh token are write-only. After you save them, Picked shows
|
|
58
|
+
only that they are stored.
|
|
59
|
+
|
|
60
|
+
## 3. Add a credential to an external API that you registered
|
|
61
|
+
|
|
62
|
+
1. Open the external API.
|
|
63
|
+
2. Choose **Use an OAuth2 credential**.
|
|
64
|
+
3. For Zoho CRM, set **Preset** to the region of your Zoho account. For another provider,
|
|
65
|
+
do steps 8 and 9 of section 2.
|
|
66
|
+
4. Do steps 10 to 12 of section 2.
|
|
67
|
+
5. Choose **Save credential**.
|
|
68
|
+
|
|
69
|
+
The credential replaces the authorization header of the external API.
|
|
70
|
+
|
|
71
|
+
## 4. Refresh only for an expired token
|
|
72
|
+
|
|
73
|
+
Some APIs answer 401 for an error that a new token does not correct. To get a new token only
|
|
74
|
+
for an expired token:
|
|
75
|
+
|
|
76
|
+
1. Open the external API.
|
|
77
|
+
2. Choose **Edit OAuth2 credential**.
|
|
78
|
+
3. In **401 rule: response body path**, enter the path of the error code in the 401 answer,
|
|
79
|
+
such as `code`. Put a dot between the parts of the path, for example `data.0.code`.
|
|
80
|
+
4. In **401 rule: values**, enter the error codes that show an expired token, such as
|
|
81
|
+
`INVALID_TOKEN`.
|
|
82
|
+
5. Choose **Save credential**.
|
|
83
|
+
|
|
84
|
+
The Zoho CRM preset sets this rule for you.
|
|
85
|
+
|
|
86
|
+
## 5. Replace a refresh token that the provider rejected
|
|
87
|
+
|
|
88
|
+
When the provider rejects the credential, the external API page shows the reason in **Last
|
|
89
|
+
refresh failure**. Each call fails with `credential_rejected` until you correct the
|
|
90
|
+
credential.
|
|
91
|
+
|
|
92
|
+
1. Make a new refresh token.
|
|
93
|
+
2. Open the external API.
|
|
94
|
+
3. Choose **Edit OAuth2 credential**.
|
|
95
|
+
4. Enter the new **Refresh token**.
|
|
96
|
+
5. Choose **Save credential**.
|
|
97
|
+
|
|
98
|
+
Leave the **Client secret** empty to keep the stored client secret. If you change the
|
|
99
|
+
**Token URL**, the **Client ID** or the **Client authentication**, enter the client secret
|
|
100
|
+
and the refresh token again.
|
|
101
|
+
|
|
102
|
+
## Related
|
|
103
|
+
|
|
104
|
+
- [External APIs](/reference/dashboard/external-apis#oauth2-credential) — every setting of
|
|
105
|
+
an OAuth2 credential, and when Picked gets a new token.
|
|
106
|
+
- [How to call an external API from a workflow](/how-to/call-an-external-api-from-a-workflow)
|
|
107
|
+
— the node that sends the request.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to render a step form
|
|
7
|
+
|
|
8
|
+
This guide shows you how to render the fields of the active step and let a visitor submit
|
|
9
|
+
them.
|
|
10
|
+
|
|
11
|
+
## 1. Put the steps in a router
|
|
12
|
+
|
|
13
|
+
`StepRouter` watches the step state and activates the `Step` whose `id` matches.
|
|
14
|
+
|
|
15
|
+
:::code-group
|
|
16
|
+
|
|
17
|
+
```tsx [React]
|
|
18
|
+
import { StepRouter, Step } from '@picked/react/router'
|
|
19
|
+
|
|
20
|
+
function MyExperience() {
|
|
21
|
+
return (
|
|
22
|
+
<StepRouter>
|
|
23
|
+
<Step id="welcome">
|
|
24
|
+
<WelcomeScreen />
|
|
25
|
+
</Step>
|
|
26
|
+
<Step id="contact">
|
|
27
|
+
<ContactForm />
|
|
28
|
+
</Step>
|
|
29
|
+
</StepRouter>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```svelte [Svelte]
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import { StepRouter, Step } from '@picked/svelte/router'
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<StepRouter>
|
|
40
|
+
<Step id="welcome">
|
|
41
|
+
<WelcomeScreen />
|
|
42
|
+
</Step>
|
|
43
|
+
<Step id="contact">
|
|
44
|
+
<ContactForm />
|
|
45
|
+
</Step>
|
|
46
|
+
</StepRouter>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
:::
|
|
50
|
+
|
|
51
|
+
:::note
|
|
52
|
+
A router must hold every Step, even when there is only one Step.
|
|
53
|
+
:::
|
|
54
|
+
|
|
55
|
+
## 2. Render the fields
|
|
56
|
+
|
|
57
|
+
Bind `form.props` to your `<form>` element and map over the fields.
|
|
58
|
+
|
|
59
|
+
:::code-group
|
|
60
|
+
|
|
61
|
+
```tsx [React]
|
|
62
|
+
import { useStep, useStoreGlobal } from '@picked/react/hooks'
|
|
63
|
+
|
|
64
|
+
function StepForm() {
|
|
65
|
+
const { fields, form } = useStep()
|
|
66
|
+
const currentFields = useStoreGlobal(fields)
|
|
67
|
+
const isSubmitting = useStoreGlobal(form.submitting)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<form {...form.props}>
|
|
71
|
+
{currentFields.map((field) => (
|
|
72
|
+
<div key={field.id}>
|
|
73
|
+
<label htmlFor={field.id}>{field.label}</label>
|
|
74
|
+
<input
|
|
75
|
+
id={field.id}
|
|
76
|
+
name={field.id}
|
|
77
|
+
value={field.value ?? ''}
|
|
78
|
+
onChange={field.onChange}
|
|
79
|
+
onBlur={field.onBlur}
|
|
80
|
+
/>
|
|
81
|
+
{field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
<button type="submit" disabled={isSubmitting}>Continue</button>
|
|
85
|
+
</form>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```svelte [Svelte]
|
|
91
|
+
<script lang="ts">
|
|
92
|
+
import { getStep } from '@picked/svelte/hooks'
|
|
93
|
+
|
|
94
|
+
const { fields, form } = getStep()
|
|
95
|
+
const isSubmitting = form.submitting
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<form {...form.props}>
|
|
99
|
+
{#each $fields as field}
|
|
100
|
+
<div>
|
|
101
|
+
<label for={field.id}>{field.label}</label>
|
|
102
|
+
<input
|
|
103
|
+
id={field.id}
|
|
104
|
+
name={field.id}
|
|
105
|
+
value={field.value ?? ''}
|
|
106
|
+
onchange={field.onChange}
|
|
107
|
+
onblur={field.onBlur}
|
|
108
|
+
/>
|
|
109
|
+
{#if field.error && field.touched}
|
|
110
|
+
<p class="error">{field.error.userMessage}</p>
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
113
|
+
{/each}
|
|
114
|
+
<button type="submit" disabled={$isSubmitting}>Continue</button>
|
|
115
|
+
</form>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
:::
|
|
119
|
+
|
|
120
|
+
The SDK checks a field on blur, and not while the visitor types.
|
|
121
|
+
|
|
122
|
+
## 3. Load a large step lazily
|
|
123
|
+
|
|
124
|
+
Pass a dynamic import to the `component` prop of `<Step>`. The SDK does not load the
|
|
125
|
+
content until that step becomes active.
|
|
126
|
+
|
|
127
|
+
## Related
|
|
128
|
+
|
|
129
|
+
- [How to run code on a form submission](/how-to/run-code-on-a-form-submission) — the
|
|
130
|
+
callbacks.
|
|
131
|
+
- [SDK steps](/reference/sdk/steps) — the `StepContext` shape.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to render an unpublished preview
|
|
7
|
+
|
|
8
|
+
This guide shows you how to render draft content on your server. Your server cannot read
|
|
9
|
+
the query string of the browser, so you must pass the token yourself.
|
|
10
|
+
|
|
11
|
+
## 1. Get the token
|
|
12
|
+
|
|
13
|
+
The management API holds the preview token of a workspace in the `previewToken` field. The
|
|
14
|
+
preview action in the dashboard also makes one for you.
|
|
15
|
+
|
|
16
|
+
## 2. Read the config with the token
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
curl "https://api.picked.app/api/v1/projects/proj_123?preview_token=<token>"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A preview response returns `versionId: null`. An invalid token returns `401`.
|
|
23
|
+
|
|
24
|
+
## 3. Pass the same token to `serialize()`
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const snapshot = await serialize(client, {
|
|
28
|
+
projectConfig,
|
|
29
|
+
attributes: { location: { pathname: '/promo' } },
|
|
30
|
+
previewToken,
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use the same token for both requests. The configuration and the content then come from the same
|
|
35
|
+
preview.
|
|
36
|
+
|
|
37
|
+
## Related
|
|
38
|
+
|
|
39
|
+
- [About the two APIs](/explanation/the-two-apis) — how a preview stays out of the search
|
|
40
|
+
index.
|
|
41
|
+
- [Preview, cache, and crawlers](/reference/delivery-api/preview-caching-crawling).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to retry a failed call with a loop
|
|
7
|
+
|
|
8
|
+
This guide shows you how to make a workflow send a request again after the request fails.
|
|
9
|
+
The workflow waits before each new try. It stops after a number of tries that you set.
|
|
10
|
+
|
|
11
|
+
You need a workflow with an **External API** node first. Refer to
|
|
12
|
+
[How to call an external API from a workflow](/how-to/call-an-external-api-from-a-workflow).
|
|
13
|
+
|
|
14
|
+
## The loop
|
|
15
|
+
|
|
16
|
+
A loop is a path of edges that goes back to a node that the run reached before. This loop
|
|
17
|
+
has three nodes:
|
|
18
|
+
|
|
19
|
+
| Node | Function |
|
|
20
|
+
| ------------------- | ------------------------------------------------ |
|
|
21
|
+
| External API `send` | Sends the request. |
|
|
22
|
+
| Condition `check` | Decides if the workflow sends the request again. |
|
|
23
|
+
| Sleep `pause` | Waits before the next try. |
|
|
24
|
+
|
|
25
|
+
When you save a workflow, Picked refuses a loop that does not have a sleep node and a
|
|
26
|
+
condition node on it. Picked also refuses an edge from a node to the same node.
|
|
27
|
+
|
|
28
|
+
## 1. Connect the error exit to a condition
|
|
29
|
+
|
|
30
|
+
1. Open the workflow.
|
|
31
|
+
2. Choose **Add condition** on the error exit of `send`.
|
|
32
|
+
3. In the rule of `check`, test `{{ nodes.send.error.retryable }}`.
|
|
33
|
+
4. Connect the `false` exit of `check` to the nodes that record the failure.
|
|
34
|
+
|
|
35
|
+
`check` runs only when the call fails. The rule can then read the error of the last try.
|
|
36
|
+
|
|
37
|
+
## 2. Add the sleep node
|
|
38
|
+
|
|
39
|
+
1. Choose **Add sleep** on the `true` exit of `check`.
|
|
40
|
+
2. Set **Wait for** to the time between two tries.
|
|
41
|
+
3. Connect the exit of `pause` to the input of `send`.
|
|
42
|
+
|
|
43
|
+
The editor shows a loop as an edge that goes back to `send`.
|
|
44
|
+
|
|
45
|
+
## 3. Set the number of tries
|
|
46
|
+
|
|
47
|
+
Each node in the loop runs one time for each try. A run can visit one node a maximum
|
|
48
|
+
number of times. This number is the visit cap of the workflow.
|
|
49
|
+
|
|
50
|
+
1. Open the workflow settings.
|
|
51
|
+
2. Set **Visit cap** to a number from 1 to 100. If you do not set it, the visit cap is 10.
|
|
52
|
+
|
|
53
|
+
A node can get to the visit cap. If the run then gets to the node again, the run stops.
|
|
54
|
+
The run shows `Failed` with the code `loop_limit_reached`.
|
|
55
|
+
|
|
56
|
+
To stop the loop before the visit cap, test `current.visit` in the rule of `check`.
|
|
57
|
+
Its value is the number of the visit to `check`. This is also the number of failed tries.
|
|
58
|
+
This rule tries a maximum of 3 times:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"and": [{ "var": "nodes.send.error.retryable" }, { "<": ["{{ current.visit }}", 3] }]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With this rule, the run continues on the `false` exit of `check` after 3 failures.
|
|
67
|
+
|
|
68
|
+
## 4. Read the result
|
|
69
|
+
|
|
70
|
+
A node reads the last visit of another node. After the loop, `{{ nodes.send.status }}`
|
|
71
|
+
gives the status of the last try.
|
|
72
|
+
|
|
73
|
+
The **Tasks** card of the run shows each visit as a task. Refer to the
|
|
74
|
+
[Workflow runs reference](/reference/dashboard/workflow-runs#tasks).
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
|
|
78
|
+
- [Workflow runs](/reference/dashboard/workflow-runs) — the tasks of each visit, and the
|
|
79
|
+
visit cap.
|
|
80
|
+
- [Workflow templates](/reference/dashboard/workflow-templates) — the data that a node
|
|
81
|
+
reads in a loop.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How to reuse a section
|
|
7
|
+
|
|
8
|
+
This guide shows you how to define a common structure one time and use it in more than
|
|
9
|
+
one Model.
|
|
10
|
+
|
|
11
|
+
## 1. Create the section
|
|
12
|
+
|
|
13
|
+
1. Click **Pages** in the sidebar.
|
|
14
|
+
2. Click **Page Sections**.
|
|
15
|
+
3. Click **New block**.
|
|
16
|
+
4. Enter a **Name** and a **Key**.
|
|
17
|
+
5. Add your fields.
|
|
18
|
+
6. Click **Create block**.
|
|
19
|
+
|
|
20
|
+
The **Category** is **Section** already, because you opened the list from **Page
|
|
21
|
+
Sections**.
|
|
22
|
+
|
|
23
|
+
## 2. Accept the section in a Model
|
|
24
|
+
|
|
25
|
+
1. Open a page layout or a step layout.
|
|
26
|
+
2. Add a **Page Sections** field to the schema.
|
|
27
|
+
3. Click **Save**.
|
|
28
|
+
|
|
29
|
+
The field now accepts any Block of the Section category. Make the field repeatable to
|
|
30
|
+
build a page from a list of sections.
|
|
31
|
+
|
|
32
|
+
If you want one section at a fixed place instead of a list, add a **Content block** field.
|
|
33
|
+
Set its **Block category** to the **Section** category.
|
|
34
|
+
|
|
35
|
+
## 3. Choose the section in an entry
|
|
36
|
+
|
|
37
|
+
1. Open a content entry that uses the Model.
|
|
38
|
+
2. Find the field that you added.
|
|
39
|
+
3. Choose your Block.
|
|
40
|
+
4. Fill in the fields of the Block.
|
|
41
|
+
5. Click **Save**.
|
|
42
|
+
|
|
43
|
+
:::warning
|
|
44
|
+
An edit to a Block does not migrate the entries that use it. Check each affected content
|
|
45
|
+
entry again, and correct every field error before you publish.
|
|
46
|
+
:::
|
|
47
|
+
|
|
48
|
+
## API and MCP
|
|
49
|
+
|
|
50
|
+
You can also do this task with the [Management API](/reference/management-api/schema), the [MCP server](/reference/tools/mcp), or code that you send to [`picked_run`](/reference/tools/mcp-run).
|
|
51
|
+
|
|
52
|
+
| Task | GraphQL | MCP tool | Run |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| Create the section | [`contentBlockCreate`](/reference/management-api/schema/cms-models#contentblockcreate) | [`create_content_block`](/reference/tools/mcp#create_content_block) | `tools.create_content_block({ workspace_id, name, key, schema })` |
|
|
55
|
+
| Accept the section in a Model | [`contentModelUpdate`](/reference/management-api/schema/cms-models#contentmodelupdate) | [`update_content_model`](/reference/tools/mcp#update_content_model) | `tools.update_content_model({ content_model_id, schema })` |
|
|
56
|
+
| Choose the section in an entry | [`contentEntryUpdate`](/reference/management-api/schema/cms-models#contententryupdate) | [`update_content_entry`](/reference/tools/mcp#update_content_entry) | `tools.update_content_entry({ content_entry_id, data })` |
|
|
57
|
+
| Check the entries after you edit a Block | [`workspaceContentEntryValidation`](/reference/management-api/schema/cms-models#workspacecontententryvalidation) | [`validate_workspace_content_entries`](/reference/tools/mcp#validate_workspace_content_entries) | `tools.validate_workspace_content_entries({ workspace_id })` |
|
|
58
|
+
|
|
59
|
+
## Related
|
|
60
|
+
|
|
61
|
+
- [About the content model](/explanation/the-content-model) — why a schema change does not
|
|
62
|
+
migrate your data.
|
|
63
|
+
- [Blocks](/reference/dashboard/content/blocks) — the reference.
|
|
64
|
+
- [Models](/reference/dashboard/content/models) — every field type.
|