appfunnel 0.6.0 → 0.7.0
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/index.js +344 -248
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/templates/default/appfunnel.config.ts +15 -0
- package/templates/default/locales/en.json +3 -0
- package/templates/default/src/app.css +1 -0
- package/templates/default/src/funnel.tsx +9 -0
- package/templates/default/src/pages/index.tsx +37 -0
- package/templates/default/src/pages/loading.tsx +76 -0
- package/templates/default/src/pages/result.tsx +38 -0
- package/templates/default/template.json +10 -0
- package/templates/default/tsconfig.json +16 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { definePage, useResponse, useNavigation } from '@appfunnel-dev/sdk'
|
|
2
|
+
|
|
3
|
+
export const page = definePage({
|
|
4
|
+
name: 'Landing',
|
|
5
|
+
type: 'default',
|
|
6
|
+
routes: [{ to: 'loading' }],
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export default function Landing() {
|
|
10
|
+
const [goal, setGoal] = useResponse<string>('goal')
|
|
11
|
+
const { goToNextPage } = useNavigation()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex min-h-screen items-center justify-center p-4">
|
|
15
|
+
<div className="w-full max-w-md space-y-6">
|
|
16
|
+
<h1 className="text-3xl font-bold text-center">Welcome</h1>
|
|
17
|
+
<p className="text-center text-gray-500">
|
|
18
|
+
Tell us about your goal to get started.
|
|
19
|
+
</p>
|
|
20
|
+
<input
|
|
21
|
+
type="text"
|
|
22
|
+
value={goal}
|
|
23
|
+
onChange={(e) => setGoal(e.target.value)}
|
|
24
|
+
placeholder="What's your goal?"
|
|
25
|
+
className="w-full rounded-xl border p-4 text-lg"
|
|
26
|
+
/>
|
|
27
|
+
<button
|
|
28
|
+
onClick={goToNextPage}
|
|
29
|
+
disabled={!goal.trim()}
|
|
30
|
+
className="w-full rounded-xl bg-blue-600 py-4 text-lg font-bold text-white disabled:opacity-50"
|
|
31
|
+
>
|
|
32
|
+
Continue
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { definePage, useNavigation } from '@appfunnel-dev/sdk'
|
|
2
|
+
import { ProgressCircle } from '@appfunnel-dev/sdk/elements'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export const page = definePage({
|
|
6
|
+
name: 'Loading',
|
|
7
|
+
routes: [{ to: 'result' }],
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const STEPS = [
|
|
11
|
+
'Analyzing your answers',
|
|
12
|
+
'Preparing your plan',
|
|
13
|
+
'Finalizing results',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export default function Loading() {
|
|
17
|
+
const { goToNextPage } = useNavigation()
|
|
18
|
+
const [progress, setProgress] = useState(0)
|
|
19
|
+
|
|
20
|
+
const currentStep = Math.min(Math.floor(progress / 34), STEPS.length - 1)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex min-h-screen flex-col items-center justify-center gap-8 p-4">
|
|
24
|
+
<h1 className="text-2xl font-bold text-center">
|
|
25
|
+
Preparing your results...
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<div className="w-40 h-40">
|
|
29
|
+
<ProgressCircle
|
|
30
|
+
duration={3000}
|
|
31
|
+
animation="easeOut"
|
|
32
|
+
trackColor="#e5e7eb"
|
|
33
|
+
fillColor="#3b82f6"
|
|
34
|
+
trackWidth={8}
|
|
35
|
+
fillWidth={8}
|
|
36
|
+
autoStart
|
|
37
|
+
onProgress={setProgress}
|
|
38
|
+
onComplete={() => setTimeout(goToNextPage, 400)}
|
|
39
|
+
renderInner={(p) => (
|
|
40
|
+
<span className="text-2xl font-bold">{p}%</span>
|
|
41
|
+
)}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div className="flex flex-col gap-3 w-full max-w-xs">
|
|
46
|
+
{STEPS.map((step, i) => (
|
|
47
|
+
<div key={i} className="flex items-center gap-3">
|
|
48
|
+
<div
|
|
49
|
+
className={`w-5 h-5 rounded-full flex items-center justify-center shrink-0 ${
|
|
50
|
+
i <= currentStep ? 'bg-blue-600' : 'bg-gray-200'
|
|
51
|
+
}`}
|
|
52
|
+
>
|
|
53
|
+
{i <= currentStep && (
|
|
54
|
+
<svg
|
|
55
|
+
width="12"
|
|
56
|
+
height="12"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="white"
|
|
60
|
+
strokeWidth="2.5"
|
|
61
|
+
>
|
|
62
|
+
<polyline points="20 6 9 17 4 12" />
|
|
63
|
+
</svg>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
<span
|
|
67
|
+
className={`text-sm ${i <= currentStep ? 'text-gray-900' : 'text-gray-400'}`}
|
|
68
|
+
>
|
|
69
|
+
{step}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { definePage, useResponse } from '@appfunnel-dev/sdk'
|
|
2
|
+
import { CountUp } from '@appfunnel-dev/sdk/elements'
|
|
3
|
+
|
|
4
|
+
export const page = definePage({
|
|
5
|
+
name: 'Result',
|
|
6
|
+
routes: [],
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export default function Result() {
|
|
10
|
+
const [goal] = useResponse<string>('goal')
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex min-h-screen flex-col items-center justify-center gap-8 p-4">
|
|
14
|
+
<h1 className="text-3xl font-bold text-center">
|
|
15
|
+
Your plan is ready!
|
|
16
|
+
</h1>
|
|
17
|
+
|
|
18
|
+
<p className="text-gray-500 text-center max-w-md">
|
|
19
|
+
Based on your goal "{goal}", here's what we found:
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<div className="flex gap-6">
|
|
23
|
+
<div className="flex flex-col items-center gap-2 rounded-xl bg-gray-50 p-6">
|
|
24
|
+
<CountUp from={0} to={94} duration={1500} suffix="%" />
|
|
25
|
+
<span className="text-sm text-gray-500">Match score</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div className="flex flex-col items-center gap-2 rounded-xl bg-gray-50 p-6">
|
|
28
|
+
<CountUp from={0} to={12} duration={1500} suffix=" days" />
|
|
29
|
+
<span className="text-sm text-gray-500">Estimated time</span>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<button className="w-full max-w-md rounded-xl bg-blue-600 py-4 text-lg font-bold text-white">
|
|
34
|
+
Get Started
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Default",
|
|
3
|
+
"description": "Simple 3-page funnel with loading screen and results",
|
|
4
|
+
"products": [
|
|
5
|
+
{ "id": "plan_1", "label": "Plan 1", "description": "The cheapest plan — typically a short-term or weekly option" },
|
|
6
|
+
{ "id": "plan_2", "label": "Plan 2", "description": "Mid-tier plan — typically monthly, best balance of value and price" },
|
|
7
|
+
{ "id": "plan_3", "label": "Plan 3", "description": "Premium plan — typically quarterly or annual, highest value" },
|
|
8
|
+
{ "id": "upsell_1", "label": "Upsell", "description": "One-time or add-on offer shown after the main purchase" }
|
|
9
|
+
]
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"paths": {
|
|
11
|
+
"@/*": ["./src/*"]
|
|
12
|
+
},
|
|
13
|
+
"baseUrl": "."
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|