@founderhq/journeys 0.3.61
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 +172 -0
- package/dist/index.cjs +9373 -0
- package/dist/index.d.cts +1430 -0
- package/dist/index.d.ts +1430 -0
- package/dist/index.js +9330 -0
- package/dist/styles.css +2 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @founderhq/journeys
|
|
2
|
+
|
|
3
|
+
`@founderhq/journeys` is a config-driven React runtime for interactive onboarding and questionnaire flows.
|
|
4
|
+
|
|
5
|
+
## What It Includes
|
|
6
|
+
|
|
7
|
+
- journey runtime with forward/back navigation
|
|
8
|
+
- conditional routing between steps
|
|
9
|
+
- computed variables derived from prior answers
|
|
10
|
+
- block-based informational pages
|
|
11
|
+
- reusable styles shipped as `styles.css`
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @founderhq/journeys
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Journey } from "@founderhq/journeys";
|
|
23
|
+
import "@founderhq/journeys/styles.css";
|
|
24
|
+
|
|
25
|
+
export function Example({ config }: { config: import("@founderhq/journeys").JourneyConfig }) {
|
|
26
|
+
return <Journey config={config} storageKey="example-journey" />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You can also fetch a journey remotely by passing `apiKey` and `journeyId` instead of an inline config.
|
|
31
|
+
|
|
32
|
+
## Injecting Runtime Data (`initialAnswers`)
|
|
33
|
+
|
|
34
|
+
Pass `initialAnswers` to seed the answer state at first render. Useful when journey config references runtime data via templates — e.g. live pricing plans from StoreKit, RevenueCat, or your own API:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<Journey
|
|
38
|
+
apiKey="fos_..."
|
|
39
|
+
journeyId="abc-123"
|
|
40
|
+
initialAnswers={{
|
|
41
|
+
pricingPlans: [
|
|
42
|
+
{
|
|
43
|
+
id: "pro_monthly",
|
|
44
|
+
name: "Pro",
|
|
45
|
+
price: { amount: 29, currency: "USD", period: "month", trial: { days: 7 } },
|
|
46
|
+
metadata: { stripePriceId: "price_xxx" },
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}}
|
|
50
|
+
onEvent={(event) => {
|
|
51
|
+
if (event.type === "purchase_intent") {
|
|
52
|
+
// event.plan.metadata.stripePriceId → kick off Stripe checkout
|
|
53
|
+
}
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
In the journey config, reference the injected value with a whole-value template:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
{ type: "pricing_plans", props: { variable: "selectedPlan", plans: "${pricingPlans}" } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Keys declared in `computedVariables` are ignored in `initialAnswers`; computed values always come from their formulas.
|
|
65
|
+
|
|
66
|
+
## Event Payloads
|
|
67
|
+
|
|
68
|
+
`onEvent` payloads keep user-provided answers and computed values separate:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
onEvent={(event) => {
|
|
72
|
+
// Raw/persisted answers only.
|
|
73
|
+
event.answers;
|
|
74
|
+
|
|
75
|
+
// Derived values from config.computedVariables.
|
|
76
|
+
event.computedVariables;
|
|
77
|
+
}}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Pricing Plan Templates
|
|
81
|
+
|
|
82
|
+
When a user selects a plan, the block stores a flattened snapshot in `answers[variable]`. Templates anywhere downstream can reach into it with dotted paths:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
You're subscribing to ${selectedPlan.name} — ${selectedPlan.amount} ${selectedPlan.currency}/${selectedPlan.period}.
|
|
86
|
+
${selectedPlan.trialDays} day free trial. Was ${selectedPlan.originalAmount}.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Available paths: `id`, `name`, `amount`, `currency`, `period`, `originalAmount`, `perUnitLabel`, `display`, `trialDays`, `introOffer`, `description`, `badge`, `icon`, `features`, `metadata`.
|
|
90
|
+
|
|
91
|
+
Equality conditions on the variable continue to match the plan id (`{ op: "equals", value: "pro_monthly" }`).
|
|
92
|
+
|
|
93
|
+
### Per-Plan Templates (`currentPlan`)
|
|
94
|
+
|
|
95
|
+
String fields anywhere inside a plan support templates, with `currentPlan` exposing this card's flattened plan fields (same shape as `selectedPlan`):
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
{
|
|
99
|
+
id: "pro_monthly",
|
|
100
|
+
name: "Pro",
|
|
101
|
+
price: { amount: 29, currency: "USD", period: "month", trial: { days: 7 } },
|
|
102
|
+
badge: "${currentPlan.trialDays} Days Free",
|
|
103
|
+
subtext: "${currentPlan.trialDays} day free trial, then ${currentPlan.amount} ${currentPlan.currency}/${currentPlan.period}",
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`subtext` is rendered as a small line under the price. Trial and intro-offer lines are no longer auto-rendered — set `subtext` explicitly if you want them.
|
|
108
|
+
|
|
109
|
+
## Optional Discount Codes
|
|
110
|
+
|
|
111
|
+
Discount codes are opened from a normal button action, so the Journey author controls whether the UI appears and where the trigger lives:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
props: {
|
|
117
|
+
label: "Have a coupon?",
|
|
118
|
+
action: {
|
|
119
|
+
type: "open_discount_code",
|
|
120
|
+
variable: "pricingDiscount",
|
|
121
|
+
planVariable: "selectedPlan",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Pass `onDiscountCodeApply` to validate and reprice in the consumer app:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<Journey
|
|
131
|
+
config={config}
|
|
132
|
+
onDiscountCodeApply={async ({ code, plan }) => {
|
|
133
|
+
const result = await validateCoupon(code, plan?.id);
|
|
134
|
+
return result.valid
|
|
135
|
+
? {
|
|
136
|
+
valid: true,
|
|
137
|
+
code,
|
|
138
|
+
planId: plan?.id,
|
|
139
|
+
message: "Discount applied",
|
|
140
|
+
pricing: {
|
|
141
|
+
total: result.total,
|
|
142
|
+
currency: result.currency,
|
|
143
|
+
originalAmount: result.originalTotal,
|
|
144
|
+
},
|
|
145
|
+
metadata: result.metadata,
|
|
146
|
+
}
|
|
147
|
+
: { valid: false, reason: result.reason };
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Set `pricing_plans.props.discountVariable` to let cards replace the visible non-strike price from `pricing.display`, `pricing.amount`, or `pricing.total`. Set `purchase.action.discountVariable` to include the full applied discount answer in `purchase_intent`. Journeys never calculates billing amounts or provider rules; those belong in the consumer callback and `metadata`.
|
|
153
|
+
|
|
154
|
+
## Config Shape
|
|
155
|
+
|
|
156
|
+
A journey config is centered around:
|
|
157
|
+
|
|
158
|
+
- `arcs`
|
|
159
|
+
Logical step groups and navigation order.
|
|
160
|
+
- `steps`
|
|
161
|
+
Individual screens such as `single_select`, `multi_select`, `input`, `slider`, and `info_page`.
|
|
162
|
+
- `computedVariables`
|
|
163
|
+
Derived values evaluated from the current answer set.
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pnpm --filter @founderhq/journeys dev
|
|
169
|
+
pnpm --filter @founderhq/journeys build
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The build emits ESM, CJS, type declarations, and `dist/styles.css`.
|