Skip to content

Commit 5018725

Browse files
committed
feat: add contact form with hCaptcha and update navigation
- Add contact form component with web3forms integration - Add hCaptcha spam protection to contact form - Create standalone contact page at /contact - Replace Subscribe component with ContactForm on homepage - Add UI components: Input, Textarea, Select - Add Demo link to navigation header - Replace FreeChatButton with ContactUsButton component - Update button styles and placements in hero section
1 parent c198c8a commit 5018725

File tree

13 files changed

+462
-15
lines changed

13 files changed

+462
-15
lines changed

app/contact/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import ContactForm from '@/components/business/contact-form'
2+
import type { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: 'Contact Us | RustFS - High-Performance Distributed Storage System Built with Rust',
6+
description: 'Get in touch with the RustFS team. Contact us for questions, support, or partnership opportunities.',
7+
openGraph: {
8+
title: 'Contact Us | RustFS - High-Performance Distributed Storage System Built with Rust',
9+
description: 'Get in touch with the RustFS team. Contact us for questions, support, or partnership opportunities.',
10+
type: "website",
11+
locale: 'en_US',
12+
},
13+
}
14+
15+
export default function ContactPage() {
16+
return (
17+
<main className="flex-1 relative">
18+
<div className="relative z-10">
19+
<ContactForm />
20+
</div>
21+
</main>
22+
)
23+
}

app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import HomeHero from "@/components/business/home-hero";
66
import HomeMultiClouds from "@/components/business/home-multi-clouds";
77
import HomeStats from "@/components/business/home-stats";
88
import SoftwareLogos from "@/components/business/software-logos";
9-
import Subscribe from "@/components/business/subscribe";
9+
import ContactForm from "@/components/business/contact-form";
1010
import { getDockerPulls } from "@/lib/docker";
1111
import { getGitHubMetrics } from "@/lib/github";
1212
import type { Metadata } from "next";
@@ -47,7 +47,7 @@ export default async function HomePage() {
4747
{/* <HomeReviews /> */}
4848
<GetStartedToday />
4949
<HomeBlog />
50-
<Subscribe />
50+
<ContactForm />
5151
</div>
5252
</main>
5353
);

