@funnelsgrove/cli 0.1.229 → 0.1.233

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 (29) hide show
  1. package/README.md +9 -0
  2. package/dist/cli.js +10 -0
  3. package/dist/locales.d.ts +6 -0
  4. package/dist/locales.js +126 -0
  5. package/funnel-contract-compatibility.json +32 -32
  6. package/package.json +4 -4
  7. package/template_docs/.funnelsgrove-docs.json +5 -5
  8. package/template_docs/AGENTS.md +1 -0
  9. package/template_docs/docs/funnelsgrove/contracts/content-answers.md +50 -0
  10. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  11. package/template_docs/funnel-docs.config.json +1 -1
  12. package/template_scaffold/.funnelsgrove-docs.json +5 -5
  13. package/template_scaffold/.funnelsgrove-scaffold.json +35 -15
  14. package/template_scaffold/AGENTS.md +1 -0
  15. package/template_scaffold/docs/funnelsgrove/contracts/content-answers.md +50 -0
  16. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  17. package/template_scaffold/funnel-docs.config.json +1 -1
  18. package/template_scaffold/package-lock.json +15 -15
  19. package/template_scaffold/package.json +3 -3
  20. package/template_scaffold/src/app/subscription-dashboard/page.tsx +22 -19
  21. package/template_scaffold/src/components/FunnelFlow.tsx +11 -5
  22. package/template_scaffold/src/components/paywall/PaywallRenewalDisclaimer.tsx +23 -0
  23. package/template_scaffold/src/components/paywall/localize-paywall-plan-labels.ts +15 -0
  24. package/template_scaffold/src/config/billing.plans.ts +11 -10
  25. package/template_scaffold/src/config/billing.test.plans.ts +11 -10
  26. package/template_scaffold/src/steps/content/runtime.content.ts +22 -0
  27. package/template_scaffold/src/steps/content/step-32-paywall.content.ts +41 -1
  28. package/template_scaffold/src/steps/content/subscription-dashboard.content.ts +54 -0
  29. package/template_scaffold/src/steps/step-32-paywall.tsx +12 -22
@@ -7,11 +7,15 @@ import type {
7
7
  StepImage,
8
8
  } from '@funnelsgrove/runtime';
9
9
 
