11"use client" ;
22
33import { Container } from "@/components/Container" ;
4- import { trackGAEvent } from "@/components/analitycs " ;
4+ import { ContactForm } from "@/components/ContactForm " ;
55import AnimatedGridPattern from "@/components/ui/animated-grid-pattern" ;
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" ;
156import { cn } from "@/lib/utils" ;
167import { useState } from "react" ;
178
18- interface ContactFormData {
19- inquiryType : "" | "support" | "sales" | "other" ;
20- deploymentType : "" | "cloud" | "self-hosted" ;
21- firstName : string ;
22- lastName : string ;
23- email : string ;
24- company : string ;
25- message : string ;
26- }
27-
289export default function ContactPage ( ) {
29- const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
3010 const [ isSubmitted , setIsSubmitted ] = useState ( false ) ;
31- const [ formData , setFormData ] = useState < ContactFormData > ( {
32- inquiryType : "" ,
33- deploymentType : "" ,
34- firstName : "" ,
35- lastName : "" ,
36- email : "" ,
37- company : "" ,
38- message : "" ,
39- } ) ;
40- const [ errors , setErrors ] = useState < Record < string , string > > ( { } ) ;
41-
42- const validateForm = ( ) : boolean => {
43- const newErrors : Record < string , string > = { } ;
44-
45- if ( ! formData . inquiryType ) {
46- newErrors . inquiryType = "Please select what we can help you with" ;
47- }
48- if ( formData . inquiryType === "support" && ! formData . deploymentType ) {
49- newErrors . deploymentType = "Please select your deployment type" ;
50- }
51- if ( ! formData . firstName . trim ( ) ) {
52- newErrors . firstName = "First name is required" ;
53- }
54- if ( ! formData . lastName . trim ( ) ) {
55- newErrors . lastName = "Last name is required" ;
56- }
57- if ( ! formData . email . trim ( ) ) {
58- newErrors . email = "Email is required" ;
59- } else if ( ! / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( formData . email ) ) {
60- newErrors . email = "Please enter a valid email address" ;
61- }
62- if ( ! formData . company . trim ( ) ) {
63- newErrors . company = "Company name is required" ;
64- }
65- if ( ! formData . message . trim ( ) ) {
66- newErrors . message = "Message is required" ;
67- }
68-
69- setErrors ( newErrors ) ;
70- return Object . keys ( newErrors ) . length === 0 ;
71- } ;
72-
73- const handleSubmit = async ( e : React . FormEvent ) => {
74- e . preventDefault ( ) ;
75-
76- if ( ! validateForm ( ) ) {
77- return ;
78- }
79-
80- // Prevent submission for self-hosted support requests
81- if (
82- formData . inquiryType === "support" &&
83- formData . deploymentType === "self-hosted"
84- ) {
85- return ;
86- }
87-
88- setIsSubmitting ( true ) ;
89-
90- try {
91- const response = await fetch ( "/api/contact" , {
92- method : "POST" ,
93- headers : {
94- "Content-Type" : "application/json" ,
95- } ,
96- body : JSON . stringify ( formData ) ,
97- } ) ;
98-
99- if ( response . ok ) {
100- trackGAEvent ( {
101- action : "Contact Form Submitted" ,
102- category : "Contact" ,
103- label : formData . inquiryType ,
104- } ) ;
105-
106- setFormData ( {
107- inquiryType : "" ,
108- deploymentType : "" ,
109- firstName : "" ,
110- lastName : "" ,
111- email : "" ,
112- company : "" ,
113- message : "" ,
114- } ) ;
115- setErrors ( { } ) ;
116- setIsSubmitted ( true ) ;
117- } else {
118- throw new Error ( "Failed to submit form" ) ;
119- }
120- } catch ( error ) {
121- console . error ( "Error submitting form:" , error ) ;
122- alert ( "There was an error sending your message. Please try again." ) ;
123- } finally {
124- setIsSubmitting ( false ) ;
125- }
126- } ;
127-
128- const handleInputChange = ( field : keyof ContactFormData , value : any ) => {
129- setFormData ( ( prev ) => {
130- const updated = { ...prev , [ field ] : value } ;
131- // Reset deploymentType when inquiryType changes and is not support
132- if ( field === "inquiryType" && value !== "support" ) {
133- updated . deploymentType = "" ;
134- }
135- return updated ;
136- } ) ;
137- if ( errors [ field ] ) {
138- setErrors ( ( prev ) => {
139- const newErrors = { ...prev } ;
140- delete newErrors [ field ] ;
141- return newErrors ;
142- } ) ;
143- }
144- } ;
14511
14612 if ( isSubmitted ) {
14713 return (
@@ -156,9 +22,13 @@ export default function ContactPage() {
15622 possible.
15723 </ p >
15824 < div className = "mt-10" >
159- < Button onClick = { ( ) => setIsSubmitted ( false ) } variant = "outline" >
25+ < button
26+ type = "button"
27+ onClick = { ( ) => setIsSubmitted ( false ) }
28+ className = "rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent hover:text-accent-foreground"
29+ >
16030 Send Another Message
161- </ Button >
31+ </ button >
16232 </ div >
16333 </ div >
16434 </ Container >
@@ -192,231 +62,9 @@ export default function ContactPage() {
19262 </ p >
19363 </ div >
19464
195- < form onSubmit = { handleSubmit } className = "mt-16 space-y-6" >
196- < div className = "space-y-2" >
197- < label
198- htmlFor = "inquiryType"
199- className = "block text-sm font-medium text-foreground"
200- >
201- What can we help you with today?{ " " }
202- < span className = "text-red-500" > *</ span >
203- </ label >
204- < Select
205- value = { formData . inquiryType }
206- onValueChange = { ( value ) =>
207- handleInputChange (
208- "inquiryType" ,
209- value as "support" | "sales" | "other" ,
210- )
211- }
212- >
213- < SelectTrigger className = "bg-input" >
214- < SelectValue placeholder = "Select an option" />
215- </ SelectTrigger >
216- < SelectContent >
217- < SelectItem value = "support" > Support</ SelectItem >
218- < SelectItem value = "sales" > Sales</ SelectItem >
219- < SelectItem value = "other" > Other</ SelectItem >
220- </ SelectContent >
221- </ Select >
222- { errors . inquiryType && (
223- < p className = "text-sm text-red-600" > { errors . inquiryType } </ p >
224- ) }
225- </ div >
226-
227- { formData . inquiryType === "support" && (
228- < div className = "space-y-2" >
229- < label
230- htmlFor = "deploymentType"
231- className = "block text-sm font-medium text-foreground"
232- >
233- What version of Dokploy are you using?{ " " }
234- < span className = "text-red-500" > *</ span >
235- </ label >
236- < Select
237- value = { formData . deploymentType }
238- onValueChange = { ( value ) =>
239- handleInputChange (
240- "deploymentType" ,
241- value as "cloud" | "self-hosted" ,
242- )
243- }
244- >
245- < SelectTrigger className = "bg-input" >
246- < SelectValue placeholder = "Select deployment type" />
247- </ SelectTrigger >
248- < SelectContent >
249- < SelectItem value = "cloud" > Cloud Version</ SelectItem >
250- < SelectItem value = "self-hosted" > Self Hosted</ SelectItem >
251- </ SelectContent >
252- </ Select >
253- { errors . deploymentType && (
254- < p className = "text-sm text-red-600" >
255- { errors . deploymentType }
256- </ p >
257- ) }
258-
259- { formData . deploymentType === "self-hosted" && (
260- < div className = "mt-4 rounded-lg border border-amber-500/50 bg-amber-500/10 p-4" >
261- < h3 className = "mb-2 text-sm font-semibold text-amber-500" >
262- Self-Hosted Support
263- </ h3 >
264- < p className = "mb-3 text-sm text-muted-foreground" >
265- We currently don't provide direct support for self-hosted
266- deployments through this form. However, our community is
267- here to help!
268- </ p >
269- < div className = "space-y-2 text-sm" >
270- < p className = "text-muted-foreground" >
271- Please use one of these channels to get assistance:
272- </ p >
273- < ul className = "ml-4 list-disc space-y-1 text-muted-foreground" >
274- < li >
275- Join our{ " " }
276- < a
277- href = "https://discord.gg/2tBnJ3jDJc"
278- target = "_blank"
279- rel = "noopener noreferrer"
280- className = "text-primary underline hover:text-primary/80"
281- >
282- Discord community
283- </ a > { " " }
284- for real-time help
285- </ li >
286- < li >
287- Open a discussion on{ " " }
288- < a
289- href = "https://github.com/Dokploy/dokploy/discussions"
290- target = "_blank"
291- rel = "noopener noreferrer"
292- className = "text-primary underline hover:text-primary/80"
293- >
294- GitHub Discussions
295- </ a >
296- </ li >
297- </ ul >
298- </ div >
299- </ div >
300- ) }
301- </ div >
302- ) }
303-
304- < div className = "grid grid-cols-1 gap-6 sm:grid-cols-2" >
305- < div className = "space-y-2" >
306- < label
307- htmlFor = "firstName"
308- className = "block text-sm font-medium text-foreground"
309- >
310- First Name < span className = "text-red-500" > *</ span >
311- </ label >
312- < Input
313- id = "firstName"
314- type = "text"
315- value = { formData . firstName }
316- onChange = { ( e ) =>
317- handleInputChange ( "firstName" , e . target . value )
318- }
319- placeholder = "Your first name"
320- />
321- { errors . firstName && (
322- < p className = "text-sm text-red-600" > { errors . firstName } </ p >
323- ) }
324- </ div >
325-
326- < div className = "space-y-2" >
327- < label
328- htmlFor = "lastName"
329- className = "block text-sm font-medium text-foreground"
330- >
331- Last Name < span className = "text-red-500" > *</ span >
332- </ label >
333- < Input
334- id = "lastName"
335- type = "text"
336- value = { formData . lastName }
337- onChange = { ( e ) =>
338- handleInputChange ( "lastName" , e . target . value )
339- }
340- placeholder = "Your last name"
341- />
342- { errors . lastName && (
343- < p className = "text-sm text-red-600" > { errors . lastName } </ p >
344- ) }
345- </ div >
346- </ div >
347-
348- < div className = "space-y-2" >
349- < label
350- htmlFor = "email"
351- className = "block text-sm font-medium text-foreground"
352- >
353- Email < span className = "text-red-500" > *</ span >
354- </ label >
355- < Input
356- id = "email"
357- type = "email"
358- value = { formData . email }
359- onChange = { ( e ) => handleInputChange ( "email" , e . target . value ) }
360- 361- />
362- { errors . email && (
363- < p className = "text-sm text-red-600" > { errors . email } </ p >
364- ) }
365- </ div >
366-
367- < div className = "space-y-2" >
368- < label
369- htmlFor = "company"
370- className = "block text-sm font-medium text-foreground"
371- >
372- Company Name < span className = "text-red-500" > *</ span >
373- </ label >
374- < Input
375- id = "company"
376- type = "text"
377- value = { formData . company }
378- onChange = { ( e ) => handleInputChange ( "company" , e . target . value ) }
379- placeholder = "Your company name"
380- />
381- { errors . company && (
382- < p className = "text-sm text-red-600" > { errors . company } </ p >
383- ) }
384- </ div >
385-
386- < div className = "space-y-2" >
387- < label
388- htmlFor = "message"
389- className = "block text-sm font-medium text-foreground"
390- >
391- How can we help? < span className = "text-red-500" > *</ span >
392- </ label >
393- < textarea
394- id = "message"
395- value = { formData . message }
396- onChange = { ( e ) => handleInputChange ( "message" , e . target . value ) }
397- placeholder = "Tell us more about your inquiry..."
398- rows = { 6 }
399- className = "flex w-full resize-none rounded-md border border-input bg-background bg-input 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"
400- />
401- { errors . message && (
402- < p className = "text-sm text-red-600" > { errors . message } </ p >
403- ) }
404- </ div >
405-
406- < div className = "flex justify-end" >
407- < Button
408- type = "submit"
409- disabled = {
410- isSubmitting ||
411- ( formData . inquiryType === "support" &&
412- formData . deploymentType === "self-hosted" )
413- }
414- className = "min-w-[120px]"
415- >
416- { isSubmitting ? "Sending..." : "Send Message" }
417- </ Button >
418- </ div >
419- </ form >
65+ < div className = "mt-16" >
66+ < ContactForm onSuccess = { ( ) => setIsSubmitted ( true ) } />
67+ </ div >
42068 </ div >
42169 </ Container >
42270 </ div >
0 commit comments