|
1 | 1 | import { Link } from 'react-router'
|
| 2 | +import { useState, useEffect, useRef } from 'react' |
2 | 3 |
|
3 | 4 | const HowItWorks = () => {
|
4 |
| - return ( |
5 |
| - <div className="flex flex-col bg-[linear-gradient(180deg,#1C246E_0%,#040617_12.5%)] relative pt-16 min-h-screen"> |
6 |
| - <div className="max-w-4xl mx-auto px-4 py-12"> |
7 |
| - <div className="text-center mb-12"> |
8 |
| - <h1 className="text-4xl font-bold text-white mb-4">Lorem Ipsum</h1> |
9 |
| - <p className="text-gray-300 text-lg">Dolor sit amet consectetur adipiscing elit</p> |
10 |
| - </div> |
11 |
| - |
12 |
| - <div className="grid md:grid-cols-3 gap-8 mb-12"> |
13 |
| - <div className="bg-[#00000033] rounded-lg p-6 backdrop-blur-sm border border-[#ffffff1a] text-center"> |
14 |
| - <div className="w-16 h-16 bg-white rounded-full flex items-center justify-center mx-auto mb-4"> |
15 |
| - <span className="text-[#1C246E] text-2xl font-bold">1</span> |
16 |
| - </div> |
17 |
| - <h3 className="text-xl font-semibold text-white mb-3">Lorem Ipsum</h3> |
18 |
| - <p className="text-gray-300 text-sm"> |
19 |
| - Dolor sit amet consectetur adipiscing elit sed do eiusmod tempor |
20 |
| - </p> |
21 |
| - </div> |
| 5 | + const [activeSection, setActiveSection] = useState('overview') |
| 6 | + const [isMobile, setIsMobile] = useState(false) |
| 7 | + const isScrollingRef = useRef(false) |
22 | 8 |
|
23 |
| - <div className="bg-[#00000033] rounded-lg p-6 backdrop-blur-sm border border-[#ffffff1a] text-center"> |
24 |
| - <div className="w-16 h-16 bg-white rounded-full flex items-center justify-center mx-auto mb-4"> |
25 |
| - <span className="text-[#1C246E] text-2xl font-bold">2</span> |
26 |
| - </div> |
27 |
| - <h3 className="text-xl font-semibold text-white mb-3">Dolor Sit</h3> |
28 |
| - <p className="text-gray-300 text-sm"> |
29 |
| - Amet consectetur adipiscing elit sed do eiusmod tempor incididunt |
30 |
| - </p> |
31 |
| - </div> |
| 9 | + const sections = [ |
| 10 | + { id: 'overview', title: 'Overview', number: null }, |
| 11 | + { id: 'signup', title: 'Website Signup Process', number: 1 }, |
| 12 | + { id: 'validation', title: 'Smart Contract Validation', number: 2 }, |
| 13 | + { id: 'indexer', title: 'Indexer Processing', number: 3 }, |
| 14 | + { id: 'authentication', title: 'Profile Download Authentication', number: 4 }, |
| 15 | + { id: 'setup', title: 'VPN Client Setup', number: 5 } |
| 16 | + ] |
32 | 17 |
|
33 |
| - <div className="bg-[#00000033] rounded-lg p-6 backdrop-blur-sm border border-[#ffffff1a] text-center"> |
34 |
| - <div className="w-16 h-16 bg-white rounded-full flex items-center justify-center mx-auto mb-4"> |
35 |
| - <span className="text-[#1C246E] text-2xl font-bold">3</span> |
36 |
| - </div> |
37 |
| - <h3 className="text-xl font-semibold text-white mb-3">Amet Consectetur</h3> |
38 |
| - <p className="text-gray-300 text-sm"> |
39 |
| - Adipiscing elit sed do eiusmod tempor incididunt ut labore dolore |
40 |
| - </p> |
41 |
| - </div> |
42 |
| - </div> |
| 18 | + useEffect(() => { |
| 19 | + const checkMobile = () => { |
| 20 | + setIsMobile(window.innerWidth < 1024) |
| 21 | + } |
| 22 | + |
| 23 | + checkMobile() |
| 24 | + window.addEventListener('resize', checkMobile) |
| 25 | + return () => window.removeEventListener('resize', checkMobile) |
| 26 | + }, []) |
43 | 27 |
|
44 |
| - <div className="bg-[#00000033] rounded-lg p-8 backdrop-blur-sm border border-[#ffffff1a] mb-8"> |
45 |
| - <h2 className="text-2xl font-semibold text-white mb-6 text-center">Lorem Ipsum Dolor</h2> |
46 |
| - <div className="grid md:grid-cols-2 gap-6 text-gray-300"> |
47 |
| - <div> |
48 |
| - <h3 className="text-lg font-semibold text-white mb-2">🔒 Lorem Ipsum</h3> |
49 |
| - <p className="text-sm">Dolor sit amet consectetur adipiscing elit sed do eiusmod tempor</p> |
50 |
| - </div> |
51 |
| - <div> |
52 |
| - <h3 className="text-lg font-semibold text-white mb-2">⚡ Dolor Sit</h3> |
53 |
| - <p className="text-sm">Amet consectetur adipiscing elit sed do eiusmod tempor incididunt</p> |
54 |
| - </div> |
55 |
| - <div> |
56 |
| - <h3 className="text-lg font-semibold text-white mb-2">🌐 Amet Consectetur</h3> |
57 |
| - <p className="text-sm">Adipiscing elit sed do eiusmod tempor incididunt ut labore dolore</p> |
58 |
| - </div> |
59 |
| - <div> |
60 |
| - <h3 className="text-lg font-semibold text-white mb-2">💎 Tempor Incididunt</h3> |
61 |
| - <p className="text-sm">Ut labore et dolore magna aliqua ut enim ad minim veniam</p> |
62 |
| - </div> |
63 |
| - </div> |
64 |
| - </div> |
| 28 | + const scrollToSection = (sectionId: string) => { |
| 29 | + isScrollingRef.current = true |
| 30 | + setActiveSection(sectionId) |
| 31 | + |
| 32 | + const element = document.getElementById(sectionId) |
| 33 | + if (element) { |
| 34 | + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) |
| 35 | + |
| 36 | + setTimeout(() => { |
| 37 | + isScrollingRef.current = false |
| 38 | + }, 1000) |
| 39 | + } |
| 40 | + } |
65 | 41 |
|
66 |
| - <div className="bg-[#00000033] rounded-lg p-8 backdrop-blur-sm border border-[#ffffff1a] mb-8"> |
67 |
| - <h2 className="text-2xl font-semibold text-white mb-6 text-center">How It Works</h2> |
68 |
| - <div className="text-gray-300 text-center"> |
| 42 | + useEffect(() => { |
| 43 | + const handleScroll = () => { |
| 44 | + if (isScrollingRef.current) return |
| 45 | + |
| 46 | + const scrollPosition = window.scrollY + 200 |
| 47 | + |
| 48 | + for (const section of sections) { |
| 49 | + const element = document.getElementById(section.id) |
| 50 | + if (element) { |
| 51 | + const { offsetTop, offsetHeight } = element |
| 52 | + if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) { |
| 53 | + setActiveSection(section.id) |
| 54 | + break |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | + } |
69 | 59 |
|
70 |
| - <p className="mb-6"> |
71 |
| - Our VPN system consists of multiple components, including smart contracts, a custom chain |
72 |
| - indexer and API, a web frontend, and OpenVPN. These pieces work together to facilitate signup |
73 |
| - and management of your VPN subscription and access to the VPN tunnel in a manner that focuses |
74 |
| - on privacy. |
75 |
| - </p> |
| 60 | + window.addEventListener('scroll', handleScroll, { passive: true }) |
| 61 | + return () => window.removeEventListener('scroll', handleScroll) |
| 62 | + }, []) |
76 | 63 |
|
77 |
| - <p className="mb-6"> |
78 |
| - The signup process typically starts on our website. While it's not strictly necessary to use |
79 |
| - our website to subscribe to our services, it does make things easier. Once you connect your |
80 |
| - wallet, we will query our API for any existing subscriptions, as well as for current region and |
81 |
| - plan information. Once you choose a plan and selection the option to purchase, the site will |
82 |
| - call our API with your wallet address and chosen plan information. This will build a transaction |
83 |
| - based on your wallet, which is then returned to the user to be signed by their wallet and |
84 |
| - submitted. |
85 |
| - </p> |
| 64 | + return ( |
| 65 | + <div className="flex relative pt-16 min-h-screen overflow-hidden"> |
| 66 | + {!isMobile && ( |
| 67 | + <div className="w-80 fixed left-0 top-32 h-[calc(100vh-4rem)] overflow-y-auto z-20"> |
| 68 | + <div className="p-6"> |
| 69 | + <nav className="space-y-2"> |
| 70 | + {sections.map((section) => ( |
| 71 | + <button |
| 72 | + key={section.id} |
| 73 | + onClick={() => scrollToSection(section.id)} |
| 74 | + className={`w-full text-left px-4 py-3 rounded-lg transition-colors ${ |
| 75 | + activeSection === section.id |
| 76 | + ? 'bg-white/10 text-white border border-white/20' |
| 77 | + : 'text-gray-300 hover:text-white hover:bg-white/5' |
| 78 | + }`} |
| 79 | + > |
| 80 | + <div className="flex items-center"> |
| 81 | + {section.number && ( |
| 82 | + <div className="w-8 h-8 bg-transparent border border-white/30 rounded-full flex items-center justify-center text-sm font-bold mr-3 flex-shrink-0"> |
| 83 | + {section.number} |
| 84 | + </div> |
| 85 | + )} |
| 86 | + <span className="text-sm font-medium">{section.title}</span> |
| 87 | + </div> |
| 88 | + </button> |
| 89 | + ))} |
| 90 | + </nav> |
| 91 | + </div> |
| 92 | + </div> |
| 93 | + )} |
86 | 94 |
|
87 |
| - <p className="mb-6"> |
88 |
| - The signup transaction will be validated by a smart contract, checking that it conforms to |
89 |
| - available regions and plans, the datum matches the expected shape, all funds are the correct amounts |
90 |
| - and going to the correct places, and other various sanity checks. |
91 |
| - </p> |
| 95 | + <div className={`flex-1 ${isMobile ? 'ml-0' : 'ml-70'}`}> |
| 96 | + <div className="max-w-4xl mx-auto px-8 py-16"> |
| 97 | + <section id="overview" className="mb-16"> |
| 98 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 99 | + <h1 className="text-3xl font-semibold text-white mb-6">How It Works</h1> |
| 100 | + <p className="text-gray-300 text-lg leading-relaxed"> |
| 101 | + Our VPN system consists of multiple components, including smart contracts, a custom chain |
| 102 | + indexer and API, a web frontend, and OpenVPN. These pieces work together to facilitate signup |
| 103 | + and management of your VPN subscription and access to the VPN tunnel in a manner that focuses |
| 104 | + on privacy. |
| 105 | + </p> |
| 106 | + </div> |
| 107 | + </section> |
92 | 108 |
|
93 |
| - <p className="mb-6"> |
94 |
| - Once the signup TX makes it into a block and on-chain, it will get picked up by our <a href="https://github.com/blinklabs-io/vpn-indexer">custom indexer</a>. |
95 |
| - The client datum will be extracted and its information written to a SQLite database. A new client TLS |
96 |
| - certificate is generated and signed by our CA certificate, and a new VPN client config built and |
97 |
| - uploaded to a private S3 bucket. |
98 |
| - </p> |
| 109 | + <section id="signup" className="mb-16"> |
| 110 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 111 | + <div className="flex items-center mb-6"> |
| 112 | + <div className="w-12 h-12 bg-transparent border-2 border-white rounded-full flex items-center justify-center text-white font-bold text-lg mr-4"> |
| 113 | + 1 |
| 114 | + </div> |
| 115 | + <h2 className="text-2xl font-semibold text-white">Website Signup Process</h2> |
| 116 | + </div> |
| 117 | + <p className="text-gray-300 leading-relaxed"> |
| 118 | + The signup process typically starts on our website. While it's not strictly necessary to use |
| 119 | + our website to subscribe to our services, it does make things easier. Once you connect your |
| 120 | + wallet, we will query our API for any existing subscriptions, as well as for current region and |
| 121 | + plan information. Once you choose a plan and selection the option to purchase, the site will |
| 122 | + call our API with your wallet address and chosen plan information. This will build a transaction |
| 123 | + based on your wallet, which is then returned to the user to be signed by their wallet and |
| 124 | + submitted. |
| 125 | + </p> |
| 126 | + </div> |
| 127 | + </section> |
99 | 128 |
|
100 |
| - <p className="mb-6"> |
101 |
| - Once a profile has been uploaded to S3, our API will allow fetching it by validating ownership of the |
102 |
| - wallet used to do the signup. This is done by generating a challenge string (the hex-encoded client |
103 |
| - ID and the current UNIX epoch time), signing this message with your wallet, and passing it to our API. |
104 |
| - We validate the signature of the challenge message against the wallet PKH provided at signup, and |
105 |
| - respond with a pre-signed S3 URL to fetch the client config. |
106 |
| - </p> |
| 129 | + <section id="validation" className="mb-16"> |
| 130 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 131 | + <div className="flex items-center mb-6"> |
| 132 | + <div className="w-12 h-12 bg-transparent border-2 border-white rounded-full flex items-center justify-center text-white font-bold text-lg mr-4"> |
| 133 | + 2 |
| 134 | + </div> |
| 135 | + <h2 className="text-2xl font-semibold text-white">Smart Contract Validation</h2> |
| 136 | + </div> |
| 137 | + <p className="text-gray-300 leading-relaxed"> |
| 138 | + The signup transaction will be validated by a smart contract, checking that it conforms to |
| 139 | + available regions and plans, the datum matches the expected shape, all funds are the correct amounts |
| 140 | + and going to the correct places, and other various sanity checks. |
| 141 | + </p> |
| 142 | + </div> |
| 143 | + </section> |
107 | 144 |
|
108 |
| - <p className="mb-6"> |
109 |
| - The downloaded VPN client config can be loaded into the OpenVPN client of your choice. The user's client |
110 |
| - TLS certificate will be validated against our CA certificate when authenticating to the VPN |
111 |
| - server. |
112 |
| - </p> |
| 145 | + <section id="indexer" className="mb-16"> |
| 146 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 147 | + <div className="flex items-center mb-6"> |
| 148 | + <div className="w-12 h-12 bg-transparent border-2 border-white rounded-full flex items-center justify-center text-white font-bold text-lg mr-4"> |
| 149 | + 3 |
| 150 | + </div> |
| 151 | + <h2 className="text-2xl font-semibold text-white">Indexer Processing</h2> |
| 152 | + </div> |
| 153 | + <p className="text-gray-300 leading-relaxed"> |
| 154 | + Once the signup TX makes it into a block and on-chain, it will get picked up by our{' '} |
| 155 | + <a |
| 156 | + href="https://github.com/blinklabs-io/vpn-indexer" |
| 157 | + className="text-blue-400 hover:text-blue-300 underline" |
| 158 | + target="_blank" |
| 159 | + rel="noopener noreferrer" |
| 160 | + > |
| 161 | + custom indexer |
| 162 | + </a>. |
| 163 | + The client datum will be extracted and its information written to a SQLite database. A new client TLS |
| 164 | + certificate is generated and signed by our CA certificate, and a new VPN client config built and |
| 165 | + uploaded to a private S3 bucket. |
| 166 | + </p> |
| 167 | + </div> |
| 168 | + </section> |
113 | 169 |
|
114 |
| - </div> |
| 170 | + <section id="authentication" className="mb-16"> |
| 171 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 172 | + <div className="flex items-center mb-6"> |
| 173 | + <div className="w-12 h-12 bg-transparent border-2 border-white rounded-full flex items-center justify-center text-white font-bold text-lg mr-4"> |
| 174 | + 4 |
| 175 | + </div> |
| 176 | + <h2 className="text-2xl font-semibold text-white">Profile Download Authentication</h2> |
| 177 | + </div> |
| 178 | + <p className="text-gray-300 leading-relaxed"> |
| 179 | + Once a profile has been uploaded to S3, our API will allow fetching it by validating ownership of the |
| 180 | + wallet used to do the signup. This is done by generating a challenge string (the hex-encoded client |
| 181 | + ID and the current UNIX epoch time), signing this message with your wallet, and passing it to our API. |
| 182 | + We validate the signature of the challenge message against the wallet PKH provided at signup, and |
| 183 | + respond with a pre-signed S3 URL to fetch the client config. |
| 184 | + </p> |
| 185 | + </div> |
| 186 | + </section> |
115 | 187 |
|
116 |
| - </div> |
| 188 | + <section id="setup" className="mb-16"> |
| 189 | + <div className="bg-[#00000020] rounded-lg p-8 border border-[#ffffff10]"> |
| 190 | + <div className="flex items-center mb-6"> |
| 191 | + <div className="w-12 h-12 bg-transparent border-2 border-white rounded-full flex items-center justify-center text-white font-bold text-lg mr-4"> |
| 192 | + 5 |
| 193 | + </div> |
| 194 | + <h2 className="text-2xl font-semibold text-white">VPN Client Setup</h2> |
| 195 | + </div> |
| 196 | + <p className="text-gray-300 leading-relaxed"> |
| 197 | + The downloaded VPN client config can be loaded into the OpenVPN client of your choice. The user's client |
| 198 | + TLS certificate will be validated against our CA certificate when authenticating to the VPN |
| 199 | + server. |
| 200 | + </p> |
| 201 | + </div> |
| 202 | + </section> |
117 | 203 |
|
118 |
| - <div className="text-center"> |
119 |
| - <Link |
120 |
| - to="/" |
121 |
| - className="inline-flex items-center px-6 py-3 bg-white text-[#1C246E] font-semibold rounded-lg hover:bg-gray-100 transition-colors duration-200" |
122 |
| - > |
123 |
| - Lorem Ipsum |
124 |
| - </Link> |
| 204 | + <div className="text-center mt-16"> |
| 205 | + <Link |
| 206 | + to="/" |
| 207 | + className="inline-flex items-center px-8 py-4 text-white border border-white/20 backdrop-blur-sm font-semibold rounded-xl shadow-lg hover:bg-gray-800 transition-colors" |
| 208 | + > |
| 209 | + <span className="mr-2">Back Home</span> |
| 210 | + <svg |
| 211 | + className="w-5 h-5" |
| 212 | + fill="none" |
| 213 | + stroke="currentColor" |
| 214 | + viewBox="0 0 24 24" |
| 215 | + > |
| 216 | + <path |
| 217 | + strokeLinecap="round" |
| 218 | + strokeLinejoin="round" |
| 219 | + strokeWidth={2} |
| 220 | + d="M13 7l5 5m0 0l-5 5m5-5H6" |
| 221 | + /> |
| 222 | + </svg> |
| 223 | + </Link> |
| 224 | + </div> |
125 | 225 | </div>
|
126 | 226 | </div>
|
127 | 227 | </div>
|
|
0 commit comments