@devvistatech/devvista-kit 0.0.10 → 0.0.12
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.
- package/CHANGELOG.md +12 -12
- package/LICENSE +6 -6
- package/README.md +15 -15
- package/app/about/page.tsx +298 -298
- package/app/adRequest/page.tsx +549 -549
- package/app/analytics/page.tsx +346 -346
- package/app/api/about/route.ts +306 -306
- package/app/api/adRequest/route.ts +567 -567
- package/app/api/analytics/[reportType]/route.ts +337 -337
- package/app/api/bio/route.ts +313 -313
- package/app/api/blog/route.ts +306 -306
- package/app/api/chat/route.ts +14 -14
- package/app/api/contact/route.ts +409 -409
- package/app/api/contacts/route.ts +224 -224
- package/app/api/files/route.ts +429 -429
- package/app/api/gallery-data/route.ts +735 -735
- package/app/api/schedule/route.ts +455 -455
- package/app/api/sync-user/route.ts +131 -131
- package/app/api/trial-request/route.ts +297 -297
- package/app/blog/[id]/page.tsx +288 -288
- package/app/blog/page.tsx +216 -216
- package/app/contact/page.tsx +284 -284
- package/app/faq/page.tsx +191 -191
- package/app/gallery/page.tsx +315 -315
- package/app/globals.css +58 -58
- package/app/layout.tsx +110 -110
- package/app/not-found.tsx +20 -20
- package/app/page.tsx +338 -338
- package/app/schedule/page.tsx +660 -660
- package/bin/init.js +219 -219
- package/components/addOns/functional/BioEditor.tsx +446 -446
- package/components/addOns/functional/CalendlyWidget.tsx +107 -107
- package/components/addOns/functional/ClassList.tsx +145 -145
- package/components/addOns/functional/ClassPopup.tsx +398 -398
- package/components/addOns/functional/ContactForm.tsx +284 -284
- package/components/addOns/functional/FileUploader.tsx +294 -294
- package/components/addOns/functional/ImageDescCarousel.tsx +730 -730
- package/components/addOns/functional/NewUserAnalytics.tsx +100 -100
- package/components/addOns/functional/ScheduleCarousel.tsx +171 -171
- package/components/addOns/functional/aboutSections/AboutSection.tsx +544 -544
- package/components/addOns/functional/aboutSections/constants/aboutSection.ts +65 -65
- package/components/addOns/functional/blogSections/BlogDashboard.tsx +184 -184
- package/components/addOns/functional/blogSections/BlogFormPopUp.tsx +554 -554
- package/components/addOns/functional/blogSections/BlogList.tsx +148 -148
- package/components/addOns/functional/blogSections/BlogSidebar.tsx +58 -58
- package/components/addOns/functional/blogSections/constants/blogDashboard.ts +28 -28
- package/components/addOns/functional/blogSections/constants/blogFormPopUp.ts +97 -97
- package/components/addOns/functional/blogSections/constants/blogList.ts +22 -22
- package/components/addOns/functional/blogSections/constants/blogSidebar.ts +15 -15
- package/components/addOns/functional/contactsDashboard/ContactsDashboard.tsx +366 -366
- package/components/addOns/functional/contactsDashboard/constants/contactsDashboard.ts +70 -70
- package/components/addOns/functional/galleries/GalleryComplex.tsx +836 -836
- package/components/addOns/functional/galleries/GallerySimple.tsx +509 -509
- package/components/addOns/functional/galleries/constants/galleryComplex.ts +106 -106
- package/components/addOns/functional/galleries/constants/gallerySimple.ts +76 -76
- package/components/addOns/functional/schedules/ScheduleGridOne.tsx +262 -262
- package/components/addOns/functional/schedules/ScheduleGridTwo.tsx +294 -294
- package/components/addOns/functional/schedules/ScheduleGridTwoBasic.tsx +288 -288
- package/components/addOns/functional/schedules/SchedulerForm.tsx +428 -428
- package/components/addOns/functional/schedules/constants/ScheduleGridTwo.ts +40 -40
- package/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.ts +40 -40
- package/components/addOns/functional/schedules/constants/SchedulerForm.ts +65 -65
- package/components/addOns/functional/schedules/constants/scheduleGridOne.ts +54 -54
- package/components/addOns/non-functional/AnnouncementBanner.tsx +46 -46
- package/components/addOns/non-functional/FeaturesSection.tsx +62 -62
- package/components/addOns/non-functional/Heros/HeroSection.tsx +142 -142
- package/components/addOns/non-functional/IconBubble.tsx +49 -49
- package/components/addOns/non-functional/SampleCarousel.tsx +204 -204
- package/components/addOns/non-functional/Testimonials.tsx +334 -334
- package/components/addOns/non-functional/ThreeSetGallery.tsx +63 -63
- package/components/addOns/non-functional/aboutSections/AboutSection.tsx +62 -62
- package/components/addOns/non-functional/aboutSections/constants/aboutSection.ts +24 -24
- package/components/addOns/non-functional/imageCarousels/ProductSlider.tsx +117 -117
- package/components/addOns/non-functional/imageCarousels/ProgramCarousel.tsx +232 -232
- package/components/addOns/non-functional/imageCarousels/constants/programCarousel.ts +39 -39
- package/components/addOns/non-functional/imageCarousels/constants/programSlider.ts +36 -36
- package/components/addOns/non-functional/spinner.tsx +21 -21
- package/components/footers/footer.tsx +453 -453
- package/components/navBars/navbar.tsx +310 -310
- package/components/other/accordion.tsx +58 -58
- package/components/other/admin-menu.tsx +68 -68
- package/components/other/alert-dialog.tsx +141 -141
- package/components/other/alert.tsx +59 -59
- package/components/other/aspect-ratio.tsx +7 -7
- package/components/other/avatar.tsx +50 -50
- package/components/other/badge.tsx +36 -36
- package/components/other/breadcrumb.tsx +115 -115
- package/components/other/button.tsx +738 -738
- package/components/other/calendar.tsx +66 -66
- package/components/other/card.tsx +86 -86
- package/components/other/carousel.tsx +274 -274
- package/components/other/chart.tsx +363 -363
- package/components/other/checkbox.tsx +30 -30
- package/components/other/collapsible.tsx +11 -11
- package/components/other/command.tsx +155 -155
- package/components/other/context-menu.tsx +200 -200
- package/components/other/dialog.tsx +122 -122
- package/components/other/drawer.tsx +118 -118
- package/components/other/dropdown-menu.tsx +200 -200
- package/components/other/form.tsx +179 -179
- package/components/other/hover-card.tsx +29 -29
- package/components/other/input-otp.tsx +71 -71
- package/components/other/input.tsx +25 -25
- package/components/other/label.tsx +26 -26
- package/components/other/menubar.tsx +236 -236
- package/components/other/mobile-icon.tsx +21 -21
- package/components/other/navigation-menu.tsx +128 -128
- package/components/other/pagination.tsx +117 -117
- package/components/other/popover.tsx +31 -31
- package/components/other/progress.tsx +28 -28
- package/components/other/radio-group.tsx +44 -44
- package/components/other/resizable.tsx +45 -45
- package/components/other/scroll-area.tsx +48 -48
- package/components/other/select.tsx +160 -160
- package/components/other/separator.tsx +31 -31
- package/components/other/sheet.tsx +140 -140
- package/components/other/skeleton.tsx +15 -15
- package/components/other/slider.tsx +28 -28
- package/components/other/social-icons.tsx +39 -39
- package/components/other/sonner.tsx +31 -31
- package/components/other/switch.tsx +29 -29
- package/components/other/table.tsx +117 -117
- package/components/other/tabs.tsx +55 -55
- package/components/other/textarea.tsx +24 -24
- package/components/other/toast.tsx +122 -122
- package/components/other/toaster.tsx +35 -35
- package/components/other/toggle-group.tsx +61 -61
- package/components/other/toggle.tsx +45 -45
- package/components/other/tooltip.tsx +30 -30
- package/components/theme-provider.tsx +8 -8
- package/components/types.ts +49 -49
- package/hooks/use-toast.ts +188 -188
- package/lib/auth-context.tsx +130 -130
- package/lib/constants/about.ts +34 -34
- package/lib/constants/adRequest.ts +113 -113
- package/lib/constants/contact.ts +40 -40
- package/lib/constants/faq.ts +34 -34
- package/lib/constants/gallery.ts +42 -42
- package/lib/constants/page.ts +69 -69
- package/lib/constants/schedule.ts +71 -71
- package/lib/google-analytics.tsx +97 -97
- package/lib/verify-user.ts +117 -117
- package/middleware.ts +42 -42
- package/netlify.toml +5 -5
- package/next.config.js +10 -10
- package/package.json +115 -115
- package/tailwind.config.ts +89 -89
- package/tsconfig.json +23 -23
- package/dist/.next/types/app/api/about/route.js +0 -52
- package/dist/.next/types/app/api/blog/route.js +0 -52
- package/dist/.next/types/app/api/files/route.js +0 -52
- package/dist/.next/types/app/api/schedule/route.js +0 -52
- package/dist/.next/types/app/api/sync-user/route.js +0 -52
- package/dist/.next/types/app/layout.js +0 -22
- package/dist/.next/types/app/page.js +0 -22
- package/dist/app/about/page.jsx +0 -258
- package/dist/app/adRequest/page.jsx +0 -531
- package/dist/app/analytics/page.jsx +0 -298
- package/dist/app/api/about/route.js +0 -285
- package/dist/app/api/adRequest/route.js +0 -440
- package/dist/app/api/analytics/[reportType]/route.js +0 -357
- package/dist/app/api/bio/route.js +0 -293
- package/dist/app/api/blog/route.js +0 -366
- package/dist/app/api/chat/route.js +0 -58
- package/dist/app/api/contact/route.js +0 -163
- package/dist/app/api/contacts/route.js +0 -234
- package/dist/app/api/files/route.js +0 -444
- package/dist/app/api/gallery-data/route.js +0 -719
- package/dist/app/api/schedule/route.js +0 -461
- package/dist/app/api/sync-user/route.js +0 -186
- package/dist/app/api/trial-request/route.js +0 -165
- package/dist/app/blog/[id]/page.jsx +0 -312
- package/dist/app/blog/page.jsx +0 -210
- package/dist/app/constants/about.js +0 -32
- package/dist/app/constants/adRequest.js +0 -113
- package/dist/app/constants/contact.js +0 -40
- package/dist/app/constants/faq.js +0 -36
- package/dist/app/constants/gallery.js +0 -42
- package/dist/app/constants/page.js +0 -69
- package/dist/app/constants/schedule.js +0 -71
- package/dist/app/contact/page.jsx +0 -119
- package/dist/app/faq/page.jsx +0 -97
- package/dist/app/gallery/page.jsx +0 -281
- package/dist/app/layout.jsx +0 -45
- package/dist/app/not-found.jsx +0 -14
- package/dist/app/page.jsx +0 -324
- package/dist/app/schedule/page.jsx +0 -500
- package/dist/components/addOns/functional/BioEditor.jsx +0 -187
- package/dist/components/addOns/functional/CalendlyWidget.jsx +0 -61
- package/dist/components/addOns/functional/ClassList.jsx +0 -158
- package/dist/components/addOns/functional/ClassPopup.jsx +0 -300
- package/dist/components/addOns/functional/ContactForm.jsx +0 -219
- package/dist/components/addOns/functional/FileUploader.jsx +0 -222
- package/dist/components/addOns/functional/ImageDescCarousel.jsx +0 -491
- package/dist/components/addOns/functional/NewUserAnalytics.jsx +0 -71
- package/dist/components/addOns/functional/ScheduleCarousel.jsx +0 -68
- package/dist/components/addOns/functional/aboutSections/AboutSection.jsx +0 -372
- package/dist/components/addOns/functional/aboutSections/constants/aboutSection.js +0 -65
- package/dist/components/addOns/functional/blogSections/BlogDashboard.jsx +0 -111
- package/dist/components/addOns/functional/blogSections/BlogFormPopUp.jsx +0 -465
- package/dist/components/addOns/functional/blogSections/BlogList.jsx +0 -170
- package/dist/components/addOns/functional/blogSections/BlogSidebar.jsx +0 -35
- package/dist/components/addOns/functional/blogSections/constants/blogDashboard.js +0 -28
- package/dist/components/addOns/functional/blogSections/constants/blogFormPopUp.js +0 -97
- package/dist/components/addOns/functional/blogSections/constants/blogList.js +0 -22
- package/dist/components/addOns/functional/blogSections/constants/blogSidebar.js +0 -15
- package/dist/components/addOns/functional/contactsDashboard/ContactsDashboard.jsx +0 -355
- package/dist/components/addOns/functional/contactsDashboard/constants/contactsDashboard.js +0 -70
- package/dist/components/addOns/functional/galleries/GalleryComplex.jsx +0 -605
- package/dist/components/addOns/functional/galleries/GallerySimple.jsx +0 -363
- package/dist/components/addOns/functional/galleries/constants/galleryComplex.js +0 -106
- package/dist/components/addOns/functional/galleries/constants/gallerySimple.js +0 -76
- package/dist/components/addOns/functional/schedules/ScheduleGridOne.jsx +0 -167
- package/dist/components/addOns/functional/schedules/ScheduleGridTwo.jsx +0 -100
- package/dist/components/addOns/functional/schedules/ScheduleGridTwoBasic.jsx +0 -97
- package/dist/components/addOns/functional/schedules/SchedulerForm.jsx +0 -188
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwo.js +0 -40
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.js +0 -40
- package/dist/components/addOns/functional/schedules/constants/SchedulerForm.js +0 -65
- package/dist/components/addOns/functional/schedules/constants/scheduleGridOne.js +0 -54
- package/dist/components/addOns/non-functional/AnnouncementBanner.jsx +0 -24
- package/dist/components/addOns/non-functional/FeaturesSection.jsx +0 -38
- package/dist/components/addOns/non-functional/HeroSection.jsx +0 -71
- package/dist/components/addOns/non-functional/Heros/HeroSection.jsx +0 -71
- package/dist/components/addOns/non-functional/IconBubble.jsx +0 -36
- package/dist/components/addOns/non-functional/SampleCarousel.jsx +0 -114
- package/dist/components/addOns/non-functional/Testimonials.jsx +0 -177
- package/dist/components/addOns/non-functional/ThreeSetGallery.jsx +0 -40
- package/dist/components/addOns/non-functional/aboutSections/AboutSection.jsx +0 -35
- package/dist/components/addOns/non-functional/aboutSections/constants/aboutSection.js +0 -24
- package/dist/components/addOns/non-functional/imageCarousels/ProductSlider.jsx +0 -80
- package/dist/components/addOns/non-functional/imageCarousels/ProgramCarousel.jsx +0 -155
- package/dist/components/addOns/non-functional/imageCarousels/constants/programCarousel.js +0 -39
- package/dist/components/addOns/non-functional/imageCarousels/constants/programSlider.js +0 -36
- package/dist/components/addOns/non-functional/spinner.jsx +0 -13
- package/dist/components/footers/footer.jsx +0 -217
- package/dist/components/navBars/navbar.jsx +0 -159
- package/dist/components/other/accordion.jsx +0 -40
- package/dist/components/other/admin-menu.jsx +0 -34
- package/dist/components/other/alert-dialog.jsx +0 -64
- package/dist/components/other/alert.jsx +0 -41
- package/dist/components/other/aspect-ratio.jsx +0 -4
- package/dist/components/other/avatar.jsx +0 -31
- package/dist/components/other/badge.jsx +0 -32
- package/dist/components/other/breadcrumb.jsx +0 -57
- package/dist/components/other/button.jsx +0 -322
- package/dist/components/other/calendar.jsx +0 -43
- package/dist/components/other/card.jsx +0 -44
- package/dist/components/other/carousel.jsx +0 -140
- package/dist/components/other/chart.jsx +0 -182
- package/dist/components/other/checkbox.jsx +0 -26
- package/dist/components/other/collapsible.jsx +0 -6
- package/dist/components/other/command.jsx +0 -68
- package/dist/components/other/context-menu.jsx +0 -88
- package/dist/components/other/dialog.jsx +0 -60
- package/dist/components/other/drawer.jsx +0 -60
- package/dist/components/other/dropdown-menu.jsx +0 -90
- package/dist/components/other/form.jsx +0 -89
- package/dist/components/other/hover-card.jsx +0 -23
- package/dist/components/other/input-otp.jsx +0 -46
- package/dist/components/other/input.jsx +0 -19
- package/dist/components/other/label.jsx +0 -23
- package/dist/components/other/login-popup.jsx +0 -1
- package/dist/components/other/menubar.jsx +0 -96
- package/dist/components/other/mobile-icon.jsx +0 -11
- package/dist/components/other/navigation-menu.jsx +0 -62
- package/dist/components/other/pagination.jsx +0 -63
- package/dist/components/other/popover.jsx +0 -25
- package/dist/components/other/progress.jsx +0 -23
- package/dist/components/other/radio-group.jsx +0 -31
- package/dist/components/other/resizable.jsx +0 -29
- package/dist/components/other/scroll-area.jsx +0 -36
- package/dist/components/other/select.jsx +0 -83
- package/dist/components/other/separator.jsx +0 -21
- package/dist/components/other/sheet.jsx +0 -74
- package/dist/components/other/signup-popup.jsx +0 -1
- package/dist/components/other/skeleton.jsx +0 -17
- package/dist/components/other/slider.jsx +0 -26
- package/dist/components/other/social-icons.jsx +0 -15
- package/dist/components/other/sonner.jsx +0 -27
- package/dist/components/other/switch.jsx +0 -23
- package/dist/components/other/table.jsx +0 -56
- package/dist/components/other/tabs.jsx +0 -32
- package/dist/components/other/textarea.jsx +0 -19
- package/dist/components/other/toast.jsx +0 -58
- package/dist/components/other/toaster.jsx +0 -31
- package/dist/components/other/toggle-group.jsx +0 -41
- package/dist/components/other/toggle.jsx +0 -39
- package/dist/components/other/tooltip.jsx +0 -24
- package/dist/components/theme-provider.jsx +0 -18
- package/dist/components/types.js +0 -1
- package/dist/hooks/use-toast.js +0 -135
- package/dist/lib/auth-context.jsx +0 -144
- package/dist/lib/constants/about.js +0 -32
- package/dist/lib/constants/adRequest.js +0 -113
- package/dist/lib/constants/contact.js +0 -40
- package/dist/lib/constants/faq.js +0 -36
- package/dist/lib/constants/gallery.js +0 -42
- package/dist/lib/constants/page.js +0 -69
- package/dist/lib/constants/schedule.js +0 -71
- package/dist/lib/google-analytics.jsx +0 -148
- package/dist/lib/utils.js +0 -9
- package/dist/lib/verify-user.js +0 -142
- package/dist/middleware.js +0 -37
- package/dist/tailwind.config.js +0 -86
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState } from 'react';
|
|
4
|
-
import { useForm } from 'react-hook-form';
|
|
5
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
-
import * as z from 'zod';
|
|
7
|
-
import { ContactSubmitButton } from '@/components/other/button'; // Updated import
|
|
8
|
-
import { Input } from '@/components/other/input';
|
|
9
|
-
import { Textarea } from '@/components/other/textarea';
|
|
10
|
-
import { Mail, Loader2, User, MessageSquare, FileText, Phone } from 'lucide-react';
|
|
11
|
-
|
|
12
|
-
const formSchema = z.object({
|
|
13
|
-
name: z
|
|
14
|
-
.string()
|
|
15
|
-
.min(2, 'First name must be at least 2 characters')
|
|
16
|
-
.max(50, 'First name cannot exceed 50 characters')
|
|
17
|
-
.regex(/^[a-zA-Z\s-]+$/, 'First name can only contain letters, spaces, or hyphens'),
|
|
18
|
-
lastName: z
|
|
19
|
-
.string()
|
|
20
|
-
.min(2, 'Last name must be at least 2 characters')
|
|
21
|
-
.max(50, 'Last name cannot exceed 50 characters')
|
|
22
|
-
.regex(/^[a-zA-Z\s-]+$/, 'Last name can only contain letters, spaces, or hyphens'),
|
|
23
|
-
email: z
|
|
24
|
-
.string()
|
|
25
|
-
.email('Invalid email address')
|
|
26
|
-
.max(100, 'Email cannot exceed 100 characters'),
|
|
27
|
-
phone: z
|
|
28
|
-
.string()
|
|
29
|
-
.regex(
|
|
30
|
-
/^(\+?1\s?)?(\(?\d{3}\)?)[-.\s]?\d{3}[-.\s]?\d{4}$/,
|
|
31
|
-
'Please enter a valid phone number (e.g., 123-456-7890)'
|
|
32
|
-
)
|
|
33
|
-
.max(14, 'Phone number cannot exceed 14 characters')
|
|
34
|
-
.transform((val) => val.replace(/[-.\s()]/g, '').replace(/^1/, '')),
|
|
35
|
-
subject: z
|
|
36
|
-
.string()
|
|
37
|
-
.min(5, 'Subject must be at least 5 characters')
|
|
38
|
-
.max(100, 'Subject cannot exceed 100 characters'),
|
|
39
|
-
message: z
|
|
40
|
-
.string()
|
|
41
|
-
.max(500, 'Message cannot exceed 500 characters')
|
|
42
|
-
.optional(),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
interface FormData {
|
|
46
|
-
name: string;
|
|
47
|
-
lastName: string;
|
|
48
|
-
email: string;
|
|
49
|
-
phone: string;
|
|
50
|
-
subject: string;
|
|
51
|
-
message?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface ToastMessage {
|
|
55
|
-
type: 'success' | 'error';
|
|
56
|
-
title: string;
|
|
57
|
-
description: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default function ContactForm() {
|
|
61
|
-
const [toastMessage, setToastMessage] = useState<ToastMessage | null>(null);
|
|
62
|
-
const {
|
|
63
|
-
register,
|
|
64
|
-
handleSubmit,
|
|
65
|
-
reset,
|
|
66
|
-
formState: { errors, isSubmitting, dirtyFields },
|
|
67
|
-
} = useForm<FormData>({
|
|
68
|
-
resolver: zodResolver(formSchema),
|
|
69
|
-
mode: 'onBlur',
|
|
70
|
-
defaultValues: {
|
|
71
|
-
name: '',
|
|
72
|
-
lastName: '',
|
|
73
|
-
email: '',
|
|
74
|
-
phone: '',
|
|
75
|
-
subject: '',
|
|
76
|
-
message: '',
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const onSubmit = async (data: FormData) => {
|
|
81
|
-
try {
|
|
82
|
-
const response = await fetch('/api/contact', {
|
|
83
|
-
method: 'POST',
|
|
84
|
-
headers: { 'Content-Type': 'application/json' },
|
|
85
|
-
body: JSON.stringify(data),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const contentType = response.headers.get('content-type');
|
|
89
|
-
if (!contentType || !contentType.includes('application/json')) {
|
|
90
|
-
throw new Error('Server returned an unexpected response. Please try again.');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const result = await response.json();
|
|
94
|
-
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
const errorMessage = result.error || 'Unable to submit request at this time';
|
|
97
|
-
throw new Error(errorMessage);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
setToastMessage({
|
|
101
|
-
type: 'success',
|
|
102
|
-
title: 'Success',
|
|
103
|
-
description: 'Your message has been sent successfully!',
|
|
104
|
-
});
|
|
105
|
-
reset();
|
|
106
|
-
setTimeout(() => setToastMessage(null), 3000);
|
|
107
|
-
} catch (error: any) {
|
|
108
|
-
console.error('Contact form submission error:', error);
|
|
109
|
-
setToastMessage({
|
|
110
|
-
type: 'error',
|
|
111
|
-
title: 'Error',
|
|
112
|
-
description: error.message || 'Unable to submit request at this time',
|
|
113
|
-
});
|
|
114
|
-
setTimeout(() => setToastMessage(null), 3000);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div className="relative w-full max-w-4xl mx-auto p-10 bg-white/80 backdrop-blur-lg rounded-3xl shadow-xl border border-black-100/90">
|
|
120
|
-
{toastMessage && (
|
|
121
|
-
<div className="fixed top-8 right-8 z-50 animate-in fade-in slide-in-from-top-2">
|
|
122
|
-
<div
|
|
123
|
-
className={`p-6 rounded-2xl shadow-lg border transition-all duration-300 ${
|
|
124
|
-
toastMessage.type === 'success'
|
|
125
|
-
? 'bg-green-50/80 border-green-200/50 text-green-900'
|
|
126
|
-
: 'bg-red-50/80 border-red-200/50 text-red-900'
|
|
127
|
-
} backdrop-blur-sm`}
|
|
128
|
-
>
|
|
129
|
-
<h3 className="font-semibold text-lg">{toastMessage.title}</h3>
|
|
130
|
-
<p className="text-base mt-2">{toastMessage.description}</p>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
|
135
|
-
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2">
|
|
136
|
-
<div className="space-y-2">
|
|
137
|
-
<div className="relative group">
|
|
138
|
-
<User className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
139
|
-
<Input
|
|
140
|
-
{...register('name')}
|
|
141
|
-
placeholder="First Name"
|
|
142
|
-
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
143
|
-
errors.name && dirtyFields.name ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
144
|
-
}`}
|
|
145
|
-
maxLength={50}
|
|
146
|
-
aria-invalid={!!errors.name && !!dirtyFields.name}
|
|
147
|
-
aria-describedby={errors.name && dirtyFields.name ? 'name-error' : undefined}
|
|
148
|
-
/>
|
|
149
|
-
</div>
|
|
150
|
-
{errors.name && dirtyFields.name && (
|
|
151
|
-
<p id="name-error" className="text-base text-red-500">
|
|
152
|
-
{errors.name.message}
|
|
153
|
-
</p>
|
|
154
|
-
)}
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
<div className="space-y-2">
|
|
158
|
-
<div className="relative group">
|
|
159
|
-
<User className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
160
|
-
<Input
|
|
161
|
-
{...register('lastName')}
|
|
162
|
-
placeholder="Last Name"
|
|
163
|
-
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
164
|
-
errors.lastName && dirtyFields.lastName ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
165
|
-
}`}
|
|
166
|
-
maxLength={50}
|
|
167
|
-
aria-invalid={!!errors.lastName && !!dirtyFields.lastName}
|
|
168
|
-
aria-describedby={errors.lastName && dirtyFields.lastName ? 'lastName-error' : undefined}
|
|
169
|
-
/>
|
|
170
|
-
</div>
|
|
171
|
-
{errors.lastName && dirtyFields.lastName && (
|
|
172
|
-
<p id="lastName-error" className="text-base text-red-500">
|
|
173
|
-
{errors.lastName.message}
|
|
174
|
-
</p>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
<div className="space-y-2">
|
|
180
|
-
<div className="relative group">
|
|
181
|
-
<Mail className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
182
|
-
<Input
|
|
183
|
-
{...register('email')}
|
|
184
|
-
type="email"
|
|
185
|
-
placeholder="Email Address"
|
|
186
|
-
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
187
|
-
errors.email && dirtyFields.email ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
188
|
-
}`}
|
|
189
|
-
maxLength={100}
|
|
190
|
-
aria-invalid={!!errors.email && !!dirtyFields.email}
|
|
191
|
-
aria-describedby={errors.email && dirtyFields.email ? 'email-error' : undefined}
|
|
192
|
-
/>
|
|
193
|
-
</div>
|
|
194
|
-
{errors.email && dirtyFields.email && (
|
|
195
|
-
<p id="email-error" className="text-base text-red-500">
|
|
196
|
-
{errors.email.message}
|
|
197
|
-
</p>
|
|
198
|
-
)}
|
|
199
|
-
</div>
|
|
200
|
-
|
|
201
|
-
<div className="space-y-2">
|
|
202
|
-
<div className="relative group">
|
|
203
|
-
<Phone className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
204
|
-
<Input
|
|
205
|
-
{...register('phone')}
|
|
206
|
-
type="tel"
|
|
207
|
-
placeholder="Phone Number"
|
|
208
|
-
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
209
|
-
errors.phone && dirtyFields.phone ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
210
|
-
}`}
|
|
211
|
-
maxLength={14}
|
|
212
|
-
aria-invalid={!!errors.phone && !!dirtyFields.phone}
|
|
213
|
-
aria-describedby={errors.phone && dirtyFields.phone ? 'phone-error' : undefined}
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
216
|
-
{errors.phone && dirtyFields.phone && (
|
|
217
|
-
<p id="phone-error" className="text-base text-red-500">
|
|
218
|
-
{errors.phone.message}
|
|
219
|
-
</p>
|
|
220
|
-
)}
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
<div className="space-y-2">
|
|
224
|
-
<div className="relative group">
|
|
225
|
-
<FileText className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
226
|
-
<Input
|
|
227
|
-
{...register('subject')}
|
|
228
|
-
placeholder="Subject"
|
|
229
|
-
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
230
|
-
errors.subject && dirtyFields.subject ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
231
|
-
}`}
|
|
232
|
-
maxLength={100}
|
|
233
|
-
aria-invalid={!!errors.subject && !!dirtyFields.subject}
|
|
234
|
-
aria-describedby={errors.subject && dirtyFields.subject ? 'subject-error' : undefined}
|
|
235
|
-
/>
|
|
236
|
-
</div>
|
|
237
|
-
{errors.subject && dirtyFields.subject && (
|
|
238
|
-
<p id="subject-error" className="text-base text-red-500">
|
|
239
|
-
{errors.subject.message}
|
|
240
|
-
</p>
|
|
241
|
-
)}
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<div className="space-y-2">
|
|
245
|
-
<div className="relative group">
|
|
246
|
-
<MessageSquare className="absolute left-4 top-5 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
247
|
-
<Textarea
|
|
248
|
-
{...register('message')}
|
|
249
|
-
placeholder="Your Message"
|
|
250
|
-
className={`pl-14 pt-5 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base min-h-[180px] resize-y transition-all duration-300 ${
|
|
251
|
-
errors.message && dirtyFields.message ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
252
|
-
}`}
|
|
253
|
-
maxLength={500}
|
|
254
|
-
rows={8}
|
|
255
|
-
aria-invalid={!!errors.message && !!dirtyFields.message}
|
|
256
|
-
aria-describedby={errors.message && dirtyFields.message ? 'message-error' : undefined}
|
|
257
|
-
/>
|
|
258
|
-
</div>
|
|
259
|
-
{errors.message && dirtyFields.message && (
|
|
260
|
-
<p id="message-error" className="text-base text-red-500">
|
|
261
|
-
{errors.message.message}
|
|
262
|
-
</p>
|
|
263
|
-
)}
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
<ContactSubmitButton
|
|
267
|
-
type="submit"
|
|
268
|
-
disabled={isSubmitting}
|
|
269
|
-
>
|
|
270
|
-
{isSubmitting ? (
|
|
271
|
-
<>
|
|
272
|
-
<Loader2 className="mr-3 h-6 w-6 animate-spin" />
|
|
273
|
-
Sending...
|
|
274
|
-
</>
|
|
275
|
-
) : (
|
|
276
|
-
<>
|
|
277
|
-
<Mail className="mr-3 h-6 w-6" />
|
|
278
|
-
Send Message
|
|
279
|
-
</>
|
|
280
|
-
)}
|
|
281
|
-
</ContactSubmitButton>
|
|
282
|
-
</form>
|
|
283
|
-
</div>
|
|
284
|
-
);
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import * as z from 'zod';
|
|
7
|
+
import { ContactSubmitButton } from '@/components/other/button'; // Updated import
|
|
8
|
+
import { Input } from '@/components/other/input';
|
|
9
|
+
import { Textarea } from '@/components/other/textarea';
|
|
10
|
+
import { Mail, Loader2, User, MessageSquare, FileText, Phone } from 'lucide-react';
|
|
11
|
+
|
|
12
|
+
const formSchema = z.object({
|
|
13
|
+
name: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(2, 'First name must be at least 2 characters')
|
|
16
|
+
.max(50, 'First name cannot exceed 50 characters')
|
|
17
|
+
.regex(/^[a-zA-Z\s-]+$/, 'First name can only contain letters, spaces, or hyphens'),
|
|
18
|
+
lastName: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(2, 'Last name must be at least 2 characters')
|
|
21
|
+
.max(50, 'Last name cannot exceed 50 characters')
|
|
22
|
+
.regex(/^[a-zA-Z\s-]+$/, 'Last name can only contain letters, spaces, or hyphens'),
|
|
23
|
+
email: z
|
|
24
|
+
.string()
|
|
25
|
+
.email('Invalid email address')
|
|
26
|
+
.max(100, 'Email cannot exceed 100 characters'),
|
|
27
|
+
phone: z
|
|
28
|
+
.string()
|
|
29
|
+
.regex(
|
|
30
|
+
/^(\+?1\s?)?(\(?\d{3}\)?)[-.\s]?\d{3}[-.\s]?\d{4}$/,
|
|
31
|
+
'Please enter a valid phone number (e.g., 123-456-7890)'
|
|
32
|
+
)
|
|
33
|
+
.max(14, 'Phone number cannot exceed 14 characters')
|
|
34
|
+
.transform((val) => val.replace(/[-.\s()]/g, '').replace(/^1/, '')),
|
|
35
|
+
subject: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(5, 'Subject must be at least 5 characters')
|
|
38
|
+
.max(100, 'Subject cannot exceed 100 characters'),
|
|
39
|
+
message: z
|
|
40
|
+
.string()
|
|
41
|
+
.max(500, 'Message cannot exceed 500 characters')
|
|
42
|
+
.optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
interface FormData {
|
|
46
|
+
name: string;
|
|
47
|
+
lastName: string;
|
|
48
|
+
email: string;
|
|
49
|
+
phone: string;
|
|
50
|
+
subject: string;
|
|
51
|
+
message?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ToastMessage {
|
|
55
|
+
type: 'success' | 'error';
|
|
56
|
+
title: string;
|
|
57
|
+
description: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function ContactForm() {
|
|
61
|
+
const [toastMessage, setToastMessage] = useState<ToastMessage | null>(null);
|
|
62
|
+
const {
|
|
63
|
+
register,
|
|
64
|
+
handleSubmit,
|
|
65
|
+
reset,
|
|
66
|
+
formState: { errors, isSubmitting, dirtyFields },
|
|
67
|
+
} = useForm<FormData>({
|
|
68
|
+
resolver: zodResolver(formSchema),
|
|
69
|
+
mode: 'onBlur',
|
|
70
|
+
defaultValues: {
|
|
71
|
+
name: '',
|
|
72
|
+
lastName: '',
|
|
73
|
+
email: '',
|
|
74
|
+
phone: '',
|
|
75
|
+
subject: '',
|
|
76
|
+
message: '',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const onSubmit = async (data: FormData) => {
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch('/api/contact', {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify(data),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const contentType = response.headers.get('content-type');
|
|
89
|
+
if (!contentType || !contentType.includes('application/json')) {
|
|
90
|
+
throw new Error('Server returned an unexpected response. Please try again.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await response.json();
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const errorMessage = result.error || 'Unable to submit request at this time';
|
|
97
|
+
throw new Error(errorMessage);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setToastMessage({
|
|
101
|
+
type: 'success',
|
|
102
|
+
title: 'Success',
|
|
103
|
+
description: 'Your message has been sent successfully!',
|
|
104
|
+
});
|
|
105
|
+
reset();
|
|
106
|
+
setTimeout(() => setToastMessage(null), 3000);
|
|
107
|
+
} catch (error: any) {
|
|
108
|
+
console.error('Contact form submission error:', error);
|
|
109
|
+
setToastMessage({
|
|
110
|
+
type: 'error',
|
|
111
|
+
title: 'Error',
|
|
112
|
+
description: error.message || 'Unable to submit request at this time',
|
|
113
|
+
});
|
|
114
|
+
setTimeout(() => setToastMessage(null), 3000);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className="relative w-full max-w-4xl mx-auto p-10 bg-white/80 backdrop-blur-lg rounded-3xl shadow-xl border border-black-100/90">
|
|
120
|
+
{toastMessage && (
|
|
121
|
+
<div className="fixed top-8 right-8 z-50 animate-in fade-in slide-in-from-top-2">
|
|
122
|
+
<div
|
|
123
|
+
className={`p-6 rounded-2xl shadow-lg border transition-all duration-300 ${
|
|
124
|
+
toastMessage.type === 'success'
|
|
125
|
+
? 'bg-green-50/80 border-green-200/50 text-green-900'
|
|
126
|
+
: 'bg-red-50/80 border-red-200/50 text-red-900'
|
|
127
|
+
} backdrop-blur-sm`}
|
|
128
|
+
>
|
|
129
|
+
<h3 className="font-semibold text-lg">{toastMessage.title}</h3>
|
|
130
|
+
<p className="text-base mt-2">{toastMessage.description}</p>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
|
135
|
+
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2">
|
|
136
|
+
<div className="space-y-2">
|
|
137
|
+
<div className="relative group">
|
|
138
|
+
<User className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
139
|
+
<Input
|
|
140
|
+
{...register('name')}
|
|
141
|
+
placeholder="First Name"
|
|
142
|
+
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
143
|
+
errors.name && dirtyFields.name ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
144
|
+
}`}
|
|
145
|
+
maxLength={50}
|
|
146
|
+
aria-invalid={!!errors.name && !!dirtyFields.name}
|
|
147
|
+
aria-describedby={errors.name && dirtyFields.name ? 'name-error' : undefined}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
{errors.name && dirtyFields.name && (
|
|
151
|
+
<p id="name-error" className="text-base text-red-500">
|
|
152
|
+
{errors.name.message}
|
|
153
|
+
</p>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div className="space-y-2">
|
|
158
|
+
<div className="relative group">
|
|
159
|
+
<User className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
160
|
+
<Input
|
|
161
|
+
{...register('lastName')}
|
|
162
|
+
placeholder="Last Name"
|
|
163
|
+
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
164
|
+
errors.lastName && dirtyFields.lastName ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
165
|
+
}`}
|
|
166
|
+
maxLength={50}
|
|
167
|
+
aria-invalid={!!errors.lastName && !!dirtyFields.lastName}
|
|
168
|
+
aria-describedby={errors.lastName && dirtyFields.lastName ? 'lastName-error' : undefined}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
{errors.lastName && dirtyFields.lastName && (
|
|
172
|
+
<p id="lastName-error" className="text-base text-red-500">
|
|
173
|
+
{errors.lastName.message}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div className="space-y-2">
|
|
180
|
+
<div className="relative group">
|
|
181
|
+
<Mail className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
182
|
+
<Input
|
|
183
|
+
{...register('email')}
|
|
184
|
+
type="email"
|
|
185
|
+
placeholder="Email Address"
|
|
186
|
+
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
187
|
+
errors.email && dirtyFields.email ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
188
|
+
}`}
|
|
189
|
+
maxLength={100}
|
|
190
|
+
aria-invalid={!!errors.email && !!dirtyFields.email}
|
|
191
|
+
aria-describedby={errors.email && dirtyFields.email ? 'email-error' : undefined}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
{errors.email && dirtyFields.email && (
|
|
195
|
+
<p id="email-error" className="text-base text-red-500">
|
|
196
|
+
{errors.email.message}
|
|
197
|
+
</p>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div className="space-y-2">
|
|
202
|
+
<div className="relative group">
|
|
203
|
+
<Phone className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
204
|
+
<Input
|
|
205
|
+
{...register('phone')}
|
|
206
|
+
type="tel"
|
|
207
|
+
placeholder="Phone Number"
|
|
208
|
+
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
209
|
+
errors.phone && dirtyFields.phone ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
210
|
+
}`}
|
|
211
|
+
maxLength={14}
|
|
212
|
+
aria-invalid={!!errors.phone && !!dirtyFields.phone}
|
|
213
|
+
aria-describedby={errors.phone && dirtyFields.phone ? 'phone-error' : undefined}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
{errors.phone && dirtyFields.phone && (
|
|
217
|
+
<p id="phone-error" className="text-base text-red-500">
|
|
218
|
+
{errors.phone.message}
|
|
219
|
+
</p>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div className="space-y-2">
|
|
224
|
+
<div className="relative group">
|
|
225
|
+
<FileText className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
226
|
+
<Input
|
|
227
|
+
{...register('subject')}
|
|
228
|
+
placeholder="Subject"
|
|
229
|
+
className={`pl-14 h-14 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base transition-all duration-300 ${
|
|
230
|
+
errors.subject && dirtyFields.subject ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
231
|
+
}`}
|
|
232
|
+
maxLength={100}
|
|
233
|
+
aria-invalid={!!errors.subject && !!dirtyFields.subject}
|
|
234
|
+
aria-describedby={errors.subject && dirtyFields.subject ? 'subject-error' : undefined}
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
237
|
+
{errors.subject && dirtyFields.subject && (
|
|
238
|
+
<p id="subject-error" className="text-base text-red-500">
|
|
239
|
+
{errors.subject.message}
|
|
240
|
+
</p>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="space-y-2">
|
|
245
|
+
<div className="relative group">
|
|
246
|
+
<MessageSquare className="absolute left-4 top-5 text-gray-400 h-6 w-6 transition-colors group-focus-within:text-gray-600" />
|
|
247
|
+
<Textarea
|
|
248
|
+
{...register('message')}
|
|
249
|
+
placeholder="Your Message"
|
|
250
|
+
className={`pl-14 pt-5 text-lg font-medium border-0 bg-gray-100/80 rounded-2xl focus:ring-2 focus:ring-gray-300 focus:bg-white text-gray-900 placeholder-gray-400 placeholder:font-normal placeholder:text-base min-h-[180px] resize-y transition-all duration-300 ${
|
|
251
|
+
errors.message && dirtyFields.message ? 'ring-2 ring-red-300 bg-red-50/80' : 'ring-gray-200'
|
|
252
|
+
}`}
|
|
253
|
+
maxLength={500}
|
|
254
|
+
rows={8}
|
|
255
|
+
aria-invalid={!!errors.message && !!dirtyFields.message}
|
|
256
|
+
aria-describedby={errors.message && dirtyFields.message ? 'message-error' : undefined}
|
|
257
|
+
/>
|
|
258
|
+
</div>
|
|
259
|
+
{errors.message && dirtyFields.message && (
|
|
260
|
+
<p id="message-error" className="text-base text-red-500">
|
|
261
|
+
{errors.message.message}
|
|
262
|
+
</p>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<ContactSubmitButton
|
|
267
|
+
type="submit"
|
|
268
|
+
disabled={isSubmitting}
|
|
269
|
+
>
|
|
270
|
+
{isSubmitting ? (
|
|
271
|
+
<>
|
|
272
|
+
<Loader2 className="mr-3 h-6 w-6 animate-spin" />
|
|
273
|
+
Sending...
|
|
274
|
+
</>
|
|
275
|
+
) : (
|
|
276
|
+
<>
|
|
277
|
+
<Mail className="mr-3 h-6 w-6" />
|
|
278
|
+
Send Message
|
|
279
|
+
</>
|
|
280
|
+
)}
|
|
281
|
+
</ContactSubmitButton>
|
|
282
|
+
</form>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
285
|
}
|