components/business/app-header.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export default function AppHeader() {
4141
url: `/download`,
4242
classes: '',
4343
},
44+
{
45+
label: 'Demo',
46+
url: 'https://play.rustfs.com',
47+
classes: '',
48+
},
4449
{
4550
label: 'Docs',
4651
url: 'https://docs.rustfs.com/installation/',

components/business/buttons/free-chat.tsx renamed to components/business/buttons/contact-us.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
import { cn } from "@/lib/utils";
44
import { ArrowRightIcon } from "lucide-react";
5+
import Link from "next/link";
56

6-
export default function FreeChatButton({ className }: {
7+
export default function ContactUsButton({ className }: {
78
className?: string | string[]
89
}) {return (
9-
<a
10+
<Link
1011
className={cn("group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground/90 active:bg-primary/80 active:text-primary-foreground/80 focus-visible:outline-primary", className)}
11-
target="_blank"
12-
href="https://tb.53kf.com/code/client/3ae82624ac86c32c1db8d311cd6d2a659/2"
12+
href="/contact"
1313
>
14-
<span className="mr-2">{'Free Consultation'}</span>
14+
<span className="mr-2">{'Contact Us'}</span>
1515
<ArrowRightIcon className="h-3 w-3 flex-none" strokeWidth={2.5} />
16-
</a>
16+
</Link>
1717
)
1818
}
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
'use client'
2+
3+
import { Button } from '@/components/ui/button'
4+
import { Input } from '@/components/ui/input'
5+
import { Select } from '@/components/ui/select'
6+
import { Textarea } from '@/components/ui/textarea'
7+
import HCaptcha from '@hcaptcha/react-hcaptcha'
8+
import { useRef, useState } from 'react'
9+
10+
const COUNTRIES = [
11+
'United States',
12+
'United Kingdom',
13+
'Canada',
14+
'Australia',
15+
'Germany',
16+
'France',
17+
'Italy',
18+
'Spain',
19+
'Netherlands',
20+
'Belgium',
21+
'Switzerland',
22+
'Austria',
23+
'Sweden',
24+
'Norway',
25+
'Denmark',
26+
'Finland',
27+
'Poland',
28+
'Portugal',
29+
'Ireland',
30+
'Japan',
31+
'South Korea',
32+
'Singapore',
33+
'Hong Kong',
34+
'China',
35+
'India',
36+
'Brazil',
37+
'Mexico',
38+
'Argentina',
39+
'Chile',
40+
'South Africa',
41+
'United Arab Emirates',
42+
'Saudi Arabia',
43+
'Israel',
44+
'Turkey',
45+
'Russia',
46+
'Other'
47+
]
48+
49+
export default function ContactForm() {
50+
const [isSubmitting, setIsSubmitting] = useState(false)
51+
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle')
52+
const [hCaptchaToken, setHCaptchaToken] = useState<string | null>(null)
53+
const captchaRef = useRef<HCaptcha>(null)
54+
const [formData, setFormData] = useState({
55+
firstName: '',
56+
lastName: '',
57+
email: '',
58+
phone: '',
59+
country: '',
60+
company: '',
61+
message: ''
62+
})
63+
64+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
65+
const { name, value } = e.target
66+
setFormData(prev => ({ ...prev, [name]: value }))
67+
}
68+
69+
const handleHCaptchaVerify = (token: string) => {
70+
setHCaptchaToken(token)
71+
}
72+
73+
const handleHCaptchaExpire = () => {
74+
setHCaptchaToken(null)
75+
}
76+
77+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
78+
e.preventDefault()
79+
80+
if (!hCaptchaToken) {
81+
setSubmitStatus('error')
82+
alert('Please complete the captcha verification')
83+
return
84+
}
85+
86+
setIsSubmitting(true)
87+
setSubmitStatus('idle')
88+
89+
try {
90+
const response = await fetch('https://api.web3forms.com/submit', {
91+
method: 'POST',
92+
headers: {
93+
'Content-Type': 'application/json',
94+
'Accept': 'application/json'
95+
},
96+
body: JSON.stringify({
97+
access_key: '6646b46d-5e9e-4cf4-99fd-701b15c8bf6e',
98+
name: `${formData.firstName} ${formData.lastName}`,
99+
email: formData.email,
100+
phone: formData.phone || undefined,
101+
country: formData.country,
102+
company: formData.company,
103+
message: formData.message,
104+
'h-captcha-response': hCaptchaToken,
105+
from_name: 'RustFS Contact Form'
106+
})
107+
})
108+
109+
const result = await response.json()
110+
111+
if (result.success) {
112+
setSubmitStatus('success')
113+
setFormData({
114+
firstName: '',
115+
lastName: '',
116+
email: '',
117+
phone: '',
118+
country: '',
119+
company: '',
120+
message: ''
121+
})
122+
setHCaptchaToken(null)
123+
captchaRef.current?.resetCaptcha()
124+
} else {
125+
setSubmitStatus('error')
126+
}
127+
} catch {
128+
setSubmitStatus('error')
129+
} finally {
130+
setIsSubmitting(false)
131+
}
132+
}
133+
134+
return (
135+
<section
136+
id='contact'
137+
className="relative overflow-hidden bg-background py-32 text-white"
138+
>
139+
<div className="mx-auto flex max-w-340 flex-col justify-center px-4 sm:px-6 lg:px-8">
140+
<div className="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
141+
<h2 className="text-2xl font-bold md:text-4xl md:leading-tight text-primary">
142+
Contact Us
143+
</h2>
144+
<p className="mt-4 text-muted-foreground">
145+
Get in touch with our team. We&apos;d love to hear from you.
146+
</p>
147+
</div>
148+
149+
<div className="mx-auto w-full max-w-2xl">
150+
<form onSubmit={handleSubmit} className="space-y-6">
151+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
152+
<div>
153+
<label htmlFor="firstName" className="mb-2 block text-sm font-medium text-foreground">
154+
First Name <span className="text-destructive">*</span>
155+
</label>
156+
<Input
157+
id="firstName"
158+
name="firstName"
159+
type="text"
160+
required
161+
placeholder="Enter your first name"
162+
value={formData.firstName}
163+
onChange={handleChange}
164+
className="w-full"
165+
/>
166+
</div>
167+
<div>
168+
<label htmlFor="lastName" className="mb-2 block text-sm font-medium text-foreground">
169+
Last Name <span className="text-destructive">*</span>
170+
</label>
171+
<Input
172+
id="lastName"
173+
name="lastName"
174+
type="text"
175+
required
176+
placeholder="Enter your last name"
177+
value={formData.lastName}
178+
onChange={handleChange}
179+
className="w-full"
180+
/>
181+
</div>
182+
</div>
183+
184+
<div>
185+
<label htmlFor="email" className="mb-2 block text-sm font-medium text-foreground">
186+
Business Email <span className="text-destructive">*</span>
187+
</label>
188+
<Input
189+
id="email"
190+
name="email"
191+
type="email"
192+
required
193+
placeholder="your.email@company.com"
194+
value={formData.email}
195+
onChange={handleChange}
196+
className="w-full"
197+
/>
198+
</div>
199+
200+
<div>
201+
<label htmlFor="phone" className="mb-2 block text-sm font-medium text-foreground">
202+
Business Phone <span className="text-muted-foreground text-sm">(Optional)</span>
203+
</label>
204+
<Input
205+
id="phone"
206+
name="phone"
207+
type="tel"
208+
placeholder="e.g., +1 (555) 123-4567"
209+
value={formData.phone}
210+
onChange={handleChange}
211+
className="w-full"
212+
/>
213+
</div>
214+
215+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
216+
<div>
217+
<label htmlFor="country" className="mb-2 block text-sm font-medium text-foreground">
218+
Country <span className="text-destructive">*</span>
219+
</label>
220+
<Select
221+
id="country"
222+
name="country"
223+
required
224+
value={formData.country}
225+
onChange={handleChange}
226+
className="w-full"
227+
>
228+
<option value="">Select a country</option>
229+
{COUNTRIES.map((country) => (
230+
<option key={country} value={country}>
231+
{country}
232+
</option>
233+
))}
234+
</Select>
235+
</div>
236+
<div>
237+
<label htmlFor="company" className="mb-2 block text-sm font-medium text-foreground">
238+
Company <span className="text-destructive">*</span>
239+
</label>
240+
<Input
241+
id="company"
242+
name="company"
243+
type="text"
244+
required
245+
placeholder="Enter your company name"
246+
value={formData.company}
247+
onChange={handleChange}
248+
className="w-full"
249+
/>
250+
</div>
251+
</div>
252+
253+
<div>
254+
<label htmlFor="message" className="mb-2 block text-sm font-medium text-foreground">
255+
Message <span className="text-destructive">*</span>
256+
</label>
257+
<Textarea
258+
id="message"
259+
name="message"
260+
required
261+
rows={6}
262+
placeholder="Please tell us about your inquiry, requirements, or how we can help you. Include any specific details about your use case, expected scale, or technical requirements."
263+
value={formData.message}
264+
onChange={handleChange}
265+
className="w-full"
266+
/>
267+
</div>
268+
269+
<div className="flex justify-center">
270+
<HCaptcha
271+
sitekey="50b2fe65-b00b-4b9e-ad62-3ba471098be2"
272+
onVerify={handleHCaptchaVerify}
273+
onExpire={handleHCaptchaExpire}
274+
ref={captchaRef}
275+
reCaptchaCompat={false}
276+
/>
277+
</div>
278+
279+
{submitStatus === 'success' && (
280+
<div className="rounded-md bg-green-50 p-4 dark:bg-green-900/20">
281+
<p className="text-sm font-medium text-green-800 dark:text-green-200">
282+
Thank you for your message! We&apos;ll get back to you soon.
283+
</p>
284+
</div>
285+
)}
286+
287+
{submitStatus === 'error' && (
288+
<div className="rounded-md bg-red-50 p-4 dark:bg-red-900/20">
289+
<p className="text-sm font-medium text-red-800 dark:text-red-200">
290+
Something went wrong. Please try again later.
291+
</p>
292+
</div>
293+
)}
294+
295+
<div className="flex justify-center">
296+
<Button
297+
type="submit"
298+
disabled={isSubmitting}
299+
size="lg"
300+
className="min-w-[200px]"
301+
>
302+
{isSubmitting ? 'Submitting...' : 'Submit'}
303+
</Button>
304+
</div>
305+
</form>
306+
</div>
307+
</div>
308+
</section>
309+
)
310+
}

0 commit comments

Comments
 (0)