Skip to content

Commit 447ad1a

Browse files
committed
fix: update statistics values for GitHub stars, Docker downloads, and contributors
1 parent c24309e commit 447ad1a

File tree

11 files changed

+776
-7
lines changed

11 files changed

+776
-7
lines changed
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { useTranslations } from "next-intl";
5+
import { Container } from "@/components/Container";
6+
import { Button } from "@/components/ui/button";
7+
import { Input } from "@/components/ui/input";
8+
import {
9+
Select,
10+
SelectContent,
11+
SelectItem,
12+
SelectTrigger,
13+
SelectValue,
14+
} from "@/components/ui/select";
15+
import { trackGAEvent } from "@/components/analitycs";
16+
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
17+
import { cn } from "@/lib/utils";
18+
19+
interface ContactFormData {
20+
inquiryType: "" | "support" | "sales" | "other";
21+
name: string;
22+
email: string;
23+
company: string;
24+
message: string;
25+
}
26+
27+
export default function ContactPage() {
28+
const t = useTranslations("Contact");
29+
const [isSubmitting, setIsSubmitting] = useState(false);
30+
const [isSubmitted, setIsSubmitted] = useState(false);
31+
const [formData, setFormData] = useState<ContactFormData>({
32+
inquiryType: "",
33+
name: "",
34+
email: "",
35+
company: "",
36+
message: "",
37+
});
38+
const [errors, setErrors] = useState<Record<string, string>>({});
39+
40+
const validateForm = (): boolean => {
41+
const newErrors: Record<string, string> = {};
42+
43+
if (!formData.inquiryType) {
44+
newErrors.inquiryType = t("errors.inquiryTypeRequired");
45+
}
46+
if (!formData.name.trim()) {
47+
newErrors.name = t("errors.nameRequired");
48+
}
49+
if (!formData.email.trim()) {
50+
newErrors.email = t("errors.emailRequired");
51+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
52+
newErrors.email = t("errors.emailInvalid");
53+
}
54+
if (!formData.company.trim()) {
55+
newErrors.company = t("errors.companyRequired");
56+
}
57+
if (!formData.message.trim()) {
58+
newErrors.message = t("errors.messageRequired");
59+
}
60+
61+
setErrors(newErrors);
62+
return Object.keys(newErrors).length === 0;
63+
};
64+
65+
const handleSubmit = async (e: React.FormEvent) => {
66+
e.preventDefault();
67+
68+
if (!validateForm()) {
69+
return;
70+
}
71+
72+
setIsSubmitting(true);
73+
74+
try {
75+
const response = await fetch("/api/contact", {
76+
method: "POST",
77+
headers: {
78+
"Content-Type": "application/json",
79+
},
80+
body: JSON.stringify(formData),
81+
});
82+
83+
if (response.ok) {
84+
// Track successful form submission
85+
trackGAEvent({
86+
action: "Contact Form Submitted",
87+
category: "Contact",
88+
label: formData.inquiryType,
89+
});
90+
91+
// Reset form and show success
92+
setFormData({
93+
inquiryType: "",
94+
name: "",
95+
email: "",
96+
company: "",
97+
message: "",
98+
});
99+
setErrors({});
100+
setIsSubmitted(true);
101+
} else {
102+
throw new Error("Failed to submit form");
103+
}
104+
} catch (error) {
105+
console.error("Error submitting form:", error);
106+
alert(t("errorMessage"));
107+
} finally {
108+
setIsSubmitting(false);
109+
}
110+
};
111+
112+
const handleInputChange = (field: keyof ContactFormData, value: any) => {
113+
setFormData((prev) => ({ ...prev, [field]: value }));
114+
// Clear error when user starts typing
115+
if (errors[field]) {
116+
setErrors((prev) => {
117+
const newErrors = { ...prev };
118+
delete newErrors[field];
119+
return newErrors;
120+
});
121+
}
122+
};
123+
124+
if (isSubmitted) {
125+
return (
126+
<div className="bg-background py-24 sm:py-32">
127+
<Container>
128+
<div className="mx-auto max-w-2xl text-center">
129+
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
130+
{t("successTitle")}
131+
</h1>
132+
<p className="mt-6 text-lg leading-8 text-muted-foreground">
133+
{t("successMessage")}
134+
</p>
135+
<div className="mt-10">
136+
<Button onClick={() => setIsSubmitted(false)} variant="outline">
137+
{t("buttons.sendAnother")}
138+
</Button>
139+
</div>
140+
</div>
141+
</Container>
142+
</div>
143+
);
144+
}
145+
146+
return (
147+
<div className="bg-background py-24 sm:py-32 relative">
148+
<AnimatedGridPattern
149+
numSquares={30}
150+
maxOpacity={0.1}
151+
height={40}
152+
width={40}
153+
duration={3}
154+
repeatDelay={1}
155+
className={cn(
156+
"[mask-image:radial-gradient(800px_circle_at_center,white,transparent)]",
157+
"absolute inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",
158+
)}
159+
/>
160+
<Container>
161+
<div className="mx-auto max-w-3xl border border-border rounded-lg p-8 bg-black z-10 relative">
162+
<div className="text-center">
163+
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
164+
{t("title")}
165+
</h1>
166+
<p className="mt-6 text-lg leading-8 text-muted-foreground">
167+
{t("description")}
168+
</p>
169+
</div>
170+
171+
<form onSubmit={handleSubmit} className="mt-16 space-y-6">
172+
<div className="space-y-2">
173+
<label
174+
htmlFor="inquiryType"
175+
className="block text-sm font-medium text-foreground"
176+
>
177+
{t("fields.inquiryType.label")}{" "}
178+
<span className="text-red-500">*</span>
179+
</label>
180+
<Select
181+
value={formData.inquiryType}
182+
onValueChange={(value) =>
183+
handleInputChange(
184+
"inquiryType",
185+
value as "support" | "sales" | "other",
186+
)
187+
}
188+
>
189+
<SelectTrigger className="bg-input">
190+
<SelectValue
191+
placeholder={t("fields.inquiryType.placeholder")}
192+
/>
193+
</SelectTrigger>
194+
<SelectContent>
195+
<SelectItem value="support">
196+
{t("fields.inquiryType.options.support")}
197+
</SelectItem>
198+
<SelectItem value="sales">
199+
{t("fields.inquiryType.options.sales")}
200+
</SelectItem>
201+
<SelectItem value="other">
202+
{t("fields.inquiryType.options.other")}
203+
</SelectItem>
204+
</SelectContent>
205+
</Select>
206+
{errors.inquiryType && (
207+
<p className="text-sm text-red-600">{errors.inquiryType}</p>
208+
)}
209+
</div>
210+
211+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
212+
<div className="space-y-2">
213+
<label
214+
htmlFor="name"
215+
className="block text-sm font-medium text-foreground"
216+
>
217+
{t("fields.name.label")}{" "}
218+
<span className="text-red-500">*</span>
219+
</label>
220+
<Input
221+
id="name"
222+
type="text"
223+
value={formData.name}
224+
onChange={(e) => handleInputChange("name", e.target.value)}
225+
placeholder={t("fields.name.placeholder")}
226+
/>
227+
{errors.name && (
228+
<p className="text-sm text-red-600">{errors.name}</p>
229+
)}
230+
</div>
231+
232+
<div className="space-y-2">
233+
<label
234+
htmlFor="email"
235+
className="block text-sm font-medium text-foreground"
236+
>
237+
{t("fields.email.label")}{" "}
238+
<span className="text-red-500">*</span>
239+
</label>
240+
<Input
241+
id="email"
242+
type="email"
243+
value={formData.email}
244+
onChange={(e) => handleInputChange("email", e.target.value)}
245+
placeholder={t("fields.email.placeholder")}
246+
/>
247+
{errors.email && (
248+
<p className="text-sm text-red-600">{errors.email}</p>
249+
)}
250+
</div>
251+
</div>
252+
253+
<div className="space-y-2">
254+
<label
255+
htmlFor="company"
256+
className="block text-sm font-medium text-foreground"
257+
>
258+
{t("fields.company.label")}{" "}
259+
<span className="text-red-500">*</span>
260+
</label>
261+
<Input
262+
id="company"
263+
type="text"
264+
value={formData.company}
265+
onChange={(e) => handleInputChange("company", e.target.value)}
266+
placeholder={t("fields.company.placeholder")}
267+
/>
268+
{errors.company && (
269+
<p className="text-sm text-red-600">{errors.company}</p>
270+
)}
271+
</div>
272+
273+
<div className="space-y-2">
274+
<label
275+
htmlFor="message"
276+
className="block text-sm font-medium text-foreground"
277+
>
278+
{t("fields.message.label")}{" "}
279+
<span className="text-red-500">*</span>
280+
</label>
281+
<textarea
282+
id="message"
283+
value={formData.message}
284+
onChange={(e) => handleInputChange("message", e.target.value)}
285+
placeholder={t("fields.message.placeholder")}
286+
rows={6}
287+
className="flex w-full rounded-md bg-input border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none"
288+
/>
289+
{errors.message && (
290+
<p className="text-sm text-red-600">{errors.message}</p>
291+
)}
292+
</div>
293+
294+
<div className="flex justify-end">
295+
<Button
296+
type="submit"
297+
disabled={isSubmitting}
298+
className="min-w-[120px]"
299+
>
300+
{isSubmitting ? t("buttons.sending") : t("buttons.send")}
301+
</Button>
302+
</div>
303+
</form>
304+
</div>
305+
</Container>
306+
</div>
307+
);
308+
}

0 commit comments

Comments
 (0)