@funnelsgrove/cli 0.1.87 → 0.1.89
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/apiClient.d.ts +1 -0
- package/dist/apiClient.js +32 -1
- package/funnel-contract-compatibility.json +28 -28
- package/package.json +3 -3
- package/template_docs/.funnelsgrove-docs.json +3 -3
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +3 -3
- package/template_scaffold/.funnelsgrove-scaffold.json +26 -471
- package/template_scaffold/PLAN.md +15 -29
- package/template_scaffold/context/build-context.md +3 -3
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/package-lock.json +9 -9
- package/template_scaffold/package.json +2 -2
- package/template_scaffold/public/assets/email-capture-preview.svg +5 -0
- package/template_scaffold/public/assets/paywall-preview.svg +6 -0
- package/template_scaffold/src/app/sdk/page.tsx +14 -26
- package/template_scaffold/src/components/FunnelFlow.tsx +8 -1
- package/template_scaffold/src/config/billing.plans.ts +71 -21
- package/template_scaffold/src/config/funnel.manifest.ts +20 -59
- package/template_scaffold/src/runtime/checkout-runtime-config.ts +0 -7
- package/template_scaffold/src/runtime/step-registry.ts +0 -28
- package/template_scaffold/src/steps/content/step-01.content.ts +4 -9
- package/template_scaffold/src/steps/content/step-32-paywall.content.ts +12 -50
- package/template_scaffold/src/steps/step-01.tsx +43 -50
- package/template_scaffold/src/steps/step-31-email-capture.tsx +18 -1
- package/template_scaffold/src/steps/step-32-paywall.tsx +93 -299
- package/template_scaffold/src/steps/step-33-subscription-started.tsx +1 -1
- package/template_scaffold/src/steps/step-content.registry.ts +1 -26
- package/template_scaffold/public/claimbee/bee.png +0 -0
- package/template_scaffold/public/claimbee/device-iphone.png +0 -0
- package/template_scaffold/public/claimbee/device-macbook.png +0 -0
- package/template_scaffold/public/claimbee/device-none.png +0 -0
- package/template_scaffold/public/claimbee/device-watch.png +0 -0
- package/template_scaffold/public/claimbee/glow.svg +0 -3
- package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
- package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
- package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
- package/template_scaffold/public/claimbee/testimonial-star.svg +0 -3
- package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
- package/template_scaffold/public/paywall/claim-history.png +0 -0
- package/template_scaffold/public/paywall/clear-progress.png +0 -0
- package/template_scaffold/public/paywall/hero.png +0 -0
- package/template_scaffold/public/paywall/money-alerts.png +0 -0
- package/template_scaffold/public/paywall/money-back-badge.png +0 -0
- package/template_scaffold/public/paywall/paywall-image.png +0 -0
- package/template_scaffold/public/paywall/r1.png +0 -0
- package/template_scaffold/public/paywall/r2.png +0 -0
- package/template_scaffold/public/paywall/r3.png +0 -0
- package/template_scaffold/public/paywall/r4.png +0 -0
- package/template_scaffold/public/paywall/r5.png +0 -0
- package/template_scaffold/public/paywall/r6.png +0 -0
- package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
- package/template_scaffold/public/please/bee.png +0 -0
- package/template_scaffold/public/please/device-iphone.png +0 -0
- package/template_scaffold/public/please/device-macbook.png +0 -0
- package/template_scaffold/public/please/device-none.png +0 -0
- package/template_scaffold/public/please/device-watch.png +0 -0
- package/template_scaffold/public/please/glow.svg +0 -3
- package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
- package/template_scaffold/public/please/steps-sprite-default.png +0 -0
- package/template_scaffold/public/please/testimonial-avatar.png +0 -0
- package/template_scaffold/public/please/testimonial-star.svg +0 -3
- package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
- package/template_scaffold/src/assets/claimbee/bee.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
- package/template_scaffold/src/assets/claimbee/glow.svg +0 -3
- package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
- package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
- package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
- package/template_scaffold/src/assets/claimbee/testimonial-star.svg +0 -3
- package/template_scaffold/src/components/FunnelStepImage.tsx +0 -28
- package/template_scaffold/src/config/offer-sets.generated.ts +0 -4
- package/template_scaffold/src/steps/content/manage-subscription.content.ts +0 -62
- package/template_scaffold/src/steps/content/paywall.content.ts +0 -191
- package/template_scaffold/src/steps/content/step-02.content.ts +0 -77
- package/template_scaffold/src/steps/content/step-03.content.ts +0 -40
- package/template_scaffold/src/steps/content/step-04.content.ts +0 -49
- package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +0 -179
- package/template_scaffold/src/steps/editor/paywall.editor.ts +0 -232
- package/template_scaffold/src/steps/editor/step-02.editor.ts +0 -41
- package/template_scaffold/src/steps/editor/step-03.editor.ts +0 -58
- package/template_scaffold/src/steps/editor/step-04.editor.ts +0 -98
- package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +0 -12
- package/template_scaffold/src/steps/step-02.tsx +0 -140
- package/template_scaffold/src/steps/step-03.tsx +0 -262
- package/template_scaffold/src/steps/step-04.tsx +0 -673
- package/template_scaffold/src/steps/step-32b-paywall-b.tsx +0 -35
- package/template_scaffold/src/steps/step-35-manage-subscription.tsx +0 -46
- package/template_scaffold/src/steps/use-claimbee-step-content.ts +0 -15
- package/template_scaffold/tests/funnel-agent-docs.test.ts +0 -751
- package/template_scaffold/tests/package.test.ts +0 -37
- package/template_scaffold/tests/src/app/[stepId]/page.test.tsx +0 -59
- package/template_scaffold/tests/src/app/layout.test.ts +0 -26
- package/template_scaffold/tests/src/components/FunnelFlow.test.tsx +0 -38
- package/template_scaffold/tests/src/components/FunnelStepImage.test.ts +0 -15
- package/template_scaffold/tests/src/config/billing.plans.test.ts +0 -143
- package/template_scaffold/tests/src/config/funnel.manifest.test.ts +0 -166
- package/template_scaffold/tests/src/contract/funnel-project.validation.test.ts +0 -79
- package/template_scaffold/tests/src/contract/golden/golden-fixtures.test.ts +0 -190
- package/template_scaffold/tests/src/contract/validate-funnel.cli.test.ts +0 -191
- package/template_scaffold/tests/src/runtime/checkout-runtime-config.test.ts +0 -60
- package/template_scaffold/tests/src/runtime/funnel-runtime.test.ts +0 -25
- package/template_scaffold/tests/src/runtime/step-content-context.test.ts +0 -38
- package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.integration.test.ts +0 -203
- package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.test.ts +0 -76
- package/template_scaffold/tests/src/steps/paywall-checkout-options.test.ts +0 -104
- package/template_scaffold/tests/src/steps/shared-header-contract.test.ts +0 -29
- package/template_scaffold/tests/src/steps/step-01.test.ts +0 -38
- package/template_scaffold/tests/src/steps/step-02.test.ts +0 -43
- package/template_scaffold/tests/src/steps/step-03.test.ts +0 -15
- package/template_scaffold/tests/src/steps/step-04.test.ts +0 -32
- package/template_scaffold/tests/src/steps/step-31-email-capture.test.ts +0 -22
- package/template_scaffold/tests/src/steps/step-32-paywall.test.ts +0 -773
- package/template_scaffold/tests/src/steps/step-33-subscription-started.test.ts +0 -44
- package/template_scaffold/tests/src/steps/step-34-upsell-form.test.ts +0 -27
- package/template_scaffold/tests/src/steps/step-image-migration.test.ts +0 -20
|
@@ -16,31 +16,15 @@ Use runtime IDs in order. Keep names human-readable.
|
|
|
16
16
|
**Interaction:** _TBD_
|
|
17
17
|
**Image:** _none | generate | provided_
|
|
18
18
|
|
|
19
|
-
### Step 2:
|
|
20
|
-
**Goal:**
|
|
19
|
+
### Step 2: email-capture - Unique identity
|
|
20
|
+
**Goal:** Establish one canonical user identity before checkout.
|
|
21
21
|
**Headline:** _TBD_
|
|
22
|
-
**Body:**
|
|
23
|
-
**CTA:** Continue
|
|
24
|
-
**Interaction:** _TBD_
|
|
25
|
-
**Image:** _none | generate | provided_
|
|
26
|
-
|
|
27
|
-
### Step 3: step-3 - Processing / Insight
|
|
28
|
-
**Goal:** Reinforce progress and maintain momentum.
|
|
29
|
-
**Headline:** _TBD_
|
|
30
|
-
**Body:** _TBD_
|
|
31
|
-
**CTA:** Continue or auto-advance
|
|
32
|
-
**Interaction:** _TBD_
|
|
33
|
-
**Image:** _none | generate | provided_
|
|
34
|
-
|
|
35
|
-
### Step 4: step-4 - Transformation
|
|
36
|
-
**Goal:** Show outcome and prepare for conversion.
|
|
37
|
-
**Headline:** _TBD_
|
|
38
|
-
**Body:** _TBD_
|
|
22
|
+
**Body:** Explain that this email is used for account access, purchase recognition, and important updates.
|
|
39
23
|
**CTA:** Continue
|
|
40
|
-
**Interaction:**
|
|
41
|
-
**Image:**
|
|
24
|
+
**Interaction:** Submit through `submitEmailCapture`. A new or unpaid email follows the manifest to `paywall`; an email with an active subscription skips checkout and opens `subscription-started` in returning-subscriber mode.
|
|
25
|
+
**Image:** _none_
|
|
42
26
|
|
|
43
|
-
### Step
|
|
27
|
+
### Step 3: paywall - Offer
|
|
44
28
|
**Goal:** Convert with clear value, pricing, and trust cues.
|
|
45
29
|
**Headline:** _TBD_
|
|
46
30
|
**Body:** _TBD_
|
|
@@ -48,26 +32,27 @@ Use runtime IDs in order. Keep names human-readable.
|
|
|
48
32
|
**Interaction:** Select plan then purchase.
|
|
49
33
|
**Image:** _optional_
|
|
50
34
|
|
|
51
|
-
### Step
|
|
52
|
-
**Goal:** Confirm purchase
|
|
35
|
+
### Step 4: subscription-started - Purchase / Returning Subscriber Handoff
|
|
36
|
+
**Goal:** Confirm a new purchase or recognize an existing subscriber, then route the user to the app/home experience.
|
|
53
37
|
**Headline:** _TBD_
|
|
54
38
|
**Body:** _TBD_
|
|
55
39
|
**CTA:** _TBD_
|
|
56
|
-
**Interaction:** Open app / deep link.
|
|
40
|
+
**Interaction:** Open app / deep link. When `isReturningSubscriber` is true, show the dedicated welcome-back content instead of new-purchase confirmation.
|
|
57
41
|
|
|
58
42
|
## Funnel Fuel
|
|
59
43
|
| Screen | Section / role | Feeling | Fuel |
|
|
60
44
|
| --- | --- | --- | --- |
|
|
61
45
|
|
|
62
46
|
## Paywall Requirements
|
|
63
|
-
- Keep
|
|
64
|
-
-
|
|
47
|
+
- Keep one live `paywall`; add variants only through an explicit experiment.
|
|
48
|
+
- Resolve plans, discounts, and experiment selection through the shared published billing runtime.
|
|
49
|
+
- Keep the starter `default-offer` usable locally with three plans and two discount stages.
|
|
65
50
|
- Keep visible `Money-back guarantee` copy.
|
|
66
|
-
- Keep guarantee badge image: `/paywall/money-back-badge.png`.
|
|
67
51
|
- Keep pricing/trial copy explicit and easy to scan.
|
|
68
52
|
- Launch Stripe checkout in a dedicated modal dialog that matches the published checkout design.
|
|
69
53
|
- In the modal, prioritize Stripe Link and express wallet buttons before the manual card form.
|
|
70
54
|
- After a successful payment, redirect to `subscription-started` so the app handoff screen appears immediately.
|
|
55
|
+
- Keep `email-capture` before `paywall`; the shared runtime owns canonical email adoption, uniqueness, active-subscription lookup, and returning-subscriber routing.
|
|
71
56
|
|
|
72
57
|
## CTA Ownership Rules
|
|
73
58
|
- `FunnelFlow` owns default Continue action bar.
|
|
@@ -75,7 +60,8 @@ Use runtime IDs in order. Keep names human-readable.
|
|
|
75
60
|
- For yes/no branches, define targets in routing config and use shared in-step controls.
|
|
76
61
|
|
|
77
62
|
## Validation Checklist
|
|
78
|
-
- Step IDs are stable (`step
|
|
63
|
+
- Step IDs are stable (`step-1`, `email-capture`, `paywall`, `subscription-started`).
|
|
64
|
+
- The default graph keeps `email-capture -> paywall`; active subscribers bypass that edge through the shared runtime and land on `subscription-started`.
|
|
79
65
|
- Routing covers each active step and avoids dead ends.
|
|
80
66
|
- Paywall trust elements are present.
|
|
81
67
|
- Mobile layout remains full-screen with safe-area spacing.
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
- `PLAN.md`: required
|
|
28
28
|
|
|
29
29
|
## Sequence Contract
|
|
30
|
-
- Keep
|
|
31
|
-
-
|
|
30
|
+
- Keep the starter route linear: `step-1`, `email-capture`, `paywall`, `subscription-started`.
|
|
31
|
+
- Do not add alternate paywalls or branches unless the funnel explicitly requires them.
|
|
32
32
|
|
|
33
33
|
## Generation Mode
|
|
34
|
-
-
|
|
34
|
+
- Use the lightweight four-step starter by default unless a wider flow is explicitly requested.
|
|
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
|
|
|
17
17
|
|
|
18
18
|
### Package release order
|
|
19
19
|
|
|
20
|
-
Release `@funnelsgrove/runtime` `0.7.
|
|
20
|
+
Release `@funnelsgrove/runtime` `0.7.10` first, then `@funnelsgrove/analytics` `0.1.59`, then `@funnelsgrove/payments` `0.7.3`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.89`. Publishing packages and deploying production remain separately approved operational actions.
|
|
21
21
|
<!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
|
|
22
22
|
|
|
23
23
|
## Version-last policy
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"name": "funnel-template",
|
|
9
9
|
"version": "0.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
11
|
+
"@funnelsgrove/analytics": "^0.1.59",
|
|
12
12
|
"@funnelsgrove/payments": "^0.7.3",
|
|
13
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
13
|
+
"@funnelsgrove/runtime": "^0.7.10",
|
|
14
14
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
15
15
|
"@stripe/stripe-js": "^8.7.0",
|
|
16
16
|
"lucide-react": "^0.553.0",
|
|
@@ -891,11 +891,11 @@
|
|
|
891
891
|
}
|
|
892
892
|
},
|
|
893
893
|
"node_modules/@funnelsgrove/analytics": {
|
|
894
|
-
"version": "0.1.
|
|
895
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.
|
|
896
|
-
"integrity": "sha512-
|
|
894
|
+
"version": "0.1.59",
|
|
895
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.59.tgz",
|
|
896
|
+
"integrity": "sha512-VA85CXnWZGmMrDTcSgr6sRjL6NGLGWX14z12br9J1Z3ABZrDUFdCp17xAhv2AFHPpSyK/laOQ8udA3oENWZ2yQ==",
|
|
897
897
|
"dependencies": {
|
|
898
|
-
"@funnelsgrove/runtime": "0.7.
|
|
898
|
+
"@funnelsgrove/runtime": "0.7.10"
|
|
899
899
|
}
|
|
900
900
|
},
|
|
901
901
|
"node_modules/@funnelsgrove/payments": {
|
|
@@ -913,9 +913,9 @@
|
|
|
913
913
|
}
|
|
914
914
|
},
|
|
915
915
|
"node_modules/@funnelsgrove/runtime": {
|
|
916
|
-
"version": "0.7.
|
|
917
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.
|
|
918
|
-
"integrity": "sha512-
|
|
916
|
+
"version": "0.7.10",
|
|
917
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.10.tgz",
|
|
918
|
+
"integrity": "sha512-V1S7kVu1lBLAi49f4ZP+n8Z9ZcINh/g9mQyplE+jG0ic+ct2kdZaB2x9FZ3frvWgg+GEBTiDF4aIAJcc0Z9kzQ==",
|
|
919
919
|
"dependencies": {
|
|
920
920
|
"posthog-js": "^1.369.2",
|
|
921
921
|
"react": "19.2.3",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"validate:funnel": "vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
15
|
+
"@funnelsgrove/analytics": "^0.1.59",
|
|
16
16
|
"@funnelsgrove/payments": "^0.7.3",
|
|
17
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
17
|
+
"@funnelsgrove/runtime": "^0.7.10",
|
|
18
18
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
19
19
|
"@stripe/stripe-js": "^8.7.0",
|
|
20
20
|
"lucide-react": "^0.553.0",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="120" viewBox="0 0 160 120" fill="none">
|
|
2
|
+
<rect width="160" height="120" rx="24" fill="#EEF1FF"/>
|
|
3
|
+
<rect x="28" y="30" width="104" height="60" rx="14" fill="white" stroke="#4361EE" stroke-width="4"/>
|
|
4
|
+
<path d="m34 39 46 34 46-34" stroke="#4361EE" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="120" viewBox="0 0 160 120" fill="none">
|
|
2
|
+
<rect width="160" height="120" rx="24" fill="#EEF1FF"/>
|
|
3
|
+
<rect x="35" y="21" width="90" height="78" rx="16" fill="white" stroke="#4361EE" stroke-width="4"/>
|
|
4
|
+
<path d="M53 43h54M53 58h38" stroke="#9AA7E8" stroke-width="5" stroke-linecap="round"/>
|
|
5
|
+
<rect x="53" y="73" width="54" height="12" rx="6" fill="#4361EE"/>
|
|
6
|
+
</svg>
|
|
@@ -6,19 +6,10 @@ export const metadata: Metadata = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
const userObjectExample = `{
|
|
9
|
-
"selectedDevices": ["iphone", "laptop"],
|
|
10
9
|
"stepChoices": {
|
|
11
|
-
"step-1": "
|
|
12
|
-
"step-3": "no"
|
|
10
|
+
"step-1": "continue"
|
|
13
11
|
},
|
|
14
|
-
"
|
|
15
|
-
"upsellEmail": "user@example.com",
|
|
16
|
-
"upsellSelectedPlanId": "price_yearly_123",
|
|
17
|
-
"selectedPaywallPlanId": "price_yearly_123",
|
|
18
|
-
"manageSubscriptionHasActive": true,
|
|
19
|
-
"manageSubscriptionReason": "cost",
|
|
20
|
-
"manageSubscriptionCancelled": false,
|
|
21
|
-
"manageSubscriptionCancelledId": ""
|
|
12
|
+
"selectedPaywallPlanId": "secondary"
|
|
22
13
|
}`;
|
|
23
14
|
|
|
24
15
|
type MethodDoc = {
|
|
@@ -37,7 +28,7 @@ const methodDocs: MethodDoc[] = [
|
|
|
37
28
|
description: 'Current rendered step id in the runtime shell.',
|
|
38
29
|
example: `const { activeStepId } = useFunnel();
|
|
39
30
|
|
|
40
|
-
if (activeStepId === '
|
|
31
|
+
if (activeStepId === 'paywall') {
|
|
41
32
|
// Step-specific behavior
|
|
42
33
|
}`,
|
|
43
34
|
},
|
|
@@ -47,7 +38,7 @@ if (activeStepId === 'step-4') {
|
|
|
47
38
|
signature: 'answers: FunnelUserAnswers',
|
|
48
39
|
description: 'Typed answers object persisted across steps.',
|
|
49
40
|
example: `const { answers } = useFunnel();
|
|
50
|
-
const
|
|
41
|
+
const selectedPlanId = answers.selectedPaywallPlanId || null;`,
|
|
51
42
|
},
|
|
52
43
|
{
|
|
53
44
|
id: 'set-answer',
|
|
@@ -57,8 +48,7 @@ const selectedDevices = answers.selectedDevices || [];`,
|
|
|
57
48
|
description: 'Typed way to write answer values.',
|
|
58
49
|
example: `const { setAnswer } = useFunnel();
|
|
59
50
|
|
|
60
|
-
setAnswer('
|
|
61
|
-
setAnswer('upsellAccepted', true);`,
|
|
51
|
+
setAnswer('selectedPaywallPlanId', 'secondary');`,
|
|
62
52
|
},
|
|
63
53
|
{
|
|
64
54
|
id: 'get-answer',
|
|
@@ -67,7 +57,7 @@ setAnswer('upsellAccepted', true);`,
|
|
|
67
57
|
'getAnswer<K extends keyof FunnelUserAnswers>(key: K): FunnelUserAnswers[K] | undefined',
|
|
68
58
|
description: 'Typed getter for a single answer key.',
|
|
69
59
|
example: `const { getAnswer } = useFunnel();
|
|
70
|
-
const
|
|
60
|
+
const selectedPlanId = getAnswer('selectedPaywallPlanId');`,
|
|
71
61
|
},
|
|
72
62
|
{
|
|
73
63
|
id: 'go-next',
|
|
@@ -131,9 +121,7 @@ setUser({ ...user, email: 'new-email@example.com' });`,
|
|
|
131
121
|
description: 'Manually emit and persist a step completion record.',
|
|
132
122
|
example: `const { completeStep } = useFunnel();
|
|
133
123
|
|
|
134
|
-
completeStep('
|
|
135
|
-
selectedDevices: ['iphone', 'laptop'],
|
|
136
|
-
});`,
|
|
124
|
+
completeStep('email-capture');`,
|
|
137
125
|
},
|
|
138
126
|
{
|
|
139
127
|
id: 'complete-funnel',
|
|
@@ -232,7 +220,7 @@ const analyticsUseCases: AnalyticsUseCaseDoc[] = [
|
|
|
232
220
|
|
|
233
221
|
// ...custom async flow...
|
|
234
222
|
|
|
235
|
-
goToStep('
|
|
223
|
+
goToStep('email-capture', {
|
|
236
224
|
type: 'complete',
|
|
237
225
|
selected: { uploadCount: files.length },
|
|
238
226
|
});`,
|
|
@@ -283,9 +271,9 @@ const analyticsMethodDocs: AnalyticsMethodDoc[] = [
|
|
|
283
271
|
description:
|
|
284
272
|
'Queues custom events only. Returns null for empty or contract-reserved event names; use their owning FunnelFlow or named helper path.',
|
|
285
273
|
example: `const eventId = publicAnalyticsSdk.track({
|
|
286
|
-
eventType: '
|
|
287
|
-
stepId: '
|
|
288
|
-
payload: {
|
|
274
|
+
eventType: 'paywall_offer_seen',
|
|
275
|
+
stepId: 'paywall',
|
|
276
|
+
payload: { offerSetId: activeOfferSetId },
|
|
289
277
|
});`,
|
|
290
278
|
},
|
|
291
279
|
{
|
|
@@ -302,7 +290,7 @@ goNext(); // FunnelFlow records the current completion and starts the next visit
|
|
|
302
290
|
signature: 'trackStepCompleted(input: PublicSdkStepEndInput): string | null',
|
|
303
291
|
description: 'FunnelFlow-owned adapter method for canonical step_end events; step components must not call it.',
|
|
304
292
|
example: `const { goToStep } = useFunnel();
|
|
305
|
-
goToStep('
|
|
293
|
+
goToStep('email-capture', {
|
|
306
294
|
type: 'complete',
|
|
307
295
|
selected: { painScore: 7 },
|
|
308
296
|
});`,
|
|
@@ -384,8 +372,8 @@ export default function SdkDocsPage() {
|
|
|
384
372
|
<h2>Analytics SDK</h2>
|
|
385
373
|
<p>
|
|
386
374
|
Use `publicAnalyticsSdk` from `@funnelsgrove/analytics` for custom analytics events.
|
|
387
|
-
It batches events and
|
|
388
|
-
`fetch(keepalive)` fallback.
|
|
375
|
+
It batches events and sends them through the first-party `/ingest` proxy using
|
|
376
|
+
`navigator.sendBeacon`, with `fetch(keepalive)` fallback.
|
|
389
377
|
</p>
|
|
390
378
|
<p>
|
|
391
379
|
Standard step start/complete events are already emitted by `FunnelFlow`. Add custom events only for
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createElement, useMemo, useSyncExternalStore, type ComponentType } from 'react';
|
|
3
|
+
import { createElement, useEffect, useMemo, useSyncExternalStore, type ComponentType } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
FunnelProvider,
|
|
6
6
|
FunnelRuntimeConfigBoundary,
|
|
7
7
|
PrimaryButton,
|
|
8
8
|
isPreviewFrameRuntime,
|
|
9
9
|
runtimePublicConfig,
|
|
10
|
+
scheduleNextStepImagePreload,
|
|
10
11
|
usePreviewDefinitionOverrideRevision,
|
|
11
12
|
} from '@funnelsgrove/runtime';
|
|
12
13
|
import { FunnelEditorPanel, FunnelEditorPanelStyles } from './FunnelEditorPanel';
|
|
14
|
+
import { funnelManifest } from '@/config/funnel.manifest';
|
|
13
15
|
import type { FunnelStepId } from '@/runtime/funnel-runtime';
|
|
14
16
|
import { useFunnelFlowController } from '@/runtime/use-funnel-flow-controller';
|
|
15
17
|
|
|
@@ -68,6 +70,11 @@ function FunnelFlowRuntime({
|
|
|
68
70
|
lockToInitialStep,
|
|
69
71
|
});
|
|
70
72
|
|
|
73
|
+
useEffect(
|
|
74
|
+
() => scheduleNextStepImagePreload(funnelManifest, controller.activeStepId),
|
|
75
|
+
[controller.activeStepId],
|
|
76
|
+
);
|
|
77
|
+
|
|
71
78
|
if (!controller.activeStepComponent || !controller.activeStepMeta || !controller.renderedStepMeta) {
|
|
72
79
|
return null;
|
|
73
80
|
}
|
|
@@ -5,10 +5,19 @@ import {
|
|
|
5
5
|
type BillingDiscountList,
|
|
6
6
|
type BillingPlanCatalog,
|
|
7
7
|
} from '@funnelsgrove/payments';
|
|
8
|
+
import {
|
|
9
|
+
type GeneratedOfferSet,
|
|
10
|
+
type ResolvedFunnelRuntimeConfig,
|
|
11
|
+
} from '@funnelsgrove/runtime';
|
|
8
12
|
import { billingTestPlans } from './billing.test.plans';
|
|
9
13
|
|
|
10
|
-
const
|
|
14
|
+
const STARTER_OFFER_SET_ID = '11111111-1111-4111-8111-111111111111';
|
|
15
|
+
const STARTER_OFFER_SET_KEY = 'default-offer';
|
|
16
|
+
|
|
17
|
+
const starterLivePlans = {
|
|
11
18
|
primary: {
|
|
19
|
+
offerSetId: STARTER_OFFER_SET_ID,
|
|
20
|
+
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
12
21
|
projectPlanId: '32b31f69-5907-4736-89c7-76d3acb38813',
|
|
13
22
|
title: '1-Week Plan',
|
|
14
23
|
providerPlanId: 'price_1T9WL2PPRN41Ty94g2NNOgif',
|
|
@@ -18,6 +27,8 @@ const claimbeeLivePlans = {
|
|
|
18
27
|
checkoutSummaryLabel: '$7.99 today',
|
|
19
28
|
},
|
|
20
29
|
secondary: {
|
|
30
|
+
offerSetId: STARTER_OFFER_SET_ID,
|
|
31
|
+
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
21
32
|
projectPlanId: 'ce0a0022-7bf0-41f2-a707-edc5e6113a07',
|
|
22
33
|
title: '1-Month Plan',
|
|
23
34
|
providerPlanId: 'price_1TP0IIPPRN41Ty94npkzVeDR',
|
|
@@ -26,8 +37,11 @@ const claimbeeLivePlans = {
|
|
|
26
37
|
perDayAmount: '$1.00',
|
|
27
38
|
amountCents: 2999,
|
|
28
39
|
checkoutSummaryLabel: '$29.99 today',
|
|
40
|
+
isDefault: true,
|
|
29
41
|
},
|
|
30
42
|
tertiary: {
|
|
43
|
+
offerSetId: STARTER_OFFER_SET_ID,
|
|
44
|
+
offerSetKey: STARTER_OFFER_SET_KEY,
|
|
31
45
|
projectPlanId: 'fb623ebc-c2fd-4e6a-8eb6-b8ed92db1b21',
|
|
32
46
|
title: '1-Year Plan',
|
|
33
47
|
providerPlanId: 'price_1T06AEPPRN41Ty94zvVt5xrk',
|
|
@@ -38,32 +52,68 @@ const claimbeeLivePlans = {
|
|
|
38
52
|
},
|
|
39
53
|
} satisfies BillingPlanCatalog;
|
|
40
54
|
|
|
41
|
-
export const
|
|
55
|
+
export const starterPlansByMode = {
|
|
42
56
|
test: billingTestPlans,
|
|
43
|
-
live:
|
|
57
|
+
live: starterLivePlans,
|
|
44
58
|
} satisfies Record<'test' | 'live', BillingPlanCatalog>;
|
|
45
59
|
|
|
60
|
+
const starterDiscounts = [
|
|
61
|
+
{
|
|
62
|
+
stage: 'first',
|
|
63
|
+
couponId: 'fg_30_off_one',
|
|
64
|
+
discountPercent: 30,
|
|
65
|
+
durationSeconds: 600,
|
|
66
|
+
previousDiscountPercent: null,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
stage: 'second',
|
|
70
|
+
couponId: 'fg_46_off_once',
|
|
71
|
+
discountPercent: 46,
|
|
72
|
+
durationSeconds: 600,
|
|
73
|
+
previousDiscountPercent: 30,
|
|
74
|
+
},
|
|
75
|
+
] satisfies BillingDiscountList;
|
|
76
|
+
|
|
77
|
+
export const starterDefaultOfferSet = {
|
|
78
|
+
id: STARTER_OFFER_SET_ID,
|
|
79
|
+
key: STARTER_OFFER_SET_KEY,
|
|
80
|
+
isActive: true,
|
|
81
|
+
metadata: {
|
|
82
|
+
discounts: starterDiscounts.map((discount, position) => ({
|
|
83
|
+
key: discount.stage,
|
|
84
|
+
testProviderCouponId: discount.couponId,
|
|
85
|
+
liveProviderCouponId: discount.couponId,
|
|
86
|
+
position,
|
|
87
|
+
metadata: {
|
|
88
|
+
stage: discount.stage,
|
|
89
|
+
discountPercent: discount.discountPercent,
|
|
90
|
+
durationSeconds: discount.durationSeconds,
|
|
91
|
+
previousDiscountPercent: discount.previousDiscountPercent,
|
|
92
|
+
},
|
|
93
|
+
})),
|
|
94
|
+
},
|
|
95
|
+
items: [
|
|
96
|
+
{ funnelPlanKey: 'primary', position: 0, isDefault: false },
|
|
97
|
+
{ funnelPlanKey: 'secondary', position: 1, isDefault: true },
|
|
98
|
+
{ funnelPlanKey: 'tertiary', position: 2, isDefault: false },
|
|
99
|
+
],
|
|
100
|
+
} satisfies GeneratedOfferSet;
|
|
101
|
+
|
|
102
|
+
export const starterBillingRuntimeFallback = {
|
|
103
|
+
revisionId: 'starter-template',
|
|
104
|
+
sourceDeploymentId: 'starter-template',
|
|
105
|
+
sourceVersionId: 'starter-template',
|
|
106
|
+
publishedAt: '1970-01-01T00:00:00.000Z',
|
|
107
|
+
offerSets: [starterDefaultOfferSet],
|
|
108
|
+
experiments: [],
|
|
109
|
+
} satisfies ResolvedFunnelRuntimeConfig;
|
|
110
|
+
|
|
46
111
|
export const billing = {
|
|
47
|
-
plans: createRuntimeModeBillingPlanCatalog(
|
|
48
|
-
discounts:
|
|
49
|
-
{
|
|
50
|
-
stage: 'first',
|
|
51
|
-
couponId: 'fg_30_off_one',
|
|
52
|
-
discountPercent: 30,
|
|
53
|
-
durationSeconds: 600,
|
|
54
|
-
previousDiscountPercent: null,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
stage: 'second',
|
|
58
|
-
couponId: 'fg_46_off_once',
|
|
59
|
-
discountPercent: 46,
|
|
60
|
-
durationSeconds: 600,
|
|
61
|
-
previousDiscountPercent: 30,
|
|
62
|
-
},
|
|
63
|
-
] satisfies BillingDiscountList,
|
|
112
|
+
plans: createRuntimeModeBillingPlanCatalog(starterPlansByMode),
|
|
113
|
+
discounts: starterDiscounts,
|
|
64
114
|
} satisfies BillingConfig;
|
|
65
115
|
|
|
66
116
|
export const billingPlans = billing.plans;
|
|
67
117
|
export const billingDiscounts = buildBillingDiscountCatalog(billing.discounts);
|
|
68
118
|
export const billingDiscountList = billing.discounts;
|
|
69
|
-
export const billingDiscountStorageKeys = ['
|
|
119
|
+
export const billingDiscountStorageKeys = ['starter-paywall-discount-v1'] as const;
|
|
@@ -11,14 +11,29 @@ export const rawFunnelManifest = {
|
|
|
11
11
|
stepContractVersion: CURRENT_STEP_CONTRACT_VERSION,
|
|
12
12
|
meta: {
|
|
13
13
|
title: 'Funnel Project',
|
|
14
|
-
description: '
|
|
14
|
+
description: 'Lightweight starter funnel with email capture and a single paywall.',
|
|
15
15
|
},
|
|
16
16
|
viewport: {
|
|
17
17
|
width: 430,
|
|
18
18
|
height: 932,
|
|
19
19
|
},
|
|
20
20
|
breakpoints: DEFAULT_FUNNEL_BREAKPOINTS,
|
|
21
|
-
assets: {
|
|
21
|
+
assets: {
|
|
22
|
+
emailCapturePreview: {
|
|
23
|
+
type: 'image',
|
|
24
|
+
src: '/assets/email-capture-preview.svg',
|
|
25
|
+
width: 160,
|
|
26
|
+
height: 120,
|
|
27
|
+
preload: 'idle',
|
|
28
|
+
},
|
|
29
|
+
paywallPreview: {
|
|
30
|
+
type: 'image',
|
|
31
|
+
src: '/assets/paywall-preview.svg',
|
|
32
|
+
width: 160,
|
|
33
|
+
height: 120,
|
|
34
|
+
preload: 'idle',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
22
37
|
steps: [
|
|
23
38
|
{
|
|
24
39
|
id: 'step-1',
|
|
@@ -29,37 +44,6 @@ export const rawFunnelManifest = {
|
|
|
29
44
|
type: 'intro_hero',
|
|
30
45
|
title: 'Step 1',
|
|
31
46
|
},
|
|
32
|
-
{
|
|
33
|
-
id: 'step-2',
|
|
34
|
-
path: '/step-2',
|
|
35
|
-
filePath: 'src/steps/step-02.tsx',
|
|
36
|
-
componentKey: 'stepTwo',
|
|
37
|
-
name: 'step-2',
|
|
38
|
-
type: 'multi_select_choice',
|
|
39
|
-
choice: {
|
|
40
|
-
answerKey: 'selectedDevices',
|
|
41
|
-
allowEmpty: true,
|
|
42
|
-
},
|
|
43
|
-
title: 'Step 2',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'step-3',
|
|
47
|
-
path: '/step-3',
|
|
48
|
-
filePath: 'src/steps/step-03.tsx',
|
|
49
|
-
componentKey: 'stepThree',
|
|
50
|
-
name: 'step-3',
|
|
51
|
-
type: 'progress_interstitial',
|
|
52
|
-
title: 'Step 3',
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: 'step-4',
|
|
56
|
-
path: '/step-4',
|
|
57
|
-
filePath: 'src/steps/step-04.tsx',
|
|
58
|
-
componentKey: 'stepFour',
|
|
59
|
-
name: 'scratchpad-entry',
|
|
60
|
-
type: 'progress_interstitial',
|
|
61
|
-
title: 'Scratchcard Entry',
|
|
62
|
-
},
|
|
63
47
|
{
|
|
64
48
|
id: 'email-capture',
|
|
65
49
|
path: '/email-capture',
|
|
@@ -68,6 +52,7 @@ export const rawFunnelManifest = {
|
|
|
68
52
|
name: 'email-capture',
|
|
69
53
|
type: 'form_input',
|
|
70
54
|
title: 'Email Capture',
|
|
55
|
+
assetIds: ['emailCapturePreview'],
|
|
71
56
|
},
|
|
72
57
|
{
|
|
73
58
|
id: 'paywall',
|
|
@@ -78,26 +63,7 @@ export const rawFunnelManifest = {
|
|
|
78
63
|
type: 'paywall_offer',
|
|
79
64
|
kind: 'paywall',
|
|
80
65
|
title: 'Paywall',
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
id: 'paywall-b',
|
|
84
|
-
path: '/paywall-b',
|
|
85
|
-
filePath: 'src/steps/step-32b-paywall-b.tsx',
|
|
86
|
-
componentKey: 'stepPaywallB',
|
|
87
|
-
name: 'paywall-b',
|
|
88
|
-
type: 'paywall_offer',
|
|
89
|
-
kind: 'paywall',
|
|
90
|
-
title: 'Paywall B',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: 'manage-subscription',
|
|
94
|
-
path: '/manage-subscription',
|
|
95
|
-
filePath: 'src/steps/step-35-manage-subscription.tsx',
|
|
96
|
-
componentKey: 'stepManageSubscription',
|
|
97
|
-
name: 'manage-subscription',
|
|
98
|
-
type: 'subscription_management',
|
|
99
|
-
kind: 'manage-subscription',
|
|
100
|
-
title: 'Manage Subscription',
|
|
66
|
+
assetIds: ['paywallPreview'],
|
|
101
67
|
},
|
|
102
68
|
{
|
|
103
69
|
id: 'subscription-started',
|
|
@@ -111,14 +77,9 @@ export const rawFunnelManifest = {
|
|
|
111
77
|
},
|
|
112
78
|
],
|
|
113
79
|
edgesByStepId: {
|
|
114
|
-
'step-1': [{ toStepId: '
|
|
115
|
-
'step-2': [{ toStepId: 'step-3' }],
|
|
116
|
-
'step-3': [{ toStepId: 'step-4' }],
|
|
117
|
-
'step-4': [{ toStepId: 'email-capture' }],
|
|
80
|
+
'step-1': [{ toStepId: 'email-capture' }],
|
|
118
81
|
'email-capture': [{ toStepId: 'paywall' }],
|
|
119
82
|
paywall: [{ toStepId: 'subscription-started' }],
|
|
120
|
-
'paywall-b': [{ toStepId: 'subscription-started' }],
|
|
121
|
-
'manage-subscription': [{ toStepId: 'step-1' }],
|
|
122
83
|
},
|
|
123
84
|
// Experiments are delivered by the version-bound runtime artifact.
|
|
124
85
|
experiments: [],
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolvePreviewPaymentPublishableKey,
|
|
3
3
|
runtimePublicConfig,
|
|
4
|
-
type RuntimeMode,
|
|
5
4
|
} from '@funnelsgrove/runtime';
|
|
6
5
|
|
|
7
6
|
const resolveCheckoutFunnelSdkPublishableKey = (): string | null => {
|
|
@@ -18,9 +17,3 @@ export const checkoutRuntimeConfig = {
|
|
|
18
17
|
funnelId: runtimePublicConfig.funnelId.trim() || null,
|
|
19
18
|
funnelSdkPublishableKey: resolveCheckoutFunnelSdkPublishableKey(),
|
|
20
19
|
} as const;
|
|
21
|
-
|
|
22
|
-
export const resolveClaimbeeCheckoutMode = (
|
|
23
|
-
runtimeMode: RuntimeMode,
|
|
24
|
-
): RuntimeMode => {
|
|
25
|
-
return runtimeMode;
|
|
26
|
-
};
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import type { FunctionComponent } from 'react';
|
|
2
|
-
import { StepFour, stepFour } from '@/steps/step-04';
|
|
3
2
|
import { StepOne, stepOne } from '@/steps/step-01';
|
|
4
3
|
import { StepEmailCapture, stepEmailCapture } from '@/steps/step-31-email-capture';
|
|
5
4
|
import { StepPaywall, stepPaywall } from '@/steps/step-32-paywall';
|
|
6
|
-
import { StepPaywallB, stepPaywallB } from '@/steps/step-32b-paywall-b';
|
|
7
5
|
import {
|
|
8
6
|
StepSubscriptionStarted,
|
|
9
7
|
stepSubscriptionStarted,
|
|
10
8
|
} from '@/steps/step-33-subscription-started';
|
|
11
|
-
import {
|
|
12
|
-
StepManageSubscription,
|
|
13
|
-
stepManageSubscription,
|
|
14
|
-
} from '@/steps/step-35-manage-subscription';
|
|
15
|
-
import { StepThree, stepThree } from '@/steps/step-03';
|
|
16
|
-
import { StepTwo, stepTwo } from '@/steps/step-02';
|
|
17
9
|
import type { FunnelStepComponentKey } from '@/config/funnel.manifest';
|
|
18
10
|
import type { FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
19
11
|
|
|
@@ -27,18 +19,6 @@ export const stepRegistry: Record<FunnelStepComponentKey, StepRegistryEntry> = {
|
|
|
27
19
|
component: StepOne,
|
|
28
20
|
meta: stepOne,
|
|
29
21
|
},
|
|
30
|
-
stepTwo: {
|
|
31
|
-
component: StepTwo,
|
|
32
|
-
meta: stepTwo,
|
|
33
|
-
},
|
|
34
|
-
stepThree: {
|
|
35
|
-
component: StepThree,
|
|
36
|
-
meta: stepThree,
|
|
37
|
-
},
|
|
38
|
-
stepFour: {
|
|
39
|
-
component: StepFour,
|
|
40
|
-
meta: stepFour,
|
|
41
|
-
},
|
|
42
22
|
stepEmailCapture: {
|
|
43
23
|
component: StepEmailCapture,
|
|
44
24
|
meta: stepEmailCapture,
|
|
@@ -47,14 +27,6 @@ export const stepRegistry: Record<FunnelStepComponentKey, StepRegistryEntry> = {
|
|
|
47
27
|
component: StepPaywall,
|
|
48
28
|
meta: stepPaywall,
|
|
49
29
|
},
|
|
50
|
-
stepPaywallB: {
|
|
51
|
-
component: StepPaywallB,
|
|
52
|
-
meta: stepPaywallB,
|
|
53
|
-
},
|
|
54
|
-
stepManageSubscription: {
|
|
55
|
-
component: StepManageSubscription,
|
|
56
|
-
meta: stepManageSubscription,
|
|
57
|
-
},
|
|
58
30
|
stepSubscriptionStarted: {
|
|
59
31
|
component: StepSubscriptionStarted,
|
|
60
32
|
meta: stepSubscriptionStarted,
|