10
- type StepPaywallLocaleContent = {
10
+ export type StepPaywallLocaleContent = {
11
+ catalogLabels: Record<string, string>;
11
12
  loadingLabel: string;
12
13
  topBarDiscountLabel: string;
13
14
  topBarCtaLabel: string;
14
15
  countdownUnits: readonly string[];
16
+ countdownAriaTemplate: string;
17
+ selectedPlanLabel: string;
18
+ refundPolicyLabel: string;
15
19
  hero: {
16
20
  pills: readonly string[];
17
21
  image: StepImage;
@@ -30,6 +34,7 @@ type StepPaywallLocaleContent = {
30
34
  cardButtonLabel: string;
31
35
  metaLabels: readonly string[];
32
36
  stripeUnavailableNote: string;
37
+ perDayLabel: string;
33
38
  };
34
39
  checkout: {
35
40
  title: string;
@@ -67,6 +72,11 @@ type StepPaywallLocaleContent = {
67
72
  legalLinks: readonly LinkItem[];
68
73
  supportText: string;
69
74
  renewalTemplate: string;
75
+ renewalConsentTemplate: string;
76
+ discountedIntroLabel: string;
77
+ selectedIntroLabel: string;
78
+ cadenceFallback: string;
79
+ cadence: Record<'day' | 'week' | 'month' | 'year', { one: string; many: string }>;
70
80
  specialOffer: {
71
81
  image: StepImage;
72
82
  discountLabel: string;
@@ -94,6 +104,7 @@ type StepPaywallLocaleContent = {
94
104
  title: string;
95
105
  description: string;
96
106
  links: readonly LinkItem[];
107
+ detailsSuffix: string;
97
108
  };
98
109
  };
99
110
 
@@ -101,10 +112,27 @@ export const stepPaywallContent = {
101
112
  defaultLocale: 'en',
102
113
  locales: {
103
114
  en: {
115
+ catalogLabels: {
116
+ "1-Week Plan": "1-Week Plan",
117
+ "1-Month Plan": "1-Month Plan",
118
+ "1-Year Plan": "1-Year Plan",
119
+ "🔥 Most Popular Choice": "🔥 Most Popular Choice",
120
+ "$7.99/week": "$7.99/week",
121
+ "$9.99/week": "$9.99/week",
122
+ "$29.99/month": "$29.99/month",
123
+ "$59.99/year": "$59.99/year",
124
+ "$7.99 today": "$7.99 today",
125
+ "$9.99 today": "$9.99 today",
126
+ "$29.99 today": "$29.99 today",
127
+ "$59.99 today": "$59.99 today",
128
+ },
104
129
  loadingLabel: 'Loading...',
105
130
  topBarDiscountLabel: '⏰ Discount Expires in',
106
131
  topBarCtaLabel: 'GET MY PLAN',
107
132
  countdownUnits: ['minutes', 'seconds'],
133
+ countdownAriaTemplate: 'Time left {minutes} minutes {seconds} seconds',
134
+ selectedPlanLabel: 'Selected plan',
135
+ refundPolicyLabel: 'refund policy',
108
136
  hero: {
109
137
  pills: ['Find hidden money and', "claim what's yours"],
110
138
  image: {
@@ -124,6 +152,7 @@ export const stepPaywallContent = {
124
152
  },
125
153
  plans: {
126
154
  title: 'Choose your plan',
155
+ perDayLabel: 'per day',
127
156
  cardButtonLabel: 'GET MY PLAN',
128
157
  metaLabels: ['Money-back guarantee', 'Cancel anytime'],
129
158
  stripeUnavailableNote:
@@ -180,6 +209,16 @@ export const stepPaywallContent = {
180
209
  },
181
210
  ],
182
211
  supportText: 'Need help? Contact us at',
212
+ renewalConsentTemplate: 'Without cancellation, before the {intro} ends, I accept that the app will automatically charge {amount} {cadence} until I cancel. Cancel online via the profile on the website or app.',
213
+ discountedIntroLabel: 'discounted intro plan',
214
+ selectedIntroLabel: 'selected plan',
215
+ cadenceFallback: 'on your selected billing schedule',
216
+ cadence: {
217
+ day: { one: 'every day', many: 'every {count} days' },
218
+ week: { one: 'every week', many: 'every {count} weeks' },
219
+ month: { one: 'every month', many: 'every {count} months' },
220
+ year: { one: 'every year', many: 'every {count} years' },
221
+ },
183
222
  renewalTemplate:
184
223
  "You'll be charged {amount} today for your selected plan. Your subscription renews automatically unless cancelled at least 24 hours before renewal date.",
185
224
  specialOffer: {
@@ -302,6 +341,7 @@ export const stepPaywallContent = {
302
341
  ],
303
342
  },
304
343
  guarantee: {
344
+ detailsSuffix: 'for details and limitations.',
305
345
  image: {
306
346
  src: '/paywall/money-back-badge.jpg',
307
347
  alt: '30 day money back guarantee badge',
@@ -0,0 +1,54 @@
1
+ import type { LocalizedStepContent } from '@funnelsgrove/runtime';
2
+
3
+ type SubscriptionDashboardContent = {
4
+ brand: string;
5
+ title: string;
6
+ description: string;
7
+ loading: string;
8
+ userId: string;
9
+ email: string;
10
+ status: string;
11
+ events: string;
12
+ notProvided: string;
13
+ unknown: string;
14
+ empty: string;
15
+ cancelAtEnd: string;
16
+ autoRenew: string;
17
+ periodEnds: string;
18
+ updating: string;
19
+ renew: string;
20
+ cancel: string;
21
+ refresh: string;
22
+ loadError: string;
23
+ cancelError: string;
24
+ renewError: string;
25
+ };
26
+
27
+ export const subscriptionDashboardContent = {
28
+ defaultLocale: 'en',
29
+ locales: {
30
+ en: {
31
+ "brand": "App Deals",
32
+ "title": "Manage Subscription",
33
+ "description": "View active plans and manage cancellation or renewal.",
34
+ "loading": "Loading subscription data...",
35
+ "userId": "User ID:",
36
+ "email": "Email:",
37
+ "status": "Status:",
38
+ "events": "Tracked events:",
39
+ "notProvided": "Not provided",
40
+ "unknown": "unknown",
41
+ "empty": "No subscriptions found for this user.",
42
+ "cancelAtEnd": "Will cancel at period end",
43
+ "autoRenew": "Auto-renew is active",
44
+ "periodEnds": "Current period ends:",
45
+ "updating": "Updating...",
46
+ "renew": "Renew",
47
+ "cancel": "Cancel",
48
+ "refresh": "Refresh",
49
+ "loadError": "Failed to load subscriptions",
50
+ "cancelError": "Failed to cancel subscription",
51
+ "renewError": "Failed to renew subscription"
52
+ },
53
+ },
54
+ } as const satisfies LocalizedStepContent<SubscriptionDashboardContent>;
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
4
4
  import Image from 'next/image';
5
+ import { localizePaywallPlanLabels } from '@/components/paywall/localize-paywall-plan-labels';
6
+ import { PaywallRenewalDisclaimer } from '@/components/paywall/PaywallRenewalDisclaimer';
5
7
  import { checkoutRuntimeConfig } from '@/runtime/checkout-runtime-config';
6
8
  import {
7
9
  billingDiscountStorageKeys,
@@ -33,7 +35,6 @@ import {
33
35
  advancePaywallDiscountState,
34
36
  buildBillingDiscountCatalog,
35
37
  buildDiscountedPaywallPlans,
36
- buildPaywallRenewalDisclaimer,
37
38
  getDefaultPlanId,
38
39
  getPaywallPromoDisplayName,
39
40
  resolvePaywallDiscountState,
@@ -226,8 +227,8 @@ function StepPaywallRuntime({
226
227
  plans: availablePlans,
227
228
  couponId: activeCouponId,
228
229
  discountPercent: discountState.discountPercent,
229
- }),
230
- [activeCouponId, availablePlans, discountState.discountPercent],
230
+ }).map((plan) => localizePaywallPlanLabels(plan, content.catalogLabels)),
231
+ [activeCouponId, availablePlans, discountState.discountPercent, content.catalogLabels],
231
232
  );
232
233
  const selectedPlan = useMemo(() => {
233
234
  if (availablePlans.length === 0) {
@@ -252,7 +253,7 @@ function StepPaywallRuntime({
252
253
  return displayPlans.find((plan) => plan.id === selectedPlanId) ?? displayPlans[0];
253
254
  }, [displayPlans, initialPlanId, selectedPlanId]);
254
255
  const checkoutSummaryLabel =
255
- selectedDisplayPlan?.checkoutSummaryLabel?.trim() || selectedDisplayPlan?.title || 'Selected plan';
256
+ selectedDisplayPlan?.checkoutSummaryLabel?.trim() || selectedDisplayPlan?.title || content.selectedPlanLabel;
256
257
  const checkoutProvider = activeBillingRuntime.provider;
257
258
  const storyOptions = useMemo<readonly StoryOption[]>(
258
259
  () =>
@@ -286,17 +287,6 @@ function StepPaywallRuntime({
286
287
 
287
288
  return getPaywallPromoDisplayName(activeDiscountStartedAtMs);
288
289
  }, [activeDiscountStartedAtMs, discountState.status]);
289
- const paymentDisclaimer = useMemo(() => {
290
- if (!selectedPlan) {
291
- return '';
292
- }
293
-
294
- return buildPaywallRenewalDisclaimer({
295
- plan: selectedPlan,
296
- hasActiveDiscount: Boolean(activeCouponId),
297
- productName: 'the app',
298
- });
299
- }, [activeCouponId, selectedPlan]);
300
290
  const appliedDiscountAmountCents = useMemo(() => {
301
291
  if (!selectedDisplayPlan) {
302
292
  return 0;
@@ -934,7 +924,7 @@ function StepPaywallRuntime({
934
924
  <div className='paywall-v2-top-controls'>
935
925
  <div
936
926
  className='paywall-v2-countdown'
937
- aria-label={`Time left ${String(minutes).padStart(2, '0')} minutes ${String(seconds).padStart(2, '0')} seconds`}
927
+ aria-label={content.countdownAriaTemplate.replace('{minutes}', String(minutes).padStart(2, '0')).replace('{seconds}', String(seconds).padStart(2, '0'))}
938
928
  >
939
929
  <p className='paywall-v2-countdown-value'>
940
930
  <span className='paywall-v2-countdown-segment'>
@@ -1011,7 +1001,7 @@ function StepPaywallRuntime({
1011
1001
  </span>
1012
1002
  ) : null}
1013
1003
  <span className='paywall-v2-promo-upgrade-badge-main'>
1014
- {discountState.discountPercent}% OFF
1004
+ {content.checkout.discountLabelTemplate.replace('{percent}', String(discountState.discountPercent))}
1015
1005
  </span>
1016
1006
  </div>
1017
1007
  <p className='paywall-v2-promo-upgrade-label'>{content.promo.upgradedLabel}</p>
@@ -1041,7 +1031,7 @@ function StepPaywallRuntime({
1041
1031
  <p className='paywall-v2-highlight-copy'>{content.highlight.description}</p>
1042
1032
  </section>
1043
1033
 
1044
- <section id='paywall-v2-plans' className='paywall-v2-plan-list' aria-label='Plans'>
1034
+ <section id='paywall-v2-plans' className='paywall-v2-plan-list' aria-label={content.plans.title}>
1045
1035
  <h2 className='paywall-v2-title'>{content.plans.title}</h2>
1046
1036
 
1047
1037
  {displayPlans.map((plan) => (
@@ -1089,7 +1079,7 @@ function StepPaywallRuntime({
1089
1079
  ) : null}
1090
1080
  <span className='paywall-v2-plan-per-day-amount'>{plan.perDayAmount}</span>
1091
1081
  </p>
1092
- <p className='paywall-v2-plan-per-day-label'>{plan.perDayLabel}</p>
1082
+ <p className='paywall-v2-plan-per-day-label'>{content.plans.perDayLabel}</p>
1093
1083
  </div>
1094
1084
  </div>
1095
1085
  </div>
@@ -1130,7 +1120,7 @@ function StepPaywallRuntime({
1130
1120
  {content.plans.cardButtonLabel}
1131
1121
  </button>
1132
1122
  <p className='paywall-v2-alt-meta'>{content.plans.metaLabels.join(' · ')}</p>
1133
- <p className='paywall-v2-disclaimer'>{paymentDisclaimer}</p>
1123
+ <PaywallRenewalDisclaimer plan={selectedPlan} hasActiveDiscount={Boolean(activeCouponId)} copy={content.checkout} catalogLabels={content.catalogLabels} />
1134
1124
  <p className='paywall-v2-support'>
1135
1125
  {content.checkout.supportText}{' '}
1136
1126
  <a href={`mailto:${supportEmail}`}>{supportEmail}</a>
@@ -1226,9 +1216,9 @@ function StepPaywallRuntime({
1226
1216
  <p className='paywall-v2-guarantee-text'>
1227
1217
  {content.guarantee.description}{' '}
1228
1218
  <a href={content.guarantee.links[0]?.href || '/refund-policy'}>
1229
- {content.guarantee.links[0]?.label || 'refund policy'}
1219
+ {content.guarantee.links[0]?.label || content.refundPolicyLabel}
1230
1220
  </a>{' '}
1231
- for details and limitations.
1221
+ {content.guarantee.detailsSuffix}
1232
1222
  </p>
1233
1223
  </div>
1234
1224
  </section>