appfunnel 0.7.0 → 0.8.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.
@@ -0,0 +1,70 @@
1
+ import { Drawer, type DrawerHandle, motion } from '@appfunnel-dev/sdk/elements'
2
+ import { forwardRef, useImperativeHandle, useRef } from 'react'
3
+
4
+ export interface ConsentDrawerHandle {
5
+ open: () => void
6
+ close: () => void
7
+ }
8
+
9
+ interface ConsentDrawerProps {
10
+ onAccept: () => void
11
+ onDecline: () => void
12
+ }
13
+
14
+ export const ConsentDrawer = forwardRef<ConsentDrawerHandle, ConsentDrawerProps>(
15
+ function ConsentDrawer({ onAccept, onDecline }, ref) {
16
+ const drawerRef = useRef<DrawerHandle>(null)
17
+
18
+ useImperativeHandle(
19
+ ref,
20
+ () => ({
21
+ open: () => drawerRef.current?.open(),
22
+ close: () => drawerRef.current?.close(),
23
+ }),
24
+ []
25
+ )
26
+
27
+ return (
28
+ <Drawer
29
+ ref={drawerRef}
30
+ height="95dvh"
31
+ borderRadius={24}
32
+ closeOnOverlayClick={false}
33
+ showHandle={false}
34
+ >
35
+ <div className="p-6 h-full flex flex-col justify-between max-w-[672px] mx-auto w-full">
36
+ <div>
37
+ <h2 className="text-[28px] font-semibold mt-4">
38
+ May we send product updates to your email?
39
+ </h2>
40
+ <p className="text-lg font-semibold mt-4">
41
+ Tips, promotions, and special offers
42
+ </p>
43
+ <p className="text-[15px] text-[#727272] mt-2">
44
+ You can change your mind at any time by clicking the unsubscribe
45
+ link in the footer of any email you receive from us.
46
+ </p>
47
+ </div>
48
+ <div className="flex flex-col gap-2 mt-auto pt-4">
49
+ <motion.button
50
+ whileTap={{ scale: 0.95 }}
51
+ transition={{ duration: 0.15 }}
52
+ onClick={onAccept}
53
+ className="w-full py-4 bg-blue-600 text-white font-bold rounded-2xl text-xl"
54
+ >
55
+ Yes, keep me updated
56
+ </motion.button>
57
+ <motion.button
58
+ whileTap={{ scale: 0.95 }}
59
+ transition={{ duration: 0.15 }}
60
+ onClick={onDecline}
61
+ className="w-full py-3 text-lg font-bold text-black/50"
62
+ >
63
+ No thanks
64
+ </motion.button>
65
+ </div>
66
+ </div>
67
+ </Drawer>
68
+ )
69
+ }
70
+ )
@@ -0,0 +1,37 @@
1
+ import { useNavigation } from '@appfunnel-dev/sdk'
2
+
3
+ export function Header({ showBack = true }: { showBack?: boolean }) {
4
+ const { progress, goBack } = useNavigation()
5
+
6
+ return (
7
+ <div className="flex flex-col gap-2 px-4 pt-4 pb-2">
8
+ <div className="flex items-center justify-between">
9
+ <div className="w-10 flex items-center">
10
+ {showBack && (
11
+ <button onClick={goBack} className="p-2 -ml-2">
12
+ <svg
13
+ width="24"
14
+ height="24"
15
+ viewBox="0 0 24 24"
16
+ fill="none"
17
+ stroke="#171717"
18
+ strokeWidth="2"
19
+ >
20
+ <path d="M15 18l-6-6 6-6" />
21
+ </svg>
22
+ </button>
23
+ )}
24
+ </div>
25
+ <span className="text-xs font-semibold text-blue-600 whitespace-nowrap">
26
+ {progress.current} / {progress.total}
27
+ </span>
28
+ </div>
29
+ <div className="h-1 bg-gray-100 rounded-full overflow-hidden">
30
+ <div
31
+ className="h-full bg-blue-600 rounded-full transition-all duration-300"
32
+ style={{ width: `${progress.percentage}%` }}
33
+ />
34
+ </div>
35
+ </div>
36
+ )
37
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ StripePaymentForm,
3
+ type StripePaymentHandle,
4
+ usePayment,
5
+ } from '@appfunnel-dev/sdk'
6
+ import { Dialog, type DialogHandle, Loading, motion } from '@appfunnel-dev/sdk/elements'
7
+ import { forwardRef, useImperativeHandle, useRef } from 'react'
8
+
9
+ export interface PaymentCheckoutDialogHandle {
10
+ open: () => void
11
+ close: () => void
12
+ }
13
+
14
+ interface PaymentCheckoutDialogProps {
15
+ onSuccess: () => void
16
+ }
17
+
18
+ export const PaymentCheckoutDialog = forwardRef<
19
+ PaymentCheckoutDialogHandle,
20
+ PaymentCheckoutDialogProps
21
+ >(function PaymentCheckoutDialog({ onSuccess }, ref) {
22
+ const dialogRef = useRef<DialogHandle>(null)
23
+ const paymentRef = useRef<StripePaymentHandle>(null)
24
+ const { loading, error } = usePayment()
25
+
26
+ useImperativeHandle(
27
+ ref,
28
+ () => ({
29
+ open: () => dialogRef.current?.open(),
30
+ close: () => dialogRef.current?.close(),
31
+ }),
32
+ []
33
+ )
34
+
35
+ return (
36
+ <Dialog
37
+ ref={dialogRef}
38
+ title="Complete payment"
39
+ maxWidth={400}
40
+ borderRadius={16}
41
+ >
42
+ <div className="flex flex-col gap-4 mt-4">
43
+ {error && <p className="text-sm text-red-500">{error}</p>}
44
+
45
+ <StripePaymentForm
46
+ ref={paymentRef}
47
+ onSuccess={() => {
48
+ dialogRef.current?.close()
49
+ onSuccess()
50
+ }}
51
+ appearance={{
52
+ theme: 'flat',
53
+ variables: {
54
+ colorPrimary: '#2563EB',
55
+ borderRadius: '12px',
56
+ },
57
+ }}
58
+ />
59
+
60
+ <motion.button
61
+ whileTap={!loading ? { scale: 0.95 } : {}}
62
+ transition={{ duration: 0.15 }}
63
+ onClick={() => paymentRef.current?.submit()}
64
+ disabled={loading}
65
+ className={`w-full py-4 rounded-2xl text-base font-bold ${
66
+ loading
67
+ ? 'bg-gray-400 text-white cursor-not-allowed'
68
+ : 'bg-blue-600 text-white cursor-pointer'
69
+ }`}
70
+ >
71
+ {loading ? <Loading size="xs" color="#fff" /> : 'Pay now'}
72
+ </motion.button>
73
+ </div>
74
+ </Dialog>
75
+ )
76
+ })
@@ -0,0 +1,66 @@
1
+ import { definePage, useDateOfBirth, useNavigation } from '@appfunnel-dev/sdk'
2
+ import { motion } from '@appfunnel-dev/sdk/elements'
3
+ import { Header } from '../components/Header'
4
+ import { useState } from 'react'
5
+
6
+ export const page = definePage({
7
+ name: 'Birthday',
8
+ routes: [{ to: 'email' }],
9
+ })
10
+
11
+ export default function Birthday() {
12
+ const [dob, setDob] = useDateOfBirth()
13
+ const [display, setDisplay] = useState('')
14
+ const { goToNextPage } = useNavigation()
15
+
16
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
17
+ let val = e.target.value.replace(/[^\d]/g, '')
18
+ if (val.length > 8) val = val.slice(0, 8)
19
+
20
+ let formatted = ''
21
+ if (val.length > 0) formatted = val.slice(0, 2)
22
+ if (val.length > 2) formatted += ' / ' + val.slice(2, 4)
23
+ if (val.length > 4) formatted += ' / ' + val.slice(4, 8)
24
+
25
+ setDisplay(formatted)
26
+
27
+ if (val.length === 8) {
28
+ setDob(val)
29
+ }
30
+ }
31
+
32
+ const enabled = !!dob?.trim()
33
+
34
+ return (
35
+ <div className="min-h-screen bg-white flex flex-col max-w-[672px] mx-auto w-full">
36
+ <Header />
37
+ <div className="flex-1 flex flex-col justify-between px-4 pt-6 pb-6">
38
+ <div>
39
+ <h1 className="text-[30px] leading-tight font-bold text-[#171717] mb-6">
40
+ What's your date of birth?
41
+ </h1>
42
+ <input
43
+ type="text"
44
+ value={display}
45
+ onChange={handleChange}
46
+ placeholder="MM / DD / YYYY"
47
+ className="w-full px-4 py-4 rounded-2xl border border-gray-200 text-base text-[#171717] placeholder-gray-400 outline-none focus:border-blue-600 transition-colors"
48
+ />
49
+ </div>
50
+ <motion.button
51
+ whileTap={enabled ? { scale: 0.95 } : {}}
52
+ transition={{ duration: 0.15 }}
53
+ onClick={goToNextPage}
54
+ disabled={!enabled}
55
+ className={`w-full py-4 rounded-2xl text-base font-bold transition-colors ${
56
+ enabled
57
+ ? 'bg-blue-600 text-white'
58
+ : 'bg-gray-100 text-gray-400'
59
+ }`}
60
+ >
61
+ Continue
62
+ </motion.button>
63
+ </div>
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,67 @@
1
+ import { definePage } from '@appfunnel-dev/sdk'
2
+
3
+ export const page = definePage({
4
+ name: 'Download',
5
+ type: 'finish',
6
+ })
7
+
8
+ export default function Download() {
9
+ return (
10
+ <div className="min-h-screen bg-white flex flex-col items-center justify-center p-6">
11
+ <div className="w-full max-w-[672px] flex flex-col items-center gap-8">
12
+ {/* Success icon */}
13
+ <div className="w-20 h-20 rounded-full bg-blue-600 flex items-center justify-center">
14
+ <svg
15
+ width="40"
16
+ height="40"
17
+ viewBox="0 0 24 24"
18
+ fill="none"
19
+ stroke="white"
20
+ strokeWidth="2.5"
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ >
24
+ <polyline points="20 6 9 17 4 12" />
25
+ </svg>
26
+ </div>
27
+
28
+ {/* Title */}
29
+ <div className="text-center">
30
+ <h1 className="text-[28px] font-bold text-[#171717]">
31
+ You're all set!
32
+ </h1>
33
+ <p className="text-base text-[#727272] mt-2">
34
+ Download the app to get started with your personalized plan.
35
+ </p>
36
+ </div>
37
+
38
+ {/* Store buttons */}
39
+ <div className="flex flex-col gap-3 w-full">
40
+ <a
41
+ href="#"
42
+ className="w-full flex items-center justify-center gap-3 py-4 rounded-2xl bg-[#171717] text-white font-bold text-base"
43
+ >
44
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="white">
45
+ <path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
46
+ </svg>
47
+ Download on the App Store
48
+ </a>
49
+ <a
50
+ href="#"
51
+ className="w-full flex items-center justify-center gap-3 py-4 rounded-2xl border-2 border-[#171717] text-[#171717] font-bold text-base"
52
+ >
53
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="#171717">
54
+ <path d="M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.61 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z" />
55
+ </svg>
56
+ Get it on Google Play
57
+ </a>
58
+ </div>
59
+
60
+ {/* Footer */}
61
+ <p className="text-sm text-[#9CA3AF] text-center">
62
+ Check your email for your account details and download links.
63
+ </p>
64
+ </div>
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ definePage,
3
+ useUser,
4
+ useNavigation,
5
+ } from '@appfunnel-dev/sdk'
6
+ import { motion } from '@appfunnel-dev/sdk/elements'
7
+ import { Header } from '../components/Header'
8
+ import {
9
+ ConsentDrawer,
10
+ type ConsentDrawerHandle,
11
+ } from '../components/ConsentDrawer'
12
+ import { useRef } from 'react'
13
+
14
+ export const page = definePage({
15
+ name: 'Email',
16
+ routes: [{ to: 'paywall' }],
17
+ })
18
+
19
+ export default function Email() {
20
+ const { email, setEmail, identify, setMarketingConsent } = useUser()
21
+ const { goToNextPage } = useNavigation()
22
+ const consentRef = useRef<ConsentDrawerHandle>(null)
23
+
24
+ const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email || '')
25
+
26
+ const handleSubmit = () => {
27
+ if (!isValid) return
28
+ identify(email!)
29
+ consentRef.current?.open()
30
+ }
31
+
32
+ return (
33
+ <div className="min-h-screen bg-white flex flex-col max-w-[672px] mx-auto w-full">
34
+ <Header />
35
+ <div className="flex-1 flex flex-col justify-between px-4 pt-6 pb-6">
36
+ <div>
37
+ <h1 className="text-[30px] leading-tight font-bold text-[#171717] mb-6">
38
+ What's your email?
39
+ </h1>
40
+ <input
41
+ type="email"
42
+ value={email || ''}
43
+ onChange={(e) => setEmail(e.target.value)}
44
+ placeholder="name@example.com"
45
+ className="w-full px-4 py-4 rounded-2xl border border-gray-200 text-base text-[#171717] placeholder-gray-400 outline-none focus:border-blue-600 transition-colors"
46
+ />
47
+
48
+ <div className="flex items-start gap-3 bg-gray-100 rounded-xl px-4 py-3 mt-4">
49
+ <svg
50
+ width="16"
51
+ height="16"
52
+ viewBox="0 0 24 24"
53
+ fill="none"
54
+ stroke="#171717"
55
+ strokeWidth="2"
56
+ className="mt-0.5 shrink-0"
57
+ >
58
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
59
+ <path d="M7 11V7a5 5 0 0 1 10 0v4" />
60
+ </svg>
61
+ <span className="text-sm text-[#171717]">
62
+ We respect your privacy and take it seriously. No spam, ever.
63
+ </span>
64
+ </div>
65
+ </div>
66
+
67
+ <motion.button
68
+ whileTap={isValid ? { scale: 0.95 } : {}}
69
+ transition={{ duration: 0.15 }}
70
+ onClick={handleSubmit}
71
+ disabled={!isValid}
72
+ className={`w-full py-4 rounded-2xl text-base font-bold transition-colors ${
73
+ isValid
74
+ ? 'bg-blue-600 text-white'
75
+ : 'bg-gray-100 text-gray-400'
76
+ }`}
77
+ >
78
+ Continue
79
+ </motion.button>
80
+ </div>
81
+
82
+ <ConsentDrawer
83
+ ref={consentRef}
84
+ onAccept={() => {
85
+ setMarketingConsent(true)
86
+ goToNextPage()
87
+ }}
88
+ onDecline={() => {
89
+ setMarketingConsent(false)
90
+ goToNextPage()
91
+ }}
92
+ />
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,109 @@
1
+ import { definePage, useNavigation } from '@appfunnel-dev/sdk'
2
+ import { motion } from '@appfunnel-dev/sdk/elements'
3
+
4
+ export const page = definePage({
5
+ name: 'Intro',
6
+ type: 'default',
7
+ routes: [{ to: 'single-select' }],
8
+ })
9
+
10
+ export default function Intro() {
11
+ const { goToNextPage } = useNavigation()
12
+
13
+ return (
14
+ <div className="min-h-screen bg-white flex flex-col items-center justify-center p-6">
15
+ <div className="w-full max-w-[672px] flex flex-col items-center gap-8">
16
+ {/* Icon */}
17
+ <div className="w-20 h-20 rounded-2xl bg-blue-600 flex items-center justify-center">
18
+ <svg
19
+ width="40"
20
+ height="40"
21
+ viewBox="0 0 24 24"
22
+ fill="none"
23
+ stroke="white"
24
+ strokeWidth="2"
25
+ strokeLinecap="round"
26
+ strokeLinejoin="round"
27
+ >
28
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
29
+ </svg>
30
+ </div>
31
+
32
+ {/* Text */}
33
+ <div className="flex flex-col items-center gap-3 text-center">
34
+ <h1 className="text-3xl font-bold text-[#171717]">
35
+ Get your personalized plan
36
+ </h1>
37
+ <p className="text-lg text-[#727272] leading-relaxed">
38
+ Answer a few quick questions so we can create a plan tailored just
39
+ for you.
40
+ </p>
41
+ </div>
42
+
43
+ {/* Features */}
44
+ <div className="flex flex-col gap-4 w-full">
45
+ {[
46
+ { icon: 'clock', text: 'Takes less than 2 minutes' },
47
+ { icon: 'target', text: 'Personalized to your goals' },
48
+ { icon: 'shield', text: 'Your data stays private' },
49
+ ].map((item) => (
50
+ <div key={item.icon} className="flex items-center gap-3">
51
+ <div className="w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center shrink-0">
52
+ {item.icon === 'clock' && (
53
+ <svg
54
+ width="20"
55
+ height="20"
56
+ viewBox="0 0 24 24"
57
+ fill="none"
58
+ stroke="#2563EB"
59
+ strokeWidth="2"
60
+ >
61
+ <circle cx="12" cy="12" r="10" />
62
+ <polyline points="12 6 12 12 16 14" />
63
+ </svg>
64
+ )}
65
+ {item.icon === 'target' && (
66
+ <svg
67
+ width="20"
68
+ height="20"
69
+ viewBox="0 0 24 24"
70
+ fill="none"
71
+ stroke="#2563EB"
72
+ strokeWidth="2"
73
+ >
74
+ <circle cx="12" cy="12" r="10" />
75
+ <circle cx="12" cy="12" r="6" />
76
+ <circle cx="12" cy="12" r="2" />
77
+ </svg>
78
+ )}
79
+ {item.icon === 'shield' && (
80
+ <svg
81
+ width="20"
82
+ height="20"
83
+ viewBox="0 0 24 24"
84
+ fill="none"
85
+ stroke="#2563EB"
86
+ strokeWidth="2"
87
+ >
88
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
89
+ </svg>
90
+ )}
91
+ </div>
92
+ <span className="text-base text-[#171717]">{item.text}</span>
93
+ </div>
94
+ ))}
95
+ </div>
96
+
97
+ {/* CTA */}
98
+ <motion.button
99
+ whileTap={{ scale: 0.95 }}
100
+ transition={{ duration: 0.15 }}
101
+ onClick={goToNextPage}
102
+ className="w-full py-4 rounded-2xl bg-blue-600 text-white text-lg font-bold"
103
+ >
104
+ Get Started
105
+ </motion.button>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
@@ -0,0 +1,79 @@
1
+ import { definePage, useResponse, useNavigation } from '@appfunnel-dev/sdk'
2
+ import { MultiSelect, motion } from '@appfunnel-dev/sdk/elements'
3
+ import { Header } from '../components/Header'
4
+
5
+ export const page = definePage({
6
+ name: 'Interests',
7
+ routes: [{ to: 'name' }],
8
+ })
9
+
10
+ export default function Interests() {
11
+ const [interests] = useResponse<string[]>('interests')
12
+ const { goToNextPage } = useNavigation()
13
+
14
+ const hasSelection = interests && interests.length > 0
15
+
16
+ return (
17
+ <div className="min-h-screen bg-white flex flex-col max-w-[672px] mx-auto w-full">
18
+ <Header />
19
+ <div className="flex-1 flex flex-col px-4 pt-8 pb-6">
20
+ <h1 className="text-[30px] leading-tight font-bold text-[#171717] mb-2">
21
+ What are you most interested in?
22
+ </h1>
23
+ <p className="text-lg text-[#727272] mb-6">Select all that apply</p>
24
+
25
+ <MultiSelect
26
+ responseKey="interests"
27
+ className="flex flex-col gap-3"
28
+ min={1}
29
+ options={[
30
+ { label: 'Fitness & Health', value: 'fitness' },
31
+ { label: 'Nutrition & Diet', value: 'nutrition' },
32
+ { label: 'Mindfulness', value: 'mindfulness' },
33
+ { label: 'Productivity', value: 'productivity' },
34
+ { label: 'Finance', value: 'finance' },
35
+ { label: 'Learning', value: 'learning' },
36
+ ]}
37
+ renderItem={({ item, active }) => (
38
+ <div
39
+ className={`w-full px-4 py-4 rounded-2xl text-left transition-all border-2 ${
40
+ active
41
+ ? 'border-blue-600 bg-blue-50'
42
+ : 'border-transparent bg-[#F5F5F5]'
43
+ }`}
44
+ >
45
+ <div className="flex items-center gap-3">
46
+ <div
47
+ className={`w-5 h-5 rounded-lg border-2 flex items-center justify-center shrink-0 ${
48
+ active ? 'border-blue-600 bg-blue-600' : 'border-gray-300'
49
+ }`}
50
+ >
51
+ {active && (
52
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3">
53
+ <polyline points="20 6 9 17 4 12" />
54
+ </svg>
55
+ )}
56
+ </div>
57
+ <span className="text-base font-semibold text-[#171717]">{item.label}</span>
58
+ </div>
59
+ </div>
60
+ )}
61
+ />
62
+
63
+ <motion.button
64
+ whileTap={hasSelection ? { scale: 0.95 } : {}}
65
+ transition={{ duration: 0.15 }}
66
+ onClick={goToNextPage}
67
+ disabled={!hasSelection}
68
+ className={`w-full py-4 rounded-2xl text-base font-bold transition-colors mt-6 ${
69
+ hasSelection
70
+ ? 'bg-blue-600 text-white'
71
+ : 'bg-gray-100 text-gray-400'
72
+ }`}
73
+ >
74
+ Continue
75
+ </motion.button>
76
+ </div>
77
+ </div>
78
+ )
79
+ }
@@ -0,0 +1,48 @@
1
+ import { definePage, useUser, useNavigation } from '@appfunnel-dev/sdk'
2
+ import { motion } from '@appfunnel-dev/sdk/elements'
3
+ import { Header } from '../components/Header'
4
+
5
+ export const page = definePage({
6
+ name: 'Name',
7
+ routes: [{ to: 'birthday' }],
8
+ })
9
+
10
+ export default function Name() {
11
+ const { name, setName } = useUser()
12
+ const { goToNextPage } = useNavigation()
13
+
14
+ const enabled = !!name?.trim()
15
+
16
+ return (
17
+ <div className="min-h-screen bg-white flex flex-col max-w-[672px] mx-auto w-full">
18
+ <Header />
19
+ <div className="flex-1 flex flex-col justify-between px-4 pt-6 pb-6">
20
+ <div>
21
+ <h1 className="text-[30px] leading-tight font-bold text-[#171717] mb-6">
22
+ What's your name?
23
+ </h1>
24
+ <input
25
+ type="text"
26
+ value={name || ''}
27
+ onChange={(e) => setName(e.target.value)}
28
+ placeholder="Your name"
29
+ className="w-full px-4 py-4 rounded-2xl border border-gray-200 text-base text-[#171717] placeholder-gray-400 outline-none focus:border-blue-600 transition-colors"
30
+ />
31
+ </div>
32
+ <motion.button
33
+ whileTap={enabled ? { scale: 0.95 } : {}}
34
+ transition={{ duration: 0.15 }}
35
+ onClick={goToNextPage}
36
+ disabled={!enabled}
37
+ className={`w-full py-4 rounded-2xl text-base font-bold transition-colors ${
38
+ enabled
39
+ ? 'bg-blue-600 text-white'
40
+ : 'bg-gray-100 text-gray-400'
41
+ }`}
42
+ >
43
+ Continue
44
+ </motion.button>
45
+ </div>
46
+ </div>
47
+ )
48
+ }